- proxy between client and server that serves client stored responses from similar previous requests
- forwards request it hasn’t already seen, stores and forwards response
- intercepts request that it has seen already, serves stored response
- stores response only temporarily
- like assistant that takes load off of boss by doing repeated tasks
- advantages:
- lower latency, because request travels shorter distance
- lower bandwith, because network behind proxy doesn’t see traffic
- less load, because server doesn’t see traffic
- disadvantages:
- complexity, because additional node in network
- stale content, because server can’t tell proxy to update content (except a public proxy it controls)
- security risk, because proxy reads every request and response
- beware: often used as synonym for browser cache ❗️
- beware: never rely on cache, neither on server nor on client, resources may not be cached, e.g. limited disk space, deletion by user, expiration, etc. ❗️
Cache types
- can be private or public
Public / Shared
- on network, e.g. ISP, CDN, load balancer, etc.
- needs to termiante HTTPS
- serves multiple users, “shared”
- used often in front of dynamic Web server
Private
- on client, e.g. memory cache, browser cache, Service Worker, HTTP/2 push cache, etc.
- doesn’t need to terminate HTTPS, because not yet on network
- serves single user
- used in every client
Cache settings
- server specifies in response headers
- beware: don’t use
Cache-Control
request header, doesn’t make sense ⚠️ Cache-Control: #<directive>
: deactivation, location, expiration, validation, multiple directives are comma-separatedEtag: <token>
: validation token- beware: client shouldn’t specify cache settings, because doesn’t know what resource is, server knows best because by definition its the resource owner ⚠️
- beware: client shouldn’t send
Cache-Control
request headers to cache to change cache behavior, cache shouldn’t sendAge
response header to client to show age of cache entry ⚠️ - beware: always set cache explicitly, otherwise cache can do whatever it wants, e.g. browser by default caches GET but not POST ❗️
- beware: browsers never cache PUT or DELETE no matter if cache is set explicitly or not ❗️
Deactivation
- if response is cached
Cache-Control: no-store
: don’t cache- beware: confusing name, should have been
no-cache
❗️ Cache-Control: no-cache
: likeno-store
but more efficient, shorthand formax-age=0, must-revalidate
, fastest way to get always fresh response- beware: makes sense only with validation, otherwise is less efficient than
no-store
because cache is wasted space since never used ❗️ - beware: confusing name, should have been
must-revalidate
❗️ - if response always changes between two requests use
no-store
, if response doesn’t always change between two requests useno-cache
- if omitted and any other
Cache-Control
is set then caches
Location
- where response is cached
Cache-Control: private
: in private cache onlyCache-Control: public
: in any cache, both public and private- if omitted uses “best guess”, usually public cache doesn’t cache, i.e. as if defaults to
private
Expiration
- how long response is cached, time until cache entry expires
Cache-Control: max-age=<seconds>
: relative to time of requestCache-Control: s-maxage=<seconds>
: likemax-age
but only respected by public caches- can specify both, public cache prefers
s-maxage
- beware: replaces older
Expires
header with absolute time ❗️ Cache-Control: must-revalidate
: disallow using expired cache entry, expired cache entry is used only in exceptional circumstances, e.g. when server is down- beware: don’t use usually, only relevant if existence of response is confirmation in itself, e.g. financial transaction ❗️
- beware: confusing name, should have been
no-expired
❗️ - if omitted uses “best guess”
- beware: some browsers (Firefox, Safari as of Jan 2021) ignore expiration when user manually reloads page, always request page from server even if has cache entry (conditionally if has cache entry with validation token), needs to set
Cache-Control: immutable
to use cache entry instead as expected ❗️
Validation
- instead of throwing away cache entry after expired, can keep it and validate with server on next client request if can continue to use it
- cache asks server if updated, if not just reuses cache entry, if did fetches new cache entry
- server generates token based on resource, usually fingerprint of content, e.g. hash
- server sends token in
ETag
response header, opaque to cache - after cache entry expired, cache sends conditional request, sends token in
If-None-Match
request header - if fingerprint didn’t change, server sends headers only with
304 Not Modified
, cache reuses expired cache entry until new expiration from headers - if fingerprint changed, server sends resource with
200 OK
, cache updates cache entry - beware: replaces older
Last-Modified
response header andIf-Modified-Since
request header that used timestamp, weaker validator because constrained by resolution of seconds ❗️
Cache entry
- representation of resource
- stored as key value pair
- key is URL and method if caches multiple methods
- value is response
- beware: a cache entry may be shared across websites just the same way the underlying resource may be shared across webpages ❗️
Variation
- resource might have multiple representation that vary based on content negotiation, e.g.
Accept-Encoding
,Accept-Language
, etc. - cache breaks website on client if doesn’t distinguish multiple representations of same resource, e.g. wrong encoding, wrong language, etc.
- server specifies request headers for content negotiation in
Vary
response header, e.g.Vary: Accept-Language
- cache uses varied request headers as additional keys
- beware: cache must normalize request header values since can differ between clients, otherwise creates duplicate cache entries, e.g.
Accept-Language
could befr-FR
orfr_FR
, etc. ⚠️ - usually only relevant for public cache, since for same client content negotiation usually doesn’t change
Updating resources
problem: update resource which is cached, when server can’t update cache (except a public proxy it controls)
if a resource is updated on the server, then in a cache can have exist either one of two versions, since a cache can have either still the old or already the new version
the server loses control over its own resource on the client, needs to wait until cache entry expired to be sure
for a single resource this is not too bad, is just outdated
but resources are almost always interdependent with other resources, e.g. assets on a webpage, webpages on a website, etc.
assume that any resource is interdependent, because almost all are except maybe a direct download link
mixing new and old versions of interdependent resources can create breakages because the update can create an incompatibility between the old version of one resource and the new version of another resource
updating two interdependent resources is enough to create a breakage if old version of one and new of other aren’t compatible
must never mix old and new versions, because can’t guarantee that old and new versions are always compatible
but can’t guarantee this, because can’t rely on existence of any cache entry, no matter the cache expiration, e.g. might have expired, might have been deleted, might not exist yet, etc.
cache has fail-open behavior that loads new version, assume resource can be new version at any point in time
assume if there are old and new versions of a resource, then eventually gets a mix of them
must not create old version, because then it’s always the new version
either don’t cache or don’t update a resource
beware: it’s not enough to minimize old versions in cache, must make sure there are never old versions in the cache in the first place ❗️
Revving
- change resource URL when content changes, allows to not update a resource
- creates new resource for each update, doesn’t update existing resource ever
- set long cache duration, e.g. 1 year
- caches resource until it updates, almost perfect cache, except that old version is left lying around wasting storage
- can use fingerprint, version number, random string, etc.
- prefer fingerprint based on contents, doesn’t change between versions if content doesn’t change, cache is not busted if didn’t change, e.g. hash
- use build workflow, e.g. modify filename, etc.
- can only rev “internal” URLs, because can change without breaking things, e.g. assets, etc.
- beware: don’t rev external URLs, because can not change, e.g. HTML documents, links from other websites, etc. ⚠️
Cache strategy
- don’t cache responses for requests that change with query parameters or request body, e.g. GraphQL API, etc.
- for “internal” / non-user-facing resources use revving and long cache, e.g. assets, etc.
- for “external” / user-facing resources don’t cache, e.g. HTML documents, HTTP API, etc.
- beware: if does cache “external” resource risks breakages due to incompatibilities from mixing of new and old versions of interdependent resources ⚠️
- cache personalised responses at most in private cache, e.g. profile page, etc.
- beware: never cache personalised responses in public cache, otherwise second client can see response for first ❗️
- usually caches only responses with certain status codes, e.g. < 500 because server error is not normal
- beware: caching and not caching a resource doesn’t correspond to a static / dynamic Web server, e.g. can statically serve a changing resource, can dynamically serve a non-changing resource ❗️