§6.4
Chapter 06 · QPACK Behavior and Header Compression Optimization

§6.4Tuning QPACK Settings for High-Latency Environments

RFC 9204RFC 9114

Learning objective. Understand how a long round trip changes the QPACK compression math, why the "reference only acknowledged entries" strategy grows expensive as latency rises, and how to choose SETTINGS_QPACK_MAX_TABLE_CAPACITY and SETTINGS_QPACK_BLOCKED_STREAMS for high-latency paths.

Why latency changes the math #

On a fast local link, the acknowledgment loop of §6.2 is nearly free: the encoder inserts an entry, the Section Acknowledgment or Insert Count Increment returns in a millisecond, and from the next request onward the entry is safe to reference. The Known Received Count tracks the encoder's inserts closely, so a conservative "reference only acknowledged entries" encoder still compresses well.

Stretch the path — a satellite hop, a cross-continent route, a mobile network at 150 ms one-way — and that loop becomes the dominant cost. Now an entry the encoder inserts is not confirmed for a full round trip, and a conservative encoder must send the intervening requests with the field spelled out as a literal, wasting the very entry it just inserted.

DecoderEncoderone-way delay ≈ 150 ms · RTT ≈ 300msConservative — reference only afterack~300 ms later —only now is L "safe"every new entry costs a full RTT beforereuseEager — reference in the same flightsame-flightinsert+ref →decodes at once, noblockcompression from request one, budget≥ 1 neededInsert L (abs 0)Insert Count Increment (+1)HEADERS ref abs 0 (compressed)Insert M (abs 1) + HEADERS refabs 1 (RIC=2)
Fig. 6.4-1On a 300 ms-RTT path, a conservative encoder cannot reuse an inserted entry until an acknowledgment returns a full round trip later, so early requests fall back to literals. An eager encoder that inserts and references in the same flight — accepting a blocked-stream budget of at least 1 — compresses from the first request.RFC 9204 §2.1.2, §2.2.2.3

The two settings, re-read for latency #

SETTINGS_QPACK_MAX_TABLE_CAPACITY (decoder-advertised) still sets whether a dynamic table exists at all and how big it is. High latency does not change its meaning, but it raises the stakes: a bigger table amortizes more repeated headers across a long-lived connection, which matters most exactly when re-establishing state is expensive. Keep it zero and you are static-only: guaranteed non-blocking, but you forgo compression on the custom headers that repeat across a session.

SETTINGS_QPACK_BLOCKED_STREAMS is the setting latency truly reshapes. It is the encoder's permission to reference entries the decoder may not have acknowledged yet: precisely the entries whose acknowledgments are a round trip away. With a budget of 0, the encoder must wait out that round trip on every new entry (§6.3); with a budget above 0, it can insert and reference in the same flight and compress immediately, risking only a bounded, recoverable block if that flight is reordered.

The tuning decision #

The high-latency choice comes down to who pays the round trip:

  • Budget 0 (never block). Every new dynamic entry costs one RTT of literals before reuse. On a 300 ms path with short-lived connections, the dynamic table may never pay for itself. The connection ends before enough requests accrue to amortize the insert. Static-only is often the honest choice here.
  • Budget > 0 (block deliberately). The encoder inserts and references together, so compression starts on request one. The exposure is bounded by the budget and, critically, a block on a long path clears in at most the time for the in-flight insert to arrive. That is the same one-way delay the data was already paying. You are not adding latency; you are declining to double it with an extra acknowledgment round trip.

The reordering risk deserves a second look on long paths. High RTT alone does not cause blocking — reordering and loss do. A long but clean path (ordered delivery) lets an eager encoder reference fresh entries with almost no blocks, because insert and reference, sent in the same flight, tend to arrive together. It is the long and lossy path (where a lost encoder-stream packet strands every request that referenced its inserts) where a large blocked budget backfires, holding many streams' worth of decoder memory while the retransmission crosses the long path (§6.5 traces exactly this).

Worked example: reuse across a 300 ms round trip #

Figure 6.4-1 contrasts the two encoders on a 300 ms-RTT link. The conservative encoder inserts entry L, then must wait for the Insert Count Increment to return (300 ms) before it dares reference L; every request in that window ships L's field as a literal. The eager encoder bundles the insert of M and a HEADERS section referencing it into the same flight with RIC = 2; because insert and reference travel together, the decoder applies the insert and immediately decodes the section, and the very first request is compressed. The only price is a blocked-stream budget of at least 1, and a bounded block if that single flight is reordered.

Note

The decoder's own feedback timing interacts with all of this. RFC 9204 §2.2.2.3 lets a decoder delay Insert Count Increments to coalesce them. That is sensible for saving packets, but on a high-latency path a decoder that also batches acknowledgments compounds the encoder's wait. If you control both ends of a long link, having the decoder acknowledge promptly is as important as tuning the encoder's budget.

In practice

editorial Decide by connection lifetime and path quality, not RTT alone. Long-lived connection on a clean high-RTT path (an API client streaming requests over satellite): turn the dynamic table on and give a real blocked-stream budget. The eager strategy wins big and blocks are rare. Short-lived connections, or a high-loss mobile path: lean static-only or a tiny budget. The dynamic table barely amortizes and every extra blocked stream is memory held hostage across a long retransmit. When unsure, measure two things: the fraction of requests that fall back to literals (too high → raise the budget) and the fraction of streams that block (too high → lower it, or fix the loss).

Takeaways #

Latency turns QPACK's acknowledgment loop from free into the dominant cost of dynamic compression. A conservative encoder pays a full round trip of literals before reusing each new entry; a SETTINGS_QPACK_BLOCKED_STREAMS budget above 0 lets it insert and reference in the same flight and compress immediately, at the price of bounded, recoverable blocking. Tune by connection lifetime and path quality: clean and long-lived favours an eager budget, short-lived or lossy favours static-only. When the long path is lossy, blocking stops being hypothetical. That is what §6.5 debugs from a real trace.