System Design · Module 6 · Lesson 4 of 4 · 45 min
Stampedes Hot Keys and Cold Cache Recovery
Protect the origin when many requests miss together, distinguish hot keys from uneven key counts, and plan a bounded cache restart.
01 The idea
The problem this lesson solves
A popular cache entry expires and five hundred requests miss together. If each performs the same expensive database query, the cache has converted one refresh into a burst of duplicate work. After a full restart, many different keys may miss instead. These are related but distinct recovery problems, and they need more than a longer TTL.
You should recognize the application, the database and the request path. Treat the database as the authority unless explicitly stated otherwise.
02 Worked example
Protect the origin during 500 simultaneous misses
03 The method
Understand the mechanism
Identify same-key stampedes and broad cold starts
A stampede occurs when many callers concurrently rebuild the same missing or expired value. The repeated origin work is largely redundant. A cold start across many distinct keys can overwhelm the origin even if every key has only one loader.
Suppose 500 callers request a7 during a 100-ms rebuild. Basic cache-aside can issue up to 500 origin loads if they all observe the miss before a fill becomes visible. A same-key coordination mechanism can reduce that to one active rebuild within its coordination scope.
Now suppose 500 callers request 500 different keys. One loader per key still means 500 origin loads. Protecting the origin therefore requires a global or appropriately partitioned concurrency/admission budget in addition to same-key deduplication.
Measure which pattern you have. Hot-key request concentration, synchronized expiry and a namespace-version rollout produce different miss distributions. The remedy should address the distribution instead of assuming every miss storm is identical.
Coalesce work with a bounded single-flight mechanism
Single-flight associates an in-progress load with a key. The first caller becomes the loader; later callers join its result rather than starting duplicate work. On completion, the result is delivered to waiters and eligible data is cached.
Bound the wait. If the loader hangs, hundreds of callers must not wait forever or retain unbounded memory. Use a deadline, cancellation policy and documented fallback. Remove or transition the in-progress record reliably when the loader fails.
An in-process single-flight map only coordinates callers within that process. With ten app instances, the origin may still receive ten simultaneous rebuilds for the same key. That can be acceptable under the budget, or it can motivate shared coordination.
Shared coordination adds failure questions. A lease can expire while a slow worker is still running, allowing another loader to begin. Ownership-aware release prevents an old worker from deleting a successor’s lock, but preventing stale side effects may require fencing or conditional version checks at the resource being written.
Protect against distinct-key overload
Use a bounded semaphore, queue or admission mechanism around origin rebuild work. If the origin can safely handle only a certain number of concurrent expensive loads, the cache layer should not exceed it merely because keys differ.
The budget should consider all callers and app instances. A local limit of twenty on each of fifty instances permits one thousand concurrent loads. If the origin’s safe budget is one hundred, those local limits do not establish the intended global protection.
Decide how callers behave when capacity is exhausted. They may wait in a bounded queue, receive a retryable error, or obtain an acceptable stale result. Each choice has latency, availability and freshness consequences. Dropping the limit under pressure defeats its purpose.
Propagate the remaining deadline. Loading a result after every waiting caller has timed out can waste scarce recovery capacity, although some deliberate background warmup may still be useful. Separate that warmup budget from interactive admission.
Spread expiry and rebuild ahead when appropriate
If many entries receive the same TTL during a bulk fill, they can expire together. Adding bounded random jitter to expiration can spread the rebuild load. Jitter changes the expiry distribution, so the resulting lifetime must still fit the freshness policy.
Refresh-ahead starts rebuilding before an entry becomes unusable. It can keep a hot value available, but it consumes origin work and needs coordination so every request does not trigger its own refresh. Refreshing cold entries wastes capacity.
Serving stale while one refresh runs can preserve availability for content that permits it. Apply a maximum stale policy and distinguish load failure from authoritative deletion or revocation. An old authorization result may be unacceptable even when an old article summary is fine.
None of these mechanisms removes the need to test a completely empty cache. A deployment, data loss or key-version change can bypass the steady-state expiry pattern. Recovery capacity needs its own plan.
Handle hot keys without confusing reads and writes
A hot read key can overload one shared-cache owner even when the database is protected by a high hit ratio. Local copies, replicated read serving or an edge cache can spread suitable read work, subject to the freshness contract.
Adding virtual nodes to a sharded cache balances key ranges more finely, but one key still belongs to one logical placement under a simple owner model. Equal key counts do not imply equal request demand.
Hot writes are harder to spread because updates may need a single ordered authority. Sharding a counter into partial counters can distribute increments, but exact reads and reset semantics become a different problem. Do not propose salting keys without explaining how the application combines or locates the resulting values.
Identify the unit of contention: one key’s lookup rate, one large value’s bandwidth, one loader’s compute cost or one ordered update stream. Different units justify different designs.
Make recovery a controlled operating mode
After cache loss, start with a known origin budget and gradually restore admitted load or selected warm keys. Prioritize data with demonstrated value rather than fetching every historical record. Measure whether warmup reduces misses enough to justify its origin cost.
Observe origin concurrency, queue time, timeout rate, rebuild duration, waiter count and cache hit ratio. A rising hit ratio can coexist with an overloaded origin if the remaining misses are expensive. Recovery is successful when user-visible behavior stabilizes within the defined target.
Test loader crashes, expired leases, stale fills and cancellation. The fill must still obey the freshness protocol from the prior lesson; single-flight reduces duplicate work but does not automatically make an old result current.
The final design combines independent protections: per-key coordination for duplicate work, bounded total origin work for broad misses, a deliberate stale-data policy and measured warmup. Each solves a specific part of the recovery problem.
Guided tutorial
Count duplicate rebuilds
Model 500 simultaneous misses for a7. Without coordination, allow each to start a load. With one shared active rebuild, count one loader and 499 waiters under that scope. Then distribute callers across ten independent local single-flight maps and explain why up to ten loaders may remain.
Change to distinct keys
Give every caller a different missing key. Show that per-key coalescing no longer lowers the number of loads. Add a total origin-concurrency budget and a bounded queue. Specify what callers receive when both the active budget and queue are full.
Inject a stalled loader
Pause the first loader until its wait or lease deadline expires. Start a successor. Explain how the old loader avoids releasing the successor’s ownership and how its late result is prevented from violating freshness. Treat these as separate requirements.
Plan a cold-start experiment
In a disposable environment, empty the cache and replay a representative mix of hot and cold keys. Record origin concurrency, user latency and errors while warming a bounded set of hot entries. Adjust one limit at a time and retain the freshness contract during recovery.
Recovery pseudocode
on miss(key):
join an existing bounded rebuild for key, if present
otherwise acquire a safe rebuild responsibility
enter the bounded origin-concurrency budget
load and conditionally fill according to freshness policy
release only the responsibility still owned by this workerCheck your result
Same-key callers join one bounded rebuild. Different keys still compete for a separate origin budget. Specify a timeout or fallback for waiting callers so protection does not create unbounded queues.
04 Cheat sheet
Review the decisions
Use this table to explain the mechanism and its limit without memorizing a product name.
| Choice or concept | Remember | Check |
|---|---|---|
| Single-flight | Duplicate work for one key | Waiters need bounds |
| TTL jitter | Synchronized expiry of different keys | Not a same-key lock |
| Origin concurrency limit | Total rebuild demand | Needs rejection or waiting policy |
| Hot-key local copy | Distributes eligible reads | More freshness coordination |
05 Where & why
Check the primary references
Use official references for protocol and implementation details. The questions here are teaching prompts.
06 Interview questions
Explain the reasoning out loud
Answer before opening the explanation. The questions progress from vocabulary to decisions and failure behavior.
What does Single-flight mean?
What does Hot key mean?
What does TTL jitter mean?
Why is single-flight not enough for every cold cache?
What does TTL jitter fail to solve?
Why not blindly delete a rebuild lock on completion?
Does adding shards split a hot key’s reads?
What should a warming plan monitor?
07 Practice problems
Practice a new case
Solve the new cases before revealing hints. Complete worked solutions follow the set.
Hit rate
EasyShow the hint
Control matching
EasyShow the hint
Average latency
MediumShow the hint
Stampede defense
MediumShow the hint
Expired lease
MediumShow the hint
Cold restart plan
HardShow the hint
Worked solutions
1. Hit rate
18 / (18 + 6) = 0.75, or 75 percent.
2. Control matching
Jitter spreads different-key expiry; coalescing reduces duplicate same-key loads; a concurrency limit bounds total origin work.
3. Average latency
Mean = 0.8 × 1 + 0.2 × 31 = 7 ms under the fixed-cost model. It says nothing by itself about p99 or queueing under load.
4. Stampede defense
Use single-flight/coalescing for one rebuild and an origin concurrency limit or bounded wait to prevent overload. Jitter spreads different keys' expiry times, but these 500 requests already missed one key together.
5. Expired lease
A could delete B’s valid lock. Release must verify A still owns the responsibility, and stale fills need their own freshness checks.
6. Cold restart plan
Use controlled warming, per-key coalescing, a total rebuild budget and bounded admission. Serve eligible bounded stale data or reject according to the contract, while observing database saturation before increasing traffic.