HTTP/2: Multiplexing Changed the Frontend Network Model
- date
- category
- Networking
- also in
- Frontend
- reading
- 2 min / 335 words
HTTP/2 keeps HTTP semantics but changes the wire format.
A request is no longer a textual block like:
GET /app.js HTTP/1.1
HTTP/2 uses binary frames.
Streams
Every request/response exchange belongs to its own stream.
A simplified model:
one TCP connection
│
├── stream 1: /app.js
├── stream 3: /styles.css
├── stream 5: /api/me
└── stream 7: /font.woff2
Frames from different streams can be interleaved.
S1 HEADERS
S3 HEADERS
S1 DATA
S5 HEADERS
S3 DATA
S1 DATA
That is multiplexing.
A slow response on one request does not stop the HTTP protocol from carrying frames of other streams.
Less need for many connections
HTTP/1.1 achieved concurrency through several TCP connections.
HTTP/2 can serve many parallel requests over a single connection.
For the frontend that reduces the point of techniques such as domain sharding.
One well used connection can be more efficient than several independent TLS/TCP connections.
Header compression
Many frontend requests carry similar headers:
Origin
Cookie
Accept
User-Agent
...
HTTP/2 uses HPACK to compress HTTP fields across messages on a connection.
That helps especially with many small requests.
It does not mean:
the number of requests stopped mattering.
Every request still has:
- server work,
- scheduling,
- bytes,
- application latency,
- cache consequences.
HTTP/2 removes part of the transport cost, not the cost of a request existing.
TCP is still shared
Here comes the subtlety.
HTTP/2 multiplexes many streams, but all of them travel over one TCP byte stream.
If TCP loses a segment:
packet loss
|
v
TCP must recover missing bytes
|
v
later bytes wait
At the transport level that can temporarily hold back data belonging to many HTTP/2 streams.
So:
HTTP/2 stream independence
!=
transport independence
That is transport-level head-of-line blocking.
Prioritization is not a magic frontend scheduler
HTTP/2 has prioritization mechanisms, but the practical behavior depends on the browser, the server and the priority signaling in use.
The frontend should not design performance around the assumption:
browser will always schedule exactly as I imagine
A better tool remains control over the dependency graph of the page:
critical CSS
critical JS
lazy content
preload where justified
What do we see in DevTools?
In the waterfall many requests can start almost simultaneously.
That does not mean many TCP connections.
They may be separate HTTP/2 streams on one connection.
Which matters for performance analysis:
parallel requests
!=
parallel sockets
HTTP/2 solved multiplexing at the HTTP level.
But it still inherited TCP.
HTTP/3 keeps a similar streaming model of HTTP and moves it onto QUIC.