Skip to main content
Caching

Write-Through vs Write-Back Caching: Consistency, Performance, and Trade-Offs

When your application writes data, how should the cache and database stay in sync? This is one of the most important decisions in cache architecture. The t...

📖 7 min read

Write-Through vs Write-Back Caching: Consistency, Performance, and Trade-Offs

When your application writes data, how should the cache and database stay in sync? This is one of the most important decisions in cache architecture. The two primary approaches — write-through and write-back (also called write-behind) — represent fundamentally different trade-offs between consistency and performance. Understanding both patterns, along with write-around, will help you design systems that balance speed with data integrity.

Write-Through Caching

How It Works

In write-through caching, every write operation updates both the cache and the backing store (database) synchronously. The write is only considered complete when both the cache and database have been updated. This ensures the cache is always consistent with the database.

class WriteThroughCache:
    def __init__(self, cache, database):
        self.cache = cache
        self.database = database
    
    def write(self, key, value):
        # Step 1: Write to database first
        self.database.save(key, value)
        
        # Step 2: Update cache with same value
        self.cache.set(key, value)
        
        # Both are updated — cache is consistent
    
    def read(self, key):
        # Always check cache first
        value = self.cache.get(key)
        if value is not None:
            return value  # Cache hit
        
        # Cache miss — fetch from DB and populate cache
        value = self.database.get(key)
        if value is not None:
            self.cache.set(key, value)
        return value

Pros

  • Strong consistency: The cache always reflects the latest data in the database.
  • Simple mental model: No stale data concerns, no async complexity.
  • Reads are always fast: After a write, the data is already in cache for subsequent reads.
  • No data loss risk: Data is written to durable storage immediately.

Cons

  • Higher write latency: Every write must wait for both cache and database operations to complete.
  • Write amplification: Data that is written but never read still gets cached, wasting cache space.
  • Not truly atomic: If the cache write succeeds but the database write fails (or vice versa), the system can still become inconsistent without proper error handling.

Write-Back (Write-Behind) Caching

How It Works

In write-back caching, writes go only to the cache initially. The cache then asynchronously flushes changes to the database in the background, typically using a queue or batch process. This provides the fastest write performance but introduces data loss risk.

import threading
import queue
import time

class WriteBackCache:
    def __init__(self, cache, database):
        self.cache = cache
        self.database = database
        self.dirty_queue = queue.Queue()
        self._start_flush_worker()
    
    def write(self, key, value):
        # Write to cache only — returns immediately
        self.cache.set(key, value)
        
        # Queue for async database write
        self.dirty_queue.put((key, value))
    
    def read(self, key):
        # Cache always has latest data
        value = self.cache.get(key)
        if value is not None:
            return value
        
        value = self.database.get(key)
        if value is not None:
            self.cache.set(key, value)
        return value
    
    def _start_flush_worker(self):
        def flush_loop():
            batch = []
            while True:
                try:
                    item = self.dirty_queue.get(timeout=1)
                    batch.append(item)
                    if len(batch) >= 100:
                        self._flush_batch(batch)
                        batch = []
                except queue.Empty:
                    if batch:
                        self._flush_batch(batch)
                        batch = []
        
        worker = threading.Thread(target=flush_loop, daemon=True)
        worker.start()
    
    def _flush_batch(self, batch):
        for key, value in batch:
            self.database.save(key, value)

Pros

  • Fastest write performance: Writes return immediately after updating the cache.
  • Reduced database load: Multiple writes to the same key can be coalesced into a single database write.
  • Batch optimization: Database writes can be batched for efficiency.

Cons

  • Data loss risk: If the cache crashes before flushing to the database, unflushed data is lost.
  • Eventual consistency: The database lags behind the cache; reads from the database directly may return stale data.
  • Complex implementation: Requires a reliable flush mechanism, error handling for failed writes, and monitoring.

Write-Around Caching

How It Works

Write-around sends writes directly to the database, bypassing the cache entirely. The cache is only populated on reads (cache misses). This avoids polluting the cache with data that may never be read.

