DevOpsInterviewPrep logo
DevOps System Design & Architecture / 09
hardNewFlipkartAmazon & AWSSwiggy

Design caching for a product catalog read 50k times a second. How do you handle invalidation, and what happens when a hot key expires?

The catalog is the easy half. The scored half is stampede dynamics: one hot key expiring can multiply origin load by a thousand in a second, and there are three distinct fixes.

Updated Sep 2026 · Grounded in researched DevOps, SRE and platform engineering interview loops, written to a senior-engineer editorial bar, and never padded to hit a word count.

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.

LayerServesTTL postureWhy
CDNAnonymous catalog pages and imagesMinutes, plus stale-while-revalidateAbsorbs the long tail of anonymous reads at the edge
In-process LRUThis instance's hot keysSecondsRemoves Redis round trips for the head of the distribution
RedisComputed views: search facets, price-joined cardsMinutes to hoursShared 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.

rendering diagram…

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:

  1. 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.
  2. 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.
  3. 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.

That one was free, and so are 10 answers per topic without an account. Signing in doubles that to 20, keeps your bookmarks, and tracks which topics you keep getting wrong.one Google click · no card · nothing to cancel
HOW DID IT GO?
0
UP NEXT ON YOUR JOURNEY
DISCUSSION · 0

Nothing here yet. Say how you would answer it.