Skip to content

Caching for HTTP/2

General

For caching, we need to consider the interactions between the browser's cache, network latency, HTTP/2 Push and even some browser bugs. This is why configuring caching correctly is no small feat.

For now we have chosen sane defaults for static asset caching. They "just work", but we will offer ways of tuning the cache behavior in the future.

This page describes in detail how do we go about managing the browser's cache and also the internal cache that the server keeps.

For the back-end application (which ShimmerCat serves API domains ), it's up to the application to set the caching headers that make more sense for its dynamic content.

HTTP/2 caching at the browser

There is no way yet to remove a fresh resource from the browser's cache

This has nothing to do with ShimmerCat, but with the state of things regarding web standards. Here is a detailed explanation of the issue:

Say that the browser receives the following headers in a response:

cache-control: max-age=172800
date: Mon, 21 Mar 2016 10:43:56 +0000
etag: "A3_ar0tAu4rs-0"
...

Notice the max-age value, it is telling the browser that this asset is to be considered "fresh" for 172800 seconds, that is, two days. The browser may not ask for it to the server until the two days go by.

As long as the asset doesn't change, this is a good thing, because it will save the browser re-downloading the asset and round-trips required for re-validation. But if you, the website developer, decide to change the asset, then the user may not receive the new version until after two days.

Unfortunately, nothing in HTTP/2 improves this situation. At the time of this writing, Chrome and Firefox have specific bugs "ensuring" that this remains broken, check for example this blog post, this answer at Stack Overflow, and this email on the W3 IETF working group.

URL query parameters are still needed to update assets at clients.

Therefore, even with HTTP/2, the way to solve the stale assets issue is to create a unique URL for each new asset version, and then ensure that the user receives that new URL when she re-visits the page This technique is known as cache busting.

For example, if your HTML page looks like this,

<html>
    <head>
    ...
        <script src="/js/myapp.js"></script>
    </head>
    ...
</html>

then the /js/myapp.js part should be modified to include something that you can change if you ever change this file. Like this:

<html>
    <head>
    ...
        <script src="/js/myapp.js?vh=3dabnfw2"></script>
    </head>
    ...
</html>

If you ever change the myapp.js file, then you can change the part after the query string to have the browser ask for the new version.

Since most web application frameworks already have a sensible way of adding these bits to URLs, we simply assume that assets may come with this query strings and for the most part ignore them.

I say for the most part because for images, when doing coordinated image loading we special-case the parameter bil. Also we reproduce all the query-string parameters when doing HTTP/2 PUSH.

If you want to be future-proof with our server, you can simply use vh as the query string parameter name for cache-busting.

Caching and HTTP/2 Push

HTTP/2 Push implies that the server sends beforehand most or all of the assets that the browser is going to need to render the page. The pushed URL must include any query parameters, otherwise the browser won't use it.

For a returning user, it does not make sense however to push all the assets, but only the assets that have changed since the user's last visit. To know which assets the user already has, we employ a technique similar to the one proposed by Kazuho Oku as an Internet Draft, adapted to the capabilities of today's browsers.

More concretely, here is how we do.

  • We use two cookies, with the names sc0csn and sc1cdig.

  • The first of the two cookies is session-specific and will expire as soon as the user closes the page. It gives an identifier to the session that we use server-side to store the assets that have been sent to the user, for a short period of time.

  • The second cookie's value is filled with a cache digest. This digest enumerates approximately which assets have been sent to the browser, and in its current implementation it uses a decreasing number of bits for each new asset assimilated into the digest. For example, a if a browser holds 100 assets, this digest uses 1000 bits.

  • Because the cache digest may fill-up very quickly, we set a max-age value for the second cookie of two days. If the user visits the site before the cookie expires and at least one of the pushed assets changed in the meantime, the server will re-spawn the cookie, making the digests in the cookie last for longer.

We only set the cookies with HTTP/2 connections. HTTP/2 uses a compression format for headers that ensures that the actual bytes making up the contents of the cookies are transferred just once for a complete session.

The value of max-age is two days

We set the value of max-age to quite a low value by default (two days), to keep in sync with the max-age attribute for the cache digest cookie. Otherwise HTTP/2 Push would be less effective upon second visits to the site.

Etags are good

We generate the Etag for each asset directly from a checksum of the file contents. This is important for swarm deployments, where a client may connect to different processes under different visits. In those cases, the server and the client always use the same Etag for the same resource, independently of which server the client connects to.

If something goes wrong

Notice that during development one often tries many versions of the assets, and this way the cache digest may grow up a lot in size, since a few bits would be added for each asset version that the browser loads. Use --disable-push if this becomes a problem.

If the cache digest grows beyond the maximum size that ShimmerCat can compress effectively, ShimmerCat empties the cookie and sets it again during the next page fetch.

If you would rather not use caching during development, use the option --disable-ua-caching.

Caching at the server

Some assets require some processing before being handed over to the browser. For example, JavaScript files, CSS, and HTML are best served as gzip-compressed files for all browsers that support it (and all modern browsers support it). Another type of processing is segmentation of image files before being handed over, as required by our technique of coordinate image loading.

Instead of doing such processing each time that an asset is requested, ShimmerCat sports a sophisticated in-disk cache. The cache contains the processed version of those assets for each relevant "degree of freedom" for the asset. One of those degrees of freedoms is for example the Content-Encoding supported by the asset. Because what we store is closed to the concept of representation which has been used in HTTP ling for years, we call this the representation cache.

Access to the in-server cache is coordinated through locks in the Redis database, so that if you have several worker processes running in the same computer, using the same scratch directory and connected to the same Redis instance (the usual for worker processes started with say, shimmercat devlove) they will still store just one version of the asset.

Conversely, if you are running workers of ShimmerCat in different computers, but they are still serving the same site and are allowed to connect to the same Redis server or cluster, then each group of ShimmerCat workers running in the same computer will use a common cache located in the filesystem of their common computer, but it will be independent of groups of workers in separated computers. This way, by default, the bandwidth that the swarm of servers has available composes in parallel, i.e., disk bandwidth is added.

The representation cache updates itself automatically

If you change any files in the locations that you indicate as the root of your virtual domains, the representation cache will pick up the latest version of the assets automatically. What is more, if several workers try to access the same representation of the new asset and a job to compute that representation is in progress, they will block until the representation becomes ready.

Where in the filesystem is the representation cache

The representation cache is stored at <scratch-dir>/r-cache. For devlove mode, the scratch folder is a sub-folder where ShimmerCat runs. You can change the scratch folder with the options --working-dir and --scratch-dir-name.