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_libraries setting that only allows pgoutput and test_decoding by 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.

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 were io.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:

  1. The consumer is down. Nothing confirms, so nothing is freed.
  2. 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.ms plus a heartbeat.action.query that writes to a small heartbeat table included in the publication. Flink CDC’s PostgreSQL connector has a heartbeat.interval option 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 gstin carried the new field with no restart. Downstream handling is the sink’s job; the Debezium JDBC sink has schema.evolution modes, including validate-only in 3.7.
  • Flink CDC applies schema.change.behavior: exception, evolve, try_evolve, lenient (default) or ignore. In lenient mode a type change becomes a rename plus a new column, and truncate and drop events are not passed on. 3.7.0 adds existing-table.schema-expansion.mode for 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.

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:

  1. Upgrade PostgreSQL to a fixed minor release; the CDC tool version does not matter here.
  2. Setups using decoderbufs or wal2json stop creating slots after the upgrade until a DBA allow-lists the plug-in. Moving to pgoutput avoids the question.
  3. pg_upgrade --check now 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, SELECT on captured tables, no CREATE on the database, publications pre-created by an administrator. Treat REPLICATION as 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

  1. Write down which consumers need events, which need a replicated copy and which need transformed tables, before choosing a tool.
  2. 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.
  3. Confirm the LICENSE at the tag you deploy and note which destinations or features are deprecated or commercial-only.
  4. Upgrade PostgreSQL to a release with the CVE-2026-6471 fix and decide your output_plugin_libraries list; prefer pgoutput.
  5. Create a dedicated role per tool with REPLICATION and SELECT on captured tables only; pre-create filtered publications as an administrator.
  6. Set REPLICA IDENTITY FULL where consumers need old values on UPDATE and DELETE, and accept the extra WAL.
  7. Configure and test heartbeats (Debezium heartbeat.action.query, Flink CDC heartbeat.interval) for quiet tables on busy databases.
  8. Decide max_slot_wal_keep_size with your DBA and monitor slot active, retained and inactive_since.
  9. Test a consumer outage, a restart and a primary failover before go-live, and write down how to drop and recreate a slot safely.
  10. Make every target idempotent on the primary key and test a crash mid-batch.

Common mistakes

  1. Treating Debezium, Flink CDC and PeerDB as three interchangeable brands and running a bake-off on one table into one sink.
  2. Leaving a stopped connector’s replication slot in place for days, with max_slot_wal_keep_size at -1.
  3. Giving the CDC role superuser so that CREATE PUBLICATION ... FOR ALL TABLES succeeds, instead of pre-creating a filtered publication.
  4. Capturing a quiet table on a busy database without heartbeats, then wondering why WAL keeps growing while the connector is “healthy”.
  5. Trusting the non-key fields in delete events under REPLICA IDENTITY DEFAULT.
  6. 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.
  7. Mixing Flink CDC jars built for Flink 1.20 with a Flink 2.2 cluster, or the reverse.
  8. Ignoring a Flink job that keeps restarting because “the job is RUNNING” in between restarts.
  9. Starting new PeerDB mirrors to Snowflake, BigQuery, Kafka or S3 without noticing that those destinations are deprecated.
  10. 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:

  1. 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.
  2. Plug-in allow-list. On 17.11 with default settings, output_plugin_libraries showed pgoutput, test_decoding. Creating slots with decoderbufs and wal2json failed with “may not be used as an output plugin”; test_decoding worked.
  3. Debezium Server, run 1 (pgoutput, slot dbz_billing, publication autocreate disabled, 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 an ALTER TABLE ... ADD COLUMN gstin followed by an insert whose event carried gstin with no restart. Events showed connector version 3.7.0.Final. Under REPLICA IDENTITY DEFAULT, updates had before: null and a delete’s before held the real id but placeholder values elsewhere. Eleven events arrived before I killed the process.
  4. Outage. With Debezium down I inserted 50 rows: the slot showed active = f, wal_status = reserved, 305 kB of lag. I also set REPLICA IDENTITY FULL on invoices.
  5. 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.
  6. Quiet table, busy database. With run 2 active, I wrote 50,000 rows into audit_noise, which is not in the publication. The slot stayed active = t, yet held 17 MB behind its confirmed position.
  7. Run 3 added heartbeat.interval.ms=5000 and a heartbeat.action.query updating a one-row dbz_heartbeat table in dbz_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.
  8. Flink CDC, attempt 1 (standalone Flink 1.20.5 on localhost, PostgreSQL source to values sink, slot flink_billing, lenient schema 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.
  9. Flink CDC, attempt 2. With flink_pub pre-created by the superuser and debezium.publication.autocreate.mode: disabled, the job created slot flink_billing, emitted a CreateTableEvent already including gstin, exported 53 snapshot rows as INSERT events (matching count(*) = 53) and moved to the stream split.
  10. 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_billing was still there, active = f, inactive_since 10: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 REPLICATION is 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 before images 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

PeerDB

PostgreSQL

Internal

comments powered by Disqus

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 more

Authorization 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 more

Kubernetes 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