Change data capture in 2026: revisit Debezium, Apache Flink CDC and PeerDB before you stream another table
Picture a SaaS company in Pune that runs billing for FMCG distributors across Maharashtra (an invented example, not a real client). Its heart is one PostgreSQL database with an invoices table that changes all day: issued, paid, cancelled, corrected and, recently, given a new gstin column. Three teams want those changes. Collections wants an event when an invoice is paid so reminder calls stop. Analytics wants the rows in ClickHouse for dashboards. The data platform team wants them in a lakehouse table, cleaned on the way. Three proposals land on the architecture review. One engineer wants Debezium because “it is the standard for CDC”. A second wants Apache Flink CDC because one YAML file can capture, transform and write. A third wants PeerDB because it promises simple Postgres to ClickHouse mirroring. The room is treating these as three brands of the same box. They are not.
Change data capture (CDC) tools in 2026 come in different shapes. Debezium is a family of connectors that read a database’s change log and emit one event per row change; you run them in Kafka Connect, in the standalone Debezium Server or inside your own Java application, and you decide where events go. Apache Flink CDC is an integration framework on Apache Flink: a YAML pipeline becomes a Flink job that snapshots, streams, transforms and writes, with checkpoints holding the state. PeerDB is a Postgres-first replication product with its own SQL interface, UI and workers, whose maintained destinations today are ClickHouse and Postgres. All three read the same PostgreSQL logical replication stream, so all three inherit the same facts: slots, publications, WAL retention and replica identity. This post compares where they stand on 14 October 2026: history, architecture, PostgreSQL mechanics, schema changes, delivery guarantees, licences, security advisories and a decision guide. Facts come from GitHub releases, tags and LICENSE files, project docs and announcements, and advisory records cross-checked against NVD and cve.org. The drill near the end is my own run of Debezium Server and Flink CDC against a local PostgreSQL 17.11. PeerDB was not run, and I say so wherever it matters.
The short version
- Decide first what you are building. An event stream that many services consume suits Debezium. A capture plus transform plus load job that you already plan to run on Flink suits Flink CDC. A Postgres to ClickHouse (or Postgres to Postgres) mirror suits PeerDB. Many teams need more than one of these, and each one costs a replication slot.
- Debezium (
debezium/debezium) is at 3.7.0.Final (29 September 2026). Apache 2.0. Connectors for PostgreSQL, MySQL, MariaDB, MongoDB, Oracle, SQL Server, Db2, Cassandra, Spanner, Vitess and more; tested with PostgreSQL 14 to 18. Debezium Server needs Java 21+ and ships sinks such as Kafka, Kinesis, Pub/Sub, Pulsar, Redis, NATS, RabbitMQ, HTTP, JDBC and Iceberg. The debezium.io footer points to the Commonhaus Foundation for its trademark policy. - Apache Flink CDC (
apache/flink-cdc) is at 3.7.0 (10 October 2026, 1:52 PM IST). Apache 2.0. YAML pipelines for Flink 1.20.x or 2.2.x. Sources include MySQL, PostgreSQL and Oracle (3.7.0 adds SQL Server and Fluss); sinks include Kafka, Iceberg, Paimon, Hudi, Doris, StarRocks and Fluss. Its PostgreSQL source runs Debezium code inside the Flink job. - PeerDB (
PeerDB-io/peerdb) is at v0.37.11 (6 October 2026, 9:37 PM IST). AGPL-3.0 since 14 January 2026 (Elastic License 2.0 before). Sources: Postgres, MySQL, MongoDB, CockroachDB, BigQuery. Maintained destinations: ClickHouse, ClickHouse Cloud and Postgres; Snowflake, BigQuery, Kafka, S3 and other destinations are deprecated but still work. It also powers ClickPipes’ Postgres CDC connector in ClickHouse Cloud. - Security history is small and old for the CDC tools themselves: Debezium CVE-2023-1419 (fixed in 2.3.0.Alpha1) and a Debezium UI issue CVE-2024-28736; Flink CDC CVE-2025-62228 (fixed in 3.5.0). I found no advisory for PeerDB in the sources I checked. The most important recent CVE for all three sits in PostgreSQL: CVE-2026-6471, fixed on 13 August 2026, which led to the new
output_plugin_librariessetting that only allowspgoutputandtest_decodingby default.
Where things stand on 14 October 2026
Dates for Flink CDC and PeerDB are from GitHub release published_at, converted from UTC to IST (UTC+5:30). Debezium does not publish GitHub releases, so its dates are the calendar dates on debezium.io. Licences are from the LICENSE files at the tagged releases. None of the listed releases is marked prerelease.
Project
Release
Date (IST)
Notes
Licence
Debezium (debezium/debezium)
3.7.0.Final
29 September 2026
Current stable series; first official Debezium CLI, Debezium Server readiness probe, native build module, Databricks ZeroBus sink, PostgreSQL incremental snapshots on read-only replicas
Apache 2.0
Debezium
3.6.3.Final
18 September 2026
Newest of the 3.6 stable series
Apache 2.0
Debezium Server (debezium/debezium-server)
3.7.0.Final
29 September 2026
Standalone runtime used in the drill; Java 21+
Apache 2.0
Apache Flink CDC (apache/flink-cdc)
3.7.0
10 October 2026, 1:52 PM
Current release; AI model functions in YAML transforms, inline Python UDFs, SQL Server and Fluss pipeline sources, existing-table schema checks for Paimon and Fluss
Apache 2.0
Apache Flink CDC
3.6.0
31 March 2026, 7:24 AM
Previous release; built on JDK 11 from this version
Apache 2.0
Apache Flink CDC
3.5.0
26 September 2025, 3:40 PM
Fix for CVE-2025-62228
Apache 2.0
PeerDB (PeerDB-io/peerdb)
v0.37.11
6 October 2026, 9:37 PM
Current release; Cloud SQL IAM auth, cron schedules for query CDC polls, MongoDB ingestion work
AGPL-3.0
PeerDB
v0.37.10
24 September 2026, 10:16 AM
Patch release
AGPL-3.0
PeerDB
v0.37.4
13 August 2026, 1:18 PM
Same 0.37 line, two months earlier
AGPL-3.0
PostgreSQL
17.11 (also 18.6, 16.15, 15.19, 14.24)
13 August 2026
Fixes CVE-2026-6471; adds output_plugin_libraries
PostgreSQL Licence
Practical notes on the table. Debezium’s releases page labels Alpha, Beta and CR builds as preview and Final builds as stable, and lists the Java, Kafka Connect and database versions each series is tested against. Flink CDC ships separate builds for Flink 1.20.x and 2.2.x, down to each connector jar (3.7.0-1.20, 3.7.0-2.2). PeerDB released eight patch versions in the 0.37 line between 13 August and 6 October 2026 and is still 0.x, so pin an exact tag. GitHub stars on fetch day were about 13,215 (Debezium), 6,483 (Flink CDC) and 3,305 (PeerDB); stars measure attention, not fitness.
Timeline
Date (IST)
Event
23 January 2016
debezium/debezium repository created on GitHub
28 July 2020
Repository that is now apache/flink-cdc created
27 February 2021
Flink CDC 1.2.0, the oldest GitHub release still listed
27 January 2023
debezium/debezium-server repository created
16 April 2023
PeerDB-io/peerdb repository created
7 December 2023
Flink CDC 3.0.0
24 May 2024
Flink CDC 3.1.0
17 November 2024
GitHub advisory GHSA-hvw5-3mgw-7rcf published for Debezium CVE-2023-1419
9 October 2025
Flink CDC CVE-2025-62228 published (fixed in 3.5.0, released 26 September 2025)
14 January 2026
PeerDB licence changed from Elastic License 2.0 to AGPL-3.0
31 March 2026
Flink CDC 3.6.0
7 July 2026
PeerDB marks every destination other than ClickHouse and Postgres as deprecated
13 August 2026
PostgreSQL fixes CVE-2026-6471 and adds output_plugin_libraries
17 September 2026
Debezium blog on native Debezium Server builds
29 September 2026
Debezium 3.7.0.Final
6 October 2026
PeerDB v0.37.11
10 October 2026
Apache Flink CDC 3.7.0
14 October 2026
This post’s research and the Debezium plus Flink CDC drill
The history explains the shapes. Debezium grew up next to Apache Kafka, and Kafka Connect is still its most common home; Debezium Server serves teams without Kafka. Flink CDC’s older API is a set of Flink source connectors with Debezium code underneath, and the 3.x line added YAML pipelines on top. PeerDB started in 2023 to be very good at Postgres rather than adequate at everything, and its 2026 changes moved it closer to ClickHouse.
First question: what are you actually trying to build?
Before comparing logos, the Pune team should write down which of these it needs:
- An event stream. “Tell every interested service that invoice 1001 was paid.” Many consumers, replay, ordering per key, a broker in the middle. This is Debezium’s home, usually with Kafka; the Kafka and Redpanda post covers the bus itself.
- A replicated copy for analytics. “Keep ClickHouse in step with Postgres.” One consumer, bulk initial load, type mapping, upserts at the target. PeerDB was built for this, and Debezium plus Kafka plus a ClickHouse ingestion path can also do it with more parts.
- Capture with transformation. “Clean, route and merge many tables into lakehouse tables.” Flink CDC does this in one job if you already run Flink or are ready to.
- Maybe you need no CDC. If a nightly load is enough, a scheduler and a query are simpler; the data pipeline orchestration post covers that side.
Deploying Flink only to move one table into Kafka buys a stream processor for a connector’s job. Writing custom Debezium consumers to rebuild ClickHouse tables rebuilds PeerDB in-house. And every tool opens another replication slot on the primary.
What each project actually is
Debezium 3.7.0.Final
Debezium is a set of CDC connectors with a shared core. Each reads a database’s own change mechanism (logical decoding for PostgreSQL, the binlog for MySQL, LogMiner or XStream for Oracle, change streams for MongoDB) and emits one event per row change with before, after, source and an op code: r snapshot read, c create, u update, d delete.
Ways to run it:
- Kafka Connect: the classic distributed deployment; events land in Kafka topics and other sink connectors move them on.
- Debezium Server: a standalone Quarkus application. One instance runs exactly one source connector and one sink, no Kafka needed. The 3.7 docs list sinks from Kinesis, Pub/Sub, Pulsar, Event Hubs, Redis, NATS JetStream and RabbitMQ to JDBC, Apache Iceberg and Databricks ZeroBus.
- Debezium Engine and the Quarkus extension: Debezium as a library in your own Java application.
- Debezium Platform and the Operator: Kubernetes management for Debezium Server pipelines; the website recommends the Platform for managed pipelines.
What 3.7 adds for operations: a readiness probe that reflects whether the engine is really polling (earlier /q/health/ready said UP as soon as the application started), a native build module for Debezium Server (the blog example uses a nightly image), the first official Debezium CLI, and PostgreSQL incremental snapshots on read-only replicas. Connectors need Java 17 or later; Debezium Server needs Java 21 or later.
Debezium fits when changes are events that many systems consume, when you need many source databases with one event format, or when Kafka is already in the estate. Its trade-off: it captures and delivers but does not transform beyond single message transforms, and Kafka Connect mode means running a Kafka Connect cluster.
Apache Flink CDC 3.7.0
Flink CDC describes itself as a streaming data integration tool. You write a YAML file with source, sink and pipeline blocks and optional transform and route blocks, and flink-cdc.sh compiles it into a Flink job on a standalone, Kubernetes or YARN cluster. The source reads a snapshot and then the change log, schema change events travel with the data, and Flink checkpoints store the offsets.
Operational facts from the 3.7 docs and release announcement:
- Runtime: Flink 1.20.x or 2.2.x and JDK 11 or later. Separate binary and connector builds for each Flink line.
- Two APIs: the YAML pipeline connectors (MySQL, PostgreSQL, Oracle, and in 3.7.0 also SQL Server and Fluss as sources) and the older Flink source connectors for Flink SQL and DataStream jobs (MySQL, PostgreSQL, Oracle, Db2, MongoDB, SQL Server, TiDB, Vitess).
- Sinks: Doris, Elasticsearch, Fluss, Hudi, Iceberg, Kafka, MaxCompute, OceanBase, Paimon and StarRocks pipeline sinks. There is no ClickHouse pipeline sink in the 3.7 list.
- Transforms: projections, filters and UDFs; 3.7.0 adds inline Python UDFs and AI model functions, with an experimental ordered asynchronous mode.
- Debezium inside: in the drill, PostgreSQL source options took a
debezium.prefix and the Flink log’s error classes wereio.debezium.*, so Debezium’s PostgreSQL defaults apply inside Flink CDC too.
Flink CDC fits when you want capture, transformation and loading as one stateful job, especially into Paimon or Iceberg, and a team already runs Flink. Its trade-off: you operate Flink (JobManager, TaskManagers, checkpoints, version alignment), and a misconfigured source can loop through restarts instead of failing once, as the drill shows.
PeerDB v0.37.11
PeerDB says plainly that it is Postgres-first. You create peers (connections) and mirrors (replication jobs) through a web UI or through SQL on a Postgres-compatible interface, so psql can manage your ETL.
Architecture from the docs:
- Nexus: a Rust query layer speaking the PostgreSQL wire protocol (port 9900 in the quickstart).
- Flow: a Go service with a Flow API and horizontally scalable workers that move data.
- Temporal orchestrates flows and a catalog Postgres stores metadata. The Docker Compose stack stages files in MinIO before loading ClickHouse, so an external ClickHouse must reach MinIO.
- Modes: log-based CDC is recommended; query replication (QRep) and XMIN modes are deprecated but still work.
The 2026 changes are the headline. On 14 January 2026 the LICENSE moved from Elastic License 2.0 to AGPL-3.0, and the README’s licensing contact became a ClickHouse address. On 7 July 2026 every destination other than ClickHouse, ClickHouse Cloud and Postgres was marked deprecated, with a guide to pin a release or fork the connector code. The README also says PeerDB runs natively in ClickHouse Cloud as the ClickPipes Postgres CDC connector. One inconsistency: the README’s licence badge still says ELv2 while the LICENSE file says AGPL-3.0. Trust the LICENSE file. The README’s speed claims against “existing tools” are the vendor’s own figures; I did not test PeerDB and do not repeat them.
PeerDB fits when the job is to keep ClickHouse (or another Postgres) in step with a Postgres primary and you want a product with a UI, parallel initial load and data-type mapping built for that pair. Its trade-off: a narrower destination list, an AGPL licence your legal team should read, and a stack of its own (Temporal, catalog database, workers, object storage) to run if you self-host.
Shape comparison
Concern
Debezium 3.7
Flink CDC 3.7
PeerDB 0.37.11
Shape
CDC connectors and event format
Integration framework on Flink
Postgres-first replication product
Unit you deploy
Connector in Kafka Connect, Debezium Server, or embedded engine
YAML pipeline compiled into a Flink job
Peers and mirrors in PeerDB’s own services
Where state lives
Kafka offsets topic, or a file, Redis or JDBC offset store (Server)
Flink checkpoints and savepoints
PeerDB catalog Postgres and Temporal
Typical output
Events in Kafka or another broker
Rows in lakehouse tables, OLAP stores or Kafka
Tables in ClickHouse or Postgres
Transformation
Single message transforms
Projections, filters, routes, UDFs
Mapping and mirror options; transformation is not its focus
Sources (2026)
Many relational and NoSQL databases
MySQL, PostgreSQL, Oracle, SQL Server, Fluss pipelines plus more Flink sources
Postgres, MySQL, MongoDB, CockroachDB, BigQuery
Interface
Properties or Kafka Connect REST, CLI, Platform UI
YAML, flink-cdc.sh, Flink UI
SQL over PG wire, web UI
Licence (tag)
Apache 2.0
Apache 2.0
AGPL-3.0
Governance
Community project; site footer points to the Commonhaus Foundation trademark policy
Apache Software Foundation (Apache Flink)
Company-led; closely tied to ClickHouse
Symptom to shape
Symptom
Likely fit
Several services must react to the same row changes, with replay
Debezium into Kafka (or Debezium Server into your existing broker)
One Postgres must be mirrored into ClickHouse with a bulk initial load
PeerDB, or ClickPipes if you are on ClickHouse Cloud
Many tables must be cleaned and routed into Paimon or Iceberg
Flink CDC
You want CDC without running Kafka
Debezium Server, Flink CDC or PeerDB, depending on target
Sources are Oracle, SQL Server or Db2
Debezium or Flink CDC; PeerDB does not list them
Target is Snowflake or BigQuery and you were planning PeerDB
Re-check: those PeerDB destinations are deprecated
The PostgreSQL mechanics none of them can hide
On PostgreSQL every one of these tools is a client of logical decoding. Learn these five objects once.
Replication slots and WAL retention
A logical replication slot remembers how far a consumer has confirmed reading. PostgreSQL keeps every WAL segment the slot still needs, even if the consumer is down for a week. The pg_replication_slots view shows active, restart_lsn, confirmed_flush_lsn, wal_status and, from PostgreSQL 17, inactive_since. The setting max_slot_wal_keep_size caps how much WAL a slot may hold; its default is -1, which means no cap. That is the safe default for data, and the dangerous one for disk.
Two things grow WAL behind a slot:
- The consumer is down. Nothing confirms, so nothing is freed.
- The consumer is up but the captured tables are quiet. If other tables or databases on the server write heavily, the slot does not move until a captured change arrives. Debezium’s docs give the fix:
heartbeat.interval.msplus aheartbeat.action.querythat writes to a small heartbeat table included in the publication. Flink CDC’s PostgreSQL connector has aheartbeat.intervaloption for tracking the latest WAL offset; test whether it moves your slot.
The drill reproduced both on a small database. On a busy primary the same pattern means gigabytes.
A query worth putting on a dashboard:
SELECT slot_name, plugin, active, wal_status, inactive_since,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) AS behind_confirmed
FROM pg_replication_slots
WHERE slot_type = 'logical';
Flink CDC adds scan.lsn-commit.checkpoints-num-delay (default 3): it confirms the LSN to PostgreSQL only after that many checkpoints, so older checkpoints stay recoverable. A job whose checkpoints fail will not move its slot.
Publications
With pgoutput, the publication decides which tables are decoded. Debezium’s publication.autocreate.mode defaults to all_tables, which runs CREATE PUBLICATION ... FOR ALL TABLES if the publication is missing. PostgreSQL requires CREATE privilege on the database to create a publication, ownership of each table added to it, and a superuser for FOR ALL TABLES; Debezium’s docs warn that the default fails for lack of permissions. Better: have a DBA create the publication for exactly the captured tables and set the mode to disabled. The Flink CDC drill hit this default through the embedded Debezium code.
Replica identity
REPLICA IDENTITY decides what PostgreSQL logs about the old row on UPDATE and DELETE. With DEFAULT, only primary key columns of the old row are available. With FULL, every old column is logged, at the cost of more WAL. In the drill, a delete under DEFAULT produced an event whose before had the right id and placeholder values in the other fields (an empty string, 0, the column default 'DRAFT' and the epoch timestamp). A consumer that trusts those fields will be wrong. Either set FULL on tables where consumers need old values, or teach consumers that only the key is meaningful in a delete.
Output plug-ins
PostgreSQL ships pgoutput and test_decoding; decoderbufs and wal2json are separate installs. Debezium 3.7 is tested with pgoutput and decoderbufs; Flink CDC’s PostgreSQL source accepts decoderbufs, wal2json variants and pgoutput. Since August 2026 PostgreSQL must also be told a plug-in is trusted (see the security section). For new pipelines pgoutput is simplest: nothing to install or allow-list.
Failover
A logical slot lives on one server; after a failover, a consumer needs the slot on the new primary or a re-snapshot. Debezium’s slot.failover=true uses PostgreSQL 17 failover slots when the standby’s physical slot is listed in synchronized_standby_slots. With Patroni or CloudNativePG, test CDC across a switchover before go-live; the PostgreSQL high availability post covers the cluster side.
Snapshots, schema changes and delivery guarantees
Initial load and re-snapshots
- Debezium snapshots by default (
snapshot.mode=initial) from the snapshot exported at slot creation, so snapshot and stream line up. Later backfills use incremental snapshots triggered by signals (table, Kafka topic, JMX or file) while streaming continues. - Flink CDC splits tables into chunks by primary key, with low and high watermarks to merge changes made during the snapshot. Tables without a primary key need
scan.incremental.snapshot.chunk.key-column. Skipping backfill is faster but, per the docs, only at-least-once. - PeerDB advertises parallel initial load of large tables as a core feature. I have not tested it.
Schema changes
- Debezium picks up the new table shape from the stream. In the drill, the first insert after
ALTER TABLE ... ADD COLUMN gstincarried the new field with no restart. Downstream handling is the sink’s job; the Debezium JDBC sink hasschema.evolutionmodes, includingvalidate-onlyin 3.7. - Flink CDC applies
schema.change.behavior:exception,evolve,try_evolve,lenient(default) orignore. Inlenientmode a type change becomes a rename plus a new column, and truncate and drop events are not passed on. 3.7.0 addsexisting-table.schema-expansion.modefor Paimon and Fluss sinks. - PeerDB lists schema changes among its supported Postgres features. Not tested here.
Exactly-once, honestly labelled
Read the fine print:
- Debezium’s PostgreSQL docs say exactly once when running normally and at least once while recovering from a fault, so duplicates are possible after a crash.
- Flink CDC describes exactly-once processing inside the Flink job, built on checkpoints. End-to-end depends on the sink, and the docs mark some cases (backfill skipped, chunk key column updated) as at-least-once.
- PeerDB says it handles idempotency and consistency through state management and retries. That is the vendor’s description; I did not verify it.
The practical rule is the same for all three: make the target idempotent on the primary key (upserts, or deduplication by source LSN), and test a crash in the middle of a batch before you trust any guarantee.
Operations and observability
Area
Debezium Server 3.7
Flink CDC 3.7
PeerDB 0.37.11
Health
/q/health; 3.7 readiness reflects real polling
Flink job state, restarts, checkpoint success
UI sync status per mirror
Metrics
JMX, with a Prometheus JMX exporter config shipped
Flink metrics system and reporters
UI and Temporal views
Position stored
Offset store (file, Redis or JDBC)
Checkpoints and savepoints
Catalog database
Restart behaviour
Resumes from stored offset and searches for the WAL resume position
Restart strategy (exponential delay in the drill)
Temporal retries
Common failure
Slot held while the server is down; offset file lost
Restart loop on bad privileges; checkpoint failures stall the slot
Destination networking (MinIO staging in Docker) and deprecated targets
A minimum dashboard: per-slot active, retained bytes and inactive_since, lag in time at the target, events per second by table, restarts, and disk free on the primary. Alert on an inactive slot long before disk.
Security (verified advisories)
I checked GitHub repository advisories, the GitHub advisory database, OSV and NVD on 14 October 2026, and for each CVE below confirmed one NVD record and an HTTP 200 on cve.org. Severity labels are from the records named.
Debezium
ID
Severity
Affected
Fixed
Notes
CVE-2023-1419 / GHSA-hvw5-3mgw-7rcf
Medium (GitHub; Red Hat CVSS 3.1 5.9)
debezium-core, MySQL and SQL Server connectors before 2.3.0.Alpha1
2.3.0.Alpha1
Script injection through unsanitised parameters; GitHub advisory published 17 November 2024
CVE-2024-28736
High (CVSS 3.1 7.1 in NVD)
Debezium UI 2.5
Not stated in the record
NVD: local attacker can run code via the UI’s refresh page function; not part of the connectors or Debezium Server used here
The Debezium repositories list no repository advisories, and OSV has nothing for the PostgreSQL connector package. The 3.7.0.Final summary says it “addressed Debezium Server CVE findings” without naming a Debezium CVE, which reads as dependency updates.
Apache Flink CDC
ID
Severity
Affected
Fixed
Notes
CVE-2025-62228 / GHSA-wqm3-w3p6-xjgm
Medium (GitHub; Apache CVSS 4.0 5.1); High in NVD’s own CVSS 3.1 score (8.8)
3.0.0 up to before 3.5.0 for the pipeline connectors and the MySQL, Oracle, Db2 and SQL Server CDC connectors
3.5.0
SQL injection through crafted database or table names; the record notes only a logged-in database user can trigger it
The PostgreSQL CDC package is not listed in that advisory, and OSV returned nothing for it. Flink CDC 3.7.0 is past the fix in any case. Remember that a Flink CDC deployment also inherits Apache Flink’s own advisories; track those separately.
PeerDB
The repository advisories API returned no entries, the GitHub advisory database had nothing for PeerDB-io/peerdb, OSV returned nothing for the Go module, and NVD keyword search for “peerdb” returned zero results on 14 October 2026. No matching advisory was found in the sources I checked; that is not a promise about the future. The quickstart uses default passwords (peerdb for the SQL interface) that must be changed before anything leaves a laptop.
PostgreSQL CVE-2026-6471: the one that touches all three
PostgreSQL’s advisory says a non-superuser with REPLICATION could make the server load any file visible to its operating system account as a decoding plug-in, and so run code as that account (CVSS 3.1 7.2). Fixed in 18.6, 17.11, 16.15, 15.19 and 14.24 on 13 August 2026.
The fix adds output_plugin_libraries, an allow-list that by default holds only pgoutput and test_decoding. The drill confirmed this on 17.11: slots with decoderbufs and wal2json failed with “may not be used as an output plugin”, while test_decoding and pgoutput worked. Three consequences:
- Upgrade PostgreSQL to a fixed minor release; the CDC tool version does not matter here.
- Setups using
decoderbufsorwal2jsonstop creating slots after the upgrade until a DBA allow-lists the plug-in. Moving topgoutputavoids the question. pg_upgrade --checknow fails if the new cluster does not allow the plug-ins of existing logical slots (from 17 onwards). Add that to the upgrade runbook.
Shared controls
- One role per tool:
REPLICATION,SELECTon captured tables, noCREATEon the database, publications pre-created by an administrator. TreatREPLICATIONas powerful even after the fix. - Keep secrets out of config files. In the drill Debezium Server read its password from an environment variable; the Flink YAML had it inline, acceptable only on a throwaway box.
- Keep admin surfaces private: Flink’s REST endpoint and UI, the Debezium Server HTTP port, PeerDB’s UI and SQL port. The drill bound everything to
127.0.0.1. - Read the licence at the tag. AGPL-3.0 adds obligations if you modify PeerDB and let others use it over a network; ask your legal team before forking a deprecated connector.
Decision guide
Situation
Prefer
Avoid thinking
Many consumers need row changes as events, with replay
Debezium with Kafka (Connect or Server)
“Each team can open its own slot”
No Kafka, one target broker or HTTP endpoint
Debezium Server
Building a custom logical decoding client
Postgres to ClickHouse mirror, self-hosted, AGPL acceptable
PeerDB
Assuming all its old destinations are still maintained
Postgres to ClickHouse Cloud
ClickPipes (built on PeerDB)
Running your own PeerDB stack just for this
Many tables, transforms and routes into Paimon, Iceberg, Doris or StarRocks
Flink CDC on an existing Flink platform
Standing up Flink for one table into Kafka
Oracle, SQL Server or Db2 sources
Debezium or Flink CDC
Picking PeerDB because it worked well for Postgres
Strict end-to-end exactly-once required
Idempotent target plus a tested crash drill, whichever tool
Trusting any single “exactly-once” line in a README
Snowflake or BigQuery destination
Debezium plus a supported loader, or another vendor tool
Starting a new PeerDB mirror to a deprecated destination
For the invented Pune billing SaaS: start with Debezium for invoice events, because several services need the same changes and Kafka is the natural place to share them. For analytics, run a short PeerDB proof of concept on a staging copy if the team self-hosts ClickHouse and accepts AGPL-3.0 and the Temporal stack; on ClickHouse Cloud, use ClickPipes. Keep Flink CDC for when the data platform team runs Flink anyway and needs transformation into lakehouse tables; the lakehouse table formats post and the real-time OLAP post cover those targets. Whatever they choose, budget slots: three tools mean three slots on the same primary, each able to hold WAL.
Checklist
- Write down which consumers need events, which need a replicated copy and which need transformed tables, before choosing a tool.
- Pin exact versions (Debezium 3.7.0.Final, Flink CDC 3.7.0 with the matching Flink line, PeerDB tag) and verify release checksums in your build.
- Confirm the LICENSE at the tag you deploy and note which destinations or features are deprecated or commercial-only.
- Upgrade PostgreSQL to a release with the CVE-2026-6471 fix and decide your
output_plugin_librarieslist; preferpgoutput. - Create a dedicated role per tool with
REPLICATIONandSELECTon captured tables only; pre-create filtered publications as an administrator. - Set
REPLICA IDENTITY FULLwhere consumers need old values on UPDATE and DELETE, and accept the extra WAL. - Configure and test heartbeats (Debezium
heartbeat.action.query, Flink CDCheartbeat.interval) for quiet tables on busy databases. - Decide
max_slot_wal_keep_sizewith your DBA and monitor slotactive,retainedandinactive_since. - Test a consumer outage, a restart and a primary failover before go-live, and write down how to drop and recreate a slot safely.
- Make every target idempotent on the primary key and test a crash mid-batch.
Common mistakes
- Treating Debezium, Flink CDC and PeerDB as three interchangeable brands and running a bake-off on one table into one sink.
- Leaving a stopped connector’s replication slot in place for days, with
max_slot_wal_keep_sizeat-1. - Giving the CDC role superuser so that
CREATE PUBLICATION ... FOR ALL TABLESsucceeds, instead of pre-creating a filtered publication. - Capturing a quiet table on a busy database without heartbeats, then wondering why WAL keeps growing while the connector is “healthy”.
- Trusting the non-key fields in delete events under
REPLICA IDENTITY DEFAULT. - Upgrading PostgreSQL to 17.11 (or 18.6, 16.15, 15.19, 14.24) without checking that the decoding plug-in is in
output_plugin_libraries. - Mixing Flink CDC jars built for Flink 1.20 with a Flink 2.2 cluster, or the reverse.
- Ignoring a Flink job that keeps restarting because “the job is RUNNING” in between restarts.
- Starting new PeerDB mirrors to Snowflake, BigQuery, Kafka or S3 without noticing that those destinations are deprecated.
- Reading “exactly-once” in a README and skipping idempotent writes at the target.
A drill I ran on 14 October 2026
Environment: shared Linux VM (x86_64), no Docker, no Kubernetes. PostgreSQL 17.11 (Debian packages) on 127.0.0.1:15439 with wal_level=logical, database billing, table public.invoices with three seed rows. Roles dbz_cdc and flink_cdc, each with REPLICATION, schema USAGE and SELECT on invoices only, no CREATE on the database. Publication dbz_pub for invoices, created by the superuser. Port 8766 was left alone for the existing static file server; every drill service bound to 127.0.0.1. Artefacts are under /workspace/revisit/drill39/. Times: about 10:26 AM to 10:33 AM IST, plus a status check at about 2:25 PM IST.
What I did:
- Checksums. Debezium Server dist 3.7.0.Final, Flink 1.20.5, Flink CDC 3.7.0 (Flink 1.20 build), its PostgreSQL and values pipeline jars, the PostgreSQL CDC SQL jar and Temurin JDK 21.0.12.1+1: all seven matched their published checksums.
- Plug-in allow-list. On 17.11 with default settings,
output_plugin_librariesshowedpgoutput, test_decoding. Creating slots withdecoderbufsandwal2jsonfailed with “may not be used as an output plugin”;test_decodingworked. - Debezium Server, run 1 (pgoutput, slot
dbz_billing, publication autocreatedisabled, HTTP sink to a small local receiver, file offsets, password from an environment variable). It created the slot, snapshotted 3 rows (op: r), then streamed my changes: inserts, updates, two deletes, and anALTER TABLE ... ADD COLUMN gstinfollowed by an insert whose event carriedgstinwith no restart. Events showed connector version3.7.0.Final. UnderREPLICA IDENTITY DEFAULT, updates hadbefore: nulland a delete’sbeforeheld the realidbut placeholder values elsewhere. Eleven events arrived before I killed the process. - Outage. With Debezium down I inserted 50 rows: the slot showed
active = f,wal_status = reserved, 305 kB of lag. I also setREPLICA IDENTITY FULLoninvoices. - Run 2 found the stored offset, skipped the snapshot, logged “WAL resume position … discovered” and delivered all 50 missed inserts. Across runs 1 and 2 the receiver got 52 create events for 52 distinct ids: no duplicates this time.
- Quiet table, busy database. With run 2 active, I wrote 50,000 rows into
audit_noise, which is not in the publication. The slot stayedactive = t, yet held 17 MB behind its confirmed position. - Run 3 added
heartbeat.interval.ms=5000and aheartbeat.action.queryupdating a one-rowdbz_heartbeattable indbz_pub. Seven heartbeats reached the sink about five seconds apart and the slot fell to 192 bytes behind. Run 3 stopped cleanly at about 10:30 AM IST. - Flink CDC, attempt 1 (standalone Flink 1.20.5 on localhost, PostgreSQL source to values sink, slot
flink_billing,lenientschema changes, 5-second checkpoints). The job went RUNNING, then failed and restarted 8 times in about 35 seconds until I cancelled it. Flink said “Fail to get or create slot … permission denied for database billing”; the PostgreSQL log showed the real statement,CREATE PUBLICATION dbz_publication FOR ALL TABLES, the embedded Debezium default. - Flink CDC, attempt 2. With
flink_pubpre-created by the superuser anddebezium.publication.autocreate.mode: disabled, the job created slotflink_billing, emitted aCreateTableEventalready includinggstin, exported 53 snapshot rows as INSERT events (matchingcount(*) = 53) and moved to the stream split. - Status check at about 2:25 PM IST. The second Flink job was still RUNNING with its slot active and checkpoints completing. Debezium’s slot
dbz_billingwas still there,active = f,inactive_since10:30 AM IST, holding only 32 kB because the database had been quiet.
The Flink CDC pipeline file from attempt 2, trimmed (password removed):
source:
type: postgres
hostname: 127.0.0.1
port: 15439
username: flink_cdc
tables: billing.public.invoices
decoding.plugin.name: pgoutput
slot.name: flink_billing
debezium.publication.name: flink_pub
debezium.publication.autocreate.mode: disabled
sink:
type: values
pipeline:
name: pune-billing-invoices
parallelism: 1
schema.change.behavior: lenient
flink-conf:
execution.checkpointing.interval: 5s
What I did not run: PeerDB (its supported path is Docker Compose or Kubernetes with Temporal, a catalog database and MinIO, which this environment did not offer; every PeerDB statement above comes from its docs and repository), Kafka or Kafka Connect, any real sink, streaming changes through the Flink job after its snapshot, a failover, TOAST columns, large tables, a load test or any advisory reproduction. No throughput or latency number comes from this drill; the sizes above only show direction on a tiny database.
The useful findings are not about speed. Slots hold WAL whenever confirmation stops, whether the consumer is down or has nothing captured to confirm, and a heartbeat into a published table fixes the second case. Privileges fail in surprising places: the least-privilege role worked once the publication was pre-created, but the default mode turned a permissions problem into a restart loop with a misleading message. And a stopped tool’s slot stays until someone drops it, as it still does on this box.
Unlearn
- Unlearn the idea that “CDC tool” is one product category. A connector family, a stream processing framework and a replication product make different promises.
- Unlearn that a running connector means healthy WAL. A quiet captured table on a busy database can hold WAL while everything reports UP.
- Unlearn that
REPLICATIONis a harmless read-only grant. On servers without the August 2026 fix it can load arbitrary libraries, and on any server it can read changes through a slot. - Unlearn trusting
beforeimages by default. What you get depends on replica identity. - Unlearn reading “exactly-once” as end-to-end without asking about the sink.
Re-learn
- Re-learn PostgreSQL logical decoding vocabulary: WAL, LSN, slot, publication, output plug-in, replica identity,
confirmed_flush_lsn. - Re-learn least privilege for CDC: pre-created publications, one role and one slot per tool, no superuser.
- Re-learn version pins from releases and tags, with IST timestamps, checksums and the LICENSE at the tag in your runbook.
- Re-learn idempotent writes at the target, keyed on the primary key or the source LSN.
- Re-learn slot hygiene: dashboards, alerts on inactive slots, and a written procedure for retiring a pipeline.
Closing
Change data capture in 2026 is still a wording trap. Debezium turns row changes into events and lets you choose where they go. Apache Flink CDC turns a YAML pipeline into a Flink job that captures, transforms and loads, with Debezium inside its PostgreSQL source. PeerDB is a Postgres-first replication product that, after its 2026 licence change and deprecations, is mainly about ClickHouse and Postgres targets. The Pune review becomes productive only after separating events, mirrors and transformation jobs. Learn what each shape is responsible for. Unlearn three-way bake-offs on one table. Re-learn slots, publications, replica identity and plug-in allow-lists on the exact PostgreSQL version you run. Practise a consumer outage, a quiet-table heartbeat and a least-privilege setup on a loopback port. Apply the shape that matches who consumes the changes, and only then stream another table.
Sources
Debezium
- Debezium 3.7 release series
- Debezium releases overview
- Debezium 3.7.0.Final release announcement
- Debezium 3.6.3.Final release announcement
- Native builds of Debezium Server
- Debezium architecture
- Debezium PostgreSQL connector
- Debezium Server
- Debezium signalling and incremental snapshots
- debezium/debezium on GitHub
- Debezium README (v3.7.0.Final)
- Debezium LICENSE (Apache 2.0)
- debezium/debezium-server on GitHub
- Debezium Server v3.7.0.Final tag
- Debezium Server dist 3.7.0.Final on Maven Central
- Commonhaus Foundation
- GHSA-hvw5-3mgw-7rcf / CVE-2023-1419
- CVE-2023-1419 (NVD)
- CVE-2023-1419 (cve.org)
- GHSA-hvw5-3mgw-7rcf (OSV)
- CVE-2024-28736 (NVD)
- CVE-2024-28736 (cve.org)
Apache Flink CDC
- apache/flink-cdc on GitHub
- apache/flink-cdc releases
- Flink CDC release 3.7.0
- Flink CDC LICENSE (Apache 2.0)
- Apache Flink CDC 3.7.0 release announcement
- Flink CDC 3.7 introduction
- Flink CDC data pipeline concept
- Flink CDC schema evolution
- Flink CDC PostgreSQL pipeline connector
- Flink CDC PostgreSQL source connector
- Apache Flink downloads
- GHSA-wqm3-w3p6-xjgm / CVE-2025-62228
- CVE-2025-62228 (NVD)
- CVE-2025-62228 (cve.org)
- CVE-2025-62228 announcement on the Apache list
PeerDB
- PeerDB-io/peerdb on GitHub
- PeerDB releases
- PeerDB v0.37.11
- PeerDB README (v0.37.11)
- PeerDB LICENSE (AGPL-3.0)
- PeerDB deprecated connectors guide (v0.37.11)
- PeerDB commit: update licence to AGPLv3
- PeerDB commit: deprecate non-ClickHouse and non-Postgres destinations
- PeerDB introduction
- PeerDB architecture
- PeerDB quickstart
- PeerDB supported connectors
- ClickPipes Postgres CDC connector
PostgreSQL
- CVE-2026-6471 (PostgreSQL security)
- CVE-2026-6471 (NVD)
- CVE-2026-6471 (cve.org)
- PostgreSQL 17.11 release notes
- Logical decoding
- pg_replication_slots view
- Replication settings (max_slot_wal_keep_size)
- CREATE PUBLICATION
- ALTER TABLE (REPLICA IDENTITY)
- Logical replication failover
Internal
Releted Posts
Data pipeline orchestration in 2026: revisit Apache Airflow, Dagster and Prefect before you schedule another DAG
Picture a two-wheeler loan company in Coimbatore with about ninety branches (an invented example, not a real client). Its data team of five runs about 140 cron jobs on two Linux VMs: shell scripts that pull collections files from a payment gateway SFTP, Python scripts that clean them, and SQL that loads a PostgreSQL reporting database every night.
Read moreAuthorization in 2026: revisit OpenFGA, SpiceDB and Cerbos before you hard-code another permission check
Picture a SaaS company in Nagpur that sells loan-origination software to cooperative banks across Vidarbha and Marathwada (an invented example, not a real client).
Read moreKubernetes autoscaling in 2026: revisit Karpenter, Cluster Autoscaler and KEDA before you scale another node
Picture a mid-size ecommerce SaaS in Ahmedabad with about 2,200 active sellers (an invented example, not a real client). Its platform team of eight runs checkout, catalogue, search and about 55 other services on Kubernetes on a public cloud.
Read more