Networking / HTTP/3 / compression · 2026-10-20
HTTP/3 QPACK: compress headers without letting one stream stall the connection
HTTP/3 carries HTTP over QUIC, where each request stream can make progress independently. A header compressor can accidentally undo that advantage: if a header block refers to a dictionary entry that has not arrived yet, the receiver cannot decode it. QPACK is the compression format designed for this exact tension. It allows dynamic compression while placing a limit on how many streams can wait for missing state.
Why HTTP/2's compressor needed a redesign
HPACK, used by HTTP/2, keeps a dynamic table synchronized through the same ordered byte stream as HTTP frames. That works well with TCP's ordered delivery. QUIC instead delivers independent streams: a later stream may arrive while bytes on another stream are lost or delayed. If a header on stream B references a table insertion that was sent on stream A but has not arrived, stream B is undecodable even though its own bytes are present.
QPACK separates encoder instructions from request streams. The encoder sends insertions on a dedicated encoder stream; the decoder sends acknowledgements and capacity updates on a decoder stream. Header blocks state how much dynamic-table progress they require. This avoids relying on a single connection-wide ordered byte stream, but it cannot make a dependency disappear.
request stream A: headers ───────▶ decode now
request stream B: references entry #12 ──┐
│ waits for #12
encoder stream: inserts #12 ─────────────┘
decoder stream: acknowledges insertions
Blocked streams: deliberate, bounded waiting
A header block is blocked when it references a dynamic-table insertion the decoder has not received. QPACK's SETTINGS_QPACK_BLOCKED_STREAMS setting limits how many request streams an encoder may leave in that state. Set the limit to zero and the encoder must avoid risky references: compression may be worse, but a missing insertion cannot block request decoding. Permit a positive value and the encoder can trade some head-of-line waiting for smaller headers.
The limit is not a timer and does not promise a stream will unblock quickly. It is a concurrency bound on blocked streams. Network loss, a slow peer, or a badly tuned encoder can still create latency. HTTP/3 removes TCP's cross-stream transport ordering dependency; it does not eliminate application-level and compression-state dependencies.
Dynamic table capacity is a memory contract
The dynamic table stores reusable name/value pairs such as frequently repeated cookie or content-type fields. The decoder advertises the maximum capacity it is willing to use, and the encoder must respect it. Entries are evicted as the table fills. A reference is useful only if both endpoints agree the entry exists at that point in the instruction sequence.
This is a protocol state machine, not merely a cache. Implementations must validate instruction order, capacity, and references. A malformed or impossible reference is a connection error, not a cue to guess what the sender meant. Resource limits matter too: untrusted peers should not be allowed to induce unbounded table allocation or endless blocked work.
Reproducible lab: observe blocking with a protocol implementation
Use a QUIC/HTTP/3 test server and client that expose QPACK settings or frame traces. First request the same large set of headers repeatedly with dynamic compression enabled. Capture a trace and identify encoder-stream insertions and header-block references. Then configure the peer's blocked-stream limit to zero and repeat. Compare encoded header bytes and verify request streams no longer wait on dynamic-table insertions. Finally introduce packet loss or delay on the encoder stream and observe how a positive blocked-stream budget affects latency. Record the implementation version and settings: different clients may choose different insertion strategies while remaining protocol-compliant.
Operational checklist
- Inspect client and server QPACK settings rather than assuming defaults.
- Measure header bytes and time-to-decode, not only total response time.
- Test loss and reordering; a clean LAN hides the dependency that matters.
- Bound dynamic-table memory and blocked streams for resource control.
- When debugging latency, separate QUIC stream delivery delay from QPACK dependency delay.
Takeaway
QPACK preserves much of HTTP/2's header compression while adapting its state exchange to QUIC's independent streams. Its central compromise is explicit: dynamic references save bytes, but may make a request wait for an insertion. The blocked-stream limit turns that compromise into a controllable resource and latency policy.