Caching strategies
Cache-aside, write-through, write-back, and refresh-ahead - pick based on read/write ratio and staleness tolerance.
"There are only two hard things in computer science: cache invalidation and naming things." Phil Karlton was right. The cache itself is trivial. Getting it right is hard.
The four patterns
Cache-aside (lazy loading). App reads cache. Miss = read DB, populate cache. Write = update DB, invalidate or update cache. This is the default. Use it unless you have a reason.
Write-through. App writes go to cache, cache writes to DB synchronously. Reads from cache. Consistent. Slower writes.
Write-back (write-behind). App writes to cache, cache writes to DB asynchronously. Fast writes. Risk: cache crash loses unflushed writes.
Refresh-ahead. Cache refreshes hot entries before TTL expires. Smooth, no stampede. Used by CDNs (stale-while-revalidate).
Invalidation: pick one model
- TTL only. Easy. Stale by default for up to TTL seconds. Use for data where staleness is acceptable.
- Explicit invalidation on write. App deletes the cache key when it writes to DB. Tight consistency. Brittle if you forget to invalidate from every write path.
- Event-driven (CDC, Debezium). DB change feed invalidates the cache. Robust. More moving parts.
The TTL+explicit hybrid is what most teams ship. TTL as a backstop, explicit invalidation for the hot paths.
The two famous failure modes
Thundering herd. TTL expires, 1000 requests miss simultaneously, all hit the DB. Solutions: locking around the recompute, jittered TTLs, refresh-ahead.
Cache stampede on cold start. Empty cache, full traffic. Pre-warm critical keys before going live.
What to cache
- Read-heavy, infrequently changed. Product catalog, user profile.
- Expensive to compute. Aggregations, recommendations.
- Read by many. Feature flags, config.
Don't cache:
- Write-heavy. Cache churn defeats the purpose.
- Per-request unique. Auth checks for one user one time.
- Critical-correctness reads. Account balances at withdrawal time.
Cache layers
Browser -> CDN -> API gateway -> App in-memory -> Redis -> DB query cache -> DB.
Each layer catches a different request type. CDN catches static assets. Redis catches expensive queries. App in-memory catches per-pod hot keys (sub-microsecond).
My defaults
Cache-aside in Redis with 5 minute TTL plus explicit invalidation on writes. Hot per-pod data in an in-memory LRU with 30 second TTL. CDN for anything public and static. Always measure hit rate. A cache with <80% hit rate is mostly cost.
Learn more
- DocsAWS: Caching strategiesAWS docs
- ArticleCloudflare: How we built a cacheCloudflare blog
- ArticleDesigning Data-Intensive Applications, ch 5Martin Kleppmann