Advanced Caching Patterns

advanced 4-7 YOE caching Redis cache-aside write-through distributed-cache system-design

We covered the basics of caching earlier. Now let’s go deeper into the different caching patterns and when to use each one. The difference between a well-cached system and a poorly-cached system isn’t whether we use a cache — it’s which pattern we pick.

The Five Caching Patterns

Caching Patterns Overview
Cache-Aside
App → Cache?
Miss → App → DB
App → Cache (fill)
App manages everything
Read-Through
App → Cache?
Miss → Cache → DB
Cache fills itself
Cache manages loading
Write-Through
App → Cache → DB
Both written together
Synchronous
Always consistent
Write-Behind
App → Cache (done!)
Cache → DB (later)
Asynchronous
Fastest writes, risky
Refresh-Ahead
Cache auto-refreshes
Before TTL expires
Proactive
No cache misses for hot data

Cache-Aside (Lazy Loading)

The most common pattern. The application talks to the cache and the database directly. The cache doesn’t know the database exists.

Read path:

  1. App checks the cache
  2. Hit — return the cached value
  3. Miss — app reads from DB, writes the value into cache, then returns it

Write path:

  1. App writes to the database
  2. App deletes (invalidates) the cache entry
  3. Next read will re-populate the cache

Why it’s popular: Simple to implement. Cache only holds data that’s actually been requested. If the cache goes down, the app still works (just slower).

The catch: The first request for any key is always a cache miss. And there’s a small window between the DB write and cache invalidation where we serve stale data.

Read-Through

Looks similar to cache-aside from the app’s perspective, but the only difference is: the cache itself is responsible for loading data from the database on a miss. The app only ever talks to the cache.

Think of it like a library. We ask the librarian (cache) for a book. If they don’t have it, they go fetch it from the warehouse (DB) — we just wait.

Pros: Simpler application code. The data-loading logic lives in one place (the cache layer). Cons: Cache needs to know how to query the database. Cache failure means no data access at all.

Write-Through

Every write goes to the cache AND the database at the same time, synchronously. The cache acts as the primary interface for writes.

App writes "user:123" → Cache stores it → DB stores it → Done

Pros: Cache is always in sync with the DB. No stale data. Pairs perfectly with read-through — together they give us a fully consistent cache layer.

Cons: Higher write latency (two writes per operation). We end up caching data that might never be read, wasting memory.

Write-Behind (Write-Back)

Write to the cache immediately, then asynchronously flush to the database in the background. The app gets a fast response because it only waits for the cache write.

App writes "user:123" → Cache stores it → Returns immediately
                          ↓ (async, batched)
                          DB stores it later

Pros: Super fast writes. We can batch multiple writes into one DB operation, reducing database load.

Cons: If the cache crashes before flushing to DB, we lose data. This is a real risk. Use this only when speed matters more than durability.

Good for: Write-heavy workloads like analytics counters, view counts, or gaming leaderboards where losing a few seconds of data is acceptable.

Refresh-Ahead

The cache proactively refreshes entries that are about to expire. If an entry’s TTL is 60 seconds, the cache might refresh it at the 50-second mark — before anyone asks for it.

Pros: Hot data never experiences a cache miss. Users get consistently fast responses.

Cons: We’re refreshing data that might not be needed. If we predict wrong, we waste resources refreshing data nobody’s reading.

Good for: Data that’s read very frequently and is expensive to compute (dashboards, popular product pages, trending feeds).

The Cache Stampede Problem

Also called the thundering herd. Here’s the scenario:

  1. A popular cache key expires
  2. 1000 requests come in simultaneously for that key
  3. All 1000 requests see a cache miss
  4. All 1000 requests hit the database at the same time
  5. The database collapses under the load

This is a real problem for hot keys with millions of reads.

Solutions

Locking (Mutex): When a cache miss happens, the first request acquires a lock, fetches from DB, and populates the cache. All other requests wait for the lock to release, then read from cache.

Early expiration (Staggered TTL): Add a random jitter to TTL values so not all keys expire at the same time. Instead of all keys expiring at exactly 60s, they expire between 55-65s.

Refresh-ahead: Proactively refresh before expiry, so the key never actually expires for readers.

Never expire + background refresh: Set keys to never expire. A background job periodically refreshes them. Readers always get a cache hit (possibly slightly stale).

Distributed Caching

When our app runs on multiple servers, a local in-memory cache (like a HashMap) on each server has problems — different servers have different data, and when a server restarts, its cache is gone.

The solution: a shared distributed cache that all servers read from and write to.

Redis Cluster

Redis is the go-to for distributed caching. Redis Cluster splits data across multiple Redis nodes using hash slots (16,384 slots total). Each node owns a range of slots. If one node fails, a replica takes over.

Memcached

Simpler than Redis. Pure key-value store. Uses consistent hashing to distribute keys across multiple servers. Slightly faster than Redis for simple get/set operations because it does less.

When to Use Which

FeatureRedisMemcached
Data structuresLists, sets, sorted sets, hashesSimple key-value only
PersistenceOptional disk persistenceNone
ReplicationBuilt-inNone
Pub/SubYesNo
Best forFeature-rich caching, sessions, leaderboardsSimple, high-throughput caching

In simple language, caching patterns are about who loads the data, who writes the data, and when. Cache-aside is the safe default — we manage everything ourselves. Read-through and write-through let the cache do more work. Write-behind is fast but risky. And always plan for the stampede — because when a hot key expires, the thundering herd is coming.