mysql
watcher
MySQL Setup
CDC Watcher uses MySQL binlog replication — the same mechanism as Debezium. No special table schema needed.
Server Configuration
# my.cnf
log_bin = ON
binlog_format = ROW
binlog_row_image = FULL
server_id = 1
Pre-configured in the provided docker-compose.yml. For an existing MySQL instance, add these to my.cnf and restart.
User Privileges
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'user'@'%';
FLUSH PRIVILEGES;
DDL Capture
MySQL captures DDL changes (CREATE, ALTER, DROP) automatically via QUERY binlog events — no extra configuration needed. DDL events are published with type: "DDL".
Snapshot Mode
Capture existing rows before streaming live changes:
# Options: none (default), initial, schema_only
app.cdc.mysql.snapshot-mode=initial
| Mode | Behavior |
|---|---|
none | Only capture changes from now on |
initial | Snapshot all existing rows on first start, then stream changes |
schema_only | Capture schema structure only, then stream changes |
Offset Persistence
MySQL binlog position is persisted automatically via NATS JetStream. On restart, the watcher resumes from the last acknowledged offset — no manual tracking needed.
Type Mapping
MySQL types are mapped to JSON-friendly values:
| MySQL Type | JSON Output |
|---|---|
DATE, DATETIME, TIMESTAMP | ISO 8601 string ("2026-03-25T08:30:00Z") |
DECIMAL, NUMERIC | JSON number (99.99) |
TINYINT(1) / BOOLEAN | JSON boolean (true / false) |
BLOB, BINARY | Base64-encoded string |
ENUM | String value ("active") |
| All others | String |
Table Filtering
Watch specific tables instead of all:
# Comma-separated list (empty = watch all tables)
app.cdc.mysql.tables=orders,products,customers
Full Application Config
# Database connection
app.cdc.mysql.url=jdbc:mysql://localhost:3306/mydb
app.cdc.mysql.username=user
app.cdc.mysql.password=secret
# CDC settings
app.cdc.mysql.enabled=true
app.cdc.mysql.tables=
app.cdc.mysql.snapshot-mode=none
app.cdc.mysql.server-id=1
# NATS
app.cdc.nats.url=nats://localhost:4222
app.cdc.nats.stream-name=cdc-stream
Example Events
MySQL uses real column names:
[CDC] INSERT table=products data={"id": 1, "name": "laptop", "price": 999.99, "stock": 10}
[CDC] UPDATE table=products data={"old": {"price": 999.99}, "new": {"price": 949.99}}
[CDC] DELETE table=products data={"id": 2, "name": "phone", "price": 499.50}
[CDC] DDL table=products data={"ddl": "ALTER TABLE products ADD COLUMN category VARCHAR(50)"}
PostgreSQL vs MySQL Comparison
| Capability | PostgreSQL (WAL) | MySQL (binlog) |
|---|---|---|
| INSERT | Full row | Full row |
| UPDATE | New only (default), old+new with REPLICA IDENTITY FULL | Full old + new row (always) |
| DELETE | PK only (default), full row with REPLICA IDENTITY FULL | Full deleted row (always) |
| DDL | capture-ddl=true | Automatic |
| Column names | Real names (RELATION message) | Real names |
| Latency | Real-time | Real-time |
| DB setup | wal_level=logical | log_bin=ROW |
| Offset tracking | Replication slot (automatic) | NATS JetStream (automatic) |