Caching Strategies
Placing a cache in your system is easy. Deciding how your application interacts with that cache is the hard part. The pattern you choose determines your data consistency, write latency, and system resilience.
1. Cache-Aside (Lazy Loading)
This is the most common pattern. The application is responsible for coordinating between the cache and the database.
Flow
- Read: App checks Cache.
- Hit: Return data.
- Miss: App reads from DB -> App writes to Cache -> Return data.
- Write: App writes to DB -> App invalidates/deletes Key in Cache.
Analysis
- Pros:
- Resilient: If the cache fails, the system just acts as if every request is a "miss" and reads from the DB. It doesn't crash completely.
- Data Model: Cache contains only what is actually requested (lazy), keeping it lean.
- Cons:
- Stale Data: There is a small window between writing to DB and invalidating cache where users might read old data.
- Thundering Herd: If a popular item expires, multiple processes might try to rebuild it simultaneously.
2. Write-Through
The cache sits in front of the database. The application treats the cache as the main data store.
Flow
- Write: App writes to Cache -> Cache writes to DB (Synchronously).
- Read: App reads from Cache.
Analysis
- Pros:
- High Consistency: Cache and DB are always in sync.
- No Stale Requests: Data in cache is never older than data in DB.
- Cons:
- High Latency: Every write operation must do 2 writes (Cache + DB). This is the slowest write performance.
- Cache Pollution: You might write data that is never read, wasting memory.
3. Write-Back (Write-Behind)
Similar to Write-Through, but the DB write happens asynchronously.
Flow
- Write: App writes to Cache -> Cache immediately acknowledges success.
- Sync: Cache asynchronously writes to DB in the background (after X seconds or X writes).
Analysis
- Pros:
- Fastest Writes: The user gets a response instantly (memory speed). Very high throughput.
- Reduced DB Load: Multiple writes to the same key can be batched into one DB update.
- Cons:
- Data Loss Risk: If the cache crashes before syncing to the DB, that data is lost forever.
- Use Case: Analytics counters, Likes/Views, non-critical logging.
4. Read-Through
Often paired with Write-Through. The cache library is responsible for fetching from the DB, not the application.
Flow
- Read: App asks Cache for Key.
- Miss: Cache (not App) fetches from DB -> Cache updates self -> Return data.
Analysis
- Pros:
- Simplifies App Code: Your code just says
cache.get(key). It doesn't need "if miss then db" logic everywhere.
- Simplifies App Code: Your code just says
- Cons:
- Vendor Lock-in: Requires a sophisticated cache provider (like Redis Gears or specific plugs) that knows how to talk to your specific DB.
5. Write-Around
The final member of the family: writes go directly to the database, bypassing the cache entirely. The cache only fills on reads (via cache-aside or read-through).
Analysis
- Pros: Write-heavy data that is rarely re-read (audit logs, bulk imports) never pollutes the cache. Combined with cache-aside reads, it's a good default for asymmetric workloads.
- Cons: The first read after every write is guaranteed to miss — recently written data is the coldest. Bad fit when users immediately view what they just wrote (e.g., posting a comment).
The Race Conditions Nobody Warns You About
Cache-aside looks bulletproof but hides two classic concurrency bugs — both favorite senior-interview probes:
Race 1: Why "Delete" Beats "Update" on Writes
Suppose writes update the cache instead of deleting the key. Two concurrent writers:
Writer A: writes value=1 to DB Writer B: writes value=2 to DB (DB now correct: 2) Writer B: sets cache to 2 Writer A: sets cache to 1 (cache now WRONG: 1, forever)
Network scheduling reordered the cache updates. With delete-on-write, the worst case is an extra cache miss, never permanent corruption. This is why the canonical pattern is "write DB, invalidate cache" — not "write DB, update cache."
Race 2: The Stale-Fill
Even with delete-on-write, a reader can interleave with a writer:
Reader: cache miss → reads value=1 from DB Writer: writes value=2 to DB → deletes cache key Reader: (finally) fills cache with the stale 1
The reader's old data lands after the writer's invalidation. Rare (the reader must stall mid-flight), but real at scale. Mitigations: short TTLs as a self-healing backstop, or compare-and-set fills that reject writing an entry older than the last invalidation timestamp.
Choosing in Practice: A Decision Recipe
- Default to Cache-Aside + delete-on-write + TTL. It's resilient (cache death ≠ outage), well-understood, and the failure modes are bounded staleness rather than corruption.
- Reads must never be stale? Write-Through — and accept the write latency tax. Common for configuration/feature-flag stores where a stale read changes behavior.
- Absorbing write floods? Write-Back for counters, metrics, and "likes" — anything where losing 5 seconds of increments in a crash is survivable. Redis persistence (AOF) narrows the loss window; see Redis Internals.
- Write-heavy, read-rarely data? Write-Around keeps it from evicting your genuinely hot keys.
Real systems mix per data type: the same service might use write-through for feature flags, cache-aside for user profiles, and write-back for view counters — the strategy attaches to the data, not to the application.
Comparison Summary
| Strategy | Read Speed | Write Speed | Consistency | Safety |
|---|---|---|---|---|
| Cache-Aside | Fast (on hit) | Fast | Good | High (DB untouched) |
| Read-Through | Fast (on hit) | n/a | Good | High |
| Write-Through | Fast | Slow | Excellent | High |
| Write-Back | Fast | Instant | Eventual | Low (Data Loss risk) |
| Write-Around | First read misses | Fast | Good | High |
Related Concepts
- Caching Overview — where caches live and why hit ratio rules everything
- Eviction Policies — what to delete when memory fills
- Redis Internals
- CDN — Content Delivery Networks
About ScaleWiki
ScaleWiki is an interactive educational platform dedicated to demystifying distributed systems, software architecture, and system design. Our mission is to provide high-quality, technically accurate resources for software engineers preparing for interviews or solving complex scaling challenges in production.
Read more about our Editorial Guidelines & Authorship.
Educational Disclaimer: The architectural patterns and system designs discussed in this article are based on common industry practices, technical whitepapers, and public engineering blogs. Actual implementations in enterprise environments may vary significantly based on specific product requirements, legacy constraints, and evolving technologies.
Related Articles
Microservices Architecture
An architectural style that structures an application as a collection of loosely coupled, independently deployable services.
Circuit Breaker Pattern
A mechanism to prevent an application from repeatedly trying to execute an operation that's likely to fail.
Event Sourcing & CQRS
Comprehensive guide to Event Sourcing and Command Query Responsibility Segregation (CQRS) patterns, covering immutable event logs, state reconstruction, read/write separation, and real-world implementations in banking, e-commerce, and audit systems.