4 ms·
I really appreciate your going into these details, but I still don't understand the "oblivious" strategy thing which supposedly improves baseline performance OO
by tannhaeuser 6y ago
I really appreciate your going into these details, but I still don't understand the "oblivious" strategy thing which supposedly improves baseline performance OOTB when the criterion is the time to first render of above-the-fold content given otherwise same conditions. You say the effect of delivering all resources concurrently is that "the most-critical resources [are delivered first], no matter where in the dependent-resource queue they started". But this isn't proven; it's just a different heuristic to apply to traffic shaping assuming small resources are needed early vs assuming dependent resources (+ HTML markup itself) are loaded asynchronously in the order the browser parses an HTML DOM (the default behaviour, and the one authors can influence directly). You're not magically increasing the bandwidth by multiplexing so something has to give.
- derefr 6y agoThe "oblivious approach" is "just a different heuristic to apply", yes. It's just one that happens to work especially well for HTML rendering, given that large resources tend to be depended upon in a way (e.g. an img or video tag) that gives them a pre-download bounding-box size, allowing the rest of the page to render around them and to not reflow once they're loaded; while the types of resources that tend to be small (like CSS or JS files) are mostly depended on with the semantics that they can potentially reflow the page entirely when they finish loading, which means the browser completely inhibits interactivity (and/or rendering, depending on the browser) until those resources finish loading. The only things that kind of break this heuristic are: • large single-file Javascript SPAs. These are usually just marked with the `async` attribute, such that the initial DOM of the page can first render, then be gradually enhanced when the SPA loads. But with HTTP/2 + ES6, you can also just not pack the SPA into a single file, instead relying on ES6 modules, which will each be individually smaller and therefore will end up being delivered first by the content-oblivious round-robin chunk-delivery strategy. • web fonts, which are large and will necessarily cause a complete reflow of the page once loaded. Currently, browsers make a special loading-precedence exception for web fonts; though it doesn't matter as much right now, as they're still mostly served from third-party CDNs rather than as first-party HTTP/2 flows. AFAIK, for regular web-page retrieval, these are exactly the total set of things that currently benefit from being server-pushed along with the first response; everything else just gets handled well even without server-push. (If you're curious, server-push is really designed for the use-case of pushing secondary API responses along with an initial API response; to allow for GraphQL-like "single-round-trip resource-hierarchy/-graph walking" in a way that's more friendly for caching layers than comingling the results into a single resource-representation. It makes the most sense in a Firebase-like system, where un-asked-for server-pushed resources can be obliviously accepted and dumped into the client-side in-memory set of synced entities asynchronously to the parsing of the initial response; and then, once the dependency is parsed out on the client side, the client can discover that it already has the entity it wants in its in-memory entity store, and doesn't even need to make the request.)
- MauranKilom 6y agoApologies if I've missed some part of the explanation that answers this. The way your previous reply described it, the benefit of HTTP/2 is loading smaller things earlier. But given that goal, couldn't the browser just prioritize JS/CSS files over images/videos for the same effect, without any new protocol that sets in stone some heuristic purely based on content size?
- derefr 6y ago> the benefit of HTTP/2 is loading smaller things earlier No, the benefit of HTTP/2 is a lack of head-of-line blocking. Head-of-line blocking can be easily seen when big things block small things, but that's not what it is. What it is, is when something doesn't make progress because another thing is being waited for. Imagine a multimedia container file-format where you can't interleave audio frames with video frames, but rather need to put the whole audio track first, or the whole video track first. This format would be unsuited to streaming, because downloading the first chunk of the file would only get you some of one track, rather than useful (if smaller) amounts of all the tracks required for playback. Note that this is true no matter which way you order the tracks within the file—whether the audio (smaller) or video (larger) track comes first, it's still blocking the progress of the other track. HTTP/2 is like a streaming multimedia container format: it interleaves the progress of the things it loads, allowing them to be loaded concurrently. This doesn't just mean that small things requested later can be prioritized over large things requested early (though it does mean that.) It also means that, for example, if you load N small Javascript files that each require a compute-intensive step to parse+load (GPU compute shaders, say), then you won't have to wait for the compute-heavy load process of the previous files to complete, before you begin downloading the next ones; but rather you can concurrently download, parse, and load all such script files at once. Insofar as they don't express interdependencies, this will be a highly-parallelizable process, much like serving independent HTTP requests is a highly-parallelizable process for a web server. One benefit of HTTP/2's lack of head-of-line blocking, that would be more talked-about if we had never developed websockets, is that with HTTP/2, you get a benefit very much like websockets, just using regular HTTP primitives. You can request a Server-Sent Events (SSE) stream as one flow muxed into your HTTP/2 connection, and receive timely updates on it, no matter what else is being muxed into the connection at the same time. Together with the ability to make normal API requests as other flows over the same connection, this does everything most people want websockets for. So the use-case where websockets are the best solution shrinks dramatically (down to when you need a time-linearized, stateful, connection-oriented protocol over HTTP.) > new protocol that sets in stone some heuristic Note that there's actually no explicit specification of the order in which HTTP/{2,3} flows should be delivered. What I'm calling "content-oblivious round-robin chunk scheduling" is just the simplest-to-implement strategy that could possibly meet HTTP/2's non-head-of-line-blocking semantics (and so likely the strategy used by many web servers, save for the ones that have been highly-optimized at this layer.) But both clients and servers are free to schedule the chunks of HTTP flows onto the socket however they like. (They can even impose a flow concurrency cap, simulating browsers' HTTP/1.1 connection limit and starving flows of progress. It'd make the client/server a non-conformant HTTP/{2,3} server, but it'd still work, as what progress "should" be being made is unknowable to the peer.) It's a bit like saying an OS or VM has a "soft real-time guarantee" for processes. Exactly how does the OS scheduler choose what process will run next on each core? Doesn't really matter. It only matters that processes don't break their "SLA" in terms of how long they go without being scheduled.