Was reading on optimal ways of caching, mainly to offload the server by reducing the number of hits.
304s are beautiful, aren’t they? Moving your scaling to the frontend has a lot of benefits which includes serve content faster, fewer origin hits, .. Let’s look at the various levels where caches could be implemented.
- Browser level
- CDN proxy level
- Application level
Browser can cache resources locally and we can specify how long the resources should be stored locally (will talk about the cache control rules in a while). It is the fastest cache amongst but have the least amount of control. You can’t invalidate browser cache on demand.
CDN proxy cache
With proxy cache, an intermediate server (mostly CDN) is used as cache layer. This reduces the number of hits to origin servers.
Application cache (Not AppCache)
Application cache is where we employ a cache layer, like redis, memcached, .. and let the application manage the cache. One example would be to store API/DB calls. This reduces load on the servers and ends up in faster responses.
Let’s take a look at the following request headers which are involved in caching
GET /dist/lib.js HTTP/1.1 Cache-Control: max-age=86400 ETag: "c7c-2268d-534cf78e98dc0" Vary: Accept-Encoding
Cache-Control header accepts comma delimited strings to specify the caching rules for a particular asset.
public- Defines the response is safe to cache, by any cache, and is shareable between requests. Ideal for scripts, stylesheets..
private- Defines the response is only safe to cache at the client, and not at a proxy, and should not be part of a shared cache
no-cache- Defines whether the response should be cached or not
must-revalidate- Says the cache layer that it must revalidate the content after expiry
proxy-revalidate- Same as must-revalidate, but only to proxy caches
max-age- Specifies the maximum age of the response (seconds)
ETag (Entity tag)
ETag header is a hash generated for a particular resource and could be generated based on content, timestamp, .. It allows the client to make conditional requests. Once the ETag value of an object is known by the client the hash value may be sent to the server in subsequent http requests in If-Match or If-None-Match headers. If the ETag hash isn’t changed, then the resource isn’t sent again and ends up in 304 status.
The Vary header tells any HTTP cache which parts of the request header, other than the path and the Host header, to take into account when trying to find the right object. It does this by listing the names of the relevant headers. If there are multiple headers that influence the response, they would all be listed in a single header, separated by a comma. This is useful for content that might have the same URI but differs based on user agent or or accept-language.
Could server generated ETag header be used to avoid explicit resource versioning? Well, without explicit versioning or hashing there’s no way for the CDN Edge to be aware of content changes in the Origin before cache expiration so the old version doesn’t get flushed when we want it to. ETags don’t really help with this at all, they just optimize the data transfer when possible.
I’ve listed down few quick points to keep in mind when implementing caching.
- Cache all static content. This includes mostly external libraries, stylesheets, images, ..
- Concatenate the files which reduces the number of HTTP requests made
- Version the concatenated files by build number (or) generated hash based on contents/meta-data of the file
- Offload it to a CDN. Still not stold why to use a CDN, read this piece - https://www.sitepoint.com/7-reasons-to-use-a-cdn/
- Use in-memory caches like redis, memcached as cache layer to cache API calls