Learning objective. Enumerate the sources of latency and jitter in a QUIC/HTTP-3 data path after the handshake: pacing, ACK delay, loss recovery, and the one that dominates real-time traffic, in-order delivery holding already-arrived data. Naming them lets the rest of the chapter attack each.
Latency is not one number #
Chapter 8 budgeted the setup cost in round trips (§8.1). Real-time traffic
(interactive audio and video, gaming, telemetry) lives in the steady state, where the connection
is already open and the question is how long each individual message takes from send() on one
side to recv() on the other, and how much that time varies. That variance is jitter, and for
interactive media it hurts as much as raw latency: a jittery stream must buffer to stay smooth,
and the buffer is added latency.
The per-message delay is a sum of distinct sources, each with its own fix:
The four sources, and which ones bite #
1. Send-side holding: pacing and the congestion window. A packet is not sent the instant the application hands it over. It may wait for the congestion controller to allow it, or be spread out by pacing, deliberately spacing packets to avoid bursts ([RFC 9002 §7.7]). On a congestion- or flow-control-limited connection this hold can be significant; for a real-time flow it is a reason to keep the send rate under the available window rather than bursting.
2. In-order delivery holding arrived data: the real-time killer. QUIC removed inter-stream head-of-line blocking: a loss on stream 4 no longer stalls stream 8 (§4.4). But within a single stream, delivery is still reliable and ordered. So if packet 2 is lost, the receiver holds packets 3 and 4 (which have already arrived) until the retransmit of 2 shows up, because the application must see the bytes in order. For a bulk download that hold is invisible; for a live video stream it is a visible stall, and it is why real-time media often should not use a reliable ordered stream at all (§9.3).
3. ACK delay. A receiver may hold its acknowledgment briefly to batch it, up to the
max_ack_delay it advertised (default 25 ms, [RFC 9000 §18.2]). This is efficient for throughput but
adds to how quickly the sender learns of loss and recovers. The sender removes the reported delay
from its RTT estimate (§8.2), but the wall-clock delay before recovery is real.
4. Loss recovery. When a packet is lost, recovering it costs about a round trip: the sender must detect the loss (by packet or time threshold, or a PTO) and retransmit, and the data arrives an RTT later (§8.2). For a reliable stream this round trip is added directly to the delivery time of everything held behind the loss — source 4 is what makes source 2 expensive.
Jitter: the variance across all of these #
Each source varies from packet to packet: pacing gaps differ, some packets are lost and some are not, ACK delay fluctuates, RTT itself wobbles with queueing. Jitter is the variance of the total. An application that needs smooth playout must absorb that variance in a de-jitter buffer, holding data for the worst expected delay so the stream plays out evenly. This means the buffer size, and thus the effective latency, is set by the tail of the delay distribution, not its average. Cutting jitter therefore cuts latency twice: directly, and by letting the playout buffer shrink.
Worked example: one lost packet in a live stream #
Trace Figure 9.1-1. The application hands three media chunks to QUIC; pacing may already have spread
their sends (source 1). Chunk B's packet is lost, but chunk C arrives — and the receiver's transport
holds C, because in-order delivery must give the application B before C (source 2). The receiver's
ACK, reporting the gap, comes after up to max_ack_delay (source 3). The sender detects the loss and
retransmits B, which arrives about a round trip later (source 4); only then can the receiver deliver B
and the held C. For a file that is fine. For a live stream, chunk C (fully received) was stuck
waiting on a retransmit of data that, for real-time purposes, was already too late to matter. That is
the core tension the rest of the chapter resolves.
The sources compound in a specific order for real-time: reliability (source 2) turns a single loss into a multi-packet stall, and loss recovery (source 4) sets how long that stall lasts. This is why the biggest real-time win is not making recovery faster but making it unnecessary — delivering data that does not have to wait in order, via unreliable DATAGRAM frames (§9.3). You cannot pace or ACK-tune your way out of a head-of-line stall; you can only avoid incurring it.
editorial When a real-time stream feels laggy or stuttery, profile
the sources separately before tuning. A steady delay with low variance points at pacing or the
congestion window (source 1). Check whether you are sending above the available rate. Periodic
spikes aligned with packet loss point at head-of-line blocking plus recovery (sources 2 and 4). The
fix is a transport choice (unreliable datagrams), not a knob. A delay that tracks max_ack_delay
points at ACK batching (source 3). Chasing the wrong source (shrinking a playout buffer to fix a
head-of-line stall, say) makes the experience worse.
Takeaways #
Steady-state latency is a sum of send-side holding (pacing/congestion window), in-order delivery holding already-arrived data behind a loss, ACK delay, and the round trip of loss recovery. Jitter is the variance across all of them, paid again in the playout buffer it forces. For real-time traffic the dominant source is in-order delivery turning one loss into a stall, which no amount of tuning removes. The chapter attacks each source in turn: packetization and MTU (§9.2), recovery trade-offs and unreliable delivery (§9.3), backpressure (§9.4), and a tuned media profile (§9.5).