What a dropped uplink actually costs: dead air per reconnect class
Not every blip costs the same, and the expensive part is not the one everybody optimises. We put a real WebRTC publisher behind a middlebox we could switch off.
On this page
TL;DR: We put a real WebRTC session behind a middlebox we could switch off and measured what an outage costs. Webrtc reconnect time is the cheap part: re-establishing the path costs the outage plus a few hundred milliseconds, at every duration that recovers at all. The seconds a viewer actually loses are spent waiting for the next keyframe — a mean of 0.97 s at a 2 s keyframe interval against 3.8 s at 8 s, on an identical eight-second outage. And there is a cliff: a twenty-second outage recovered every time and a forty-five-second one never did, twenty trials each. The threshold itself we did not measure — we bracketed it, and RFC 7675 puts ICE consent expiry at thirty seconds inside that bracket.
Not every blip costs the same, and the difference is not the one people optimise for.
The instinct is that a longer outage costs proportionally more, and that the fix is faster reconnection. Measured, the first half is true in an uninteresting way and the second half is mostly wrong. Below the cliff, recovery is nearly free and the bill is a setting in your encoder. Above it, no reconnection speed helps, because nothing reconnects.
What was measured, and what it is not
A publisher and a receiver in separate network namespaces, with a middlebox between them that we can
darken and restore. Real pion/webrtc: real ICE, real DTLS, real SRTP. The outage is netem loss 100% on both of the middlebox's egresses, which blackholes the path while leaving both endpoint
interfaces up — that distinction is load-bearing, because tearing down an interface at an endpoint
makes ICE react to a local device disappearing, which is a different code path from a path that still
looks present and answers nothing.
Signalling runs over a second, unshaped network so that a blackout cannot stall the instrument as well as the subject. Everything below is measured at the receiver, which locates the silence in its own arrival record — the controller never tells it when the outage happened, because when the outage happened is the one number a controller cannot report honestly about itself.
This is a lab and every figure here says so. There is no radio, no handover, no competing subscriber and no carrier. It measures what a total path outage costs a WebRTC session, which is a mechanism a cellular link produces — not a measurement of cellular. And nothing is encoded or decoded: the payload is sixteen bytes, so "waiting for a keyframe" here means waiting for a marked packet, and a real decoder adds its own cost on top.
The two legs are bounded in opposite directions, and calling both of them "floors" would be wrong. The transport leg is a floor: one host, no NAT, no TURN, no competing traffic, so a real deployment can only be slower. The keyframe leg is a ceiling: our receiver never asks for anything. It sends no RTCP at all — no PLI, no FIR — and the publisher emits keyframes on a fixed schedule regardless, so what we measure is always the wait for the next scheduled one. A production receiver requests a keyframe the moment it recovers and typically gets one sooner than the schedule would have delivered it. So: transport at least this bad, keyframe at most this bad.
Three classes, and the stack sorted them itself
We did not assign the classes. Each trial's RTCPeerConnection reported its own state transitions,
and they fall into three groups:
| Outage | What the connection reported | Class |
|---|---|---|
| 2 s | connected → closed |
it never noticed |
| 8 s, 20 s | connected → disconnected → connected |
dropped, then returned on its own |
| 45 s | connected → disconnected → **failed** |
never returned — 0 of 20 recovered |
That third row is the one worth internalising. We bracketed the cliff rather than locating it: the longest outage that recovered was twenty seconds and the shortest that never did was forty-five, so everything between them is untested. RFC 7675 puts ICE consent expiry at thirty seconds without a response, which falls inside that bracket — a consistent explanation for where the boundary is, not a measurement of it. If the exact number matters to your design, run the durations between twenty and forty-five; we did not.
Below the bracket the path is remembered and resumes. Above it the session is gone, and the publisher we tested — which does nothing clever, no ICE restart, no reconnect loop — stays gone.
Why is re-establishing the path so cheap?
Because below the cliff there is nothing to re-establish. The candidate pair is still valid, consent has not lapsed, and the keys are still the keys — so recovery is not a negotiation, it is packets starting to arrive again.
The transport leg, measured from the last packet before the silence to the first packet after it:
| Outage | Transport leg | Overhead beyond the outage |
|---|---|---|
| 2 s | 2,027 ms | ~27 ms |
| 8 s | 8,131 ms | ~131 ms |
| 20 s | 20,410 ms | ~410 ms |
| 45 s | never recovered | — |
(Means across all twenty trials at each duration that recovered — both keyframe arms pooled, because the transport leg does not depend on the keyframe interval and the next section is where that is established. Drop the trials whose recovery arrived in a burst, explained below, and the same figures are 2,027 / 8,028 / 20,243 ms — the burst inflates this leg because a held-then-released group really did arrive late. The burst-clean eight-second figure has now come back within three milliseconds on three independent runs: 8,027, 8,030, 8,028.)
The overhead does not grow with the outage in any way that matters — burst-clean it is 27 ms at two seconds and 28 ms at eight, rising to about 240 ms at twenty; counting the burst trials too, 27, 131 and 410 ms. So "how fast does it reconnect" is very nearly the wrong question below the cliff: the answer is "as fast as the link comes back", and every engineering hour spent shaving that is an hour spent on a tenth of a second.
Where the seconds actually go
The reader does not see packets. They see a picture, and a picture needs a keyframe. So the leg that matters is the wait from the first packet after recovery to the first decodable one — and that leg is not a property of the network at all.
| Outage | Keyframe interval | Mean keyframe wait | Range across trials | Trials |
|---|---|---|---|---|
| 8 s | 2 s | 972 ms | 141–1,658 ms | 8 |
| 8 s | 8 s | 3,782 ms | 579–7,780 ms | 8 |
| 2 s | 2 s | 888 ms | 240–1,760 ms | 10 |
| 2 s | 8 s | 4,370 ms | 140–7,861 ms | 10 |
(Ten trials per cell; the count column is how many survive the burst filter described below. Quote the n whenever you quote one of these means — an earlier version of this article published a mean over three trials, and three draws from a distribution this wide is not a measurement.)
Same link, same harness, same outage in each pair — the only difference is a number in the encoder's configuration, and it is worth seconds of what the viewer experiences.
The eight-second rows are the ones to read closely, because they land close to where the prediction written before the run says they should. A recovery arrives at a random point in the keyframe cycle, so the expected wait is half the interval: 1,000 ms and 4,000 ms predicted, 972 ms and 3,782 ms measured, over eight trials each. The two-second-outage rows agree — 888 ms and 4,370 ms over ten each — which is the same point made twice, since the keyframe leg does not know how long the outage was.
Now read the ranges, because they matter more than the means. Each one spans nearly its whole interval: 141–1,658 ms where the interval is 2,000, and 579–7,780 ms where it is 8,000. That is a uniform draw, and it is the real finding. The mean is a property of the distribution, not a prediction about your next outage — any single drop lands anywhere in the range. So the honest thing to publish is the interval and the shape: expect a wait uniform on zero to one keyframe interval, averaging half of it. A single "dead air" figure is a number pulled out of that spread and presented as though it were a constant.
The keyframe leg does not care how long the outage was, which is the other half of the point: it is set by your encoder, not by the network.
That is the finding this article exists for. If you publish a single "dead air" number, most of it is your own configuration, and you will have described your encoder while appearing to describe the network.
The trade is a real one, not a free win. A shorter keyframe interval costs bitrate continuously, every second of every stream, in exchange for a shorter wait on the rare occasions the link drops. On an always-on source that is a permanent tax against an occasional benefit, and the right setting depends on how often your link actually goes away — which is a thing you have to measure rather than assume.
- 2 s keyframe interval
- 8,000 ms outage
- 28 ms re-establishing the path
- 972 ms waiting for a keyframe
- 8 s keyframe interval
- 8,000 ms outage
- 28 ms re-establishing the path
- 3,782 ms waiting for a keyframe
What we excluded, and why it is in the article
One cell of the matrix is not quotable and it would have been easy to leave out quietly.
At a twenty-second outage with a two-second keyframe interval, nine of ten trials are contaminated and one survives. One trial is not a mean, so that cell has no number in this article. It failed the same way in the first run, before we had a name for what was happening.
Here is what was happening, because the diagnosis turned out to be worth more than the anomaly.
The first explanation was that the outage durations were whole multiples of the keyframe interval, so recovery landed at the same phase every time. The harness now randomises that phase, which fixed the other cells — and this one still read zero, so the explanation was incomplete.
Three measurements settled it, and all three are now columns in results.txt.
- The sequence jump across the silence is about what the outage should destroy — around a thousand packets at twenty seconds, fifty a second. So the publisher kept sending throughout and the middlebox dropped what it sent. Nothing stalled at either end.
- The first packet through is never the keyframe — false in all sixty trials that recovered. The keyframe arrives a fraction of a millisecond behind it, and packets are sent twenty milliseconds apart, so those two did not travel that way. Something between the ends released a group together.
- How long the group had been waiting. The publisher stamps each packet with its own send time, so the oldest packet in a burst can be aged at the receiver. Every contaminated trial comes back at a whole number of seconds: fifty-one or fifty-two packets aged about 1,010 ms, a hundred-odd aged about 2,035 ms, one at 3,066 ms. Something buffers in whole seconds and then flushes.
In those trials the keyframe leg is not measuring what it claims. It is measuring how far into a recovery burst the keyframe happened to sit, which is close to zero by construction. The transport leg is affected too, and in the opposite direction — a held-then-released group genuinely arrives late, so those trials show a longer path recovery, which is why the table above quotes the burst-clean figures beside the pooled ones.
It is not one strange cell, and it is not confined to long outages either — it simply gets more likely as the outage grows: none of the twenty two-second trials, four of the twenty eight-second ones, twelve of the twenty at twenty seconds. So the rule is per-trial rather than per-cell: any trial whose recovery arrives as a burst has a meaningless keyframe leg, it now says so in its own row, and the means are computed over the survivors with the count printed beside them.
A single dead-air figure is mostly your own encoder
FAQ
Does a client that reconnects properly change the cliff?
It changes what happens after it, not where it is. Consent failure is a property of the session, and
past that point the session is over regardless — what a re-offering client buys you is a new
session, whose cost is signalling plus ICE plus DTLS on top of the outage. Our publisher deliberately
does nothing clever, so NEVER here means "this session did not recover on its own", not "recovery
is impossible".
Would a real cellular link behave like this? Worse, and in more ways. This is one host, one process per end, no NAT, no TURN relay, no competing traffic and no radio. Every one of those makes recovery slower or less certain. Treat these as the best case a WebRTC session can have, and the shape of the answer — cheap transport, expensive keyframe, hard cliff — as the transferable part rather than the milliseconds.
Why measure a marked packet instead of a decoded frame? Because a decoder would add its own behaviour to a measurement about the link. Note which way that cuts: decoding adds to what a viewer waits, but our missing PLI subtracts from it, and the second effect is the larger one. A production receiver asks for a keyframe on resume instead of waiting for the schedule, so the keyframe leg here is an upper bound rather than a lower one. What survives is the comparison between the two arms, which is the article's actual claim: both were measured under the same no-PLI publisher, so the ratio between them is not an artefact of the omission.
How do we know the harness did not just measure itself? Two ways, both built in. The signalling path is a separate unshaped network, so a blackout cannot stall the instrument. And the receiver finds the silence in its own arrival record rather than being told when the outage was applied — so the controller's timing never enters a reported number.
The three things worth taking away
- Below the consent window, reconnecting is nearly free. Tens of milliseconds at short outages, a few hundred at twenty seconds — against an outage measured in whole seconds. If you are optimising reconnection speed for outages in that band, you are optimising the cheap leg.
- The expensive leg is one you configured. The difference between a two-second and an eight-second keyframe interval averaged 2.8 seconds of viewer-visible dead air on an identical outage, and any individual drop can cost the whole interval. Publish the legs separately or you will publish your encoder settings as though they were the network's fault.
- There is a cliff and it is not gradual. Past roughly thirty seconds of silence the session does not degrade, it ends. Everything about recovery changes on the other side of that line, so any plan that treats a forty-five second outage as "a longer twenty-second outage" is planning for a case that does not exist.
The method, the harness and the raw trials are committed alongside this article, including the cell we could not explain and the prediction we wrote before running it.
What an outage costs the people watching, rather than what it costs the session, is on always-on video that survives a bad uplink.
If your stream comes back and your viewers still say it was down for ten seconds, the gap between those two facts is the thing we measure.