← all work

Software · 04 / 06 · Kafka · Debezium · PostgreSQL

CDC Pipeline

Streams every MySQL insert, update and delete through Debezium and Kafka into PostgreSQL, then proves the copy is identical, even after crashes.

141 msp95 lag, MySQL commit to Postgres commit
1,000,021row changes replayed at 3,100/s
Livecolumn added mid-run, no restart
0missing, extra or mismatched rows

Why I built this

In the real world

Companies keep their live database separate from analytics, search and reporting. Nightly copies are always a day stale, and writing to two places from app code slowly drifts apart. Change data capture is how fintech and e-commerce teams keep those copies in sync within milliseconds, without silently getting balances or reports wrong.

What I wanted to find out

How do you keep a second copy of a live database in sync within milliseconds, and know it is right instead of hoping? I wanted to stream every change across, crash the pipeline midway, then prove the copy matched row by row.

What I built

Debezium reads MySQL's binlog and publishes every row change to Kafka. A sink I wrote applies the changes to PostgreSQL idempotently, keyed on primary key and binlog position, and evolves the schema on the fly.

At the end of every run it waits for catch-up and compares every table row by row, plus a SHA-256 per table.

How it works

MySQL8 writer processes
Debeziumreads the binlog
Kafkakeyed by primary key
3 sinksidempotent apply
PostgreSQLverified replica

Each target row stores the binlog position that wrote it, so replaying an old change is a no-op.

How it stays correct

Per-row ordering

Debezium keys by primary key, so each row lives in one partition, handled by one consumer at a time.

Replays are no-ops

ON CONFLICT ... WHERE target.position < incoming.position.

No loss on crash

Commit PostgreSQL first, then the Kafka offset. At-least-once delivery, idempotent apply.

Schema changes without downtime

New columns are added in the same transaction as the data, under an advisory lock.

Measured

Run
1,000,021 changes at 3,100/s, 3 sinks, a column added at 500k, a sink SIGKILLed at 700k
Lag
p50 89 ms, p95 141 ms, p99 183 ms
Verification
All 5 tables identical, row by row and by SHA-256, across 440k rows
Tests
33 tests, 93% coverage, including kills inside and between commits