1. Why Cache Write Strategy Matters
Adding a cache is straightforward for reads: check the cache, fall back to the database on a miss. Writes are more complex because you now have two places data could live — the cache and the database — and they can diverge. The write strategy defines exactly how and when writes propagate between the two, balancing three competing concerns:
- Consistency: Is the cache always in sync with the database?
- Write latency: How long does a write operation take?
- Durability: Is data safe if the cache crashes?
2. Cache-Aside (Lazy Loading)
Cache-aside is the most widely used pattern in web applications. The application code is responsible for managing the cache — the cache does not communicate with the database on its own.
READ PATH (Cache-Aside):
App → Cache: GET user:123
Cache HIT → return data to App
Cache MISS → App fetches from DB
→ App writes to Cache (SET user:123 ttl=300)
→ return data to App
WRITE PATH (Cache-Aside):
App → DB: UPDATE users SET name='Alice' WHERE id=123
DB write succeeds
App → Cache: DEL user:123 (invalidate stale entry)
Next read will repopulate cache from DB
The most common invalidation approach is delete on write: when data changes, delete the cache key. The next read will be a miss and will load fresh data from the database. This avoids stale reads at the cost of one extra cache miss per write.
Cache-Aside in Redis — Python Example
Use redis.get(key) on read; if None, fetch from DB and redis.setex(key, ttl, value). On write: update DB first, then redis.delete(key). Always update the DB before deleting from cache — if the cache delete fails, the TTL will eventually expire the stale entry.
3. Write-Through Cache
In write-through, every write goes to the cache and the database simultaneously. The write operation is not considered complete until both have been updated. The cache is always in sync with the database — there is no stale data.
WRITE-THROUGH FLOW:
App → Cache Layer: write(key, value)
│
├──▶ Cache: SET key value (synchronous)
│
└──▶ Database: UPDATE/INSERT (synchronous)
│
Both must succeed ──▶ return success to App
READ FLOW (always a cache hit after first write):
App → Cache: GET key → HIT (data always present)
Write-through is ideal for read-heavy workloads where data must never be stale. The downside is write latency — you pay the database write cost on every operation, even for data that is never read again. This also means the cache fills up with data that may never be accessed, wasting memory.
4. Write-Back (Write-Behind) Cache
Write-back optimises for write performance by decoupling the cache write from the database write. The application writes only to the cache, and a background process asynchronously flushes dirty data to the database.
WRITE-BACK FLOW:
App → Cache: SET key value (dirty=true) ← fast, synchronous
│
└──▶ return success to App immediately
Background Flusher (async):
│
├── every N seconds OR when cache is full:
├── find all entries where dirty=true
├── write to Database
└── set dirty=false on success
RISK: If cache crashes before flush:
dirty entries are LOST (never reached DB)
Write-back is used in CPU L1/L2 caches, where the performance gain from not writing to RAM on every cache line update is enormous. It is also used in some database buffer pools (MySQL InnoDB dirty pages) and write-heavy NoSQL use cases. The dirty flag bit tracks which entries need to be flushed.
Write-Back Data Loss Risk
If a Redis node with write-back dirty data crashes before the async flush completes, those writes are permanently lost. Only use write-back where some data loss is acceptable (e.g. analytics counters, session data) or where you have strong cache persistence (Redis AOF with fsync every write). For financial or transactional data, use write-through instead.
5. Read-Through Cache
Read-through is a variant of cache-aside where the cache layer itself fetches from the database on a miss, rather than the application doing it. The application only ever talks to the cache.
- Application calls
cache.get(key) - On a hit: cache returns the value directly
- On a miss: the cache fetches from DB, stores the result, and returns it
- Application code does not contain any DB fallback logic
Read-through simplifies application code but requires the cache to understand the data model. AWS DynamoDB Accelerator (DAX) is a managed read-through/write-through cache for DynamoDB. Hibernate's second-level cache is another example.
6. Strategy Comparison
| Strategy | Who manages cache? | Write latency | Stale data risk | Data loss risk | Best for |
|---|---|---|---|---|---|
| Cache-Aside | Application | Low (write to DB only) | Low (invalidate on write) | None | General-purpose, most web apps |
| Write-Through | Cache layer | High (write to cache + DB) | None | None | Read-heavy, consistency-critical |
| Write-Back | Cache layer | Very low (cache only) | None (after flush) | High (pre-flush crash) | Write-heavy, loss-tolerant |
| Read-Through | Cache layer | Low (write to DB only) | Low (with invalidation) | None | Simplifying application code |
7. Real-World Examples by Strategy
Cache-Aside — Redis in Web Applications
The vast majority of web applications using Redis follow cache-aside. The application checks Redis on every read and manages cache invalidation on writes. Redis's rich data structures (strings, hashes, sets) make it easy to cache complex objects. A typical pattern: cache user sessions, product listings, and computed results with TTLs of 5–60 minutes.
Write-Through — CDN Edge Caches
When you publish new content to a CDN (e.g. Cloudflare, CloudFront), the CDN pushes the content to edge nodes as part of the write operation. Every edge node receives the update synchronously — this is write-through at a global scale. The benefit: every user gets fresh content immediately after publication, with no cache-miss latency on the first request.
Write-Back — CPU L1/L2 Cache
Modern CPUs use write-back for their cache lines. When the CPU writes data, it goes to the L1 cache only (very fast). The cache line is marked dirty. It is flushed to L2, L3, or RAM later by the cache controller. This is why a sudden power loss can corrupt file system state — dirty cache lines in the CPU were never flushed to RAM (and then to disk). The OS uses fsync and write barriers to force dirty data through.
Write-Back — MySQL InnoDB Buffer Pool
InnoDB keeps modified (dirty) pages in its buffer pool and flushes them to disk asynchronously via the dirty page flusher thread. This is write-back caching at the database level. The redo log (WAL) provides durability — even if dirty pages are lost in a crash, they can be replayed from the redo log, giving write-back semantics with durability guarantees.
How We Research and Update This Guide
We test the underlying formula or workflow, compare outputs with reliable references, and revise examples whenever the page content changes.
- The workflow or formula is tested directly in the tool and compared against independent reference examples.
- Examples are kept practical so readers can verify the result without hidden assumptions.
- Pages are revised whenever the interface, calculation flow, or surrounding guidance materially changes.
Frequently Asked Questions — Caching Strategies
Write-through writes data to the cache and the database simultaneously. Every write is synchronous — the operation only completes when both the cache and DB are updated. There is no risk of data loss, but write latency is higher. Write-back (write-behind) writes to the cache only and asynchronously flushes to the database later. Write latency is low, but if the cache crashes before the flush, data is lost. Write-through favours consistency; write-back favours write performance.
In cache-aside, the application manages the cache directly. On a read: check the cache first; if it is a miss, fetch from the DB, store in cache, and return the result. On a write: write to the DB, then invalidate (or update) the cache. The cache is populated lazily — only items that are actually requested get cached. This is the most common pattern in web applications using Redis or Memcached.
Use write-through when data consistency between cache and database is critical and you can tolerate slightly higher write latency. It is ideal for read-heavy workloads where stale data is unacceptable — for example, user profile data or inventory levels that must be accurate. CDNs use a write-through variant where content is pushed to edge nodes on write. The trade-off is that rarely-read items also get cached, wasting memory.
In write-back caching, each cache entry has a "dirty" bit. When data is written to the cache but not yet flushed to the database, the dirty bit is set to 1. When the background flusher writes the data to the DB successfully, it clears the dirty bit to 0. On cache eviction, the cache must check the dirty bit — if it is set, it must flush the data to DB before evicting. If the system crashes with dirty entries in cache, those writes are lost.
In read-through, the cache itself is responsible for fetching from the database on a miss — the application only ever talks to the cache. On a miss, the cache transparently loads the data from the DB, stores it, and returns it to the application. This simplifies application code (no cache-miss handling logic) but requires the cache layer to understand the data model. AWS ElastiCache with DAX (DynamoDB Accelerator) is an example of a read-through cache.
Cache invalidation is the hardest part of caching. In cache-aside, the two common approaches are: (1) Delete-on-write — when data changes, delete the cache key. The next read will be a miss and will reload fresh data. This is simpler and avoids stale data but causes a brief thundering herd on the first read. (2) Update-on-write — write the new value to the cache on every DB write. This keeps the cache warm but risks race conditions if writes from two threads arrive out of order.