PostgreSQL Logical Replication Failover: Keep CDC Running After Promotion
A production guide to PostgreSQL 17+ logical failover slots: configure slot synchronization, protect WAL, verify standby readiness, and recover CDC consumers safely after promotion.

Logical replication is often the last mile between a PostgreSQL database and the systems that depend on its changes: a Debezium connector feeding Kafka, a warehouse ingestion pipeline, a search index, or another service. Teams commonly protect the database with a streaming standby and assume that promoting it will preserve every downstream feed. That assumption is unsafe unless the logical replication slots themselves are prepared for failover.
PostgreSQL 17 introduced native synchronization of logical failover slots to a physical standby. PostgreSQL 18 continues to document and support the mechanism. It closes an important gap for change-data-capture (CDC) architectures, but it is not automatic merely because physical replication is healthy. You must opt in for the logical slots, configure the standby to synchronize them, ensure the standby has received the relevant WAL, and verify slot readiness before promotion.
This guide explains the failure mode, the PostgreSQL 17+ configuration, the checks to automate, and a practical failover runbook. It focuses on self-managed PostgreSQL; managed database services may expose only a subset of these settings or manage the topology differently.
1. The hidden failure mode: physical HA does not automatically preserve CDC
Physical streaming replication ships WAL from a primary to a standby so the standby can be promoted after a failure. Logical replication uses a logical replication slot to remember how far a consumer has decoded changes. The slot retains the WAL and catalog information needed for that consumer to continue.
Before native logical failover-slot synchronization, a primary failure could leave a promoted standby with the database data but without a usable copy of the logical slot that a CDC consumer depended on. The database could be available while Debezium or another logical decoding client could no longer resume from its previous position. Recreating a slot at the new primary is not a safe substitute: it can start at a later point and create a gap, or require a carefully planned re-snapshot and reconciliation.
ComponentWhat it protectsWhat it does not provePhysical standbyDatabase pages and WAL replayThat each logical consumer's slot is readyLogical replication slotConsumer decoding position and required resourcesThat the slot exists and is synchronized on the standbyFailover slot synchronizationCopies eligible logical slot state to a physical standbyThat synchronization has completed at the instant you promoteCDC connectorConsumes changes and tracks its own offsetsThat the new publisher endpoint and slot are usableThe key operational distinction is between configured and ready. PostgreSQL synchronizes failover slots asynchronously. A configuration file that enables synchronization is not evidence that the slot has reached a promotable state.
2. How native failover slots work
A logical slot can be marked with failover = true. The primary then treats it as eligible for synchronization to a physical standby. On the standby, the slotsync worker periodically copies eligible slot state when sync_replication_slots is enabled. The standby also needs a physical replication slot to protect the WAL it has not yet received, and hot_standby_feedback must be enabled so catalog rows needed by the logical slot are not prematurely removed.
There is a second, equally important ordering control: synchronized_standby_slots on the primary. It makes logical WAL senders wait until the named physical standby replication slots confirm receipt of WAL. Without that guard, a logical consumer could advance beyond the WAL durably received by the standby that you intend to promote. That would undermine continuity even if slot metadata synchronization is enabled.
The mechanism therefore has four parts:
- Eligible logical slots: created or altered with failover enabled.
- Standby slot synchronization:
sync_replication_slots = onon the hot standby. - Catalog and WAL protection: a physical replication slot, a valid
primary_conninfowith a database name, andhot_standby_feedback = onon the standby. - WAL receipt ordering: the primary lists the standby's physical slot in
synchronized_standby_slots.
3. Configure the primary and standby deliberately
First, confirm the primary is configured for logical decoding and has enough sender and slot capacity for both physical and logical consumers. Values below are examples, not universal sizing recommendations.
# Primary: postgresql.conf
wal_level = logical
max_wal_senders = 12
max_replication_slots = 12
# Name of the physical replication slot used by the standby.
# This setting must match the standby's primary_slot_name.
synchronized_standby_slots = 'ha_standby_1'
On the standby, configure the physical connection and enable slot synchronization. Keep credentials out of configuration committed to source control; use the service's protected configuration or secret mechanism.
# Standby: postgresql.conf
primary_conninfo = 'host=primary.internal port=5432 user=replicator dbname=postgres application_name=standby_1'
primary_slot_name = 'ha_standby_1'
hot_standby = on
hot_standby_feedback = on
sync_replication_slots = on
Restart or reload according to each parameter's context and the deployment's configuration-management process. Several of these settings are server configuration parameters rather than ordinary session settings. Validate the effective values on both nodes instead of assuming a config-management rollout succeeded.
Check the primary/standby physical replication relationship and ensure the physical slot exists. A physical slot is not just a label: it retains WAL while the standby is disconnected or behind. Monitor retained WAL and size the storage budget accordingly.
4. Enable failover for every logical consumer slot
For native PostgreSQL subscriptions, create the subscription with the failover option enabled:
CREATE SUBSCRIPTION analytics_sub
CONNECTION 'host=publisher.internal port=5432 dbname=app user=replicator'
PUBLICATION app_publication
WITH (
copy_data = false,
failover = true
);
copy_data = false is shown only to illustrate an already-seeded subscriber; do not copy it blindly into a new subscription. If initial table data has not been copied, choose the correct initial synchronization behavior for your rollout.
For a slot created outside a PostgreSQL subscription—for example, a Debezium logical decoding slot—create it with failover enabled. The SQL function supports a failover argument in current PostgreSQL versions:
SELECT *
FROM pg_create_logical_replication_slot(
'debezium_orders',
'pgoutput',
false,
false,
true
);
The positional arguments shown correspond to the function signature in current releases; verify the exact signature for the PostgreSQL major version you operate before using it in automation. If the slot already exists, use the supported slot-alteration mechanism for your version to enable failover rather than dropping and recreating a live CDC slot. A drop-and-recreate operation can lose the consumer's continuity point.
For a PostgreSQL subscription, keep the subscription's failover property and the publisher slot's failover property aligned. PostgreSQL documents that a mismatch can cause the slot to be synchronized even when the subscription says otherwise, or not synchronized when the subscription expects it.
5. Prove readiness with slot state, not assumptions
On the primary, list the persistent logical slots that are marked for failover:
SELECT slot_name, slot_type, database, active, failover, synced,
restart_lsn, confirmed_flush_lsn, wal_status, safe_wal_size
FROM pg_replication_slots
WHERE slot_type = 'logical'
ORDER BY slot_name;
On the standby, check that the expected logical slots exist and report synced = true:
SELECT slot_name, slot_type, database, failover, synced,
restart_lsn, confirmed_flush_lsn, wal_status
FROM pg_replication_slots
WHERE slot_type = 'logical'
ORDER BY slot_name;
Interpret the columns carefully. On a hot standby, a slot with synced = true is a synchronized copy from the primary. Such a slot cannot be used for logical decoding or manually dropped while the server remains a standby. After promotion, the slot becomes part of the new primary's operational state. Do not mistake synced = false on the primary for a failure; the column's meaning is specifically about a slot synchronized from another server.
For PostgreSQL subscriptions, identify the slots that should be present on the standby from the subscriber side:
SELECT subname, subslotname, subfailover
FROM pg_subscription
WHERE subfailover AND subslotname IS NOT NULL;
Also account for table synchronization slots while initial table copy is in progress. PostgreSQL's documented failover procedure includes checking those slots too. A runbook that verifies only the steady-state subscription slot can miss a table-copy operation that is still underway.
Automate these checks and alert when a required slot is absent, invalidated, or not synchronized. The promotion gate should fail closed if the required slot set is not ready. If you cannot prove that a subscriber's required slot has synchronized, do not claim seamless logical replication failover.
6. A controlled failover runbook
- Inventory consumers. List every logical slot, its owner, downstream connector, expected database, and recovery contact. Include non-PostgreSQL CDC clients such as Debezium.
- Check physical health. Confirm the candidate standby is streaming, has received and flushed the required WAL, and has no unexpected replay lag.
- Check slot synchronization. Compare the primary's failover-enabled slot inventory with the standby's synchronized slot inventory. Confirm
synced = truefor every required slot. - Check invalidation and capacity. Ensure required slots are not invalidated and the standby has enough disk, WAL, and connection capacity for the post-promotion workload.
- Fence the old primary. Use the HA controller or an established fencing procedure to prevent split brain. Never promote a standby while the former primary may still accept writes.
- Promote the standby. Follow the platform's promotion procedure only after the readiness gate passes.
- Repoint consumers. Update each CDC connector's publisher endpoint to the new primary. For native subscriptions, PostgreSQL documents altering the subscription connection; coordinate disable/alter/enable steps with the promotion procedure.
- Verify progress. Confirm each connector resumes from its existing position, observe LSN movement and downstream lag, and reconcile critical row counts or business invariants.
- Rebuild redundancy. Recreate or reconfigure the physical standby relationship and ensure slot synchronization is active for the new topology.
Do not run promotion commands from a generic shell snippet copied into a blog. Promotion is topology- and orchestration-specific. Your HA manager, cloud provider, or runbook must own leader election and fencing.
7. Failure modes and the trade-offs you are accepting
Slot synchronization is asynchronous
The standby can temporarily lag the primary's slot state. A failover immediately after a new slot is created or after a consumer advances may occur before the standby has synchronized the latest state. The mitigation is an explicit readiness check at promotion time, not a fixed sleep.
The physical slot can retain too much WAL
Replication slots protect required WAL and catalog rows, but they can also prevent cleanup. A disconnected standby or stalled logical consumer can cause retained WAL to grow. Alert on pg_replication_slots, WAL directory usage, slot age, and disk headroom. Set a retention policy appropriate to your recovery objectives and understand that a limit which invalidates a slot changes the recovery story: it is not a free disk-space safeguard.
Standby feedback can contribute to bloat
hot_standby_feedback helps preserve catalog rows required for synchronized slots, but feedback can delay cleanup of dead tuples on the primary. Monitor table and index bloat, vacuum progress, and long-running standby queries. This setting trades some cleanup freedom for replication safety.
The standby must be the right standby
synchronized_standby_slots is a list of physical replication slot names. If the listed slot is missing, invalidated, or not advancing, logical WAL senders can wait and logical replication can stall. This is intentional ordering protection, but it means a broken standby path can become backpressure on CDC consumers. Alert on both the standby's receive progress and the age of the oldest unconsumed logical change.
Managed services may limit control
Some managed PostgreSQL offerings abstract replication slots, expose parameter groups with restart requirements, or control promotion themselves. Check the provider's supported PostgreSQL major version and parameter availability. Do not assume that a setting documented by upstream PostgreSQL is user-configurable on every managed tier.
Failover slots do not replace consumer recovery design
Slot continuity is one part of the pipeline. A connector may still need a new endpoint, valid credentials, a healthy network route, and compatible offsets. Consumers must remain tolerant of retries and duplicate delivery. Validate the whole pipeline, not only the database view.
8. What to monitor in production
- Slot inventory: expected logical slot count, failover flag, synchronization state, invalidation reason, and slot activity.
- WAL retention: retained WAL bytes,
restart_lsnage, safe WAL size where supported, and free disk headroom. - Physical replication: standby receive/replay LSN, replay delay, physical slot activity, and connection health.
- CDC freshness: connector lag, last processed source LSN, queue depth, and age of the oldest unprocessed event.
- Promotion readiness: a continuously evaluated boolean that checks the full required slot inventory and the candidate standby's state.
- Post-failover recovery: time until every connector resumes, error rate, duplicate/replay volume, and downstream reconciliation results.
Keep dashboards and alerts tied to service objectives. For example, an alert on a slot's raw byte lag is less useful without knowing the expected write rate and remaining storage headroom. A promotion gate should be deterministic and machine-readable; a dashboard screenshot is not an operational control.
9. Test the failure, not just the configuration
A meaningful test should use a disposable environment with the same PostgreSQL major version, slot types, physical replication topology, and CDC connector version as production. Create a failover-enabled logical slot, stream changes, and verify that the standby synchronizes the slot. Then stop or isolate the primary using the same fencing and promotion path that operations will use during an incident.
After promotion, reconnect the consumer to the new primary and verify that it continues from its prior position. Compare source and sink checkpoints, validate representative records, and deliberately test the negative path: promote a standby before the required slot is synchronized and ensure the readiness gate blocks the operation. Also test a disconnected standby, slot invalidation, and storage pressure.
Record the exact time from failure detection to primary promotion, from promotion to slot readiness, and from promotion to CDC recovery. Those measurements reveal whether the database RTO and the downstream data-freshness objective can both be met.
Conclusion
PostgreSQL 17+ native logical failover slots make it possible to preserve logical replication continuity across a physical standby promotion, but the feature is a coordinated configuration—not a checkbox. The logical slots must be marked for failover, the standby must synchronize them, physical WAL receipt must be protected, and operators must verify the synchronized slot state before promotion.
For CDC-heavy systems, treat logical slot readiness as a first-class part of database HA. Put it in automated checks, alerts, and promotion gates; test the complete connector recovery path; and monitor the WAL-retention cost. Physical database availability is only the beginning. Your downstream systems are recovered when their consumers can safely resume from the new primary without gaps.
Further reading
Related articles

PostgreSQL Async I/O: How to Measure Before You Tune
A practical PostgreSQL 18 guide to asynchronous I/O: understand io_method, benchmark scan-heavy workloads, read pg_stat_io, and roll out tuning safely.
Read article
Python asyncio TaskGroups: Structured Concurrency That Survives Cancellation
A production-focused guide to Python 3.11+ asyncio TaskGroups: child-task ownership, sibling cancellation, ExceptionGroup handling, deadlines, and safe fan-out patterns.
Read article
Building Reliable FastAPI Services in Production: Timeouts, Retries, Idempotency, and Observability
A production-focused guide to making FastAPI services resilient with deadlines, bounded retries, idempotency, database safeguards, backpressure, and observability.
Read articleLet's talk
Have an idea? Let's figure out how to build it.
You don't need a technical specification or a perfectly defined plan. Tell us what you're trying to achieve, and we'll help identify the next steps.