Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Google Chrome does not do multiplexing with http2

I am building a webapp and serving it over http2. However when I analyze network in Google Chrome (Version 59.0.3071.115 (Official Build) (64-bit))'s developers tools, it is clear that multiplexing does not work as there are only 6 active connections (like with http1.1) and the rest of connections are queued.

Why is this? Or are my expectations not correct?

The screenshot (you can see that protocol is http2):

Chrome's network tab showing only 6 active connections with the rest queued in spite of using http2

Update #1:

  • The backend runs on nginx 1.13;
  • I am using custom modules loader which loads all the scripts at once (by creating script tag with async attribute in a loop);
  • The screenshot shows that for line 8 and beyond the browser has received request to start downloading resources, but the white portion of line shows that this scripts were queued and the actual downloading started only when slots became available (see how line 8, 7 and 9 start loading once lines 2, 3 and 4 are done; the same goes for lines 11, 12, 13 and 5, 6, 7).
like image 818
Kirill G. Avatar asked Jul 29 '17 00:07

Kirill G.


People also ask

Does Google Chrome use HTTP2?

HTTP/2 protocol is Not Supported on Google Chrome 27. If you use HTTP/2 protocol on your website or web app, you can double-check that by testing your website's URL on Google Chrome 27 with LambdaTest.

What is multiplexing in HTTP2?

Multiplexing HTTP requests allows the usage of a single connection per client, meaning that a single connection between the client and the webserver can be used to serve all requests asynchronously, enabling the webserver to use less resources, thus support more users at the same time.

Why is HTTP2 not used?

Due to strict origin rules in the protocol, one HTTP/2 connection cannot control the other across IP addresses and domain names.


2 Answers

I think this is a bug in Chrome, or at least a needless restriction.

This is easily tested.

I created a simple example HTML file, which downloads 25 copies of the same javascript file (with a query param to make it look like a different resource):

<!DOCTYPE HTML>
<html>
<head>
        <title>Test for Lots of JS files</title>
        <meta name="robots" content="noindex">
<body>
</body>
        <h1>This is a test for Lots of JS files</h1>

        <script src="/assets/js/test.js?v=01"></script>
        <script src="/assets/js/test.js?v=02"></script>
        <script src="/assets/js/test.js?v=03"></script>
        <script src="/assets/js/test.js?v=04"></script>
        <script src="/assets/js/test.js?v=05"></script>
        <script src="/assets/js/test.js?v=06"></script>
        <script src="/assets/js/test.js?v=07"></script>
        <script src="/assets/js/test.js?v=08"></script>
        <script src="/assets/js/test.js?v=09"></script>
        <script src="/assets/js/test.js?v=10"></script>
        <script src="/assets/js/test.js?v=11"></script>
        <script src="/assets/js/test.js?v=12"></script>
        <script src="/assets/js/test.js?v=13"></script>
        <script src="/assets/js/test.js?v=14"></script>
        <script src="/assets/js/test.js?v=15"></script>
        <script src="/assets/js/test.js?v=16"></script>
        <script src="/assets/js/test.js?v=17"></script>
        <script src="/assets/js/test.js?v=18"></script>
        <script src="/assets/js/test.js?v=19"></script>
        <script src="/assets/js/test.js?v=20"></script>
        <script src="/assets/js/test.js?v=21"></script>
        <script src="/assets/js/test.js?v=22"></script>
        <script src="/assets/js/test.js?v=23"></script>
        <script src="/assets/js/test.js?v=24"></script>
        <script src="/assets/js/test.js?v=25"></script>

</html>

I then did the same, but adding the async attribute, in case Chrome decide to block downloading while processing the Javascript:

        <script src="/assets/js/test.js?v=01" async=""></script>
        <script src="/assets/js/test.js?v=02" async=""></script>
        ....etc.

and the same again but with the defer attribute:

        <script src="/assets/js/test.js?v=01" defer=""></script>
        <script src="/assets/js/test.js?v=02" defer=""></script>
        ....etc.

