Storage & Data
Caching
The fastest way to answer a read is to not ask the database at all, and the hard part was never storing the copy, it's knowing when to throw it away.
Storage & DataFoundation5 min read
After this lesson, you should be able to:
- Explain why the hard part of caching is invalidation, not storing the copy in the first place.
- Compare TTL expiry against explicit invalidation on write: what each guarantees and what each costs to implement.
- Diagnose a stale-read bug as a missed or delayed invalidation, not a database problem.
- Identify which kind of data (tolerant of brief staleness versus not) belongs on which invalidation strategy.
At a Glance
- A cache trades read latency for staleness risk: every cached read is a bet that nothing changed since it was stored.
- Storing the copy is the easy part. The hard part is knowing exactly when to throw it away.
- Caching absorbs reads so the database only has to handle the traffic a cache can't answer, not every request.
01 The Problem
A product page for a popular item gets hit 5,000 times a second, and each hit runs a query joining product, pricing, and inventory tables. Even a well-indexed Postgres instance chokes at that read volume on one row, because every one of those 5,000 requests is doing the same work over and over for an answer that hasn't changed since the last request asked.
Two options, both with a cost. Don't cache, and the database falls over the moment traffic spikes, whether that's a flash sale or an item going viral. Cache the page response instead, and now when the price changes, a markdown, a correction, the cached copy keeps serving the old price for however long the cache is configured to live. A customer sees a price that's wrong, or buys something that's actually out of stock.
This is caching. Every cache is the same bet, made over and over: is the read cheap enough to be worth serving an answer that might be slightly wrong, and if it's wrong, how fast do you find out.
02 What It Is
A cache is a copy of data stored somewhere faster to read from than the original source, memory instead of disk, a nearby node instead of a database call across the network, kept around so a read can be answered from the copy instead of recomputing or refetching from the source. Three things define how a cache behaves: what triggers a write into it (usually populated on the first read that misses, "cache-aside"), how long an entry lives before it's assumed stale (a TTL), and what happens to the cached copy when the source itself is written.
Storing the copy is the easy 10% of caching. The hard 90% is invalidation: knowing precisely when a cached value no longer reflects the source of truth, and updating or evicting it before something wrong gets served. A cache with no invalidation strategy isn't a cache, it's a countdown timer on how long you're willing to be wrong.
In Plain Terms
A whiteboard in a shared office lists today's parking spot assignments, copied by hand from the building manager's master list so nobody has to walk to her office every time they need to check. It's faster to glance at the whiteboard than to go ask her. But if she reassigns a spot at 9am and nobody updates the whiteboard, everyone who checks it for the rest of the day gets a wrong answer with total confidence, because the whiteboard never says "I might be out of date," it just says an answer. The whiteboard isn't the problem. Forgetting to erase it is.
03 How It Works
Rendering diagram…
Try it: send a request (cache miss, fetches from the database), then update the DB value and send another request before the TTL runs out. The cache returns the old price, not the new one. Then try Invalidate Cache instead of waiting out the TTL.
Cache
Empty
No value cached
Database
$19.99
Source of truth
No requests sent yet.
04 Trade-offs
| TTL expiry | Explicit invalidation on write | |
|---|---|---|
| Staleness window | Bounded but guaranteed: stale for up to the TTL, every single time, even if nothing changed | Usually zero: the cache is corrected the moment the write happens |
| Implementation cost | Trivial. Set a TTL and forget it. | Real work. Every write path has to know exactly which cache keys it affects. |
| Failure mode | Silent: a value that's simply wrong until the TTL clock runs out | A missed invalidation path is also silent, and worse, it has no expiry to eventually self-correct |
| Best for | Data that's fine to be a little wrong for a bit: view counts, non-critical listings | Data where a stale read causes real damage: price, inventory, account balance |
Watch out
It's tempting to think adding a cache is free performance: same data, just faster. It isn't. A cache is a second copy of the truth that can silently disagree with the first one, and every write path added afterward has to remember to keep them in sync. The failure mode isn't "the cache is slow to update," caches don't get slow, they get wrong, and wrong looks exactly like right until the moment it doesn't.
05 Where You'll Actually See This
The URL shortener case study is this mechanism end to end: Redis is checked first, Postgres is the source of truth behind it, and its 03 diagram is exactly the cache-aside pattern shown above, applied to one specific problem.
It also grounds two things covered elsewhere. SQL vs NoSQL already names Redis as the fast key-value layer sitting in front of a relational source of truth: caching is the actual mechanism that pairing depends on. And latency vs throughput talks about a fixed pool of workers queueing under load: a cache is exactly what keeps that queue from forming in the first place, by absorbing the reads a database would otherwise have to answer one at a time.
Caching only ever helps with that read side, though. Once writes are the bottleneck, a cache has nothing left to give, which is exactly where database sharding picks up.
Everything above assumes the cache and the source are close together, the same data center, a network hop measured in single-digit milliseconds. CDN is this exact TTL-versus-invalidation trade, stretched across a planet instead of a rack.
How to reason about it
When asked how you'd add caching to a system, don't start with which cache technology. Start with invalidation: what happens to the cached value when the source changes, and how long is the system allowed to be wrong if that path fails. A TTL-only answer signals you haven't thought about correctness. Naming the specific write paths that have to invalidate specific keys is what signals you have.
06 Quick Check
A team adds a Redis cache in front of their product-price database to speed up reads. Reads get faster immediately. Three weeks later, a price correction doesn't show up on the site for six hours. What's the most likely cause?