Caching patterns
A cache keeps a copy of data somewhere fast so you do not pay the full cost of fetching it again. Reading from memory is orders of magnitude faster than reaching across a network to a database, and that speed gap is why caches exist. The moment you add one, the same piece of data lives in two places, and you have to decide how those two copies stay in step.
Every caching pattern answers two decisions. Make both, and the cache's behavior is settled.
Cache-aside: the application owns the cache
Cache-aside, also called look-aside, puts the application in charge of both the cache and the database. On a read, the application checks the cache first. On a miss, it reads from the database and then writes that value back into the cache, so the next read finds it (Microsoft's cache-aside reference, which AWS calls lazy loading).
Two properties follow. The cache fills only with data someone has requested, so you never spend memory on values nobody reads. And the cache stays an optimization rather than a dependency: if a cache node fails, reads slow down instead of failing.
The cost appears on a miss. A miss takes three trips before the application has its answer: cache, then database, then cache again (AWS). That cost scales well in practice. Facebook ran its memcache tier as a look-aside cache and pushed it to billions of requests per second (Nishtala et al.).
sequenceDiagram
participant App as Application
participant Cache
participant DB as Database
App->>Cache: read key
alt cache hit
Cache-->>App: value
else cache miss
Cache-->>App: miss
App->>DB: read key
DB-->>App: value
App->>Cache: write value
end
Read-through: the cache owns the database read
Read-through moves the database read that cache-aside leaves to the application into the cache itself. The application reads only from the cache. On a miss, the cache loads the value from the backing store on its own (Hazelcast's read-through loader).
The application code stays simpler, because no call site has to remember the load-on-miss logic. You trade that simplicity for coupling. The cache now has to know how to query your store, so the loading logic lives beside the cache instead of in your application. The complexity moves rather than disappears.
sequenceDiagram
participant App as Application
participant Cache
participant DB as Database
App->>Cache: read key
alt cache hit
Cache-->>App: value
else cache miss
Cache->>DB: read key
DB-->>Cache: value
Cache-->>App: value
end
Write-through: cache and database commit together
With the read path settled, the question becomes when a write reaches the database.
Write-through writes every change to the cache and the database together, and the write is not finished until both have stored it (Hazelcast, AWS). Because the cache is never older than the database, a read that follows a write returns the new value. You pay for that guarantee in two ways. Each write is slower because of the second hop, and the cache fills with written data nobody reads again unless you add a time-to-live (TTL) to expire it (AWS).
sequenceDiagram
participant App as Application
participant Cache
participant DB as Database
App->>Cache: write value
Cache->>DB: write value
DB-->>Cache: ack
Cache-->>App: ack
Write-back: cache now, database later
Write-back, also called write-behind, makes the opposite trade from write-through. It writes to the cache, acknowledges the write right away, and writes through to the database later in the background, often folding several updates to the same key into a single database write (Hazelcast).
Writes become fast and absorb bursts, because the slow database write is off the critical path. The risk is durability. If the cache crashes before it flushes, the changes that had not yet reached the database are lost.
sequenceDiagram
participant App as Application
participant Cache
participant DB as Database
App->>Cache: write value
Cache-->>App: ack
Note over Cache,DB: later, batched, in the background
Cache->>DB: flush values
Write-around: write the database, skip the cache
Write-around sends writes straight to the database and does not touch the cache. The cached copy fills only later, when a read misses and the read path repopulates it. Writes avoid evicting cache entries nobody will read back, and in exchange the first read after a write is a guaranteed miss. Cache-aside's write path is write-around: update the database, then leave the cache to repopulate on demand.
sequenceDiagram
participant App as Application
participant Cache
participant DB as Database
App->>DB: write value
DB-->>App: ack
Note over Cache: cache untouched, fills on a later read miss
Choosing a pattern follows from the two decisions
Because a pattern is a read path paired with a write path, the choice comes apart into the same two decisions.
Match the read path to who should own database access. Choose read-through when you want application code to stay simple and the cache to own loading. Choose cache-aside when you want the application in control and the cache to remain a removable optimization.
Match the write path to how fresh your reads must be. Strong read-after-write freshness points to write-through. Write-heavy or bursty work that tolerates a small window of possible loss points to write-back. Work that rarely re-reads what it wrote points to write-around.
How a write clears or refreshes the cached value is a separate subject, cache invalidation, and it carries more failure modes than the write path alone.
The patterns at a glance
| Pattern | Decision | What happens | Main trade |
|---|---|---|---|
| Cache-aside (look-aside) | Read | App checks the cache, loads from the DB on a miss, backfills the cache | Three trips on a miss. Caches only requested data |
| Read-through | Read | App reads only the cache. The cache loads from the DB on a miss | Simpler application, but the cache couples to the store |
| Write-through | Write | Write to cache and DB together, synchronously | Fresh reads, slower writes |
| Write-back (write-behind) | Write | Write to cache, acknowledge, persist to the DB later | Fast writes, risk of loss on a crash |
| Write-around | Write | Write to the DB only. The cache fills on a later read miss | No wasted cache writes, but a guaranteed first-read miss |