MySQL CDC: Binlog Replication Explained

Intergalactic Data Labs
โ
โ
12 min read
MySQL change data capture (CDC) reads the server's binary log the way a replica does and turns each committed insert, update, and delete into an event applied elsewhere. As of October 2026 there are three ways to run it. Use a Kafka Connect stack such as Debezium, a managed service such as Fivetran or ClickPipes, or a single-process reader such as Filament, Maxwell, or Sling.
By the end you will have a MySQL table streaming into Postgres with deletes applied, a checkpoint that survives restarts, and a scheduled catch-up run.
Need | Value | Where it is set |
|---|---|---|
MySQL version | 8.0 or later | Source server |
Binlog format | ROW with full row image | my.cnf or parameter group |
Binlog retention | Longer than your longest pause | binlog_expire_logs_seconds or vendor setting |
Privileges | REPLICATION SLAVE, REPLICATION CLIENT, SELECT, LOCK TABLES | GRANT on the CDC user |
Connector maturity | MySQL source beta, Postgres sink beta | Filament docs |
Time to complete | About 30 minutes on a clean server | Your terminal |
How MySQL binlog replication works
A CDC reader is a fake replica. It registers with the server, asks for a binlog dump from a position, and decodes the row events that follow. The 8.4 reference manual says the binary log "contains 'events' that describe database changes such as table creation operations or changes to table data."
The reader never queries your tables after the first snapshot, which is why CDC beats polling an updated_at column. A timestamp cursor "cannot see a hard delete", as the replication modes page puts it, while the binlog carries the deleted row.
Four settings decide whether the stream works at all, and two more decide how a reader names columns and identifies itself. ROW has been the default format since MySQL 5.7.7, and the binlog is on by default in 8.0 and later.
Setting | What CDC needs | Default in MySQL 8.4 |
|---|---|---|
binlog_format | ROW | ROW, deprecated since 8.0.34 |
binlog_row_image | full | full |
binlog_row_metadata | MINIMAL works for Filament, FULL for most tools | MINIMAL |
binlog_expire_logs_seconds | Cover the longest outage | 2592000, 30 days |
gtid_mode | ON preferred | OFF |
server_id | Unique per reader | Set by the tool |
Defaults come from the binary log options page and the GTID options page. With minimal row image the before image of an update carries only the primary key, so a merge sink gets partial rows. Column names are not in the binlog, so a reader needs FULL metadata or looks names up in information_schema.
The checkpoint is the other half. A GTID set survives failover to a promoted replica, per the GTID concepts page, while a file and position checkpoint breaks when the file sequence changes.
How to set up MySQL CDC step by step
Galaxy builds Filament, the worked example below. Its MySQL source is beta, per the connector page, and the whole flow runs from one binary with checkpoints in local SQLite. No public benchmark covers a MySQL CDC route, so this guide makes no speed claim.
Step 1. Check the binlog settings on the source
Run the checks every CDC vendor asks for before touching anything else.
You want ON, ROW, FULL, retention that covers a weekend, and ideally ON for GTID.
Step 2. Fix format and retention where you run MySQL
On a self-managed server set the values in my.cnf and restart. Retention is the setting most teams get wrong, so size it for the longest time the reader could be down.
Managed services move these knobs. On RDS for MySQL, automated backups control whether the binlog exists at all and binlog_format lives in a custom parameter group, per the RDS binlog page. Retention is a stored procedure in the RDS reference.
Service | Binlog on | Format | Retention setting |
|---|---|---|---|
Self-managed 8.x | Default ON | my.cnf | binlog_expire_logs_seconds |
RDS for MySQL | Automated backups on | Parameter group | binlog retention hours, max 168 |
Aurora MySQL | Cluster parameter group, default OFF | Cluster parameter group, reboot | binlog retention hours, max 2160 |
Cloud SQL | Point-in-time recovery | Managed, always ROW | Transaction log days, 1 to 35 |
Azure Flexible Server | Always ON | Always ROW | binlog_expire_logs_seconds |
PlanetScale | No raw binlog | Vitess VStream | Use the PlanetScale Debezium connector |
Sources are Aurora, Cloud SQL PITR and flags, Azure, and PlanetScale. Azure's default retention of zero deletes a binlog as soon as its handle is freed, so set it.
Step 3. Create the replication user
The reader needs both replication privileges plus SELECT and LOCK TABLES on the tables it bootstraps. The manual's replication user example is the model.
MySQL 8.4 defaults new accounts to caching_sha2_password, which needs TLS or RSA key exchange, per the authentication plugin docs. Use TLS.
Step 4. Install the reader
One command installs the CLI, per the installation page. The local CLI uses file-backed SQLite, so there is no Postgres or NATS to stand up first.
Step 5. Register the MySQL source in CDC mode
The CLI reference documents the flag rule, where connector fields become flags with a source- prefix and underscores become hyphens. The docs show it with a Postgres example, so the MySQL flags below are derived from the connector's key table. Confirm them with filament source create --help, which lists the selected connector's fields.
The replication field switches the connection from query reads to binlog streaming. Discovery prints resource names, primary keys, and estimated row counts, so you see which tables have the keys a merge needs.
Step 6. Register the Postgres sink
This is the documented sink command, since the CLI reference example is already a Postgres sink.
The sink page advertises cdc_append and cdc_merge. Merge "maintains current state" by upserting inserts and updates and deleting on deletes, while append keeps every change as a history row.
Step 7. Create the CDC pipeline with merge writes
CDC applies to the whole route, so every resource in the pipeline streams from the binlog.
Merge is the right default for one documented reason. The connector page says "A crash during a transaction replays the whole transaction, so use merge writes that converge by primary key."
Step 8. Run it once, then schedule the catch-up
The first run bootstraps each table. The reader captures the binlog position, holds read locks for the instant it takes to open a consistent snapshot, copies the table, then streams from that position.
Every later run is a catch-up. It captures the current binlog position as a watermark, streams from the checkpoint up to it, and returns, so continuous CDC "can run on a schedule". Put filament run shop-cdc on a cron, or deploy the server and attach a schedule through the pipelines API.
How to verify MySQL CDC worked
Check the position on both sides, then prove a delete lands. On MySQL 8.4 the position statement was renamed, per the 8.4 changes list, so use the new form.
Then run the real test. Delete one order in MySQL, run filament run shop-cdc, and confirm the row is gone from analytics.public.orders. Update a customer and confirm the new values replaced the old ones rather than appearing as a second row.
A run is done only when its state is completed, which the runs and recovery page defines as the sink having committed. CDC saves its position only after that commit, so a failed attempt never advances the checkpoint.
What to do when MySQL CDC breaks
Five failures account for most MySQL CDC outages, and each has one fix. Codes and strings come from the 8.4 server error reference and the binlog sender source in the MySQL server repo.
Error | Code | Cause | Fix |
|---|---|---|---|
Source purged required binary logs | 1789 | Retention shorter than the pause | Re-snapshot, raise retention |
Could not find first log file name | 1236 | File position rotated away | Same as above, prefer GTID |
A replica with the same server_id has connected | 1236 | Two readers share a server_id | Set a unique server_id per reader |
Access denied, REPLICATION SLAVE privilege | 1227 | Missing grant | Re-run the GRANT from step 3 |
Got a packet bigger than max_allowed_packet | 1153 | Large row or blob event | Raise the limit on server and client |
The purged-log pair is the one that bites in production. Both mean the reader paused longer than the binlog lived, and the only recovery is a fresh snapshot, which is why step 2 sized retention for a weekend.
The duplicate server_id error is subtle because the newer connection kicks the older one off, so a staging reader pointed at production breaks the real one. Filament's default server_id is 62347, so change it for a second reader.
The large packet error, on the packet too large page, needs the change on both ends because a binlog event is itself a packet. One Filament-specific case remains. After DDL that changes the column count, the run fails on purpose rather than mis-mapping values, so re-snapshot that table.
Other ways to run MySQL CDC and what they cost you
Every tool below reads the same binlog, so the differences are deployment, license, and billing unit. Licenses come from LICENSE files, not README badges.
Tool | Runs as | License | Billing unit | Documented limit on MySQL CDC |
|---|---|---|---|---|
Debezium | Kafka Connect or Debezium Server | Apache 2.0 | Free | Needs FULL row image and table locks on RDS |
Airbyte | Platform, embeds Debezium | ELv2 | Credits | Needs FULL row image, release stage alpha |
Fivetran | SaaS | Proprietary | Monthly active rows | Binlog bypasses column grants |
Estuary Flow | SaaS | BSL 1.1 | Per GB plus per connector | Wants 7 days of retention |
Maxwell | JVM daemon | Apache 2.0 | Free | Writes to queues only, no warehouse merge |
Sling | CLI | GPL 3.0 | Pro token for CDC | Fails if binlog is purged |
ingestr | CLI, Go | FSL 1.1 | Free | Fails rather than partial snapshot on lost position |
ClickPipes | ClickHouse Cloud | Proprietary | Per GB | Requires GTID and 72 hour retention |
Filament | Single binary or Go library | Apache 2.0 | Free | No CDC into its ClickHouse sink |
Debezium is the reference implementation with eight snapshot modes in its MySQL connector docs, but you run a Kafka Connect cluster or a Debezium Server for it. Fivetran calls binlog "the most efficient mechanism for capturing change data" in its setup guide and warns that column permissions do not apply to binary logs.
Maxwell shipped a release in September 2026, but its producers are queues, so something else must apply the events. Sling makes each run bounded and safe to schedule, and gates CDC behind a paid tier on its change capture page.
ClickPipes went generally available on September 2, 2026 and fits ClickHouse Cloud destinations. dlt has no MySQL binlog source, so it is not in the table. Pick a Kafka tool when several consumers need the events, a managed service when nobody will run the reader, and a single-process reader when the destination is one database you already operate.
Next steps
The Postgres side of the same job is Postgres CDC, every method compared.
The primer is What is change data capture and the read mode choice is Full load vs incremental vs CDC.
The destination routes are MySQL to Postgres, MySQL to Snowflake, and MySQL to ClickHouse.
The tools above are ranked in Best CDC tools in 2026.
Frequently asked questions
What is MySQL CDC?
MySQL change data capture reads the server's binary log as a replica would and turns each committed insert, update, and delete into an event a downstream system applies. It needs ROW format, enough binlog retention, and a user with the replication privileges. Unlike timestamp polling, it sees hard deletes and never rescans tables.
Is the MySQL binary log enabled by default?
Yes on MySQL 8.0 and later, where log_bin is ON unless the server was started with the initialize option. The default format is ROW and the default retention is 30 days. Managed services differ. RDS turns the binlog on only with automated backups enabled, and Aurora defaults binlog_format to OFF.
What is the best binlog format for MySQL CDC?
ROW. Statement and mixed logging record SQL text, which a change reader cannot turn into before and after row images, so every CDC tool rejects them. Pair ROW with binlog_row_image set to full so update and delete events carry every column a merge sink needs.
Can MySQL CDC capture deletes?
Yes, and that is the main reason to pick it over cursor-based incremental sync. A delete appears in the binlog as a row event with the old row image, so the reader emits a delete that a merge sink applies by primary key. An updated_at cursor never sees the row vanish.
Does MySQL CDC affect database performance?
Lightly. The server already writes the binlog for backups and replicas, so a CDC reader adds one more replica connection streaming events over TCP. The costs that do show up are disk for longer retention and the brief read locks some tools take while bootstrapping a table.
What happens when the binlog is purged before CDC reads it?
The stream cannot resume. MySQL returns error 1789 for a GTID checkpoint or a fatal error 1236 saying it could not find the first log file for a file position. The fix is a fresh snapshot of the affected tables plus longer retention through binlog_expire_logs_seconds or the vendor's setting.
Does Filament need a separate full load before MySQL CDC?
No. When a selected table has no CDC checkpoint, Filament reads it in full through a consistent snapshot and continues the stream from the binlog position captured just before that snapshot opened. The baseline and the stream share one point, so a pipeline starts on CDC alone.
Does Filament use GTIDs for MySQL CDC checkpoints?
When gtid_mode is ON it checkpoints the GTID set, which the docs call preferred because it survives replica promotion. With GTID off it stores the binlog file and position instead. An existing file position checkpoint keeps that format even if GTID is enabled later, so enable GTID before the first run.
More articles
Questions
Answered
FAQ
What does Galaxy do?
What is Filament?
What is enterprise context management?
What does working with Galaxy look like?
How do you handle security and compliance?
Why does Galaxy build in the open?
Company
Copyright ยฉ 2026 Galaxy. All rights reserved.