Configuring Failover Slots in PostgreSQL 17

PostgreSQL 17 turns a logical replication slot into an object that can survive a primary promotion by synchronizing it to standbys, and configuring it…

PostgreSQL 17 turns a logical replication slot into an object that can survive a primary promotion by synchronizing it to standbys, and configuring it correctly means aligning four things: failover = true at slot or subscription creation, sync_replication_slots = on, standby_slot_names on the primary, and hot-standby feedback on the standby. Miss any one and the slot either never syncs or syncs to a position the standby cannot honor, so this guide — part of Failover & Slot Recovery — specifies the exact settings, the pg_sync_replication_slots() mechanics, and the verification queries that prove a slot will actually fail over.

PostgreSQL 17 failover-slot synchronization A primary holds a logical slot created with failover true. A synchronization worker enabled by sync_replication_slots copies the slot's restart_lsn and confirmed_flush_lsn to a standby, where the slot shows synced true and hot_standby_feedback is on. After promotion the slot is already present on the new primary, so the consumer resumes from confirmed_flush_lsn without a resnapshot. A note below explains that standby_slot_names stops the primary advancing a physical standby past the logical slot's needs, keeping the synced slot resumable. Primary slot: failover = true wal_sender streams restart_lsn advances Standby slot: synced = true hot_standby_feedback = on streams named phys. slot After promotion slot already present resume, no resnapshot from confirmed_flush sync_replication_slots copies restart / flush LSN promote standby_slot_names = 'standby1' the primary must not advance a physical standby past the logical slot's needs, so the synced slot stays inside retained WAL and is resumable after promotion
In PostgreSQL 17 a failover = true slot is copied to the standby by the sync_replication_slots worker; standby_slot_names keeps the synced slot's WAL retained so the promoted node resumes without a resnapshot.

GUC and Attribute Semantics

Failover slots are governed by one slot attribute and three server parameters that must be consistent across the primary and its standbys. The attribute decides whether a slot is a candidate for synchronization; the GUCs decide whether the copy actually happens and stays valid. The table maps each to its guarantee and its failure mode.

Setting Where Value Guarantee / logical-replication behavior
failover (slot / subscription) slot object true Marks the slot for synchronization to standbys; a false slot is never copied and is lost on promotion
sync_replication_slots standby on Runs the background slot-sync worker that pulls failover slots from the primary on an interval
standby_slot_names primary physical slot name(s) Primary refuses to send logical changes past what the named physical standbys have flushed, so the synced slot never references recycled WAL
hot_standby_feedback standby on Stops the primary vacuuming catalog rows the synced slot’s catalog_xmin still needs; without it a synced slot can be invalidated
sync_replication_slots interval standby internal Sync is periodic, not per-commit; a manual pg_sync_replication_slots() forces an immediate copy before promotion

The critical interaction is between standby_slot_names and the synced slot’s restart_lsn. A synchronized slot copies the primary’s restart_lsn, but that LSN is only usable on the standby if the standby actually holds the WAL from that point forward. standby_slot_names enforces exactly that ordering: the primary will not let a logical consumer’s changes — or the WAL trimming that follows — outrun the named physical standby, so the standby is always at or ahead of every synced logical slot’s needs. Leave it unset and the standby can recycle WAL the synced slot references, producing a slot that shows synced = true but is invalidated the moment you promote. The durability semantics of the slot itself, independent of failover, are catalogued in replication slot types.

Synchronization is periodic, not transactional, so the synced slot’s confirmed_flush_lsn on the standby always trails the primary’s by one sync interval plus whatever the physical stream lags. That trailing gap is normal and safe — after promotion the consumer simply replays the small window between the standby’s copied position and the true last-applied position, which an idempotent apply absorbs. What is not safe is a gap that grows without bound: if the sync worker stalls (for example because the standby fell out of standby_slot_names eligibility), the synced position freezes while the primary advances, and a promotion then forces a large replay or a resnapshot. Monitor the byte distance between the primary’s slot position and the standby’s synced copy the same way you monitor physical lag, and alert when it stops shrinking.

Diagnostic Patterns

Verify configuration on both nodes, because the primary and standby each hold half the mechanism. On the primary, confirm every production slot is a failover candidate and the physical target is named:

sql
-- Primary: which logical slots will fail over, and is standby_slot_names set?
SELECT slot_name, failover, active, wal_status
FROM pg_replication_slots
WHERE slot_type = 'logical';
SHOW standby_slot_names;              -- must name the physical slot the failover-target standby uses

On the standby, confirm the sync worker actually copied the slot and that it is reserved, not lost. synced = true with wal_status = 'reserved' is the healthy state; synced = true with wal_status = 'lost' means WAL retention failed and the slot will not survive promotion:

