From f00165b0170e7ae20f3874144d5822a47378bbee Mon Sep 17 00:00:00 2001 From: Fabrice Chapuis Date: Fri, 7 Nov 2025 18:04:15 +0100 Subject: [PATCH] Resync logical replication slot after switchover When the former primary is started up after clean shutdown, it was refusing to synchronize the logical failover slots with the new primary because synced flag is false. The following error message is produced: "exiting from slot synchronization because same slot name already exists on the standby". If local slot has failover=true, it can be overwrite to be synced with the primary. This patch mainly change the drop_local_obsolete_slots() function and check that a local failover slot could be dropped. --- src/backend/replication/logical/slotsync.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c index 31d7cb3ca77c..873ed3c7979c 100644 --- a/src/backend/replication/logical/slotsync.c +++ b/src/backend/replication/logical/slotsync.c @@ -384,7 +384,7 @@ update_local_synced_slot(RemoteSlot *remote_slot, Oid remote_dbid, } /* - * Get the list of local logical slots that are synchronized from the + * Get the list of local logical slots that are synchronized or not with the * primary server. */ static List * @@ -399,7 +399,7 @@ get_local_synced_slots(void) ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i]; /* Check if it is a synchronized slot */ - if (s->in_use && s->data.synced) + if (s->in_use && (s->data.synced || s->data.failover)) { Assert(SlotIsLogical(s)); local_slots = lappend(local_slots, s); @@ -423,6 +423,7 @@ local_sync_slot_required(ReplicationSlot *local_slot, List *remote_slots) { bool remote_exists = false; bool locally_invalidated = false; + bool synced_slot = false; foreach_ptr(RemoteSlot, remote_slot, remote_slots) { @@ -438,13 +439,14 @@ local_sync_slot_required(ReplicationSlot *local_slot, List *remote_slots) locally_invalidated = (remote_slot->invalidated == RS_INVAL_NONE) && (local_slot->data.invalidated != RS_INVAL_NONE); + synced_slot = local_slot->data.synced; SpinLockRelease(&local_slot->mutex); break; } } - return (remote_exists && !locally_invalidated); + return (remote_exists && !locally_invalidated && synced_slot); } /* @@ -498,10 +500,12 @@ drop_local_obsolete_slots(List *remote_slot_list) * slot by the user. This new user-created slot may end up using * the same shared memory as that of 'local_slot'. Thus check if * local_slot is still the synced one before performing actual - * drop. + * drop. Yes, we actually check 'failover', not 'synced', because + * it could have been created on primary which is now a standby. */ + SpinLockAcquire(&local_slot->mutex); - synced_slot = local_slot->in_use && local_slot->data.synced; + synced_slot = local_slot->in_use && (local_slot->data.failover || local_slot->data.synced); SpinLockRelease(&local_slot->mutex); if (synced_slot)