← Home
🏗Crack System Design with DSA AnimatorSystem DesignNEW
🏗 System Design›Reliability & Scalability

🗄Caching Patterns

Pick caching strategy by consistency tolerance, read/write shape, and failure behavior.

📌 Key IdeaMost systems start with cache-aside. Move to write-through when freshness matters, and only use write-behind for specialized high-write workloads with strong durability controls.
✨

Why It Matters

  • Caching decisions can improve performance dramatically or create stale-data outages.
  • Pattern choice determines read latency, write cost, and failure behavior.
  • Interview rounds often test whether you can explain these trade-offs clearly.
🖼

Visual

🗄 Cache strategy map
Cache-aside (lazy):
  read miss -> DB -> put cache
  ✅ simple, common
  ⚠ stale windows + stampede risk

Write-through:
  write -> DB + cache
  ✅ fresher reads
  ⚠ write latency / dual-write handling

Write-behind:
  write -> cache, async flush DB
  ✅ high write throughput
  ⚠ durability + replay complexity

Pattern choice is mostly consistency vs latency trade-off, not coding style.

✅

When to Use

  • Cache-aside for read-heavy APIs and hot-key lookups.
  • Write-through when stale reads are costly after writes (profiles, product metadata).
  • Write-behind for buffered telemetry/counters where eventual persistence is acceptable.
  • Add stampede protection for high-fanout keys.
🚫

When NOT to Use

  • Strictly consistent financial invariants without reconciliation controls.
  • Low-traffic endpoints where cache complexity gives little benefit.
  • Write-behind without durable queue/log and replay strategy.
💻

Code Variations

1️⃣ Cache-aside (default)java
UserProfile get(String userId) {
    UserProfile p = redis.get(userId);
    if (p != null) return p;
    p = repo.fetch(userId);
    redis.set(userId, p, ttlWithJitter());
    return p;
}
Simple and common; combine with single-flight for hot misses.
2️⃣ Write-throughjava
@Transactional
void updateProfile(UserProfile p) {
    repo.save(p);
    redis.set("user:" + p.id(), p, Duration.ofMinutes(10));
}
Keeps cache warm/fresh after write path; write latency is slightly higher.
3️⃣ Stampede protection (single-flight)java
return singleFlight.computeIfAbsent(userId, k -> {
    UserProfile fresh = repo.fetch(k);
    redis.set(k, fresh, ttlWithJitter());
    return fresh;
});
Coalesces concurrent misses so DB sees one fetch per key burst.
4️⃣ Write-behind sketchjava
void incrementView(String key) {
    redis.incr(key);
    outbox.append(new FlushCounterEvent(key)); // durable event
}
Needs durable event log + idempotent flusher to avoid loss/duplication.
📊

Caching strategy comparison

AspectCache-asideWrite-throughWrite-behind
Read latency✅ Low on hit; miss pays DB✅ Low and typically warm✅ Low if cache available
Write latency✅ Lowest⚠ Medium (DB + cache)✅ Lowest in request path
Freshness after write⚠ Depends on invalidation✅ Usually freshest⚠ Eventual
Failure complexity⚠ Invalidation/stampede⚠ Dual-write ordering❌ Highest (durability/replay)
Best fitGeneral read-heavy APIsRead-after-write sensitive pathsHigh-write buffered workloads
🔀

Common Confusions Cleared Up

Cache-aside vs Write-through

Cache-aside optimizes simplicity and read path. Write-through optimizes read freshness right after writes at the cost of more complex write flow.

TTL invalidation vs event invalidation

TTL is simple but introduces stale windows. Event invalidation is fresher but requires reliable delivery and ordering.

🎤

Interview Q&A

🧾

Quick Cheat Sheet

DefaultCache-aside + TTL jitter + explicit invalidation.
Key designNamespaced + versioned keys (user:v2:{id}).
ObserveHit ratio, miss burst, stale-read incidents, evictions.
Protect DBSingle-flight + backpressure + graceful stale fallback.
🔗

Related Topics