Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

Cache-Aside
HLD

Cache-Aside

The default caching pattern — application manages reads and writes explicitly, cache stays simple.

The Pattern

 READ:
   val = cache.get(key)
   if val: return val                        ← hit (~1ms)
   val = db.query(key)                       ← miss
   cache.set(key, val, ttl=300)              ← fill for next time
   return val

 WRITE:
   db.update(key, new_val)                   ← source of truth first
   cache.delete(key)                         ← invalidate, don't update

 the APPLICATION owns the logic; the cache is a dumb fast KV.
 hence the alias: LAZY LOADING — data loaded into cache
 only when first needed.

Why Delete-on-Write (Not Update)

 write path alternatives:

 UPDATE cache directly:
   ✗ race: two writers interleave db/cache ops → stale value
     cached INDEFINITELY (no TTL to save you mid-window)
   ✗ cache holds derived shapes sometimes → writer must know them

 DELETE + lazy refill:
   ✓ next read pulls FRESH from db — self-healing
   ✗ one extra read after each write (usually fine)
   ✗ tiny window where old value still served pre-delete

 delete is simpler AND more correct. default to it.

The Known Race (and why it rarely matters)

 interleaving that poisons the cache:

 A: read miss → queries db (old value v1)
 B: writes db (v2), deletes key
 A: sets cache = v1            ← STALE until TTL expiry!

 window is milliseconds and needs precise timing;
 TTL bounds the damage. high-consistency keys need
 versioned values or short TTLs or lock-assisted fills.
 know it exists; don't over-engineer for it by default.

Strengths and Costs

✓ Wins✗ Costs
Cache holds only REQUESTED data (no waste)First read of anything = full latency
Cache down = slow, not broken (resilient)Miss penalty on every cold key
Simple; no broker contractsApp code carries the pattern everywhere
Works with any storage backendTTL staleness between refreshes

The resilience row matters operationally: flush Redis during an incident and the site degrades instead of dying.

When Cache-Aside Is Right

 ✓ read-heavy entities with tolerant staleness (profiles, catalogs)
 ✓ irregular access patterns (lazy = only cache what's asked)
 ✓ teams wanting zero magic — logic visible in code
 
 reconsider when:
 - write-heavy (constant invalidation churn)
 - strict freshness required (TTL windows unacceptable)
 - thundering-herd-prone hot keys (needs coalescing additions)

Interview Framing

This is the expected DEFAULT answer for “how do you cache user profiles?” — name it, sketch both paths, justify delete-over-update with the race argument, cite TTL bounding the known flaw. Contrast one line with read-through (“broker-managed fills”) to show you chose, not defaulted blindly.

My Private Notes

Notes are auto-saved locally to this device.