From f4e17832ad6c3729f552189575767637e9fc2bd3 Mon Sep 17 00:00:00 2001
From: alterego665 <824662526@qq.com>
Date: Mon, 23 Jun 2025 21:48:51 +0800
Subject: [PATCH v4] Add threshold-based sleep to SnapBuildWaitSnapshot for hot
 standby

Optimize transaction waiting during logical decoding on hot standby servers.
On hot standby servers, XactLockTableWait falls back to polling
TransactionIdIsInProgress() with fixed 1ms sleeps when transactions from
the primary have no local lock table entries, causing excessive CPU usage.

Implement threshold-based sleep in SnapBuildWaitSnapshot: sleep 1ms for
the first 5 seconds to keep normal operations responsive, then switch to
1s sleeps to reduce CPU overhead during long waits.

This change only affects logical decoding on hot standby servers, with no
impact on primary server operations.
---
 src/backend/replication/logical/snapbuild.c | 40 ++++++++++++++++++++-
 src/backend/storage/lmgr/lmgr.c             |  2 --
 2 files changed, 39 insertions(+), 3 deletions(-)

diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index 0d7bddbe4ed..1c2d6795531 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -1443,7 +1443,45 @@ SnapBuildWaitSnapshot(xl_running_xacts *running, TransactionId cutoff)
 		if (TransactionIdFollows(xid, cutoff))
 			continue;
 
-		XactLockTableWait(xid, NULL, NULL, XLTW_None);
+		/*
+		 * Wait for the transaction to complete. On primary servers, we can
+		 * use XactLockTableWait which efficiently acquires ShareLocks on
+		 * the transaction. On hot standby servers, transactions from the
+		 * primary have no local lock table entries, causing XactLockTableWait
+		 * to fall back to polling TransactionIdIsInProgress() with fixed 1ms
+		 * sleeps. This can result in excessive CPU usage during long waits.
+		 *
+		 * To optimize the standby case, we implement threshold-based sleep:
+		 * sleep 1ms for the first 5 seconds to keep normal operations
+		 * responsive, then switch to 1s sleeps to reduce CPU overhead
+		 * during potentially long waits for primary server transactions.
+		 */
+		if (RecoveryInProgress())
+		{
+			int left_till_hibernate = 5000;
+
+			for (;;)
+			{
+				if (!TransactionIdIsInProgress(xid))
+					break;
+
+				CHECK_FOR_INTERRUPTS();
+
+				if (left_till_hibernate > 0)
+				{
+					pg_usleep(1000L);		/* 1ms */
+					left_till_hibernate--;
+				}
+				else
+				{
+					pg_usleep(1000000L);	/* 1s */
+				}
+			}
+		}
+		else
+		{
+			XactLockTableWait(xid, NULL, NULL, XLTW_None);
+		}
 	}
 
 	/*
diff --git a/src/backend/storage/lmgr/lmgr.c b/src/backend/storage/lmgr/lmgr.c
index 3f6bf70bd3c..b5270fb86b1 100644
--- a/src/backend/storage/lmgr/lmgr.c
+++ b/src/backend/storage/lmgr/lmgr.c
@@ -718,7 +718,6 @@ XactLockTableWait(TransactionId xid, Relation rel, ItemPointer ctid,
 		 */
 		if (!first)
 		{
-			CHECK_FOR_INTERRUPTS();
 			pg_usleep(1000L);
 		}
 		first = false;
@@ -761,7 +760,6 @@ ConditionalXactLockTableWait(TransactionId xid, bool logLockFailure)
 		/* See XactLockTableWait about this case */
 		if (!first)
 		{
-			CHECK_FOR_INTERRUPTS();
 			pg_usleep(1000L);
 		}
 		first = false;
-- 
2.49.0

