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 layer | Invalidation tool |
|---|---|
| Browser | Can’t push! Short TTLs or versioned URLs |
| CDN | Purge APIs by URL/tag; versioned asset names |
| Redis | DEL / versioned keys / pub-sub |
| Local L1 | Bus 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.
Premium Content
Unlock Cache Invalidation and all premium lessons with a subscription.
All premium lessons
Ad-free experience
Priority support
From ₹199.99/year — See plans