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 Invalidation
HLD

Cache Invalidation

The hard problem, named — strategies from TTL-only to event-driven purging, and the trade each makes.

Why It’s Hard

 keeping copies fresh requires knowing WHEN sources change —
 but changes happen in code paths far from every cache that
 holds a copy:

 write path:  orders service updates trip 77
 caches holding trip-77-derived data:
   - api pods' local L1 caches (dozens of instances!)
   - shared Redis
   - CDN edge copies of the public trip page
   - search index replicas

 invalidation = notifying ALL of them. miss one → staleness bug.
 notify wrongly → cache thrash. this is why the joke exists:
 "there are only two hard things: naming and cache invalidation."

The Strategy Ladder

 1. TTL ONLY          wait it out
                      + zero coupling    − stale up to full TTL
 
 2. WRITE-THROUGH     update/delete on write path
                      + immediate        − couples writes to cache
 
 3. EVENT-DRIVEN      publish "trip:77 changed"; subscribers purge
                      + decoupled+fast   − infra: bus, consumers
 
 4. VERSIONED KEYS    key includes version; bump = new key
                      + no deletion races − old entries linger till TTL
 
 production systems COMBINE: events for speed,
 TTL as the safety net under missed events.

Event-Driven Invalidation Shape

 [orders svc] ──publish──► [ bus: entity.invalidated ]

              ┌─────────────────┼────────────────┐
              ▼                 ▼                ▼
        [cache cleaner]  [cdn purge api]  [search reindex]

 message: { entity: "trip", id: "77", version: 42 }

 rules:
 - messages are HINTS, not guarantees → keep TTLs anyway
 - coalesce bursts (10 edits/minute = 1 purge)
 - include version so late/stale hints can be ignored

Versioned Keys: The Pragmatic Favorite

 instead of deleting user:912, write to a NEW key:

 data_version = db.get("ver:user:912")     // bumped on every write
 key = f"user:912:v{data_version}"

 write path: bump version → old key orphaned (expires via TTL)
 read path: current version's key only

 wins:
 - NO delete race conditions (the cache-aside flaw vanishes)
 - concurrent readers never see torn state
 costs:
 - version lookup per read (or embed in auth token/session)
 - orphans consume memory until TTL — set TTLs regardless

What Each Layer Needs

Cache layerInvalidation tool
BrowserCan’t push! Short TTLs or versioned URLs
CDNPurge APIs by URL/tag; versioned asset names
RedisDEL / versioned keys / pub-sub
Local L1Bus broadcast, or accept short-TTL staleness

Browser row explains a pattern: you cannot invalidate browsers, so make assets immutable-and-versioned and let HTML point at new versions.

Interview Framing

“User updates their profile — what happens to cached copies?” is the probe. Scored shape: enumerate affected layers, pick event-driven purge + TTL backstop for shared layers, versioned keys where delete-races hurt, admit browser layer needs TTL/versioning since push is impossible. Saying “we’d invalidate” without naming mechanisms and layers is the answer that stops interviews early.

My Private Notes

Notes are auto-saved locally to this device.