skip to content

Explain how the Postgres Debezium connector consumes the WAL: logical replication, output plugins (pgoutput/wal2json), replication slots, and the operational risks of slots.

level: seniorimportance: should knowfreq 50%

answer

  1. wal_level=logical
  2. output plugin: pgoutput (built-in) / wal2json
  3. replication slot tracks confirmed_flush_lsn
  4. slot retains WAL -> disk fills if connector down
  5. publication = which tables (pgoutput)
  6. heartbeat advances slot on idle/filtered DBs

basics

~20 s

The Postgres connector uses logical replication: Postgres must run with wal_level=logical. A logical decoding output plugin (pgoutput, built in, or wal2json) turns WAL records into change events, delivered through a replication slot that tracks the last LSN the connector confirmed. The big risk: if the connector is down, the slot holds WAL on disk and can fill the disk.

solid answer

~50 s

Postgres's WAL is binary and physical, so Debezium relies on **logical decoding**: with `wal_level=logical`, an **output plugin** decodes WAL into logical row changes. `pgoutput` is built into Postgres 10+ (no extension to install) and is the recommended plugin; `wal2json` and the older `decoderbufs` are alternatives requiring an extension. The connector creates (or uses) a **replication slot** — a named, server-side cursor that remembers the last **LSN** (log sequence number) the consumer confirmed, guaranteeing WAL is retained until consumed. With `pgoutput` you also need a **publication** defining which tables are replicated. Key config: `plugin.name`, `slot.name`, `publication.name`, `publication.autocreate.mode`. The major operational hazard: **a slot retains WAL as long as it is not advanced** — if the connector is stopped or lagging, `pg_wal` grows unbounded and can fill the disk, taking the database down. Monitor `pg_replication_slots.confirmed_flush_lsn` and slot lag; drop unused slots.

go deeper

for a junior

Know Postgres needs wal_level=logical and a replication slot for Debezium.

for a middle

Explain output plugins (pgoutput vs wal2json), publications, and that the slot tracks the LSN for safe resumption.

for a senior

Diagnose slot-driven WAL/disk growth, use heartbeats and monitoring, and configure publications for least privilege.

for a principal

Plan for failover/slot survival, max_slot_wal_keep_size trade-offs, and HA topologies; set SLOs around slot lag.

**WAL and logical decoding.** PostgreSQL's **Write-Ahead Log (WAL)** records every change at the *physical* level (block/page changes) for crash recovery and physical replication. That format is unusable for CDC directly. **Logical decoding** is Postgres's mechanism to translate WAL into a *logical* stream of row-level changes (insert/update/delete with column values). It is enabled by setting `wal_level = logical` in `postgresql.conf` (requires a restart) and having enough `max_replication_slots` and `max_wal_senders`. **Output plugins.** Logical decoding needs an **output plugin** that formats the decoded changes: - **`pgoutput`** — built into PostgreSQL since v10, no extension install needed; the default/recommended choice for Debezium. It is the same plugin native logical replication uses. - **`wal2json`** — a separate extension producing JSON; was popular before pgoutput matured. - **`decoderbufs`** — Protobuf-based, maintained by the Debezium team; needs installing. The choice is set via `plugin.name`. **Publications (pgoutput).** `pgoutput` filters tables via a **publication** — a Postgres object (`CREATE PUBLICATION`) listing the tables whose changes are streamed. Debezium can auto-create it (`publication.autocreate.mode` = `all_tables` / `disabled` / `filtered`) or you create it manually for least privilege. `publication.name` and `slot.name` name these objects. **Replication slots.** A **replication slot** is a durable, server-side marker that tracks the **LSN (Log Sequence Number)** — a monotonically increasing pointer into the WAL — up to which a consumer has confirmed receipt. Its purpose: **Postgres will not recycle/delete WAL segments that the slot still needs**, guaranteeing the connector never loses changes even across restarts. The slot's `confirmed_flush_lsn` advances as Debezium acknowledges processed LSNs. This is what makes resumption after a connector restart safe and exactly-positioned. **The operational hazard — disk fill.** The same retention guarantee is a double-edged sword: **if the connector is stopped, crashed, or badly lagging, the slot's confirmed LSN stops advancing, and Postgres must keep every WAL segment since that LSN.** `pg_wal` then grows without bound and can **fill the disk and crash the database**. This is the single most common production incident with Postgres CDC. Mitigations: - Monitor `SELECT slot_name, confirmed_flush_lsn, pg_size_bytes(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) AS retained FROM pg_replication_slots;` - Alert on retained WAL / slot lag. - **Drop slots you no longer use** (`pg_drop_replication_slot`) — an abandoned slot silently fills the disk. - Use `max_slot_wal_keep_size` (PG 13+) to cap retained WAL — but note this *invalidates* the slot if exceeded, meaning the connector then needs a re-snapshot. Choose the failure mode deliberately. - Debezium's **heartbeat** (`heartbeat.interval.ms`) emits periodic events so the slot advances even on low-traffic or filtered databases; otherwise a slot on an idle/filtered DB can lag because Debezium only acks LSNs it has seen relevant changes for. **Failover caveat.** Before PG 16, logical replication slots did **not** survive a physical failover to a standby (slots weren't replicated). After a failover the slot is gone and a re-snapshot is required. PG 16+ adds failover slots, and Patroni/managed services have their own handling — worth confirming for HA setups. **REPLICA IDENTITY.** To get full `before` images for updates/deletes, the table needs `ALTER TABLE ... REPLICA IDENTITY FULL`; otherwise only primary-key columns appear in `before`.

  • Your Postgres disk is filling up and the DB is at risk. What CDC-related cause should you check first?
    An inactive or lagging replication slot. If the Debezium connector is down or stuck, its slot's confirmed_flush_lsn stops advancing and Postgres retains all WAL since that LSN. Check pg_replication_slots for retained WAL and drop abandoned slots.
  • Why does Debezium need a heartbeat on a low-traffic or filtered Postgres database?
    The slot only advances when Debezium acks LSNs. On a DB with little traffic or where most changes are filtered out, the slot can lag and retain WAL. Heartbeat events force periodic LSN acknowledgement so the slot advances and WAL is released.
  • What does max_slot_wal_keep_size do and what's the trade-off?
    It caps how much WAL a slot can retain (PG 13+), protecting disk space. The trade-off: if exceeded, Postgres invalidates the slot, and the connector must re-snapshot to recover — bounded disk risk traded for a potential re-snapshot.

saying these in an interview costs you the question

  • Saying Debezium reads the physical WAL directly without logical decoding/an output plugin.
  • Claiming pgoutput requires installing an extension (it's built into PG 10+).
  • Forgetting that an idle/abandoned replication slot can fill the disk.
  • Saying wal_level=replica is sufficient (logical decoding needs wal_level=logical).
  • Assuming logical slots automatically survive a pre-PG16 failover.

context