Caching strategies

Cache-aside, read-through, write-through, write-behind. Pick one, run traffic through it, and see the read path light up - and the database load drop.

9 min read

A cache is a small, fast store in front of a slow one. The idea takes one sentence. Everything interesting about caching lives in the second-order effects: what happens on a miss, what happens on a write, and what happens the moment the cache is wrong or gone.

Going from 90% to 95% hit rate halves your database load

The hit rate is the share of requests the cache answers. It is the number that decides whether the database behind it is comfortable or on fire, and it does not behave the way the percentage suggests. What reaches the database is the miss rate, and going from 10% misses to 5% is a halving.

served by the cachemiss, goes to the database
Hit rate
90%
Database queries, of 10,000/s
1,000/s
90%
At 90% the database sees 1,000 queries a second. Drag to 95% and it sees 500.

Improvements at the top of the range are worth far more than they look on a dashboard: 99% to 99.5% halves the backend load again. It runs the other way too, which is why a small dip in hit rate during a deploy can double database load with nothing else changing.

That makes a cache load-bearing in the literal sense. The database is provisioned for the miss traffic, not the real traffic, so the day the cache disappears is the day you find out what you were actually running.

Four strategies, four different things to be wrong about

Every strategy reads roughly the same way: try the cache, fall back to the database on a miss. They differ in who fetches on a miss, and above all in what happens on a write.

The app checks the cache, reads the database itself on a miss, then fills the cache. Writes go to the database and delete the cached copy.
Read that misses
1.Read: miss
2.App reads the database (30 ms)
3.App writes the result into the cache
Cache logic spreads through your code, and a cold key sends a herd of misses to the database.
The read path barely changes between strategies. The write path is where each one picks what to sacrifice.

Cache-aside is the default because it is explicit, and the application stays in control. Read-through moves the miss handling into the cache. Write-through keeps the cache and database in lockstep at the cost of slower writes.

Write-behind is the one to choose deliberately. Its risk is not stale reads, because reads go to the cache, which has the newest value. The risk is that you have told the user “saved” while the only copy lives in one process's memory. That makes it excellent for view counters and terrible for payments.

Three ways a cache takes down what it protects

Caches fail in ways specific to caches, and all three failures share a shape: the database suddenly receives traffic it was never sized for.

Database queries per second, log scale
At 8 seconds a hot key expires and 10,000 requests for it miss in the same second.
Peak: 10,000 queries a second against a database sized for 1,000.
All three are the same event: the miss rate jumps from a few percent to all of it, against a database sized for the few.

A stampede is a hot key expiring while thousands of requests want it. Nothing coordinates them, so they all miss together. Penetration is requests for keys that do not exist, which are never cached, so each one costs a query; a bloom filter (see the bloom filter lab) turns them into free rejections. An avalanche is many keys expiring at once, or a cache node restarting empty.

Jittered TTLs fix avalanches for the same reason jitter fixes retry storms in the retries lab: synchronised clients are the real problem in both, and randomness breaks the synchrony. What changes when there is more than one cache is the subject of distributed caching.

The short version

  • The database sees the miss rate, so 90% to 95% hits halves its load.
  • Strategies differ mainly on writes: explicit, inline, in lockstep, or deferred.
  • Write-behind is fast and can lose acknowledged writes.
  • Stampedes, penetration and avalanches all jump the miss rate to 100%; locks, bloom filters and jitter stop them.