Reference · Cheat sheet
Lazy-load the cache; then stop the herd. Print it, pin it.
val = GET key
if val is nil: # miss
val = db.query(...)
SET key val EX (base + jitter)
return val
DEL key).| Pattern | Loads cache | When |
|---|---|---|
| Cache-aside (lazy) | app code | read miss |
| Read-through | cache layer | read miss (transparent) |
| Write-through | app/backend | every DB write (sync) |
| Write-behind | cache | cache now, DB later (async) |
A hot key expires → all concurrent requests miss at once → all hit the DB together. Worse when many keys share a TTL (lockstep expiry).
| Mitigation | Idea | Use when |
|---|---|---|
| TTL jitter | EX base + rand() — desync expiry | always (free) |
| Single-flight lock | SET lock t NX EX: one rebuilds, rest wait/stale | expensive hot keys |
| XFetch (early recompute) | probabilistically refresh before expiry | no per-key stall allowed |
| Stale-while-revalidate | serve stale, refresh in background | staleness tolerable |
Don't release with a bare DEL — if work outran the lock TTL you'd delete someone
else's lock. Store a random token; release with a Lua compare-and-delete:
if redis.call('get',KEYS[1])==ARGV[1]
then return redis.call('del',KEYS[1]) else return 0 end
Locking across nodes? Single-instance locks race under async replication → use Redlock.
← lesson 0004 · Sources: AWS caching patterns · Cache stampede · Redis distributed locks