Avoiding Cache Stampedes in Redis: Random TTL Jitter & Request Coalescing
When many Redis keys expire at once, concurrent requests can overwhelm the database. Adding a small random TTL offset and using a single‑flight lock pattern keeps traffic smooth and prevents stampedes.
31 Aug 2025, 10:16 UTC

Problem: The Cache Stampede in Action
In high‑traffic systems, a popular key often lives in Redis for a fixed TTL (time‑to‑live). When that TTL expires, every request that previously hit the cache now misses and goes straight to the database. If the key is popular, a sudden burst of misses can overload the database, causing latency spikes or even outages. This phenomenon is known as a cache stampede or thundering herd.
Why TTL Jitter Helps
Redis does not guarantee that a key expires at an exact second. Even with a strict TTL of 300 seconds, the key will be removed somewhere between 300 s and 301 s after insertion. When many keys share the same TTL, the expiration events cluster, creating a burst of misses. Adding a small random offset—called TTL jitter—spreads those expirations over a window.
Example: For a product catalog entry you might set the TTL to 300 seconds plus a random value between 0 and 30 seconds. The key will expire anywhere from 300 to 330 seconds after creation, smoothing the load.
Request Coalescing (Single‑Flight) in Redis
Even with jitter, a sudden miss can still trigger multiple concurrent rebuilds if many requests hit the same key at the same time. Request coalescing ensures that only one worker recomputes the value while others wait or serve stale data. In Redis the most common implementation uses a short‑lived lock:
# Attempt to acquire a lock with a 5 second lease
SET lock:product:123 NX PX 5000
# If the command returns OK, this process is the builder.
# If it returns nil, another process is already rebuilding.
The lock is created with the NX (only if not exists) and PX (expire in ms) options, which were added in Redis 2.6.12. The builder fetches fresh data, stores it in the cache, releases the lock, and returns the value. Other callers either retry after a short back‑off or serve the last cached copy (stale‑while‑revalidate).
Worked Example: Product Catalog Cache
- Cache Write (with jitter)
# Cache the product data for 5–5.5 minutes SETEX product:123 300 + RAND(0,30) "{...json...}" # In practice: SET product:123 "{...}" EX 300 + RND 30 - Cache Read
If the key exists, return it immediately.GET product:123 - Cache Miss Handling
# Try to become the builder SET lock:product:123 NX PX 5000 IF OK THEN # Builder path data = fetch_from_db(123) SET product:123 EX (300 + RAND(0,30)) data DEL lock:product:123 RETURN data ELSE # Coalesced path # Option 1: wait a bit and retry SLEEP 50 GET product:123 # may now exist IF exists THEN RETURN value # Option 2: serve stale if acceptable RETURN stale_product_123 END IF
All commands run from the application process that needs the product data. The lock is short‑lived (5 s) to avoid long‑running rebuilds, and the jittered TTL ensures that subsequent expirations are spread out.
Trade‑offs & Limitations
- Stale Data Window: The lock holder may take a few hundred milliseconds to rebuild, so callers that hit the stale value may see slightly out‑of‑date data. This is acceptable for many catalog or pricing scenarios but not for real‑time inventory.
- Lock Reliability: A simple SET NX PX lock is not safe across Redis failover or network partitions. For strict mutual exclusion, consider a Redlock‑style algorithm or a dedicated distributed lock service.
- Debugging Complexity: Random TTLs make hit‑rate diagnostics harder. Log the effective TTL when writing a key so you can correlate expiration patterns in
INFO stats. - Database Capacity: Jitter can reduce the peak load, but if the underlying database is already saturated, you still need back‑pressure (e.g., queueing, circuit breakers).
Actionable Checklist
- Enable
lazyfree-lazy-server-delif you want immediate key removal on a busy server. - Wrap all cache writes with a jittered TTL:
EX 300 + RND 30. - Implement a single‑flight lock using
SET key NX PX 5000around any data rebuild logic. - On a cache miss, first try to acquire the lock; if you fail, wait a short back‑off and retry, or serve stale data if acceptable.
- Instrument
INFO statsandMONITORduring load tests to confirm thatkeyspace_missesandexpired_keysare lower than before. - Document the TTL jitter policy so that ops can verify the spread of expirations.
By combining TTL jitter with request coalescing, you can dramatically reduce the risk of cache stampedes in Redis‑backed services, keeping your database under control even during traffic spikes.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.