TL;DR: Layer it: CDN for anonymous traffic with stale-while-revalidate, short-TTL in-process caches for per-instance heat, Redis for shared computed views. Invalidation rides change events from an outbox. Against stampedes, coalesce requests (singleflight) and refresh probabilistically before expiry; never make expiry a synchronized event.
How to approach it
Split reads by who is asking: anonymous browsers, logged-in app clients, and internal services have different consistency needs and hit different layers. Then answer the two failure questions proactively: what happens at key expiry, and what happens when Redis itself goes down.
A strong answer
Layering.
| Layer | Serves | TTL posture | Why |
|---|---|---|---|
| CDN | Anonymous catalog pages and images | Minutes, plus stale-while-revalidate | Absorbs the long tail of anonymous reads at the edge |
| In-process LRU | This instance's hot keys | Seconds | Removes Redis round trips for the head of the distribution |
| Redis | Computed views: search facets, price-joined cards | Minutes to hours | Shared across instances, bounded memory |
At 50k reads per second the arithmetic forces this shape rather than suggesting it: 50,000 calls/s × 0.0005s gives 25 average in-flight calls if 0.5ms is elapsed latency. It is 25 cores only if the value is CPU time per call. Measure Redis capacity and client overhead before deciding which keys need another cache.
Invalidation. The catalog tolerates seconds of staleness on descriptions and imagery; it does not tolerate stale prices or inventory at checkout. So: content flows through cache-aside with TTLs measured in minutes, and writes publish invalidation events through a transactional outbox (the same DB commit that changes the product emits the event), so nothing invalidates based on a hope that the write succeeded. Consumers can clear Redis keys and CDN paths, but deletion races with stale in-flight fills. Carry source versions and atomically reject older cache writes, or use a versioned-key/current-version design; TTLs still bound tolerated stale copies. Validate authoritative prices and stock at checkout. The defensible position: do not build real-time invalidation for everything, shorten TTLs instead where staleness of a few seconds is acceptable, because invalidation fan-out is its own distributed-systems project.
Stampede mechanics and fixes. One product page serves 5,000 reads per second during a sale. Its cache entry expires. Up to thousands of requests can miss during recomputation, all queue on the database for the same row, origin load can surge for the seconds of recompute, replication lags, connection pools fill, and neighbouring queries suffer. Three complementary fixes:
- Request coalescing (singleflight). Within each instance, concurrent misses for the same key wait on the first request's fetch instead of issuing their own. Turns five thousand misses into roughly one per instance. Cheap to implement, no protocol changes.
- Probabilistic early refresh. Each reader recomputes the value slightly before expiry with a small probability that grows as the deadline approaches (the XFetch trick), so refresh begins smoothly ahead of expiry and the expiry moment finds a fresh value already landing. No locks, no coordination, just jittered eagerness.
- Serve-stale-while-refreshing. On expiry, return the stale value immediately while exactly one worker refreshes under a lease. Users never see origin latency; correctness holds because the catalog already accepts TTL-scale staleness.
Jitter every TTL by ±10 percent to reduce synchronized expiry without promising unique expiry times. Deployments are stampede events too: a rolling restart wipes in-process caches fleet-wide, so pre-warm top keys from a snapshot of access logs before shifting traffic.
Degradation. When Redis fails, fall back to slightly-stale local copies, shed the remainder, and keep prices authoritative-checked at cart regardless, since checkout must consult the source of truth anyway. A cache outage degrades richness, not correctness.
What interviewers probe next
"Why not write-through everywhere?" Write-through keeps cache warm but couples write latency to cache availability and still needs eviction for capacity; catalog writes are rare relative to reads, making cache-aside's simplicity win here.
"Negative caching?" Essential: missing or delisted SKUs get short-TTL negative entries, or every scrape of a dead product ID bypasses all layers into the database forever.
"How do you know the stampede fix works?" Measure origin fetches per key over time and run the drill: delete a hot key in staging under production-shaped load. Hit ratios hide stampedes; origin QPS spikes reveal them.
Common mistakes
Caching prices at the edge without an authority check at cart. Stale money is a different class of incident than stale images.
Locks as the only stampede defense. Distributed locks add latency on the happy path, fail partially under network partitions, and still thunder if the lock service blips; coalescing and probabilistic refresh carry most of the load.
One global TTL constant. Hot keys need different lifetimes than long-tail ones, and uniform TTLs synchronise your worst moments.