Concept 6 · Scaling and Trade-offs·14 min read
Caching Strategies & Cache Invalidation
A second copy of the truth, and every way of keeping it honest.
Big Idea
A cache only helps if it’s usually right and cheap to check, and every caching strategy is really a different answer to one question: when the underlying data changes, how does the cache find out, and what does a reader see in the meantime.
Why This Exists
Why The Hardest Part Is Not The Caching
Databases are slow relative to memory, and the same data (a user's profile, a product page, a popular post) often gets read far more often than it changes, recomputing or refetching it from scratch on every single read wastes capacity that a cache could save entirely.
The moment you add a cache, you've created a second copy of the truth, and now you have to decide what happens when the original changes: does the cached copy update immediately, eventually, or does it just quietly go stale and serve wrong data until something notices.
Phil Karlton's famous line, 'there are only two hard things in computer science: cache invalidation and naming things,' is not a joke about caching being finicky, it's a real acknowledgment that keeping a cache correct under concurrent reads and writes is a genuinely hard distributed systems problem, not an afterthought.
Think Of It Like
A Cheat Sheet Of Your Bank Balance
A cache is like keeping a printed cheat sheet of your bank balance instead of calling the bank every time you want to check it. It’s fast, you glance at the paper instead of waiting on hold. But the moment money moves in or out of the actual account, that paper is wrong until you update it.
Do you update the paper the instant a transaction happens (write-through), let it happen in the background a moment later (write-back), or just throw the paper away after an hour and force a fresh call next time you need it (TTL)? Each choice trades a bit of speed for a bit of risk of looking at stale numbers.
The Concept
The Pieces That Make Up A Caching Decision
The most common pattern: the application checks the cache first, on a miss it reads from the database, then writes the result into the cache for next time. Reads are fast after the first miss, but the cache and database can drift out of sync if the underlying data changes without going through this same path.
Every write goes to the cache and the database together, synchronously, before the write is considered done. This keeps the cache always consistent with the database, at the cost of every write now paying the latency of two systems instead of one.
A write goes to the cache immediately and is considered done, then gets flushed to the database asynchronously later. This makes writes fast, but risks data loss if the cache fails before the flush happens, and needs careful design to avoid it.
The simplest invalidation strategy: every cached entry expires automatically after a set duration, forcing a fresh read from the source of truth. It doesn't require any explicit invalidation logic, but it means the cache can serve stale data for up to the full TTL window, and picking that window is itself a trade-off between staleness and load on the database.
The application actively deletes or updates a cache entry the moment the underlying data changes, e.g. a user edits their profile, and that write path also deletes the cached profile so the next read is forced to refetch. This gives tighter consistency than TTL alone but requires the application to correctly find and invalidate every cache entry that could be affected by a given write, which gets hard fast as relationships between cached data grow. The usual answer is to track, alongside the cache, which cached entries depend on which underlying thing, so changing that thing can invalidate all of them in one step instead of hoping every write path remembered.
Happens when a popular cached entry expires and a burst of concurrent requests all miss the cache at the same instant, all hammering the database simultaneously trying to refill it. Mitigations include locking so only one request refills while others wait, or staggering TTLs so not everything expires at the same second.
Decides what gets removed when the cache is full, not just when data goes stale. LRU (least recently used) is the most common default, evicting whatever hasn't been accessed in the longest time, on the assumption that recently used data is likely to be used again soon. When that assumption fails, counting how often an entry is used, with older activity fading over time, keeps genuinely popular data resident while letting a one-off scan pass through without flushing everything.
A cache can live in the browser (HTTP caching headers), at the CDN edge, in front of an application server (like Redis or Memcached), or even inside a single process's memory. Each layer trades how close it is against how much it can hold and how many different clients can share it.
Matters as much as how: caching data that's read far more often than it's written, and that can tolerate at least some staleness, gives the best return. Caching data that changes on nearly every read, or where staleness is unacceptable (like real-time account balances), often isn't worth the complexity a cache adds.
Requests that ask for something which never existed, like a probe for user ID -1 or a stream of invented identifiers. Every one misses the cache, reaches the database, finds nothing, and writes nothing back, so the next identical request repeats the whole trip. Unlike a stampede this can be driven deliberately, and the fix is counter-intuitive: cache the "not found" answer too, with a short expiry, so the second request stops at the cache.
Two shapes of the same problem. Breakdown is one popular key expiring and sending a focused burst at the database. Avalanche is thousands of unrelated keys, all written at the same moment by a restart or a nightly job, expiring within the same second and shifting their combined traffic at once. Locking fixes the first, because there is one key to queue behind. Only spreading expiry times apart fixes the second.
When one cache holds both disposable data and data that must survive, like sign-in sessions alongside cached query results, a blanket eviction policy will happily throw the sessions away to make room. The usual answer is to let eviction consider only entries that carry an expiry, and give an expiry only to the disposable ones, so anything without a deadline is never a candidate for removal.
Key Terms
The Vocabulary Of A Second Copy
A pattern where the application checks the cache first, falls back to the database on a miss, and populates the cache afterward, the most common general-purpose caching pattern.
A duration after which a cached entry automatically expires and must be refetched from the source of truth, the simplest and most common invalidation mechanism.
A surge of simultaneous requests all missing the cache at once (often right after a popular entry expires) and overwhelming the database trying to refill it.
The rule that decides which entries get removed when a cache reaches capacity, most commonly LRU (least recently used).
A caching strategy where writes update the cache and the underlying database synchronously, keeping them always consistent at the cost of write latency.
Cached data that no longer matches the current state of the source of truth, the central risk every invalidation strategy is trying to manage.
Repeated requests for data that does not exist anywhere, which miss the cache every time and reach the database every time, because there is no result to store.
Many cached entries expiring at nearly the same moment, usually because they were all written together, sending their combined read traffic at the database at once.
Worked Example
Write-Through vs Write-Back
The Shape Of It
Cache-Aside On A Read
is the data there?
not found or expired
the source of truth
for next time
future reads hit the cache
Failure Mode
When The Cache Stops Absorbing Load
Stampede: a popular key expires
Unprotected refill
Locked refill
Penetration: a key that never existed
A stampede needs a real entry to expire. Penetration needs nothing to exist at all. Requests arrive for an identifier that was never in the database, perhaps a probe for user ID -1 or a stream of invented IDs. Each one misses the cache, reaches the database, finds nothing, and writes nothing back, so the cache never learns and the next identical request makes the same trip.
That is the part worth sitting with: a cache only shields the database from questions it has already answered, and “nothing found” never gets recorded as an answer. The fix is to record it anyway, storing the empty result with a short expiry so the second request stops at the cache. Short, because the record might genuinely appear later.
Seen In The Wild
Where The Read-To-Write Ratio Pays
Reddit and Twitter/X use Memcached and Redis extensively to cache hot content (popular posts, feeds) so millions of reads don't hit the database directly for the same rapidly re-requested data.
CDNs like Cloudflare and Akamai apply TTL-based caching to static assets at the edge, letting a browser or edge node serve a cached copy for minutes to days without contacting the origin server at all.
Facebook's TAO caching layer sits in front of its social graph database specifically to absorb the enormous read-to-write ratio of social data, most content is read far more often than it's ever edited.
Amazon's product pages use aggressive caching with careful invalidation on price and inventory changes, since showing a stale price is a real business problem, illustrating how the choice of TTL versus explicit invalidation depends on how costly staleness actually is.
Key Points
What To Carry Forward
Every caching strategy answers the same question differently: how does the cache learn the source of truth changed, and what does a reader see before it does.
Cache-aside is the default pattern: check cache, miss, read database, populate cache.
Write-through keeps cache and database always in sync at the cost of write latency; write-back is fast but risks losing data before it's flushed.
TTL is the simplest invalidation approach but trades correctness for simplicity, entries can be stale for up to the full TTL window.
Cache stampede is a real failure mode when a hot entry expires and many requests hit the database at once, mitigated with locking or staggered expiration.
Cache what's read far more than it's written and can tolerate some staleness, that's where caching pays off the most.
Common Mistakes
Where This Usually Goes Wrong
Adding a cache without a real invalidation plan, treating 'it'll expire eventually' as sufficient for data where staleness actually matters.
Using a TTL so long that users see meaningfully outdated data, or so short that the cache barely reduces database load at all, without deliberately choosing the trade-off.
Not accounting for cache stampede, letting a single hot key's expiration cause a burst of simultaneous database load right when the system can least afford it.
Caching data that changes on nearly every read, adding cache complexity and a consistency risk for a hit rate that's barely better than not caching at all.
Forgetting that a cache is a second copy of the truth, and every code path that writes the underlying data needs to also handle the cache, not just the 'main' write path.
Never caching the answer 'this does not exist', so a stream of requests for invented identifiers passes straight through the cache to the database every single time.
“I keep a mental cache of which humans give treats on command. Never invalidated it once. That’s why I still try the trick on the mail carrier.”
Try It Yourself
Two Ways To Keep It Fresh
Quiz Review
Check your understanding
Question 1 of 6
Why is cache invalidation considered a genuinely hard problem instead of just an implementation detail?
- ✓Because it requires knowing, at the moment underlying data changes, every cached representation of that data that now needs to be updated or removed, across potentially many cache keys, layers, and services, while concurrent reads and writes keep happening. Getting this wrong doesn't crash the system, it silently serves incorrect data, which is often worse than an outage because nobody notices immediately.