CDC from MySQL Binlog vs PostgreSQL Logical Decoding
Debezium tailing a MySQL binlog and a PostgreSQL logical decoding pipeline look similar from Kafka. Underneath, snapshots, schema changes, and retention fail in opposite ways.
Notes for the problems that show up after launch: bad plans, awkward migrations, index debt, vacuum pressure, replica lag, and the small decisions that make PostgreSQL easier to operate.
Debezium tailing a MySQL binlog and a PostgreSQL logical decoding pipeline look similar from Kafka. Underneath, snapshots, schema changes, and retention fail in opposite ways.
Plain REFRESH held ACCESS EXCLUSIVE for four minutes and queued every dashboard reader we had. CONCURRENTLY fixed the lock and quietly created a bloat problem — the full cost sheet, plus the rename-swap pattern I use instead.
Every weekday at 9:05 the order system's p99 went from forty milliseconds to eleven seconds, caused by one report holding a twelve-gigabyte memory grant. How grants are sized, how to read the queue in sys.dm_exec_query_memory_grants, and how to fence the pattern.
Setting MAXDOP to 1 because of CXPACKET waits amputates a feature to treat a symptom. NUMA-aware MAXDOP guidance, why the default threshold of 5 is ancient history, and where each setting should actually live.
A two-node Galera cluster survives fewer failures than a single node. Here is the quorum arithmetic behind that, how garbd breaks ties, what a non-primary component actually does to your writes, and how to bootstrap safely after a full outage.
Every shared MariaDB box eventually meets the query that should never have been allowed to run for six hours. max_statement_time is the built-in kill switch — here is how to scope it, what it actually kills, and why it will not save you from a retry loop.
VARCHAR(255) is a MySQL-era reflex with real historical reasons behind it. Here is where the habit came from, why PostgreSQL teams reach for text plus CHECK constraints, and which performance claims survive scrutiny.
PostgreSQL's 32-bit transaction IDs wrap around after roughly two billion transactions of visibility horizon. How freezing prevents it, how to monitor age(datfrozenxid), and how to dig out of the emergency.
At peak our p99 commit latency jumped from 4 ms to 2.8 seconds while CPU pinned at 100% and the disks sat idle. pg_stat_activity showed 1,400 sessions queued on wait_event='WALInsertLock' — this is how we found it and what actually fixed it.
Most MySQL SQL runs on PostgreSQL unchanged. This post catalogs the part that does not: quoting, upserts, LIMIT, GROUP BY strictness, string functions, and the silent integer division trap.
The instance had 128 GB of memory and the buffer pool could not hold fifteen minutes of hot data, because 41 GB of the plan cache was plans executed exactly once. Measuring single-use bloat and the three levers that actually fix it.
Dump-and-restore means downtime. This is the dual-run playbook for migrating MySQL to PostgreSQL with CDC: initial snapshot, catch-up, a concrete cutover checklist, and a real rollback window.