CachingCache Patterns (Aside, Through, Back)Hard⏱️ ~3 min

Write Back Pattern: Asynchronous Persistence with Durability Trade Offs

Write back, also called write behind, inverts the persistence order to optimize write latency. The application writes to the cache, which immediately acknowledges the write to the caller. The cache then persists to the source of truth asynchronously via a buffer or queue, often batching and compacting multiple updates. This delivers the fastest possible write latency since the application only waits for the in memory cache update, and it can dramatically reduce load on the source database by batching writes. For example, 1000 increments to a counter can be compacted into a single database update of plus 1000. The critical trade off is durability risk and eventual consistency. If a cache node fails before flushing its write buffer, those writes are lost unless the buffer itself is durable and replicated. Write reordering is another hazard: if updates flush out of order, an older state can overwrite a newer one in the source. Production implementations require Write Ahead Log (WAL) like durability at the cache layer, replicated buffers across nodes, and idempotent merge semantics in the source database (such as versioned upserts that reject older versions). Strict flush policies bound the data loss window, for example flushing every 1 to 5 seconds with maximum queue depth limits that trigger backpressure to callers when exceeded. Write back is ideal for write heavy ingestion workloads like metrics, logs, or counters where eventual consistency is acceptable and the data is append only or merge friendly. It is rarely used for user facing transactional data where loss is unacceptable. Systems must implement careful recovery: on restart, replay the WAL in order, deduplicate by idempotency keys, and verify the last applied version in the source to prevent reordering. Observability is critical; teams must track write queue lag (time mutations spend in buffer), flush success rate, and replay duration on recovery to detect problems before data loss occurs.
💡 Key Takeaways
Fastest write latency: application writes to cache and receives immediate acknowledgment, persistence happens asynchronously via buffer or queue with 1 to 5 second typical flush intervals
Batching and compaction reduce database load: 1000 counter increments become single plus 1000 update, dramatically lowering write operations per second on source
Durability risk: cache node failure before flush causes data loss unless write buffer is durable and replicated across nodes with WAL semantics
Requires idempotent merge semantics in source database with versioned upserts that reject older versions to prevent write reordering hazards
Best for append only or merge friendly data like metrics, logs, counters where eventual consistency acceptable; rarely used for user transactional data
Must bound data loss window with strict flush policies: maximum time in queue, queue depth limits triggering backpressure, and replicated buffers
📌 Examples
Metrics ingestion system: Counter increments written to cache with immediate ACK. Background worker batches 1000 updates over 5 seconds, compacts to single UPDATE counters SET value = value + 1000 WHERE key = X AND version > last_seen to ensure idempotency and prevent reordering.
Write back with WAL recovery: On write, append to replicated WAL with sequence number, update cache, ACK caller. Flush worker reads WAL in order, applies to database with idempotency key. On node restart, replay WAL from last checkpointed sequence, skip already applied updates via idempotency table.
Backpressure implementation: if (writeQueue.depth() > MAX_DEPTH || writeQueue.oldestAge() > MAX_AGE) { return ERROR_BACKPRESSURE; } else { wal.append(mutation); cache.set(key, value); return ACK; } This prevents unbounded queue growth and data loss.
← Back to Cache Patterns (Aside, Through, Back) Overview