Caching & CDNs

Cache Strategies

18 min Lesson 3 of 10

Cache Strategies

Knowing where to cache is only half the problem. The other half is knowing how the cache interacts with your database when data is read or written. Get this wrong and you end up with stale data, cache stampedes, or a cache that fills up with entries nobody ever reads. There are four canonical strategies every system designer needs to understand: cache-aside, read-through, write-through, and write-back.

Strategy 1: Cache-Aside (Lazy Loading)

Cache-aside is by far the most common pattern in the wild — Redis in the typical web-app stack almost always operates this way. The application code owns the cache interaction entirely: the cache sits beside the database and is populated only on demand.

Read flow:

  1. Application looks up the key in the cache.
  2. Cache hit → return the value directly. Done.
  3. Cache miss → application queries the database, stores the result in the cache under the same key (with a TTL), then returns it to the caller.

Write flow: the application writes directly to the database. The cache entry is either invalidated (deleted) or simply left to expire via its TTL. On the next read, it will be repopulated from the fresh database value.

Cache-Aside (Lazy Loading) flow Application Cache (e.g. Redis) Database 1. check HIT → return 2. MISS → query DB 3. result 4. populate Legend Request / check Cache hit Populate / return
Cache-aside: the application manages cache population manually on a cache miss.

Real-world example: a product page on an e-commerce site. The first visitor after a deployment triggers a cache miss and pays the ~5 ms database query cost. All subsequent visitors within the TTL window hit the cache and pay ~0.3 ms. At 50,000 product pages and a 5-minute TTL, only a fraction of those pages will ever be "hot" — cache-aside naturally populates only what is actually needed, keeping memory lean.

Best practice: Always set a TTL even in cache-aside. Relying solely on explicit invalidation is fragile — a missed invalidation path leaves stale data forever. A short TTL is a safety net.

Trade-offs:

  • Pro: cache contains only data that has actually been requested — no wasted memory on cold data.
  • Pro: the cache can tolerate a full restart; the application simply repopulates on misses.
  • Con: the very first request after a miss (or after a cache restart) is always slow — the cold start problem.
  • Con: under sudden traffic spikes, many concurrent requests may all miss at the same moment and hammer the database simultaneously — the thundering herd or cache stampede.

Strategy 2: Read-Through

Read-through moves the cache-miss logic out of the application and into the cache layer itself (or a caching library that wraps the database). From the application's perspective there is only one place to ask for data — the cache. If the cache doesn't have it, the cache fetches it from the database, stores it, and returns it, all transparently.

This is the default behaviour in caching frameworks like Hibernate second-level cache, ActiveRecord's Rails cache, or a properly configured Varnish reverse-proxy cache.

Trade-offs:

  • Pro: simpler application code — no cache-miss branching in every service method.
  • Pro: the cache and application speak the same abstraction; swapping the backing store is transparent.
  • Con: the first request for any key is still slow (same cold-start issue as cache-aside).
  • Con: requires a cache that supports a "loader" or "fill" callback, which not all infrastructure supports.
Cache-aside vs read-through: The data flow on a miss is identical — database is queried, result is stored in cache, result is returned. The difference is who runs that logic: the application (cache-aside) or the cache layer itself (read-through). In practice, many teams call their cache-aside implementation "read-through" colloquially.

Strategy 3: Write-Through

Write-through means every write goes to the cache and the database synchronously, in the same request before the caller is acknowledged. The cache is always consistent with the database for any key it holds.

Write flow:

  1. Application writes to the cache.
  2. The cache (or application) immediately writes the same data to the database.
  3. Both writes succeed → acknowledge success to the caller.
Write-through vs Write-back comparison Write-Through App Cache Database write sync ACK both Write latency = DB latency Write-Back (Write-Behind) App Cache Database write ACK fast async Write latency = cache only
Write-through (left) keeps cache and DB always in sync at the cost of write latency. Write-back (right) acknowledges on cache write and flushes to DB asynchronously.

Real-world example: a user-profile service. When a user updates their name, you want any subsequent read — whether from cache or directly from the database — to return the new name. Write-through guarantees that. Compare this to cache-aside with invalidation: between the DB write and the cache delete, a reader could see stale data. Write-through eliminates that window entirely.

Trade-offs:

  • Pro: cache is never stale for keys it holds — reads are always consistent.
  • Pro: if the database goes down, the cache still holds the most-recent committed values.
  • Con: every write is as slow as the database. If your DB write latency is 10 ms, your API write latency is at least 10 ms, even for hot in-memory data.
  • Con: the cache fills with infrequently-read data that was written but never subsequently fetched — "write-heavy, read-light" workloads waste cache memory.

Strategy 4: Write-Back (Write-Behind)

Write-back trades safety for speed. The application writes to the cache only and receives an immediate acknowledgment. The cache then flushes the dirty data to the database asynchronously — on a schedule, when memory pressure requires eviction, or when the cache is shutting down gracefully.

Real-world example: a real-time multiplayer game tracking player scores. Scores change thousands of times per second. Writing each increment to a relational database synchronously would saturate the DB within seconds. Instead, write to Redis immediately (sub-millisecond), and flush aggregated scores to MySQL every 10 seconds. Players always see an up-to-date score; the database absorbs a manageable write rate.

Write-back is also the dominant strategy in CPU/hardware caches — when your CPU writes to L1, it doesn't immediately write to DRAM. The cache line is marked "dirty" and written back only when evicted.

Danger — write-back is lossy on failure. If the cache node crashes before it has flushed its dirty entries, those writes are permanently lost. Only choose write-back when you can tolerate some data loss (game scores, view counts, analytics events) or when you have a durable write-ahead log (WAL) in the cache layer itself.

Trade-offs:

  • Pro: dramatically reduces write latency — the application is not blocked on DB I/O at all.
  • Pro: batches and coalesces writes, reducing database load — essential for write-heavy workloads.
  • Con: potential data loss on cache failure — dirty (un-flushed) entries are gone.
  • Con: the database lags behind the cache; any process reading directly from the DB sees stale values.
  • Con: considerably more complex to implement correctly, especially for ordered operations.

Choosing the Right Strategy

There is no universally correct choice — the right strategy depends on the workload's read/write ratio and tolerance for stale data or data loss:

  • Read-heavy, occasional writes, some stale data tolerablecache-aside with TTL. Most web APIs.
  • Read-heavy, writes must be immediately consistentwrite-through. User profiles, permissions.
  • Write-heavy, low-latency writes required, data loss acceptablewrite-back. Analytics counters, game scores, IoT telemetry.
  • Framework-driven, many service methodsread-through to keep application code clean.

In large systems you will typically mix strategies across different data types. An order-management service might use write-through for order status (strong consistency required) while using write-back for page-view counters on product listings (eventual consistency fine, high write volume).

Key insight: Every cache strategy is ultimately a decision about who is responsible for keeping the cache consistent with the source of truth — the application code, the cache layer, or an async background process. Recognising that framing makes the trade-offs obvious.