intermediate
Caching patterns
Apply cache-aside, write-through, TTL jitter, stampede protection, and invalidation around actual consistency needs.
Caching patterns define how application and Redis share truth: when to read cache first, when to write through, how to invalidate, and how to survive stampedes. Redis is a performance layer — correctness rules still live in the source of record.
// Cache-aside (lazy loading)
async function getProduct(id) {
const cached = await redis.get(`product:${id}`)
if (cached) return JSON.parse(cached)
const row = await db.products.findById(id)
await redis.set(`product:${id}`, JSON.stringify(row), 'EX', 300 + Math.floor(Math.random() * 30))
return row
}
// Invalidate on write
await db.products.update(id, patch)
await redis.del(`product:${id}`)
| Pattern | Idea | |---------|------| | Cache-aside | App loads cache on miss | | Write-through | Write DB and cache together | | Write-behind | Write cache first, async DB — risky | | TTL + jitter | Bound staleness; spread expiry | | Stampede protection | Lock, single-flight, or early refresh |
Define stale tolerance per entity. Use probabilistic early expiration or mutex (`SET lock NX EX`) when hot keys expire together. Version keys (`product:9:v3`) simplify rolling invalidation.
On interviews: walk through read and write paths for one entity and state what inconsistency users may see.
Common pitfalls: cache as sole source of truth; no invalidation on update; thundering herd on popular TTL; caching errors or empty results; ignoring eviction under memory pressure.
The trade-off is latency and load reduction versus consistency complexity — every pattern shifts staleness and failure modes to a place you must document and test.
Checklist:
- Pick cache-aside vs write-through per entity.
- Set TTL with jitter on hot keys.
- Define invalidation on every write path.
- Plan stampede mitigation for top keys.