Caching
A cache keeps a copy of data that is slow to get somewhere that is fast to read. Done well, it cuts latency and database load by ten times or more. Done badly, it shows users wrong prices. This lesson covers both sides.
By the end you can
- Name the layers where caches live and what each one is good for
- Implement cache-aside with ioredis, a TTL with jitter, and delete-on-write
- Explain a cache stampede and stop one with a Redis lock
- Set HTTP
Cache-Controlheaders that are safe for public and private data - Decide, per feature, how stale the data may be
The everyday picture
In a neighbourhood grocery store, the fastest-selling items sit on the shelf right next to the counter: water, chips, soft drinks, mobile top-up cards. The cashier reaches them in two seconds. Everything else is on the main floor, the storeroom at the back holds more, and the distribution centre (DC) holds the rest. Walking to the storeroom takes five minutes. Ordering from the DC takes a day.
The front shelf is small, so the manager must choose what goes there (the fast movers), when to remove things (expired or slow-selling items), and what to do when the price changes (replace the price tag, or the cashier charges the old price). A software cache has exactly the same three questions: what to keep, when to throw it away, and how to keep it correct.
Where caches live
A cache can sit at any layer between the user and the database. The closer to the user, the faster it is, and the harder it is to clear.
| Layer | Rough read time | Good for | Hard part |
|---|---|---|---|
| Browser or app (HTTP cache, local storage) | ~0 (no network) | Images, product lists, settings | You cannot clear it remotely; only expire it |
| CDN edge (Files, object storage and CDNs) | a few to tens of ms | Static files, public API responses | Never put private data here |
| API process memory | well under 1 ms | Small, rarely changing lookups (VAT rates, config) | Each copy has its own; clearing one clears only one |
| Redis | ~0.5 to 1 ms on the same network | Shared cache for all API copies | Another system to run; invalidation |
| Postgres buffer cache | part of every query | Hot table and index pages, automatically | Not under your control (sized by shared_buffers, Running Postgres in production) |
Two words you will hear all the time. A cache hit means the data was in the cache. A cache miss means it was not, so you went to the source. The share of requests that hit is the hit ratio.
Quick check
Which of these caches can you not clear from your side once a copy is stored there?
You run Redis and the API processes, so you can delete keys or restart them. A copy in the user's browser lives on their device. You can only wait for it to expire, which is why its max-age needs care.