The /assets/js/test.js file was empty. So there would be no execution delays, nor dependencies except those that the browser added.

I saw some interesting results! This is all with Chrome 60.0.3112.78 or 60.0.3112.101, and I'm using Apache, but saw same results as you saw for Nginx.

With an HTTP/2 server we see the following results:

With a plain script tag all the scripts are loaded in parallel (but presumably executed in order). There is no 6 connection limit as under HTTP/1.1: Javascript with no async or defer

With an async script tag the scripts are loaded in parallel in groups of 6 - exactly as you noted: Javascript with async

Clicking on them shows they WERE downloaded over HTTP/2.

With a defer script tag the scripts is the same as the results for using the async tag - a throttling to 6 downloads at a time.

This does not make sense - Chrome is restricting your Javascript downloads, but only if you use async or defer to improve your downloads from blocking rendering!

As sbordet stated, the same does not happen for images in the viewport - so multiplexing DOES work on Chrome, it just appears to be needlessly limited for Javascript in async or defer mode. This is a real limitation, if you are considering not bundling scripts together any more under HTTP/2, as many advise you no longer need to do.

The same does not happen on Firefox, nor Edge. Though it does happen on Opera (a Chromium based browser).

So that's the bad news. The good news is that they "may" have fixed it. When I try Chrome Canary (62.0.3190.0) I can't repeat this behaviour. However when I use Web Page Test with Canary (which it gives 62.0.3190.1 in the user agent string, so should be practically the same) it is repeatable, so not 100% sure they have fixed this after all...

Have raised a bug with the Chrome team for this so will see what they say: https://bugs.chromium.org/p/chromium/issues/detail?id=757191

All in all, HTTP/2 on both server and client does seem a little in flux at the moment, as both sides tweak and tune their implementations to get optimal use out of this still relatively new protocol. Still, it's surprising to see Chrome hit with this since Google started this off with their SDPY implementation (which HTTP/2 is heavily based upon) so you would expect them to be ahead of the curve not behind...

** Update **

Chrome team got back and confirm this is a restriction of current implementation of HTTP/2 in Chrome. They were seeing performance issues when many assets very called at once, as HTTP/2 allows, so restrict non-critical items (including async/defer and items not visible in the viewport) to HTTP/1.1 limit of 6.

Even though HTTP/2 has concept of prioritisation of requests after they are sent, the performance issues were seen before they were prioritised and sent (e.g. checking cache, cookies... etc) so HTTP/2 prioritisation doesn't help here.

They hope to improve this in future.

So guess I was right that it's an implementation issue as we get used to the new HTTP/2 world and have to optimise our browsers and servers for it!

like image 167
Barry Pollard Avatar answered Nov 10 '22 03:11

Barry Pollard


There may be multiple reasons why Chrome decides to limit multiplexing when using HTTP/2.

For example, the behavior is very different when you are downloading a page with a large number of images, depending on whether the images are shown or not in the browser viewport.

The documents you are downloading are scripts and scripts may block, or depend on each other, or otherwise change the way the browser downloads resources.

In fact, if you go to online examples of HTTP/2 such as https://http2.golang.org/gophertiles?latency=0, you will see that Chrome does multiplex really well the download of images (but only if they are displayed in the viewport).

Therefore for your case it could be something with the scripts; perhaps they have dependencies on each other, and that is why Chrome cannot multiplex them beyond 6 at a time.

I would not be surprised if this is a limit of JavaScript loaders that assume HTTP/1.1 and are now obsolete with HTTP/2.

You can use the "Performance" tab in the Chrome Developer Tools to understand more about the performance of your page.

You also want to look at tools such as Page Speed, that give you an idea of how to optimize your page.

In summary, I don't think it's an issue with how Chrome implements HTTP/2, but rather something in your application/scripts that is not optimized for HTTP/2.

like image 28
sbordet Avatar answered Nov 10 '22 02:11

sbordet