sql
-- Standby: has the slot been synchronized and is its WAL still retained?
-- ALERT: synced = true but wal_status <> 'reserved' -> standby_slot_names/feedback misconfigured.
SELECT slot_name, synced, temporary, wal_status,
       restart_lsn, confirmed_flush_lsn
FROM pg_replication_slots
WHERE slot_type = 'logical';

Check that hot-standby feedback is reaching the primary, since a synced slot’s catalog xmin depends on it:

sql
-- Primary: is the standby sending feedback that protects the synced slot's catalog rows?
-- ALERT: backend_xmin NULL for the failover-target standby -> hot_standby_feedback likely off.
SELECT application_name, state, backend_xmin, sync_state
FROM pg_stat_replication;

Safe Deployment Sequence

Enable failover slots without disrupting the running stream by applying the reload-only settings first and adding the failover attribute deliberately.

1. Prepare the standby. Set hot_standby_feedback = on and sync_replication_slots = on, then reload. Both take effect without a restart.

sql
-- On the standby.
ALTER SYSTEM SET hot_standby_feedback = 'on';
ALTER SYSTEM SET sync_replication_slots = 'on';
SELECT pg_reload_conf();

2. Retain WAL for the physical standby on the primary. Point standby_slot_names at the physical slot the failover-target standby streams through. Until this is set, do not treat any synced slot as recoverable.

sql
-- On the primary. 'standby1' is the physical slot the failover target uses.
ALTER SYSTEM SET standby_slot_names = 'standby1';
SELECT pg_reload_conf();

3. Make each slot a failover candidate. New slots take failover = true at creation. Existing slots created without it must be recreated, because failover cannot be toggled on a live slot for a native subscription — recreate the subscription with the attribute, accepting the initial copy if needed.

sql
-- New failover-enabled slot, created explicitly:
SELECT pg_create_logical_replication_slot('cdc_orders', 'pgoutput', false, false, true);
--                                          name         plugin      temp   twophase failover

-- Or via a native subscription that requests failover on its own slot:
CREATE SUBSCRIPTION sub_orders
  CONNECTION 'host=pg-primary dbname=analytics application_name=sub_orders'
  PUBLICATION pub_orders
  WITH (failover = true, copy_data = true, create_slot = true);

4. Force a sync and verify before trusting it. Synchronization is periodic, so trigger an immediate copy and confirm the standby shows the slot reserved.

sql
-- On the standby: force an immediate copy, then verify.
SELECT pg_sync_replication_slots();
SELECT slot_name, synced, wal_status FROM pg_replication_slots WHERE slot_type = 'logical';
-- Expect: synced = true, wal_status = 'reserved'.

5. Revert cleanly if needed. To back out, disable the sync worker on the standby and unset the retention setting; the failover attribute stays on existing slots but simply stops being synchronized. None of these require a restart.

sql
-- Revert (standby then primary):
ALTER SYSTEM SET sync_replication_slots = 'off';  SELECT pg_reload_conf();  -- standby
ALTER SYSTEM RESET standby_slot_names;            SELECT pg_reload_conf();  -- primary

Pipeline Integration

Failover slots change how a consumer treats a promotion, so encode that in the reconnect logic. A consumer reading a failover = true slot can, after a promotion, reconnect to the new primary and find its slot already present at confirmed_flush_lsn, so the correct behavior is to resume — not to recreate a slot from scratch. Guard against the misconfiguration where the slot exists but is lost by checking wal_status on reconnect and falling back to the resnapshot path only then, the decision made explicit in recovering logical slots after primary failover.

python
# On reconnect after a promotion: trust a synced, reserved slot; resnapshot only if lost.
def recover_slot(conn, slot="cdc_orders"):
    row = conn.execute(
        "SELECT synced, wal_status FROM pg_replication_slots WHERE slot_name = %s",
        (slot,),
    ).fetchone()
    if row is None or row["wal_status"] == "lost":
        return "resnapshot"        # slot absent or invalidated -> rebuild
    return "resume"                # synced + reserved -> resume from confirmed_flush_lsn

Export synced and wal_status per slot into the same metrics stream the rest of the topology uses, and alert when a production slot reports failover = false or a synced slot leaves reserved — those are the two conditions that quietly re-introduce resnapshot risk. A Debezium connector benefits identically: with a failover-enabled slot present on the promoted node, it resumes from its committed Kafka offset instead of triggering a fresh snapshot.

Two deployment shapes deserve explicit handling. With multiple standbys, list every physical slot the failover targets stream through in standby_slot_names, not just the preferred one, so the synced slot stays resumable regardless of which standby the orchestrator promotes. With a cascading standby (a standby that streams from another standby rather than the primary), slot synchronization follows the same chain, but each hop adds latency to the synced position — keep the failover targets streaming directly from the primary where recovery-time objectives are tight. In both cases, run the standby-side verification query on every candidate node, because a slot that is synced and reserved on one standby proves nothing about the node the orchestrator actually chooses.

Authoritative references