Cache Stampede
Understand how a mass cache invalidation event triggers a write thunderstorm, why it differs from thundering herd, and how stale-while-revalidate and background refresh prevent it.
Introduction
A cache stampede occurs when a write invalidates a hot cache key and many concurrent readers miss together, overwhelming the backing store while trying to rebuild the same value. The anti-pattern is not simply "using invalidation"; it is creating a large miss window with no coordination or graceful stale-data policy.
Mental model: deleting one popular key can turn one cheap cache hit into N simultaneous database reads. The mitigation keeps readers served while one controlled path refreshes the value.
TL;DR
- A cache stampede is a write thunderstorm triggered by cache invalidation, not expiry. Something updates the underlying data, invalidates a cache key, and every reader tries to repopulate it simultaneously.
- Unlike thundering herd (expiry-driven), stampedes are often event-driven: a single database write broadcasts a cache delete to every consumer, who all race to re-read the DB.
- The three defences are: stale-while-revalidate (serve stale content while refreshing in background), write-through cache (update cache before invalidating), and event-driven background refresh (the write path publishes a "refresh needed" event, a single worker handles it).
- Stampedes are especially dangerous on hot write paths: a high-traffic product price update can invalidate a key that 50,000 readers are actively using.
What It Is
Your product catalog caches every category page for 10 minutes. A merchandiser updates a product price. Your application invalidates the cache key for that category. This is correct behaviour (you don't want stale prices).
But the price update happened at 11:58 a.m. on Black Friday, when 50,000 users are concurrently browsing that category. The moment the cache delete executes, all 50,000 in-flight requests get a cache miss. Every one calls SELECT * FROM products WHERE category_id = ?. Your database connection pool exhausts in milliseconds.
The merchandiser just accidentally DDoSed your own database by updating one price.
This kind of incident is easy to misdiagnose: it looks like a traffic spike even though a single cache invalidation caused the database load.
What makes stampedes particularly cruel is that they can follow a legitimate freshness decision. Skipping invalidation avoids this particular failure mode, but serves stale data instead. The challenge is finding the middle ground, which is what the fix section covers.
Here's the timeline of what happens under the hood:
T+0ms: Merchandiser clicks "Save" on price update
T+5ms: DB write completes
T+6ms: Application calls redis.DEL("category:electronics")
T+7ms: 50,000 in-flight readers get MISS
T+8ms: 50,000 SELECT queries hit the database
T+10ms: Connection pool exhausted (max 200 connections)
T+15ms: DB starts rejecting new connections
T+500ms: First query completes, cache repopulated
T+500ms: 49,800 queries already failed or timed out
How It Develops
Stampedes emerge from the "invalidate on write" pattern, which sounds correct in isolation. Data changed, so the cache should reflect that. The problem isn't the invalidation itself; it's the gap it creates.
Hard delete creates a miss window. When you DEL a cache key, every concurrent reader instantly sees a miss. There's no grace period, no "serve stale while refreshing." The key is just gone.
Write timing is unpredictable. Unlike TTL expiry (which you can plan for), invalidation happens whenever someone writes. A merchandiser updating a price at peak traffic doesn't know they're about to trigger 50,000 DB queries.
Writes fan out to all readers. A single write event affects every reader of that cache key. If the key is popular, the amplification factor is enormous. One write becomes 50,000 reads.
The insidious part: every individual decision here is defensible. You should invalidate stale data. You should use cache-aside for simplicity. The problem only emerges under concurrent load, which is exactly when you can't afford it.
Stampede vs Thundering Herd
These are often confused, and the confusion costs teams real debugging time. The distinction matters because the mitigations differ.
A common diagnostic mistake is to label every duplicate-read burst a "thundering herd" and add mutex-on-miss without checking the trigger. The symptoms are similar, but mutex-on-miss does not address a hard DEL caused by a write; the first question is whether the burst follows TTL expiry or invalidation.
| Thundering Herd | Cache Stampede | |
|---|---|---|
| Trigger | TTL expiry (time-based) | Cache invalidation (event-based) |
| Cause | Key expires while under concurrent load | A write deletes a key while readers are active |
| Predictable timing | Somewhat (you know the TTL) | No, triggered by arbitrary writes |
| Best fix | Probabilistic early expiry, mutex-on-miss | Stale-while-revalidate, write-through |
Symptoms and Diagnosis
Stampedes look like thundering herds in their symptoms but differ in one key way: the timing is irregular. There's no TTL-interval sawtooth pattern.
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with SDEpedia Premium.