Learning objective. Put streams, flow control, prioritization, and loss isolation together by designing a concrete stream plan for an application whose traffic mixes render-critical, bulk, and real-time data.
The workload #
Consider a single connection serving a rich web application. Its traffic falls into classes with genuinely different needs:
- Render-critical. The HTML document, the style sheet, the main script. Must arrive reliably and early; a page cannot paint without them.
- Bulk media. Images, fonts, a video segment. Reliable, but not render-blocking; users tolerate progressive arrival.
- Real-time. Live telemetry, cursor/position updates, a presence heartbeat. Latency- sensitive and disposable: a late value is worthless (§3.4).
- Background. Prefetch, analytics beacons, software updates. Should never contend with anything above.
The design task is to map each class onto the right transport primitive using the four levers from this chapter: reliable stream vs datagram, one stream per object vs shared, urgency, and incremental.
A stream plan #
| Class | Primitive | Per object? | urgency (u) | incremental (i) |
|---|---|---|---|---|
| HTML document | reliable bidi stream | yes | 1 | no |
| CSS / JS (render-blocking) | reliable bidi stream | yes | 2 | no |
| Images / fonts | reliable bidi stream | yes | 4 | yes |
| Live telemetry / positions | DATAGRAM |
n/a | n/a | n/a |
| Prefetch / analytics / updates | reliable bidi stream | yes | 7 | no |
Every reliable object gets its own stream, so a loss isolates to that object (§4.4) and the server can schedule them independently by urgency (§4.3). Render-critical resources take the lowest urgency values so they win the shared pipe; images share a lower priority level as incremental so they render progressively in round-robin rather than one-at-a-time; background traffic sits at urgency 7 where it yields to everything. Telemetry leaves the stream world entirely for datagrams: unreliable and unordered, so it can never head-of-line-block a real request and never wastes bandwidth retransmitting a stale position.
Sizing and pitfalls #
Two cross-cutting choices finish the plan:
- Flow-control windows for the bulk class. The media transfers are the ones that need a
window covering the bandwidth-delay product (§4.2); size
initial_max_dataand the stream windows for them, and make sure a stalled media reader cannot exhaust the shared connection window and starve the render-critical streams. - Stream-count limits for the concurrency. A page opening dozens of object streams at once
will stall on
STREAMS_BLOCKEDifinitial_max_streams_bidiis set for a handful (§4.1); provision the limit for the fan-out you expect.
editorial The single highest-leverage decision here is splitting real-time off the reliable path. Teams often push position updates or telemetry down a normal stream "because it's simpler," then discover that under loss those updates queue behind their own retransmissions and arrive in a stale burst. Moving them to datagrams removes both the head-of-line coupling and the wasted retransmission in one change. The second-highest is not over-bundling reliable objects: keep them on separate streams so loss and priority act per object, not per bundle (§4.4).
Takeaways #
A good stream plan is four decisions per traffic class — reliable-or-datagram, per-object-or- shared, urgency, incremental — plus flow-control and stream-count sizing for the whole connection. Render-critical resources get their own high-priority streams, bulk media round-robins beneath them, real-time data rides datagrams, and background yields to all. That completes the streams-and-flow-control chapter; Chapter 5 moves up a layer to how HTTP/3 frames turn these streams into requests and responses.