A popular cache key expires, and thousands of concurrent requests all miss the cache at the same instant, hammering the database simultaneously. What is this failure mode called, and what techniques prevent it?
answer
- thundering herd = cache stampede = dog-piling
- single-flight/request coalescing dedupes concurrent misses
- jitter TTLs to desync expiry
- probabilistic early expiration refreshes before hard expiry
- stale-while-revalidate serves old value + background refresh
basics
~20 sIt's called a thundering herd or cache stampede. When a popular cached item expires, every request that arrives at that moment finds the cache empty and rushes to the database at once, which can overload it. Fixes include locking so only one request refetches, spreading out expiration times, or serving a slightly old copy while refreshing in the background.
solid answer
~50 sThis is the thundering herd (or cache stampede) problem: because caching concentrates traffic onto a few hot keys, the moment a hot key's TTL lapses, every concurrent request that was previously a cheap cache hit becomes a simultaneous cache miss, and all of them independently query the database at once — a load spike proportional to the key's popularity, arriving in a single instant. Mitigations include: a mutex/lock so only the first request that misses actually queries the database while others wait or get a slightly stale value; request coalescing/single-flight, where concurrent misses for the same key are deduplicated into one in-flight database call whose result is shared to all waiters; jittered TTLs, randomizing expiration slightly per entry so hot keys don't all expire in lockstep; probabilistic early expiration (recompute slightly before actual expiry, with increasing probability as expiry approaches); and stale-while-revalidate, serving the expired value immediately while asynchronously refreshing it in the background.
go deeper
Should recognize the term thundering herd/cache stampede and describe in plain language that many requests hitting an empty cache at once can overload the database.
Should name at least two mitigation techniques (e.g., locking and jittered TTL) and explain roughly how each reduces simultaneous database load.
Should distinguish request coalescing from simple locking, explain jitter versus probabilistic early expiration, and know stale-while-revalidate as an HTTP-level mitigation.
Should design a layered anti-stampede strategy across CDN, application cache, and database for a system with known hot keys, and reason about the staleness-vs-availability trade-off each mitigation makes, citing a concrete mechanism like single-flight or leases.
## The failure mode **Thundering herd** — also called a **cache stampede** or **dog-piling** — describes what happens when the very thing that makes caching effective (concentrating a large volume of read traffic onto a small number of cached hot keys) turns into a liability at the exact moment that hot key's cache entry disappears. While the entry is cached, thousands of requests per second for the same key are served instantly from memory and the origin sees essentially none of that traffic. The instant the entry expires (or is evicted, or the cache node restarts empty), every one of those requests that would have been a hit is now simultaneously a miss, and because caching logic is typically written per-request ('if not in cache, fetch from database and populate the cache'), every one of those thousands of concurrent requests independently decides to query the database at the same moment. The database, which was previously shielded from nearly all of that traffic, suddenly receives a spike equal to the full uncached request rate for that key — often enough to overload connections, exhaust a connection pool, or dramatically slow down unrelated queries sharing the same database, which in turn slows down the very requests trying to repopulate the cache, sometimes triggering a feedback loop where retries pile on more load faster than the database can drain the backlog. ## Why it is structural, not an edge case The problem exists structurally, not as an edge case, because Zipfian/power-law traffic (a small number of items receiving a disproportionate share of requests — think a viral product page, a celebrity's profile, a trending news article) is exactly the traffic pattern caching is designed to help most, and it's precisely that concentration that makes the expiration moment so dangerous: an unpopular key expiring causes at most one or two near-simultaneous misses, but a viral key expiring can cause thousands. ## The mitigations 1. **Request coalescing, sometimes called 'single-flight'**, is the most direct fix: when multiple concurrent requests miss on the same key, instead of letting each one independently query the database, the caching layer recognizes that a fetch for that key is already in flight and has every other concurrent requester simply wait on (and share) the result of that one in-flight fetch rather than issuing a redundant one. This collapses N simultaneous database queries into exactly one, no matter how large N is. It's typically implemented with a per-key mutex or an in-memory 'promise/future' registry inside the caching layer or application process; libraries like Go's `singleflight` package or Guava's `LoadingCache` in Java implement this pattern directly. 2. **The lock-and-wait pattern** is a related but distinct technique: the first request to miss acquires a short-lived lock (e.g., a Redis SETNX-based lock) and proceeds to query the database and repopulate the cache, while every other concurrent request that finds the lock already held either waits briefly and retries the cache read, or falls back to serving a stale/default value rather than hitting the database itself. This achieves a similar effect to coalescing but is more commonly used across separate processes/servers (not just within one process) since the lock lives in shared cache infrastructure rather than in-process memory. 3. **Jittered TTLs** address a subtly different cause: many systems populate a batch of related cache entries at the same time (e.g., a cache warm-up job, or many keys all set with the same round-number TTL like 'expires in exactly 300 seconds'), which means they also all expire at the same instant, creating a synchronized stampede across many keys simultaneously rather than just one hot key. Adding a small random jitter to each entry's TTL (e.g., 300 seconds plus or minus a random 0-30 seconds) desynchronizes expirations so they spread out over a window instead of firing in lockstep. 4. **Probabilistic early expiration** (sometimes called 'early recomputation' or the XFetch algorithm) takes a proactive approach: rather than waiting for a key to actually expire and letting whichever request happens to arrive first trigger a synchronous refetch, the cache periodically recomputes a probability of early refresh that increases as the entry approaches its real expiration time, and a small fraction of requests near that window trigger an early, asynchronous background refresh — so by the time the entry would have actually expired, it's almost certainly already been refreshed by one of those early, low-probability triggers, and no request ever actually experiences a hard miss on a hot key. 5. **Stale-while-revalidate** takes yet another angle, common in HTTP caching (it's an actual `Cache-Control` directive) and CDN/reverse-proxy configurations: instead of ever exposing a hard miss to the end user, the cache serves the just-expired (stale) value immediately — which is usually 'good enough,' since the data likely hasn't changed drastically in the last few seconds — while kicking off exactly one background refresh to update the cache for subsequent requests. This trades a small, bounded, explicitly-accepted staleness window for guaranteeing the database never sees a stampede at all. ## Layering them in production In production, these are frequently layered: a CDN might use stale-while-revalidate at the edge, an application cache in front of a database might use request coalescing plus jittered TTLs, and a particularly hot key might additionally use probabilistic early expiration so it's essentially never allowed to go fully cold. A well-known real-world example is the concept of 'leases' described in Facebook's Memcache/TAO work — a mechanism functionally similar to lock-and-wait, specifically built to prevent stampedes at Facebook's scale, where a single popular key could otherwise generate an enormous coordinated spike.
- How does request coalescing (single-flight) differ from simply adding a lock around the database query?A basic lock (like a Redis SETNX) just serializes access so only one requester queries the database at a time, but other requesters typically still have to poll or retry the cache themselves after the lock is released. True request coalescing goes further by having those other concurrent requesters directly share the in-flight fetch's result — they attach to the same pending call and all receive its answer the moment it completes, without each one independently retrying.
- Why doesn't simply lowering the TTL on hot keys prevent thundering herd?A lower TTL makes the key expire more often, which actually increases the frequency of stampede events rather than preventing them — it doesn't change the fact that whenever the key does expire, every concurrent request in flight at that instant still misses simultaneously. Lower TTL trades staleness for more frequent stampedes, the opposite of the desired fix.
- In what scenario would stale-while-revalidate be a poor choice for preventing a cache stampede?It's a poor fit whenever serving even briefly outdated data is unacceptable for correctness — for example, a cached account balance or an inventory count near zero stock, where showing a stale value could let a user act on wrong information (overspend, oversell). In those cases, coalescing or lock-and-wait, which force the fetch to be genuinely fresh, are more appropriate even at the cost of some request latency during the refresh.
Imagine a single water fountain (the database) that a thousand people quietly sip from a shared jug (the cache) all day — fine, until the jug runs dry at the exact same second for everyone, and all thousand people rush the fountain at once instead of one person refilling the jug while everyone else waits.
saying these in an interview costs you the question
- Doesn't recognize the connection between popularity/hot keys and stampede severity
- Proposes only 'add more cache servers' without addressing the coordination problem
- Confuses thundering herd with plain cache invalidation staleness
- Thinks lowering TTL fixes stampedes
- Can't name at least one concrete coalescing or jitter mechanism