PostgreSQL 19 builds on that foundation with retry-aware manual synchronization and clearer visibility into skipped slot sync attempts. In this post, we look at how these capabilities work and how contributions from the Fujitsu PostgreSQL development team help make logical replication failover more reliable and easier to operate

Making logical replication failover-ready
Logical replication in PostgreSQL depends on replication slots to track subscriber progress and retain the WAL those subscribers need. Before PostgreSQL 17, these slots existed only on the primary server. When a standby was promoted after a failure or during a planned switchover, the new primary had no record of how far subscribers had progressed, leading to service downtime and often a full, time-consuming resynchronization of subscriber data.
PostgreSQL 17 addressed this with a complete failover slot synchronization feature: a new failover option for logical slots, required standby configuration, and two synchronization methods - manual and automatic. PostgreSQL 19 also introduced statistics to track skipped slot synchronizations. The manual synchronization function was also enhanced with retry logic to help bring slots to a persistent state when they are not immediately ready. This post describes both the features and the enhancements, contributed by the Fujitsu PostgreSQL development team.
How PostgreSQL evolved logical slot support on standbys
Before looking at failover slot synchronization itself, it helps to understand the role logical replication slots play in PostgreSQL and how standby support has evolved over time.
What logical replication slots do
A logical replication slot is a publisher-side server object that tracks the progress of a subscriber or decoding client. For each slot, PostgreSQL retains the WAL segments and catalog state needed for that client to resume decoding from exactly where it left off, even across restarts. The slot's restart_lsn marks the oldest WAL position retained for the replication slot. WAL older than restart_lsn is eligible for recycling, while WAL from restart_lsn onward is retained.
Evolution of logical slot support on standbys
Support for logical replication slots on standbys did not arrive all at once. The table below traces how it evolved release by release:
| PostgreSQL version | Logical slot support on standby |
| PostgreSQL 14 and PostgreSQL 15 | Logical slots could not be created on standby at all. |
| PostgreSQL 16 | Logical slots could be manually created on a standby but would not be synchronized from the primary. |
| PostgreSQL 17 and PostgreSQL 18 | Logical slots can exist on a standby and can be automatically or manually synchronized from the primary, using the new failover option, required standby configuration, and the two synchronization methods described below. |
| PostgreSQL 19 | pg_sync_replication_slots() gained retry logic and availability classification for slots not yet ready due to missing WAL. pg_stat_replication_slots and pg_replication_slots also gained columns tracking how often, when, and why synchronization was skipped. Both enhancements are covered later in this post. |
The challenge before PostgreSQL 17
Prior to PostgreSQL 17, logical replication slots existed only on the physical streaming primary server and not on the physical streaming standby by default. A physical standby could be promoted after a failure, but the logical slots needed for logical replication were not present on it. After promotion, the new primary had no record of how far subscribers had progressed, which WAL to retain for them, or what catalog metadata they needed.
A switchover prior to PostgreSQL 17 required the following sequence:
1 Stop the primary node
2 Promote the standby
3 Disable all subscriptions on the subscriber side
4 Truncate all tables (if using copy_data=true)
5 Drop and re-create all subscriptions
When subscriptions are re-created with the default copy_data=true setting, this triggers a full initial data copy - potentially GB, TB, or PB of data - causing significant service downtime. Using copy_data=false avoids the resync if the subscriber data is already consistent but requires manual verification. The pg_createsubscriber tool, introduced in PostgreSQL 17, automates this by using physical replication to set up the subscriber and then switching to logical replication with copy_data=false, avoiding a full data copy. Regardless of the approach, the logical slot positions are lost and subscriptions must be re-created from scratch.
Failover slot synchronization in PostgreSQL 17
PostgreSQL 17 introduced native support for synchronizing logical replication slots to standby servers. The feature has several components that work together: a failover option on slots, standby configuration prerequisites, two methods for synchronization (manual and automatic), and new metadata in pg_replication_slots to track synced slots.
The following diagram illustrates the setup. On the left, normal operation: the primary holds logical replication slots with failover = true, and the standby continuously mirrors them. On the right, after failover: the subscriber reconnects to the promoted standby, which already holds the slot at the correct WAL position - no data resync is needed:
The standby continuously mirrors failover slots from the primary
Slot position already preserved - no data resync needed
The failover option
A new failover option was added to logical replication slots. When set to true, the slot becomes eligible for synchronization with hot standbys. By default, failover is false, and it is always false for physical slots.
The failover option can be set when a subscription is created:
-- Set failover at subscription creation CREATE SUBSCRIPTION sub1 CONNECTION '...' PUBLICATION pub1 WITH (failover = true);
It can also be set directly on a slot via the low-level SQL API,
-- Set failover at slot creation SELECT pg_create_logical_replication_slot( slot_name => 'my_slot', plugin => 'test_decoding', failover => true );
It can also be changed on an existing subscription using ALTER SUBSCRIPTION (the subscription must be disabled first):
ALTER SUBSCRIPTION sub1 DISABLE; ALTER SUBSCRIPTION sub1 SET (failover = true); ALTER SUBSCRIPTION sub1 ENABLE;
Required standby configuration
For slot synchronization to work, the standby must have the following configuration in place:
- primary_slot_name - a physical replication slot name must be set
- hot_standby_feedback = on
- A valid dbname must be specified in primary_conninfo
-- postgresql.conf on the standby primary_slot_name = 'standby_1' hot_standby_feedback = on primary_conninfo = 'user=repl_user host=primary port=5432 dbname=postgres ...'
Synchronization methods
Regardless of which synchronization method is used, it is highly recommended to configure the synchronized_standby_slots GUC on the primary with the names of physical standby slots. When set, the walsender on the primary will ensure all listed standby nodes have received and flushed changes before sending them to subscribers. This provides a guarantee that the promoted standby will not be behind any subscriber, whether slots are synced manually or automatically.
Manual synchronization: pg_sync_replication_slots()
The SQL function pg_sync_replication_slots() can be called on the hot standby to perform a single synchronization cycle. When called, the function:
- Connects to the primary using primary_conninfo
- Fetches all logical slots from the primary that have failover = true
- Drops any previously synced slots on the standby that no longer exist on the primary
- Creates new synced slot copies for slots that are missing on the standby
- Updates existing synced slots whose state has advanced on the primary
Manual synchronization is useful when automatic synchronization is not enabled or for one-off operations. Note that calling this function is not allowed while the automatic slotsync process is running.
Automatic synchronization: the slotsync process
To eliminate the need for manual intervention entirely, PostgreSQL 17 also introduced a dedicated process - the slotsync process - controlled by a new GUC parameter, sync_replication_slots. When enabled, the process runs an ongoing synchronization loop without any external trigger, performing the same connect-and-sync steps as pg_sync_replication_slots() described above. The slotsync process cannot run at the same time as the manual SQL function.
To enable automatic synchronization, add the following to the standby configuration:
-- postgresql.conf on the standby (PostgreSQL 17+) primary_conninfo = 'host=primary port=5432 dbname=postgres ...' primary_slot_name = 'standby_1' hot_standby_feedback = on sync_replication_slots = on -- new in PostgreSQL 17
Beyond the shared sync steps, the slotsync process adds:
- Adaptive sleep intervals: after a cycle that found changes, the process sleeps briefly (starting at 200 ms); when no changes are found it backs off up to 30 seconds
- WAL boundary handling: slots whose required WAL is not yet available are kept as temporary and retried automatically in the next cycle; a log message is emitted each time
When the standby is promoted to primary, the slotsync process exits cleanly so that transient synchronization state is not carried forward into the new primary role.
The following flowchart shows the slotsync process cycle:
Automatic sync: With sync_replication_slots = on, slot synchronization is an autonomous background process on the standby. Administrators no longer need cron jobs, external orchestration, or manual calls to keep failover slots in sync.
WAL availability and temporary slots
A slot cannot be marked as sync-ready if the standby's oldest retained WAL LSN has already advanced past that slot's restart_lsn; the WAL history needed for logical decoding is simply gone. In both the manual and automatic synchronization paths, such slots are kept as temporary on the standby and retried rather than silently dropped. A log message is emitted when this occurs. These temporary slots are automatically dropped when the standby is promoted.
Properties of synced slots
When a slot is successfully synchronized to the standby, it appears in pg_replication_slots. PostgreSQL 17 added two new columns to this view: failover and synced:
postgres=# SELECT slot_name, plugin, failover -- NEW in PG17 restart_lsn, synced -- NEW in PG17 temporary, active FROM pg_replication_slots WHERE synced = true; slot_name | plugin | failover | restart_lsn | synced | temporary | active --------------+---------------+----------+-------------+--------+-----------+------- logical_slot | test_decoding | t | 0/15C3958 | t | f | f
Synced slots have the following characteristics:
- Read-only on standby: Synced slots cannot be dropped or consumed from the standby; any logical decoding attempt results in an error
- Invalidation propagation: If a slot is invalidated on the primary, it is also invalidated on the standby
- Re-creation after invalidation: If a slot becomes valid on the primary after being invalidated on the standby, the standby's copy of the slot is dropped, then re-created and synchronized during the next synchronization cycle
- Temporary until sync-ready: Any slots that are still temporary because they have not become sync-ready by the time the standby is promoted are automatically dropped during promotion
- Behaviour after promotion: After promotion, synced slots behave as regular logical replication slots, and the failover flag remains true so they can be synced to the new primary's standby
Verifying sync readiness before failover
Before performing a failover or switchover, administrators should verify that all required slots are sync-ready on the standby. See the PostgreSQL documentation on logical replication failover for further details.
Limitations
- No cascading standby support
A failover-enabled logical slot can only be created and synced on a standby that connects directly to the true primary. A standby cannot create such a slot or pass a synced slot onward to its own downstream standbys, so multi-hop cascading standby topologies are not supported. - Logical slots only
Only logical replication slots can be synchronized. Physical replication slots are not supported.
The PostgreSQL 19 enhancement: Retry-aware pg_sync_replication_slots()
The initial implementation of pg_sync_replication_slots() in PostgreSQL 17 had a practical limitation: slots whose required WAL was not yet available on the standby were simply left as temporary and not persisted as ready. The function returned successfully but gave no indication of which slots were in this state and provided no retry. Callers had to implement their own retry loops, polling until all slots were ready, and still had to determine separately whether an unsynchronized slot was recoverable or permanently lost.
The gap: The sync function put the retry and error-classification logic onto the caller. Even a carefully written call could not reliably complete synchronization, and it required additional work outside PostgreSQL.
Design approach
Rather than requiring every caller to implement their own retry and error-classification logic, we moved that intelligence into pg_sync_replication_slots() itself. The function was redesigned to distinguish between slots that are not yet sync-ready (due to missing WAL) and those which just need to be updated.
How the enhanced function works
A single call to pg_sync_replication_slots() in PostgreSQL 19 processes all slots that can potentially become valid, continuing until each one is either synchronized or determined to be unresolvable. Slots that permanently lack the necessary WAL do not persist. The function fetches the eligible slots from the primary once, at the start of the call, and then repeatedly attempts to persist that same set of slots; it does not re-fetch the slot list from the primary on each attempt.
When WAL on the standby is not yet sufficient to mark a slot as ready, the function logs the reason and retries, repeating until the primary's slot has advanced far enough that synchronization can proceed. For slots that are present but lagging in metadata, the function updates restart_lsn, confirmed_flush_lsn, and catalog_xmin to bring them in line with the primary.
Warning: if a slot on the primary never advances, the SQL function could loop indefinitely and log the waiting message continuously. In such a situation, the operation can be cancelled with Ctrl+C.
-- Before PostgreSQL 19: callers needed external retry logic LOOP SELECT pg_sync_replication_slots(); -- check if all slots sync-ready (i.e persisted); sleep and retry if not END LOOP;
In PostgreSQL 19, the same goal is achieved with a single call:
-- PostgreSQL 19: a single call handles retries internally SELECT pg_sync_replication_slots(); -- returns after all slots fetched from the primary at the start of
-- synchronization have been successfully persisted and synchronized. -- If a slot cannot be persisted on the first attempt, the reason is
-- logged while synchronization is retried.
Gap closed: The sync function now handles retries and classifies slot availability internally. Callers no longer need wrapper logic, and the function correctly handles slots that have not yet advanced the restart_lsn past the WAL that is now available on the standby.
This enhancement is particularly valuable in rolling upgrade and catchup scenarios, where a new standby is created, and some slots on the primary still require WAL that has already been flushed on the standby. Previously that lag would silently leave slots unsynchronized; now the function waits it out.
Tracking skipped synchronizations
PostgreSQL 19 also made skipped synchronizations directly observable, regardless of whether pg_sync_replication_slots() or the automatic slotsync process performed the skip. The pg_stat_replication_slots view gained two new columns: slotsync_skip_count, a running count of how many times synchronization for that slot has been skipped, and slotsync_last_skip, the timestamp of the most recent skip.
The pg_replication_slots view gained a complementary slotsync_skip_reason column, recording why the last synchronization attempt for that slot was skipped. The column is NULL once synchronization succeeds; otherwise it holds one of four reasons:
- wal_not_flushed - the standby has not yet flushed the WAL corresponding to the remote slots confirmed_flush_lsn
- wal_or_rows_removed - The remote slot is far enough behind that the required WAL, or the rows needed to build its snapshot, may already be removed or are at risk of being removed on the standby.
- no_consistent_snapshot - The standby could not yet build a consistent snapshot for the slot
- slot_invalidated - The standby slot is invalidated because the corresponding primary slot was invalidated, or the standby slot itself became invalid.
Together, these three columns let administrators see, for any synced slot, whether it is skipping synchronization, how often, and why - directly from SQL, without reading log files:
-- NEW in PostgreSQL 19: slotsync_skip_reason, slotsync_skip_count, slotsync_last_skip SELECT r.slot_name, r.slotsync_skip_reason, s.slotsync_skip_count, s.slotsync_last_skip FROM pg_replication_slots r JOIN pg_stat_replication_slots s USING (slot_name) WHERE r.synced = true; slot_name | slotsync_skip_reason | slotsync_skip_count | slotsync_last_skip -----------+----------------------+---------------------+------------------------------- sub | slot_invalidated | 6 | 2026-07-21 17:52:26.101884+10 my_slot | slot_invalidated | 6 | 2026-07-21 17:52:26.101892+10 (2 rows)
Key takeaways
Together, the PostgreSQL 17 failover slot synchronization feature and the PostgreSQL 19 enhancement to the manual sync function substantially advance PostgreSQL's high-availability story for logical replication:
- Reduced operational burden. Standby administrators no longer need external tooling to maintain failover slot readiness. Enabling sync_replication_slots is sufficient for continuous automatic synchronization.
- Improved failover confidence. When a standby needs to be promoted, failover slots are kept continuously synchronized, making logical replication consumers recoverable without manual intervention.
- Clearer error semantics. Operators can now distinguish between a slot that needs more time and one that is permanently lost, without writing their own diagnostic queries.
- Composable with manual workflows. The enhancements to pg_sync_replication_slots() benefit any environment where the function is called, and benefits customers not using the dedicated slotsync worker.
Failover slot synchronization has progressed from a missing feature to a well-automated, correctly classified, and reliably retried capability. PostgreSQL 17 introduced the complete failover slot synchronization feature - the failover option, required standby configuration, both a manual SQL function and an automatic slotsync process - dramatically simplifying failover and switchover for logical replication deployments. PostgreSQL 19 removed the reliability gap in the manual path by embedding retry and availability classification into the sync function itself.
These features were designed and contributed by the Fujitsu PostgreSQL development team as part of our ongoing investment in high-availability and logical replication infrastructure for the PostgreSQL community.
References
Commits
- Commit that introduced failover logical slots and the slotsync process in PostgreSQL 17: https://github.com/postgres/postgres/commit/93db6cbda037f1be9544932bd9a785dabf3ff712
- Commit that added retry logic to pg_sync_replication_slots() in PostgreSQL 19 : https://github.com/postgres/postgres/commit/0d2d4a0ec3eca64e7f5ce7f7630b56a561b2663c
PostgreSQL documentation
- PostgreSQL documentation on Replication Slot Synchronization: https://www.postgresql.org/docs/current/logicaldecoding-explanation.html#LOGICALDECODING-REPLICATION-SLOTS-SYNCHRONIZATION
- PostgreSQL documentation on Logical Replication Failover: https://www.postgresql.org/docs/current/logical-replication-failover.html
Blogs
- Failover Logical Slots - Ensuring High Availability of Logical replication in PostgreSQL 17: https://www.postgresql.fastware.com/blog/failover-logical-slots-ensuring-high-availability-of-logical-replication-in-postgresql-17




