BizTechLab

IDEASINNOVATIONIMPACT

System Design Concepts

The Dual-Write Bug & CDC

Writing to a database and a cache as two separate steps looks fine — until the process crashes between them.

3 August 20267 min read

Overview

The dual-write bug is what happens when application code writes to a database and a cache as two separate, non-atomic steps — if the process crashes, times out, or races with another write between those two steps, the cache silently drifts out of sync with the database, with no error raised anywhere.

Why It Exists

It's tempting to write `db.save(order); cache.set(order.id, order)` right in the request handler — it looks correct, and it works in every manual test. The dual-write bug exists precisely because these two writes are never actually atomic: any crash, timeout, retry, or concurrent request landing between them leaves the cache holding stale data that nothing will ever correct on its own.

Real World Example

A checkout service updates an order's status to 'shipped' in Postgres, then updates the same order in Redis. The deploy pipeline restarts the service between those two lines during a rolling deployment. The database says 'shipped'; the cache still says 'processing' — and because cache reads are what the customer-facing app actually serves, the customer sees a stale status indefinitely, until the cache entry happens to expire.

Example Data

The same two writes, two different outcomes

ScenarioDatabaseCacheResult
Normal pathshippedshippedConsistent
Crash between writesshippedprocessing (stale)Silently inconsistent

The Bug, and the Fix

Why Dual Writes Aren't Atomic

Writing to two different systems from application code means two separate network calls, two separate points of failure, and no shared transaction spanning both — there's no way to guarantee both writes happen, or neither does.

Why It's Silent

Neither system raises an error when this happens — the database write succeeded, and the cache write either succeeded with stale data or failed independently. Nothing detects or repairs the divergence automatically.

Change Data Capture (CDC) — Reading From the Source of Truth

CDC tools like Debezium read the database's write-ahead log directly and stream every committed change to downstream consumers, including the cache — removing the second, independent write from application code entirely.

Why CDC Actually Fixes It

Because CDC reads only from the database's committed log, the cache is always updated from what the database actually persisted — there's no code path where the cache can diverge from a write the database never committed.

Diagram

Dual write (two independent points of failure) vs. CDC (single source of truth)

App writes to DB

step 1

App writes to cache

step 2 — can fail independently

CDC instead: App writes to DB only

Debezium streams the change to the cache

Common Mistakes

Writing to the database and the cache from the same request handler and assuming both will succeed

Why: Any crash, timeout, or deployment restart between the two calls leaves the cache stale with no error surfaced anywhere in the system.

Fix: Use CDC to update the cache from the database's committed write-ahead log instead of writing to the cache directly from application code.

Trying to 'fix' dual writes by wrapping both calls in a try/catch and retrying on failure

Why: A retry after a partial failure can't tell whether the first write actually succeeded or not, and re-running both writes risks double-applying one of them.

Fix: Remove the second, independent write entirely rather than trying to make an inherently non-atomic operation reliable through retries.

Assuming a short cache TTL 'solves' dual-write staleness

Why: A TTL bounds how long the staleness lasts, but doesn't prevent it — for the duration of the TTL, incorrect data is still being served as if it were current.

Fix: Treat CDC (or cache invalidation triggered by the same transaction) as the actual fix, and use TTLs only as an unrelated, additional safety net.

Interview Questions

beginner

What is the dual-write bug?

It's the data-corruption risk that comes from writing to a database and a cache as two separate, non-atomic steps in application code — if the process fails between the two writes, the cache silently ends up holding stale data.

intermediate

Why doesn't wrapping both writes in a try/catch block with a retry fix the dual-write bug?

A retry can't distinguish a fully-failed attempt from a partial failure where the first write already succeeded — retrying risks re-applying the database write, or leaves an ambiguous window where you can't tell what state the cache is actually in relative to the database.

senior

How does Change Data Capture eliminate the dual-write bug at an architectural level, rather than just reducing its likelihood?

CDC removes the second, independent write from application code entirely — instead of the app writing to the cache directly, a tool like Debezium tails the database's write-ahead log and streams every committed change downstream. Because the cache update is now derived from what the database actually committed, rather than from a second best-effort write, there's no code path left where the two can diverge — it's a structural fix, not a probabilistic one.

Production Best Practices

Do

Use CDC to derive cache updates from the database's committed write-ahead log.

Treat any independent, uncoordinated write to a second system as a potential source of silent drift.

Use cache TTLs as an additional safety net, not as the primary consistency mechanism.

Don't

Don't write to a database and a cache as two independent, non-atomic steps in application code.

Don't try to fix dual writes with retries — the ambiguity of a partial failure makes retries unsafe.

Don't assume a short TTL prevents incorrect data from being served, only that it bounds how long it lasts.

Comparison

Source of TruthFailure ModeConsistency Guarantee
Dual WriteTwo independent writesSilent divergence on partial failureNone
CDCDatabase's write-ahead logCache always reflects committed writesEventually consistent, but never silently wrong

Related Articles