Back to All Concepts
CachingArchitectureDesign PatternsIntermediate

Caching Strategies

A breakdown of where to place your cache and how to keep it in sync with your database.

Last updated: By the ScaleWiki Editorial Team

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

  1. Read: App checks Cache.
    • Hit: Return data.
    • Miss: App reads from DB -> App writes to Cache -> Return data.
  2. 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

  1. Write: App writes to Cache -> Cache writes to DB (Synchronously).
  2. 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

  1. Write: App writes to Cache -> Cache immediately acknowledges success.
  2. 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

  1. 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.
  • 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

  1. 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.
  2. 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.
  3. 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.
  4. 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

StrategyRead SpeedWrite SpeedConsistencySafety
Cache-AsideFast (on hit)FastGoodHigh (DB untouched)
Read-ThroughFast (on hit)n/aGoodHigh
Write-ThroughFastSlowExcellentHigh
Write-BackFastInstantEventualLow (Data Loss risk)
Write-AroundFirst read missesFastGoodHigh

Related Concepts

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