Caching for HTTP/2
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" ...
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>
/js/myapp.js part should be modified to include something that you
can change if you ever change this file.
<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
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
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
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-agevalue 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.
--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
Caching at the server
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
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
For devlove mode, the scratch folder is a sub-folder where ShimmerCat runs.
You can change the scratch folder with the options