Cache Strategies
Cache-aside / write-through / write-behind / refresh-ahead side-by-side.
This interactive explanation is built for system design interview prep: step through Cache Strategies, watch the internal state change, and connect the concept to real distributed-system trade-offs.
Overview
A cache only pays for itself if you know which writes populate it and which reads bypass it. The three strategies that cover 95% of real systems are cache-aside, write-through, and write-behind, and they differ only in who writes to the cache and when. Cache-aside is the lazy default: the application reads from the cache, falls back to the database on a miss, then repopulates the cache. Write-through synchronously writes to both cache and database on every write, trading write latency for guaranteed cache freshness. Write-behind writes to the cache only and flushes to the database asynchronously, trading durability for raw throughput. Each choice changes the failure modes of the system. Cache-aside can serve stale data after a DB write; write-through doubles your write path but keeps reads consistent; write-behind loses the last few seconds of writes if the cache node dies. Picking the wrong strategy for your workload is one of the top five ways to break production.
How it works
Cache-aside reads check the cache first. On a miss, the app loads from the database, inserts into the cache with a TTL, and returns. On a write, the app updates the database and explicitly invalidates (not updates) the cache key; this avoids races where two concurrent writers write to the cache in the wrong order. The TTL is a cheap defense against stale data when invalidation is missed. Write-through intercepts writes at the cache layer. Every write goes cache-then-DB (or a wrapper that does both); the call does not return until both succeed. Reads hit cache and never miss for populated keys, so read latency is near-constant but write latency is the sum of cache and DB latency. This is the preferred strategy for read-heavy workloads with small working sets, such as user-profile services. Write-behind treats the cache as the source of truth for a short window. The cache writes are acknowledged immediately, and a background flusher drains a queue of dirty entries into the database in batches. Throughput is cache-write-speed, not DB-write-speed, which is why systems like MySQL's InnoDB buffer pool and Redis AOF-lazy-flush use it under the hood. The cost is data loss risk: a cache crash loses anything not yet flushed. Production systems usually pair it with replicated caches or a WAL on the cache node.
Implementation
public class CacheAsideService {
private final Cache<String, User> cache;
private final UserRepository db;
private final Duration ttl = Duration.ofMinutes(10);
public CacheAsideService(Cache<String, User> cache, UserRepository db) {
this.cache = cache; this.db = db;
}
public User get(String id) {
User u = cache.get(id);
if (u != null) return u;
u = db.findById(id);
if (u != null) cache.put(id, u, ttl);
return u;
}
public void update(String id, User u) {
db.save(u);
cache.invalidate(id); // invalidate, not update, to avoid write-write races
}
}
public class WriteThroughService {
private final Cache<String, User> cache;
private final UserRepository db;
public WriteThroughService(Cache<String, User> c, UserRepository d) { this.cache = c; this.db = d; }
public void put(String id, User u) {
db.save(u); // block until durable
cache.put(id, u); // then publish to readers
}
public User get(String id) { return cache.get(id); }
}
public class WriteBehindService {
private final Cache<String, User> cache;
private final BlockingQueue<User> flushQueue = new LinkedBlockingQueue<>(100_000);
private final UserRepository db;
public WriteBehindService(Cache<String, User> c, UserRepository d, ScheduledExecutorService exec) {
this.cache = c; this.db = d;
exec.scheduleWithFixedDelay(this::drain, 100, 100, TimeUnit.MILLISECONDS);
}
public void put(String id, User u) {
cache.put(id, u);
flushQueue.offer(u); // acked immediately
}
private void drain() {
List<User> batch = new ArrayList<>(256);
flushQueue.drainTo(batch, 256);
if (!batch.isEmpty()) db.batchSave(batch);
}
}
Complexity
- cache hit latency:
~1 ms in-process / ~1 ms over LAN - cache miss penalty:
DB latency + cache write (~10 ms) - write-through write:
cache RTT + DB RTT (sum) - write-behind durability window:
batch interval (~100 ms typical)
Key design decisions & trade-offs
- Cache-aside vs write-through — Chosen: Cache-aside for general workloads, write-through for read-heavy. Cache-aside keeps the cache optional and failure-tolerant; write-through guarantees freshness at the cost of write latency.
- Invalidate vs update on write — Chosen: Invalidate only. Two concurrent writers can reorder puts; an invalidation lets the next reader fetch the true DB state rather than stale interleaved writes.
- Write-behind durability — Chosen: Accept small loss window for throughput. Only acceptable for non-critical data (metrics, session counters). Financial data must not use write-behind without a replicated log.
Common pitfalls
- Stampede on expiry: 1000 requests miss simultaneously and hit the DB — use singleflight / request coalescing
- Cache penetration: queries for nonexistent keys never populate — cache negative lookups briefly
- Cache avalanche: a whole shard expires at once because TTLs were set together — jitter TTLs
- Forgetting to invalidate on delete and serving ghost rows
- Write-behind queue overflow during DB outage silently drops writes
Interview follow-ups
- Add singleflight / request coalescing to prevent thundering-herd on cold keys
- Introduce a two-tier cache (L1 in-process + L2 Redis) for tail-latency reduction
- Use a refresh-ahead pattern for predictable hot keys
- Add consistent-hashing for cache sharding to minimize re-fills on node changes
Recommended reading
- Alex Petrov, Database Internals — storage engines and distributed systems internals.
- Martin Kleppmann, Designing Data-Intensive Applications (DDIA) — data models, replication, partitioning, consistency.
- The System Design Primer — high-level design building blocks.
- Foundational networking + web-security references (TCP/IP, TLS 1.3, OWASP Top 10).