class WriteAroundCache:
    def __init__(self, cache, database):
        self.cache = cache
        self.database = database
    
    def write(self, key, value):
        # Write only to database
        self.database.save(key, value)
        
        # Invalidate cache to prevent stale reads
        self.cache.delete(key)
    
    def read(self, key):
        value = self.cache.get(key)
        if value is not None:
            return value
        
        # Cache miss — populate from DB
        value = self.database.get(key)
        if value is not None:
            self.cache.set(key, value, ttl=300)
        return value

This is essentially the cache-aside pattern with explicit invalidation on writes. It works well for write-heavy workloads where most written data is not immediately read.

Strategy Comparison

Aspect Write-Through Write-Back Write-Around
Write Latency High (cache + DB) Low (cache only) Medium (DB only)
Read After Write Latency Low (in cache) Low (in cache) High (cache miss)
Consistency Strong Eventual Eventual (brief stale window)
Data Loss Risk None High (cache failure) None
Cache Pollution Yes (all writes cached) Yes (all writes cached) No (only reads cached)
Implementation Complexity Low High Low
Best For Read-heavy, consistency-critical Write-heavy, latency-sensitive Write-heavy, rarely re-read

Real-World Examples

CPU Cache Hierarchy

Your CPU uses write-back caching between L1/L2 caches and main memory. When the CPU writes a value, it updates the L1 cache and marks the cache line as "dirty." The dirty data is flushed to RAM only when the cache line is evicted. This is why modern CPUs are so fast — most writes never wait for slow main memory.

Database Caching with Redis

Most web applications use write-around (or cache-aside) with Redis. When the application updates a user's profile in the database, it deletes the cached profile. The next read fetches from the database and repopulates the cache. This is simple, avoids cache pollution, and works well for read-heavy workloads.

File System Caching

Operating systems use write-back caching for file I/O. When you write to a file, the OS writes to an in-memory page cache and returns immediately. The dirty pages are flushed to disk by a background process. This is why sudden power loss can cause data corruption — unflushed writes are lost.

Content Delivery Networks

CDN edge servers use write-through semantics conceptually — when origin content changes, the CDN either serves stale content until TTL expires or receives a purge request that propagates to all edge servers.

Hybrid Approaches

Production systems often combine strategies:

  • Write-through for critical data, write-around for the rest: User account data uses write-through for consistency; activity logs use write-around to avoid polluting the cache.
  • Write-back with WAL: Write to cache and a Write-Ahead Log simultaneously. The WAL provides durability; the async process flushes from cache to database. Redis AOF persistence works this way.
  • Read-through + write-through: The cache itself manages both read and write operations transparently, acting as a proxy between the application and database.

Choosing the Right Strategy

Choose write-through when: Data consistency is critical, you cannot afford stale reads, and you can tolerate slightly higher write latency. Examples: financial systems, user authentication data, inventory counts.

Choose write-back when: Write performance is critical, you can tolerate eventual consistency, and temporary data loss is acceptable. Examples: logging systems, analytics counters, session tracking.

Choose write-around when: Your workload is write-heavy and most data is not immediately re-read. Examples: bulk data imports, audit logs, archival data. This is the most common pattern in web applications when paired with cache-aside reads.

Frequently Asked Questions

Can I use write-through and write-back at the same time?

Yes, you can use different strategies for different data types within the same application. Critical data (user accounts, payment information) can use write-through for strong consistency, while less critical data (view counts, recommendations) can use write-back for better performance. The key is to classify your data by consistency requirements.

How do I handle write-through failures?

If the database write succeeds but the cache update fails, delete the cache key to prevent stale reads — the next read will repopulate the cache. If the database write fails, do not update the cache and return an error to the caller. Consider using a transaction or compensation pattern to keep both stores consistent. Cache invalidation strategies can help manage failure scenarios.

Is write-back safe for financial transactions?

Generally no. Write-back introduces data loss risk because the database update happens asynchronously. For financial transactions, use write-through or bypass the cache entirely and write directly to a durable database with ACID guarantees. The slight increase in write latency is worth the data safety.

How does write-back coalesce multiple writes?

If a key is updated 10 times in rapid succession, all 10 writes go to the cache (each overwriting the previous value). When the background flush process runs, it writes only the final value to the database — a single database write instead of 10. This is particularly valuable for frequently updated counters or status fields.

Related Articles