gotchajavascriptMajor
Cache Stampede / Thundering Herd Prevention
Viewed 0 times
cache stampedethundering herddogpilehot key expiryredis lockset nx
Problem
A popular cache key expires. Hundreds of concurrent requests all get a cache miss simultaneously, all query the DB at once, overwhelming it. This is the cache stampede (or thundering herd) problem.
Solution
Use one of three strategies: (1) Probabilistic Early Expiration — recompute the value slightly before it expires based on a probability. (2) Locking — only one process recomputes; others wait on the lock. (3) Stale-While-Revalidate — serve the stale value while one background process refreshes it.
Why
Without protection, a single key expiry can cause a DB-level traffic spike proportional to your concurrency. At scale this can cascade into a full outage.
Gotchas
- Distributed lock with Redis SET NX + EX is the simplest lock. Release the lock in a finally block to avoid deadlocks.
- If the lock-holder crashes before writing, all waiting processes will eventually timeout. Set a max wait time and fall back to DB.
- Probabilistic early expiration requires storing the computation time alongside the cached value.
Code Snippets
Redis lock to prevent stampede
async function getCachedOrCompute(key, compute, ttl = 60) {
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
const lockKey = `lock:${key}`;
const acquired = await redis.set(lockKey, '1', 'NX', 'EX', 10);
if (acquired) {
try {
const value = await compute();
await redis.set(key, JSON.stringify(value), 'EX', ttl);
return value;
} finally {
await redis.del(lockKey);
}
} else {
// another process is computing — poll briefly
await new Promise(r => setTimeout(r, 200));
const result = await redis.get(key);
return result ? JSON.parse(result) : compute(); // fallback
}
}Revisions (0)
No revisions yet.