Skip to content

Cache

a stored copy a browser may reuse instead of asking

What it is

A cache is a store that keeps copies of data close to whoever reads them, so that a repeated read is answered from the copy instead of from the slower place where the data really lives. The plural is caches; one stored item is a cache entry, and the data in it is a cached copy.

A cache is never the source of truth. The file, row or server response it copies stays authoritative, and every entry can be thrown away and fetched again. That separates a cache from a replica, which replication keeps complete and in step on purpose, and from a buffer, which holds data in transit that may exist nowhere else yet. The Linux page cache is both: it keeps file data that was read and takes writes first, marking those pages dirty until they reach the disk.

Caches stack up between users and data. The kernel’s page cache sits under every file a program reads. PostgreSQL keeps its own pool of pages, shared_buffers, on top of it. A browser keeps HTTP responses in a private cache for one person; a CDN or reverse proxy keeps a shared cache for many. A page’s script can keep responses in Cache Storage through a service worker, apart from the browser’s HTTP cache. And an application can keep values it has computed in a key-value store such as Redis.

The vocabulary is the same at every layer. A read that finds its entry is a hit; one that does not is a miss and goes on to the origin. The share of reads that hit is the hit ratio. Each entry is found by its key, which for an HTTP cache is at least the request method and the URL. A full store drops entries to make room, which is eviction, and its eviction policy picks what goes; least recently used, or LRU, is the common one.

Why it matters

A cache is usually the cheapest speed a system can buy, cheaper than new hardware or a new schema. The bill arrives as memory, as copies in places that are hard to reach and as copies that stop being true. Three decisions about the store settle most of it.

The first is where it sits. The nearer the copy is to the reader, the more it saves: a copy in the browser skips the network as well as the server. It is also further from the owner. A Redis instance can be emptied with one command; a copy on a visitor’s phone stays there under whatever rules were sent with it.

The second is size, because memory given to a cache is taken from something else. PostgreSQL’s documentation puts the default for shared_buffers at typically 128MB. On a dedicated server with 1GB or more of memory it suggests starting at 25%, and because PostgreSQL depends on the operating system’s cache as well, it says more than 40% is unlikely to work better than a smaller amount. Two caches share one machine.

The third is who shares the store. RFC 9111 separates a private cache, for one user, from a shared cache, which reuses the responses it stores for more than one user. A personal response in a shared store is how one person’s page reaches another, so a shared cache must not reuse a response to a request that carried an Authorization header unless the response explicitly allows it.

One rule follows from all three: a cache must be safe to empty. If emptying it loses data, it has become a database without a database’s guarantees. How long an entry stays good enough to serve, and what removes it early, belong to caching, the practice; the store’s own job is to hold copies, find them by key and give up space when it runs short.

What it looks like in practice

On a 464 MB host that runs its own forge, database and web server, the kernel’s page cache decides what an alarm should measure. A dead-man’s switch that raises the alarm by going quiet runs there every fifteen minutes, with jitter: a script measures available memory and free disk and pings an external service only while both are above their thresholds. Available is the right measure because on a healthy Linux box free memory sits near zero, with the kernel using idle memory as cache. The script read the correct column, but six variable names around it said “free”. Someone trusting those names against a ten per cent threshold would see twelve megabytes free on 464 MB, call the check broken and make a one-character change, leaving an alarm that breaches permanently and is muted within a week. The naming has since been corrected.

One layer up, an application cache sits in front of a database. The note on optimising a database before sharding it puts one in front of a sell-out. When every unit of an item is claimed, the database can say when the earliest claim lapses, ten minutes after it was made, and the note caches “fully reserved” with exactly that expiry. Until then visitors are turned away by the cache rather than by the database. A claim released early, by a declined card or an abandoned basket, deletes the entry, so the unit is on sale again at once. The cache may refuse; only the database sells, through a conditional UPDATE. A stale entry can cost a sale for a few seconds and can never sell one unit twice.

The outermost layer is the visitor’s own device, where the site keeps no store of its own. Its page on cookies names IndexedDB, Cache Storage and a service worker among the places it leaves unused, so no offline copy of the site is kept on the device.

Where it goes wrong

After a restart, the origin takes every read at once. A cold cache misses on everything, so a deploy, a cache restart or a failover sends the whole read load to the database. A system that coped only because most reads were hits can fall over before the cache refills. Size the origin for the miss path, warm the cache before it takes traffic, and bring traffic back in steps. PostgreSQL’s pg_prewarm does this for the buffer cache, and can record what shared buffers held and read it back after a restart.

The cache grows until the machine runs out of memory. Redis sets no memory limit by default on 64-bit systems, so an instance used as a cache grows with its key space until the operating system steps in. Set a limit and a policy together. Under noeviction, a full instance answers commands that would cache new data with an error, which suits a store that must keep everything and surprises one meant to forget. Redis’s documentation calls allkeys-lru a good default absent a reason to prefer another:

maxmemory 256mb
maxmemory-policy allkeys-lru

A visitor is served a page in the wrong language. The key leaves out an input the value depends on. A page rendered in Danish is stored under its URL alone, and the next reader gets Danish whatever they asked for. Build the key from every input that changes the output, such as language, currency or role. For HTTP caches, the Vary response header names the request headers that belong in the key, and RFC 9111 lets a cache reuse a stored response only when those headers match or after revalidating it.

The installed app keeps running old code. Entries in Cache Storage carry no expiry of their own and outlive an update of the service worker script. Files changed under the same cache name keep being served from the old copies, and a cache renamed each release without cleanup leaves one more on the device every time. Name each cache with a version, as the Service Workers specification recommends, and delete every older one once the new worker takes over.

Further reading

Glossary