If a CDC tool such as Debezium, AWS DMS, Fivetran or Airbyte reads from your PostgreSQL 14 database, it uses a logical replication slot. A major upgrade from 14 does not keep that slot. On a self-managed server, pg_upgrade drops it without an error or a warning. On RDS and Aurora, the upgrade refuses to start until you drop it yourself. Either way, the consumer has to restart from a new position, and changes written in between need a plan.
The lab for this page upgraded PostgreSQL 14.24 to 18.6 with pg_upgrade --link in Docker, with generated data. The script and full output are public.
What the lab showed
The PG14 cluster had wal_level = logical, a publication cdc_pub and a slot named like a Debezium slot:
slot_name | plugin | slot_type
-----------------+----------+-----------
debezium_orders | pgoutput | logicalpg_upgrade --check passed with the slot present. No line in the output mentions replication slots:
Checking for prepared transactions ok
Checking for contrib/isn with bigint-passing mismatch ok
Checking data type usage ok
Checking for not-null constraint inconsistencies ok
Checking for presence of required libraries ok
Checking database user is the install user ok
Checking for prepared transactions ok
Checking for new cluster tablespace directories ok
*Clusters are compatible*After the upgrade, on PostgreSQL 18 with wal_level = logical set again, the publication was there and the slot was not:
SELECT slot_name, plugin, slot_type FROM pg_replication_slots;
slot_name | plugin | slot_type
-----------+--------+-----------
(0 rows)
SELECT pubname FROM pg_publication;
pubname
---------
cdc_pubThat combination is easy to misread. The publication looks like the replication setup survived, and a connector that reconnects may simply create a new slot at the current WAL position and carry on. Anything committed between the old slot's last confirmed position and the new slot's creation never reaches the consumer.
Why: slots migrate only from PostgreSQL 17
PostgreSQL 17 taught pg_upgrade to carry logical slots across, but only when the old cluster is already 17 or later. The logical replication upgrade documentation says: "Logical slots on clusters before version 17.0 will silently be ignored." Subscriptions on a PG14 subscriber are treated the same way.
So the 14 → 18 jump loses the slots, and the next major upgrade from 18 can keep them if the conditions hold: wal_level = logical and enough max_replication_slots on the new cluster, the output plugin installed, and every slot fully consumed before the upgrade.
RDS and Aurora: the upgrade is blocked instead
Managed PostgreSQL behaves differently, which is safer but still needs a plan. The RDS major upgrade guide states that an upgrade can't occur while the database has logical replication slots, and that slots are typically used by AWS DMS and pipelines to data lakes and BI tools. You drop them, upgrade, and recreate them. AWS adds that on source version 17 and later, slots on non-replica instances can be kept.
Blue/Green deployments do not help here. For a major version upgrade they use logical replication themselves, and the blue database can't be a logical publisher or subscriber; the switchover guardrails check for it. The CDC slot has to go before switchover and be recreated on the new production database. AWS DMS tasks can't resume from the old checkpoint either. Details in RDS and Aurora 14 → 18 with Blue/Green.
Find every slot and who owns it
SELECT slot_name, plugin, database, active, active_pid,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal,
confirmed_flush_lsn
FROM pg_replication_slots
WHERE slot_type = 'logical';Match each slot to a consumer and a team. An inactive slot with a large retained_wal is worth investigating before any upgrade: it is holding WAL on disk, and nobody may know what it was for.
Plan the CDC restart
- Agree the gap strategy with the consumer's owner. Options are a fresh snapshot of the published tables after the upgrade, an incremental re-sync from an
updated_atcolumn, or stopping writes until the old slot is fully consumed and creating the new slot before writes resume. - Drain the old slot. Let the consumer catch up so
confirmed_flush_lsnreachespg_current_wal_lsn()while writes are stopped, then stop the connector. - Drop the slot (required on RDS and Aurora, implicit with self-managed
pg_upgrade):SELECT pg_drop_replication_slot('debezium_orders'); - Upgrade, set
wal_level = logical(on RDS,rds.logical_replication = 1in the new parameter group) and check publications. - Recreate the slot before writes resume, or let the connector create it, then run the agreed snapshot or reconciliation.
- Validate with row counts or checksums on a few busy tables at the destination against the source.
In Debezium the gap behavior depends on the connector's snapshot.mode and stored offsets; test the exact restart on the rehearsal copy rather than assuming it.
Checklist
- List logical slots, publications and subscriptions on every PG14 database.
- Name the owner and the downstream system for each slot.
- Decide snapshot, incremental re-sync or write freeze for the gap.
- Rehearse the drop, upgrade, recreate and connector restart on a copy.
- Reconcile row counts after cutover.
The rest of the sequence is in the PostgreSQL 14 upgrade checklist. For blockers that pg_upgrade --check does catch, see pg_upgrade --check failures and fixes.