Building Blocks
Caching Strategies
Cache-aside, read-through, write-through, write-around, write-back — five answers to the same trade-off between latency, freshness, complexity, and the risk of stale or lost data.
Updated 2026-06-09
Caching Strategies
A caching strategy defines how an application reads from and writes to the cache. Every one of them trades latency, consistency, complexity, and failure behaviour differently. Two questions shape them all:
- On a read miss, who fetches from the source of truth?
- On a write, what gets updated, in what order, and when is the write acknowledged?
The answers decide your latency, freshness, and what happens when the cache or database fails.
Cache-aside (lazy loading)
The application owns the logic: check the cache, fall back to the DB on a miss, then store the result. On writes, update the DB and delete the affected cache entry.
1. GET key ─▶ Cache ──hit──▶ value
│ miss
2. read ───────▶ Database ─▶ value
3. store with TTL ─▶ Cache
The cache is an optional accelerator — if it's down, reads still work, just slower. Works with any plain KV cache (Redis, Memcached) and lets you set per-type TTLs. The cost: application code must implement fills and invalidation, cold reads always pay the DB round trip, and a missed invalidation leaves stale data.
Best for: read-heavy workloads with infrequent updates — product catalogs, user profiles, config.
Read-through
The application asks the cache directly; the cache layer handles the miss by loading from the DB, storing, and returning.
1. GET key ─▶ Cache layer (with loader) ──miss──▶ Database
◀── store & return ──
The miss path lives inside the cache layer, not the app — centralising load logic across services so reads look like simple lookups. The cost: the cache layer needs DB access and schema knowledge, cold reads still pay the loader, and loader failures become cache failures.
Best for: many services sharing one read pattern (e.g. a Caffeine-backed local cache).
Write-through
The application writes to the cache layer, which writes to the DB synchronously and only acks after the DB commits.
1. SET key=value ─▶ Cache layer (writer) ─▶ Database
◀── commit OK ──
store value & ACK
Cache and DB stay aligned, manual invalidation disappears, and callers get strong read-after-write behaviour. The cost: write latency now includes both the DB write and the cache work, all writers must go through the cache path (a direct DB write leaves the cache stale), and the cache is on the critical write path.
Best for: reads commonly follow writes, write latency is tolerable, all writers route through the cache.
Write-around
The application writes straight to the DB, bypassing the cache. Cached data is populated only on the next read miss (usually via cache-aside).
1. write key=value ─▶ Database (cache bypassed)
... populated on next read miss ...
Avoids caching write-once data, keeps writes simple, and keeps the cache focused on actually-read data. The downside: the first read after a write is always a miss, and any already-cached entry goes stale until it expires or is invalidated — weaker read-after-write than write-through.
Best for: write-heavy workloads where fresh writes are rarely read soon — bulk imports, event ingestion, append-only logs.
Write-back (write-behind)
The application writes to the cache, which acks immediately; the real DB write happens later in the background.
Request: 1. write ─▶ Cache ─▶ 2. ACK immediately
Backflush: 3. flush in batches ─▶ Database
Lowest user-visible write latency, and background workers can batch and coalesce
writes to cut DB load — great for absorbing write spikes. But the acked write is only
as durable as the cache and its pending-write buffer: if the cache dies before the
flush, those writes are lost. It needs a durable buffer (a WAL or replicated queue),
plus ordering/retry/recovery logic. (Redis AOF helps, but the default everysec
fsync can still lose ~1s of writes, and always removes the latency win.)
Best for: low-risk, high-throughput data where delayed durability is OK — counters, view counts, metrics. Never for payments, audit logs, or user content.
Comparison
| Strategy | Read miss | Write path | ACK after | Consistency |
|---|---|---|---|---|
| Cache-aside | App loads from DB | App writes DB, deletes cache | DB commit + delete | Eventual |
| Read-through | Cache layer loads | (not a write pattern) | — | Eventual |
| Write-through | Cache layer loads | App → cache → DB | DB commit | Strong (if all writers use it) |
| Write-around | App loads from DB | App writes DB directly | DB commit | Eventual; entries can go stale |
| Write-back | App loads from DB | App → cache, async flush | Cache write | Eventual; loss risk without durable buffer |
Choosing a strategy
| Workload | Consider |
|---|---|
| Reads dominate, writes occasional | Cache-aside or read-through |
| Reads frequently follow writes | Write-through |
| Many services share read logic | Read-through |
| Write-heavy, recent writes rarely read | Write-around |
| Latency-sensitive writes, low-risk data | Write-back + durable buffer |
| Loss/staleness unacceptable | Read from the DB; cache only safe-to-stale data |
Many production systems combine strategies — cache-aside reads paired with write-around writes is common, and write-back is reserved for specific high-volume, low-risk paths rather than applied everywhere.
Summary
There's no single best strategy — each is a different answer to "how much latency, complexity, and risk is acceptable for the throughput and freshness I need?" Cache-aside is the flexible default. Read-through centralises the read path. Write-through keeps cache and DB aligned at the cost of write latency. Write-around keeps the cache clean for write-heavy loads. Write-back optimises write throughput when delayed durability is acceptable. Choose by your read/write ratio and your tolerance for stale or lost data.
