§ 6.2 Module 6 — Surviving the Network: Loss, Congestion, and Jitter
"The network is reliable." — Fallacy #1 of distributed computingCourse notes
By the end of this lesson
The failure that forced the design
A router's queue fills before it overflows: delay rises before loss appears. Google Congestion Control's insight was to watch the delay gradient — packets arriving progressively later than sent — and back off before the first packet dies. The feedback plumbing migrated from receiver-computed REMB to sender-side transport-wide-cc, putting the whole estimator where the encoder lives.
Why loss-based congestion control is too late for real-time: delay-gradient estimation, the arrival-time filter, REMB's receiver-side legacy versus transport-cc's sender-side design, probing, and how BWE couples to the encoder's rate controller.
Cross-references: GCC & Bandwidth Estimation.
A queue announces congestion through rising delay before it drops packets. GCC groups packets, compares send-time and arrival-time deltas, filters the noisy gradient, and lets an AIMD controller reduce the encoder target before bufferbloat consumes the latency budget.
Engineering consequence. Bufferbloat; delay gradient as the early-warning signal. Verify the behavior with a controlled trace before interpreting a live call: establish the expected state, change one variable, and compare deltas rather than isolated values. In production, attach the observation to a timestamp and media direction so the same evidence cannot be mistaken for a different failure.
a=rtcp-fb:<pt> transport-cc means the receiver reports per-packet arrival times (using the transport-wide sequence number header extension) and the sender computes the estimate. a=rtcp-fb:<pt> goog-remb means the receiver computes an estimate and sends a bitrate back. Mixing an old SFU that only speaks REMB with modern browsers relocates ownership of rate control, so your sender-side availableOutgoingBitrate is absent or meaningless and the tuning you did on the sender has no effect.
Grep the negotiated SDP for which feedback types survived, and verify: TWCC in use means candidate-pair.availableOutgoingBitrate is populated and tracks reality. If you inherited a REMB-only server, that is a migration, not a tuning exercise.
The operational claim is precise: Arrival-time model, per-group deltas, Kalman → trendline evolution. Treat it as an observable contract. Record the state transition or counter that proves it, test the failure path as well as the happy path, and keep units and clock domains explicit. That discipline is what separates a plausible WebRTC explanation from a production diagnosis.
Engineering consequence. Arrival-time model, per-group deltas, Kalman → trendline evolution. Verify the behavior with a controlled trace before interpreting a live call: establish the expected state, change one variable, and compare deltas rather than isolated values. In production, attach the observation to a timestamp and media direction so the same evidence cannot be mistaken for a different failure.
Interactive minigame
Guessing the pipe — delay gradient, over-use, AIMD
A 3D instrument you drive yourself, one variable at a time. It needs JavaScript and WebGL, so it is not shown in this static copy of the page.
The controller increases cautiously while the path is normal, holds when evidence is ambiguous, and decreases multiplicatively on overuse. A loss backstop remains useful, but waiting for double-digit loss alone reacts after the real-time experience is already damaged.
Engineering consequence. AIMD controller: increase/hold/decrease; loss-based backstop (>10%). Verify the behavior with a controlled trace before interpreting a live call: establish the expected state, change one variable, and compare deltas rather than isolated values. In production, attach the observation to a timestamp and media direction so the same evidence cannot be mistaken for a different failure.
Everyone looks for the bandwidth estimate next to the stream it constrains. It is not there: with BUNDLE there is one estimate for the whole transport, so it lives on the selected candidate-pair. availableIncomingBitrate is on the same object and only appears when REMB is in use, which is why it is usually missing entirely. Neither is present until ICE has selected a pair.
Walk transport.selectedCandidatePairId to the pair object and read it from there. Compare it against the sum of your outbound-rtp target bitrates: a large persistent gap means something other than bandwidth (CPU, maxBitrate, encoder floor) is limiting you.
The operational claim is precise: REMB (legacy, receiver-side) vs transport-cc (send-side TWCC feedback). Treat it as an observable contract. Record the state transition or counter that proves it, test the failure path as well as the happy path, and keep units and clock domains explicit. That discipline is what separates a plausible WebRTC explanation from a production diagnosis.
Engineering consequence. REMB (legacy, receiver-side) vs transport-cc (send-side TWCC feedback). Verify the behavior with a controlled trace before interpreting a live call: establish the expected state, change one variable, and compare deltas rather than isolated values. In production, attach the observation to a timestamp and media direction so the same evidence cannot be mistaken for a different failure.
The operational claim is precise: Probing, ALR, pacing; BWE → encoder target coupling. Treat it as an observable contract. Record the state transition or counter that proves it, test the failure path as well as the happy path, and keep units and clock domains explicit. That discipline is what separates a plausible WebRTC explanation from a production diagnosis.
Engineering consequence. Probing, ALR, pacing; BWE → encoder target coupling. Verify the behavior with a controlled trace before interpreting a live call: establish the expected state, change one variable, and compare deltas rather than isolated values. In production, attach the observation to a timestamp and media direction so the same evidence cannot be mistaken for a different failure.
GCC infers congestion from increasing one-way delay gradients. 802.11n/ac/ax aggregate frames, batching packets and releasing them together, which produces recurring 20–40 ms delay steps with no congestion behind them. The estimator reads them as queue buildup and backs off, so clients on excellent Wi-Fi settle at a few hundred kbit/s on a link with 100 Mbit/s available. On LTE the opposite failure occurs: 1–2 s of carrier buffering means delay rises so late that the estimator overshoots badly before it reacts.
Do not tune your application around a single user's estimate; compare availableOutgoingBitrate against a plain HTTP throughput test to see whether the estimator is the limit. Ethernet-versus-Wi-Fi is a legitimate support recommendation for pinned-low estimates, and it is testable in a minute.
Start with the claim “Bufferbloat; delay gradient as the early-warning signal” and turn it into a falsifiable trace. Write down the input condition, the expected state transition, the counter or packet field that should change, and the deadline by which it must change. This prevents a familiar mistake: observing a symptom after several mechanisms have already reacted and assigning it to whichever mechanism has the most recognizable name. The causal order matters. A queue grows before loss; an ICE path fails before a restart selects a new pair; a decoder loses reference state before frames stop rendering. Preserve that order in the evidence.
Now hold every unrelated variable still. Use canned timestamps, SDP, stats-shaped records, or a permission-free loopback source so the same input can be replayed. Record both media directions separately. For cumulative counters, retain two samples and divide the difference by their timestamp interval. For state machines, retain the ordered event sequence rather than the last state alone. For negotiated features, compare the offer, answer, and live stats result. These three views answer different questions: what was proposed, what was accepted, and what the browser actually used.
The first lab, The Delay-Gradient Detective, applies this discipline to a bounded implementation problem. Given canned per-packet-group (sendTime, arrivalTime, size) traces, implement inter-group delay-variation and a trendline/threshold overuse detector emitting normal/overuse/underuse. Assertions: overuse fires within N groups of the known congestion onset, zero false positives on the jittery-but-clean trace (the hard case); hints catch per-packet-instead-of-per-group deltas and single-spike triggering. Before coding, identify the invariant hidden in that brief and the tempting shortcut the tests should reject. Then run the reference trace, perturb one boundary value, and explain why the result changes. If the code passes but the explanation cannot predict the perturbed result, the implementation is memorized rather than understood.
GCC starts around 300 kbit/s and ramps multiplicatively while probing with padding, so a high-RTT relayed path can take 20–40 s to reach 2 Mbit/s. There is no standard minBitrate or start bitrate API. The one available knob is a Chrome-specific fmtp munge for VP8/VP9 — a=fmtp:96 x-google-start-bitrate=1200;x-google-min-bitrate=600;x-google-max-bitrate=2500 — and those values are in kilobits per second, unlike maxBitrate next to them in bits. Probing padding also shows up as bytesSent growth with no frame growth, which looks like a leak.
Only raise the start bitrate when you know the path (a corporate LAN, a known-good server leg); on a genuinely thin link, aggressive probing causes the loss it was trying to avoid. Annotate probe traffic in your graphs by tracking frames alongside bytes.
A browser loopback is deliberately cleaner than the internet. It removes signaling latency, hostile NAT behavior, relay geography, wireless contention, device heat, and competing encoders. That makes it ideal for proving protocol and API invariants, but a dangerous basis for capacity claims. Carry the invariant into a second test with a canned hostile trace: reordered packets, a delayed candidate, a missing remote stats object, a bitrate step, or a state transition that recovers before the debounce timer. The implementation should remain correct even when optional fields are absent and events arrive more than once.
Feature detection is also part of correctness. Browser support for a codec, scalability mode, encoded transform, or transport API does not imply that the negotiated peer supports it or that hardware acceleration is active. Prefer capability checks, preserve a standards-based fallback, and label any simulation honestly. The graded result should measure the learner's algorithm; an optional live coda may demonstrate the real API without making a camera, operating-system codec, or network service a hidden test dependency.
Finally, connect the mechanism to the last topic in the lesson: “Probing, ALR, pacing; BWE → encoder target coupling”. Advanced WebRTC failures cross boundaries. Congestion control changes encoder output; layer selection changes keyframe demand; end-to-end encryption constrains what an SFU can inspect; a watchdog can collide with negotiation; observability can mislabel a path if its joins are wrong. At every boundary, define ownership and an idempotent action. A retry must not create duplicate work, a stale event must not overwrite newer state, and an escalation must have a terminal cleanup path.
Use the problem set as an executable notebook. Keep a short record beside each answer: the invariant, the decisive evidence, the rejected alternative, and the production caveat. That record is more valuable than a green checkmark because it can be reused when a real call presents the same mechanism through noisier evidence.
A common error
onicecandidate firing with null means failure — it signals end of gathering.
Implement the decision rules, run every assertion, then use the explanations to distinguish a wrong result from a wrong model.
The Delay-Gradient Detective. Given canned per-packet-group (sendTime, arrivalTime, size) traces, implement inter-group delay-variation and a trendline/threshold overuse detector emitting normal/overuse/underuse. Assertions: overuse fires within N groups of the known congestion onset, zero false positives on the jittery-but-clean trace (the hard case); hints catch per-packet-instead-of-per-group deltas and single-spike triggering.
Lab 1
The Delay-Gradient Detective
Graded in the browser against 3 assertions; the editor and harness require JavaScript.
The AIMD Rate Controller. Implement the rate-control state machine consuming ex1's detector output plus loss reports: 0.85× multiplicative decrease, additive increase, hold state. Run against three canned scenarios (ramp-up, sudden bottleneck, periodic cross-traffic). Assertions: converges within ±20% of bottleneck, backs off within 2s of onset, bounded oscillation, never increases during overuse. A real-api coda observes availableOutgoingBitrate on live loopback stats and confirms transport-cc in the SDP; the coda is feature-detected and never blocks the grade (label reflects the most environment-demanding component per the cadence convention).
Lab 2
The AIMD Rate Controller
Graded in the browser against 3 assertions; the editor and harness require JavaScript.
Check your understanding
Auto-graded check
4 auto-graded questions with an explanation for every wrong answer. Requires JavaScript. (m6-l2-quiz)
WebRTC: From First Principles · Module 6, Lesson 2