HTTP/2 file download is a bit slower than HTTP/1.1 for two main reasons: frame overhead and flow control.
In HTTP/1.1, if you use Content-Length
delimited downloads, the only bytes that are downloaded are the content bytes.
In HTTP/2, however, each DATA
frame carries 9 extra bytes for the frame header. At the normal max frame size of 16384 bytes that is a small overhead, yet it is present.
The bigger contributor to possible slow downs is HTTP/2 flow control.
Client must be sure to enlarge the default session and stream flow control windows, by default both at 65535 bytes.
The way HTTP/2 works is that the server keeps a send window for each HTTP/2 session (connection) and for each stream in that session.
When the download begins, the server is entitled to send only a number of bytes allowed by the send window, for that stream or that session, whichever exhausts first. Then it has to wait for the client to send WINDOW_UPDATE
frames, that replenish both the stream and session flow control windows, telling the server that the client is ready to receive more data.
For small windows such as the default ones, this mechanism may kill the download performance due to the network latency between client and server, especially if it is implemented naively.
The server will be stalled most of the time waiting for the client to send a WINDOW_UPDATE
so that the server can send more data.
Multiplexing plays a double role. While it allows to initiate the download of many files concurrently (possibly many more files than HTTP/1.1, that may be limited by the fact that it can only open a smaller number of connections), it is also true that data downloaded for each stream contributes to reduce the session send window. Each stream may still have a non exhausted send window (and therefore it could send more data), but the session window is exhausted and so the server must stall. The streams are competing with each other to consume the session send window. The server implementation is also important because it has to correctly interleave frames from multiple streams.
Having said that, it is still possible for HTTP/2 to achieve parity with HTTP/1.1, provided that you have a pretty advanced implementation of both the client and the server, and that you have enough tuning knobs to control the critical parameters.
Ideally, on the client:
- ability to control the session and stream initial flow control windows
- a good implementation that sends
WINDOW_UPDATE
frames to the server while the server is still downloading, so that the server never stalls; this may require self-tuning features depending on the bandwidth-delay product (similarly to what TCP does)
Ideally, on the server:
- ability to correctly interleave frames from multiple streams of the same session (e.g. avoid to download all frames of the first stream, then all the frames of the second stream, etc., but rather one frame of the first stream followed by one frame of the second stream, then again one frame of the first stream, etc.)
[Disclaimer, I am the HTTP/2 maintainer of Jetty]
Jetty 9.4.x supports all the features above, since we have worked with the community and customers to make sure that HTTP/2 downloads are as fast as possible.
We implemented proper interleaving on the server, and Jetty's HttpClient
and HTTP2Client
provide respectively high level and low level APIs to deal with HTTP and HTTP/2 requests. The flow control is implemented in BufferingFlowControlStrategy
and allows to tune when WINDOW_UPDATE
frames are sent (although not yet dynamically).
The clients also have options to configure the initial flow control windows.
Everything in Jetty is pluggable, so you may write even more advanced flow control strategies.
Even if you don't use Java or Jetty, make sure to dissect (or write) the libraries you're using on both the client and the server so that they provide the features mentioned above.
Finally, you need to try and measure; with a proper HTTP/2 implementation and configuration, the multiplexing effects should come into play, therefore increasing parallelism and reducing resource utilization on both client and server, so that you will have an advantage over HTTP/1.1.