Caching and Invalidation
Adding a cache takes an afternoon. Knowing when its data is wrong is the actual work.
The story
An online shop caches product details in Redis for 24 hours. When a price changes, the code does the textbook thing: update the database, then delete the cache key so the next read fetches the new price.
During a sale, the marketing team drops a TV from ₹60,000 to ₹45,000. The database has the new price. The cache key was deleted. And yet for the rest of the day, product pages show ₹60,000, and conversion on the sale's headline item is zero.
Here's what happened, in the space of about 10 milliseconds:
The reader fetched the old price before the update and wrote it into the cache after the delete. Each step was correct on its own. The ordering wasn't.
Phil Karlton's line, "There are only two hard things in Computer Science: cache invalidation and naming things", gets quoted as a joke. This is why it isn't one.
The idea: caches are layered
You rarely have one cache. You have several, and each one can serve stale data.
Each layer closer to the user is faster and harder to invalidate. You can delete a Redis key instantly. You can't reach into a user's browser. So the closer to the user, the shorter the TTL or the more immutable the content (versioned filenames like app.3f9a2c.js, cached for a year).
How data gets into the cache
| Pattern | Good for | Risk |
|---|---|---|
| Cache-aside | Read-heavy data. Only caches what's actually read. | First read is slow, and the race in the story |
| Read-through | Same, with the cache library doing the DB read | Same staleness issues |
| Write-through | Data that's read right after it's written | Slower writes, caching data nobody reads |
| Write-behind | Very write-heavy counters, metrics | Data loss if the cache dies before the flush |
| Write-around | Write-once, rarely re-read data (logs, uploads) | First read after write always misses |
How data gets out: invalidation
Four tools, usually combined:
- TTL. Every key expires eventually. This is your safety net: whatever bug you have, staleness is bounded by the TTL. A 24-hour TTL on a price is a 24-hour bug.
- Delete on write. Update the DB, then delete the key. Cheap and usually right. The story's race is rare but real; it's fixed by a short TTL, a second delete a moment later ("delayed double delete"), or the next option.
- Event-driven invalidation. Read the database's change log (CDC, e.g. Debezium on the Postgres WAL) and delete the keys for every row that changed. It catches writes from any code path, including the admin script nobody remembered.
- Versioned keys. Put a version in the key (
product:42:v17) and bump it on write. Old values are never read again and age out on their own.
The other failure: the stampede
A hot key, say the home page feed, expires. In the next 100 ms, 5,000 requests miss the cache at the same moment and all hit the database with the same expensive query. The database falls over, so the cache never gets refilled, so the next requests miss too.
Fixes:
- Request coalescing / lock: the first miss takes a short lock and rebuilds the value, and everyone else waits for it or gets the stale value.
- Stale-while-revalidate: serve the expired value while one background job refreshes it.
- Jittered TTLs:
TTL = 300s ± 10%so keys written together don't all expire together. - Early refresh: refresh hot keys shortly before they expire.
Let a hot key expire, then try the fixes:
Cheat sheet
| Question | Answer |
|---|---|
| Default pattern | Cache-aside + delete on write + a TTL as the safety net |
| How long a TTL? | As long as the business can tolerate wrong data: seconds for prices and stock, hours for profile pictures |
| Must never be stale (balance, stock at checkout) | Don't serve from cache. Read the DB for the decision. |
| Writes from many code paths | Event-driven invalidation from CDC |
| Static assets | Versioned filenames + cache for a year |
| Hot key | Coalesce misses, stale-while-revalidate |
| Many keys expire together | Jitter the TTL |
| Cache full | LRU eviction by default. LFU if a few keys are always hot. |
| Cache goes down | The DB must survive the miss rate, or you need graceful degradation (SD 6) |
Say it in the interview
"Cache-aside in Redis for product pages: read the cache, fill it on a miss, delete the key when the product changes, and keep a 5-minute TTL as the safety net for missed invalidations. Hot keys get request coalescing so an expiry can't stampede the database. Checkout reads price and stock from the database, never the cache."
Go deeper
- SD 1 · Caching: layers, patterns and eviction in depth.
- SD 2 · Chapter 9 — Caching: why caches work, and how they fail.
- SD 3 · Q19 — Cache Stampede and Q18 — Cache Strategies.
- SD 6 · Drill: The Cache Dies: what your design does when Redis is gone.