CMD Guide
HomeSystem DesignScalable Systems (Advanced Topics)

What Is Distributed Locking for Cache Rebuilds, and How Does It Prevent Cache Stampedes

A distributed lock stops a cache stampede by making the first request that sees an expired key atomically claim a shared marker (in Redis, SET lock:key <token> NX PX ttl); because only one writer can create a key that does not yet exist, exactly one process is elected to hit the database while every other concurrent request either waits briefly or serves the stale copy — so N simultaneous misses collapse into a single recompute.

The problem it solves: the stampede (dogpile)

A cache stampede (also called the dogpile effect or cache miss storm) happens when a hot key expires and many in-flight requests all miss at the same instant. Each one falls through to the origin — a database, an API, an expensive aggregation — and they all recompute the same value at once. The redundant load spikes the backend, latency climbs, more requests pile up behind the slow ones, and the extra load pushes recompute time even higher: a feedback loop that can take down the origin (the classic thundering herd).

This is not merely theoretical. Facebook's own paper Scaling Memcache at Facebook (NSDI 2013) describes exactly this failure and their production fix — a token mechanism they call leases, which is functionally a per-key rebuild lock. (Note: the widely-repeated claim that Facebook's September 2010 outage was a four-hour cache stampede is inaccurate — that ~2.5-hour outage was a configuration-cache invalidation feedback loop, a related but distinct failure. The honest reference point for stampede prevention at scale is the leases mechanism in the memcache paper.)

The mechanism, and the correct minimal recipe

Coordination lives in a shared store (Redis, Memcached, or a database row) so that all nodes agree on who is rebuilding. The single atomic operation that makes it work is set-if-absent: the winner creates the lock key, does the one expensive rebuild, writes the fresh value, and releases the lock; losers see the key already present and back off.

Why the naive version is wrong

The textbook SETNX-then-EXPIRE-then-DEL sketch has two real bugs:

The fix is a single atomic SET ... NX PX with a unique token, and a compare-and-delete release via a Lua script so you only unlock what you still own:

token = uuid4()
ok = redis.set("lock:feed", token, nx=True, px=5000)   # atomic: claim + 5s TTL

if ok:                                 # we are the sole regenerator
    try:
        data = rebuild_from_db()       # the ONE expensive query
        redis.set("feed", data, px=600000)   # cache fresh value, 10 min
    finally:
        redis.eval(RELEASE, 1, "lock:feed", token)  # release-if-owner
else:                                  # someone else is rebuilding
    return cache.get_stale("feed")     # serve stale, or short backoff+retry
-- RELEASE (Lua): delete only if the token still matches
if redis.call('get', KEYS[1]) == ARGV[1] then
  return redis.call('del', KEYS[1])
else
  return 0
end

The get+del must be one Lua script because it runs atomically on the Redis server — a read-then-delete in application code has the same lost-race as blind DEL.

diagram
diagram

Worked example: a 3-second report at 10 rps

A dashboard report costs 3,000 ms to compute from the database and is requested 10 times per second. Its cache entry has just expired at t=0. Without a lock, the 30 requests that arrive during the rebuild window each miss and fire their own 3-second query — 30 concurrent expensive queries, exactly when the DB can least afford it. With the lock:

TimeEventDB queries
t = 0 msKey expired. R1 arrives, SET lock:report tok NX PX 5000 → OK. R1 starts the DB query.1
t = 100–2900 msR2 … R30 arrive (~1 every 100 ms). Each SET NX → fails. Each serves the stale report instantly (0 ms added latency).0
t = 3000 msR1's query returns. R1 writes the fresh value with a 600 s TTL, then runs the Lua release (token matches → unlock).0
t > 3000 msAll subsequent requests get a fast fresh cache hit.0

Result: 1 database query instead of 30 — a 30× reduction in origin load at the worst moment — and the 29 waiters paid roughly zero extra latency because they served stale rather than queuing behind a 3-second query. The lock collapsed the herd into a single recompute per expiration interval.

Pitfalls

When to use a distributed lock — and when not to

Reach for a distributed rebuild lock when all of these hold: the recompute is genuinely expensive (heavy query, fan-out, or an external API you are billed for); you need fleet-wide at-most-one, so per-node deduplication is not enough; and a few hundred milliseconds of coordination latency plus an operational dependency on the lock store are acceptable. The signal is: "one key, many nodes, and each redundant rebuild really hurts the origin."

Weigh it against the main lock-free alternatives:

Choose the distributed lock when a single rebuild across the whole fleet is worth a coordination round-trip and the recompute is expensive enough that even a handful of duplicates is unacceptable. Prefer XFetch when you want lock-free simplicity across many keys and can tolerate rare duplicates. Prefer single-flight when one node (or per-node dedup) is enough. Prefer stale-while-revalidate whenever a few seconds of staleness beats making any user wait — and combine it with one of the others to dedupe the refresh.

Takeaways


Re-authored and deepened for this guide. Sources: Redis documentation on SET, distributed locks and Redlock; Martin Kleppmann, "How to do distributed locking" (2016) and Salvatore Sanfilippo's rebuttal; Nishtala et al., "Scaling Memcache at Facebook" (NSDI 2013) for the leases mechanism; Vattani, Chierichetti & Lowenstein, "Optimal Probabilistic Cache Stampede Prevention" (VLDB 2015) for XFetch; and the Go golang.org/x/sync/singleflight package. The unverified "Facebook 2010 four-hour stampede" claim from the original page was removed and replaced with the accurate leases reference.

🤖 Don't fully get this? Learn it with Claude

Stuck on What Is Distributed Locking for Cache Rebuilds, and How Does It Prevent Cache Stampedes? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **What Is Distributed Locking for Cache Rebuilds, and How Does It Prevent Cache Stampedes** (System Design) and want to truly understand it. Explain What Is Distributed Locking for Cache Rebuilds, and How Does It Prevent Cache Stampedes from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **What Is Distributed Locking for Cache Rebuilds, and How Does It Prevent Cache Stampedes** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **What Is Distributed Locking for Cache Rebuilds, and How Does It Prevent Cache Stampedes** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **What Is Distributed Locking for Cache Rebuilds, and How Does It Prevent Cache Stampedes** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes