The dual-write trap — no atomicity boundary across DB and Kafka
A service that commits to its DB and then publishes to Kafka has no atomicity boundary spanning both, so every crash window between the two is a permanent inconsistency the system cannot heal on its own.
Scene 01
The dual-write trap
- Watch
- Try it
- Predict
- Capture
Watch the healthy baseline. The service writes user.balance=100 to its DB, then publishes BalanceUpdated to Kafka — two separate writes to two separate systems. That pair-without-a-shared-transaction is the dual-write problem. As long as nothing crashes, both downstream views agree.
Highlighted lines are the ones running in the diagram right now.
def handleSignup(event):tx = db.begin()tx.execute('UPDATE users SET balance=100 ...')tx.commit() # leg 1: DBkafka.publish('BalanceUpdated', # leg 2: Kafka{balance: 100})return ok
def handleSignup(event):tx = db.begin()tx.execute('UPDATE users SET balance=100 ...')tx.commit()for attempt in range(MAX_RETRIES):try:kafka.publish('BalanceUpdated',{balance: 100})return okexcept AckTimeout:continue # broker may have it alreadyraise PublishFailed
# T1: commit OK, publish dies -> event lost# T2: publish OK, commit fails -> phantom event# T3: both OK, ack lost, retry -> duplicate event# T4: two writers race -> DB and Kafka# disagree on order## All four are the same bug: the service has one# transaction (the DB tx); the publish escapes it.
Where this sits in Build a CDC pipeline (Debezium + outbox)
Scene 01 of 12. Service writes to its DB and publishes to Kafka — and any crash between those two writes is permanent inconsistency. Four scenarios, four divergences, one structural fix.
Up next. Dual-write is broken because there is no commit boundary covering both the DB row and the Kafka event — so the natural reach is for some way to drive the event from the DB itself.
All 12 scenes in Build a CDC pipeline (Debezium + outbox) · Every curriculum