From 16b7ad82a521043f580bc1a1a9a60995f8ca3b57 Mon Sep 17 00:00:00 2001
From: Vadim Ponomarev <vbponomarev@gmail.com>
Date: Sat, 15 Aug 2026 12:08:13 +0300
Subject: [PATCH v2 1/5] Wake the released sync-rep waiters after the queue
 lock is down

SyncRepWakeQueue() sets each released backend's latch while holding
SyncRepLock exclusively.  A latch is a kill() syscall whenever its proc is
asleep, and at a high commit rate the walsender runs one of them per
released commit inside the very section every committer lines up on.
ProcArrayGroupClearXid() already wakes its batch only after ProcArrayLock
is down, for the same reason.

Collect the released procs into a list instead, and set their latches once
the lock is released.  The unlink, the write barrier and the state store
stay under the lock: a waiter reads syncRepState without the lock and must
never find itself completed while still on the queue.  Nothing in the
deferred loop can error out, so a released proc cannot be left completed
but unlatched short of the process dying outright -- a window the in-lock
SetLatch had as well.  A proc that noticed its state on its own and moved
on, even into a new wait, gets a spurious latch set, which every latch
sleeper tolerates.

The list is sized to MaxBackends and allocated once per releasing process.
A proc waits in at most one queue, so one list of that size bounds a walk
over all three.
---
 src/backend/replication/syncrep.c | 72 ++++++++++++++++++++++++++++---
 1 file changed, 65 insertions(+), 7 deletions(-)

diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c
index d870f09e0a0..2824cc0e36f 100644
--- a/src/backend/replication/syncrep.c
+++ b/src/backend/replication/syncrep.c
@@ -84,6 +84,7 @@
 #include "storage/proc.h"
 #include "tcop/tcopprot.h"
 #include "utils/guc_hooks.h"
+#include "utils/memutils.h"
 #include "utils/ps_status.h"
 #include "utils/wait_event.h"
 
@@ -98,8 +99,16 @@ static bool announce_next_takeover = true;
 SyncRepConfigData *SyncRepConfig = NULL;
 static int	SyncRepWaitMode = SYNC_REP_NO_WAIT;
 
+static struct
+{
+	Latch	  **arr;
+	int			n;
+}			SyncRepWakeList = {NULL, 0};
+
 static void SyncRepQueueInsert(int mode);
 static void SyncRepCancelWait(void);
+static void SyncRepInitWakeList(void);
+static void SyncRepWakeFromList(void);
 static int	SyncRepWakeQueue(bool all, int mode);
 
 static bool SyncRepGetSyncRecPtr(XLogRecPtr *writePtr,
@@ -512,6 +521,8 @@ SyncRepReleaseWaiters(void)
 	 * We're a potential sync standby. Release waiters if there are enough
 	 * sync standbys and we are considered as sync.
 	 */
+	SyncRepInitWakeList();
+
 	LWLockAcquire(SyncRepLock, LW_EXCLUSIVE);
 
 	/*
@@ -575,6 +586,9 @@ SyncRepReleaseWaiters(void)
 
 	LWLockRelease(SyncRepLock);
 
+	/* wake the released backends now that the lock is down */
+	SyncRepWakeFromList();
+
 	elog(DEBUG3, "released %d procs up to write %X/%08X, %d procs up to flush %X/%08X, %d procs up to apply %X/%08X",
 		 numwrite, LSN_FORMAT_ARGS(writePtr),
 		 numflush, LSN_FORMAT_ARGS(flushPtr),
@@ -902,13 +916,53 @@ SyncRepGetStandbyPriority(void)
 	return (SyncRepConfig->syncrep_method == SYNC_REP_PRIORITY) ? priority : 1;
 }
 
+/*
+ * Initialize the list a release collects the procs to latch into, allocating it
+ * the first time this process releases anybody.  It is sized for every
+ * backend to be waiting at once; a proc waits in at most one queue, so one
+ * list of that size is enough for a pass over all three.
+ */
+static void
+SyncRepInitWakeList(void)
+{
+	if (SyncRepWakeList.arr == NULL)
+		SyncRepWakeList.arr = (Latch **)
+			MemoryContextAlloc(TopMemoryContext,
+							   MaxBackends * sizeof(Latch *));
+}
+
+/*
+ * Set all latches queued to be set.
+ */
+static void
+SyncRepWakeFromList(void)
+{
+	/*
+	 * Wake the released backends now that the lock is down.  Each latch is a
+	 * kill() for a sleeping proc, and running one per released commit inside
+	 * the exclusive section makes every committer wait for those syscalls.
+	 * The procs below are off the queue with their state already complete, so
+	 * nothing here needs the lock's protection.  Nothing here can error out
+	 * either: only this process dying outright could leave a released proc
+	 * completed but unlatched, and the in-lock SetLatch had that same window.
+	 * A proc that noticed its state on its own and moved on, even into a new
+	 * wait, gets a spurious latch set, which every latch sleeper tolerates.
+	 */
+	while (SyncRepWakeList.n > 0)
+		SetLatch(SyncRepWakeList.arr[--SyncRepWakeList.n]);
+}
+
 /*
  * Walk the specified queue from head.  Set the state of any backends that
- * need to be woken, remove them from the queue, and then wake them.
- * Pass all = true to wake whole queue; otherwise, just wake up to
+ * need to be woken and remove them from the queue; the proc's latches to
+ * wake are appended to static wakelist to latch once the lock is down.
+ * Pass all = true to release the whole queue; otherwise, just release up to
  * the walsender's LSN.
  *
- * The caller must hold SyncRepLock in exclusive mode.
+ * The caller must hold SyncRepLock in exclusive mode, and must set the
+ * latches with SyncRepWakeFromList after releasing it.  Unlink, barrier and
+ * state stay together in here: a waiter reads syncRepState without the lock
+ * and must never find itself completed while still on the queue.
  */
 static int
 SyncRepWakeQueue(bool all, int mode)
@@ -948,10 +1002,9 @@ SyncRepWakeQueue(bool all, int mode)
 		 */
 		proc->syncRepState = SYNC_REP_WAIT_COMPLETE;
 
-		/*
-		 * Wake only when we have set state and removed from queue.
-		 */
-		SetLatch(&(proc->procLatch));
+		/* the list is sized to every process that can ever queue here */
+		Assert(SyncRepWakeList.n < MaxBackends);
+		SyncRepWakeList.arr[SyncRepWakeList.n++] = &proc->procLatch;
 
 		numprocs++;
 	}
@@ -974,6 +1027,8 @@ SyncRepUpdateSyncStandbysDefined(void)
 	if (sync_standbys_defined !=
 		((WalSndCtl->sync_standbys_status & SYNC_STANDBY_DEFINED) != 0))
 	{
+		SyncRepInitWakeList();
+
 		LWLockAcquire(SyncRepLock, LW_EXCLUSIVE);
 
 		/*
@@ -1000,6 +1055,9 @@ SyncRepUpdateSyncStandbysDefined(void)
 			(sync_standbys_defined ? SYNC_STANDBY_DEFINED : 0);
 
 		LWLockRelease(SyncRepLock);
+
+		/* wake the released backends now that the lock is down */
+		SyncRepWakeFromList();
 	}
 	else if ((WalSndCtl->sync_standbys_status & SYNC_STANDBY_INIT) == 0)
 	{
-- 
2.43.0

