From 437b48759126a4d8289fd10eb3a885392cf96a47 Mon Sep 17 00:00:00 2001
From: Maksim Melnikov <m.melnikov@postgrespro.ru>
Date: Tue, 18 Nov 2025 17:20:09 +0300
Subject: [PATCH] This patch reduce connection init/close time.

ProcArrayRemove/ProcArrayAdd are expensive in terms of accessing pgxactoff
field in PGPROC, because its are placed on different pages and it the
reason of page_faults occurence. Now one of the use case with pgxactoff
is iteration with gaps over PGPROCs and read/write pgxactoff, PGPROC
allocate ~1KB and it quite enough to have page_faults in case of such
accessing.

So we placed all pgxactoff in the separate array pgxactoffs to have adjacent
pages for them all. Indexes in allProcs aligned with pgxactoffs array, so
the Nth element in pgxactoffs refer to Nth PGPROC.

Exentually it helps to avoid extra page_faults and reduce connection
init/close time.

Tags: lwlock_scale
---
 src/backend/access/transam/varsup.c       | 10 +++---
 src/backend/commands/indexcmds.c          |  2 +-
 src/backend/commands/vacuum.c             |  2 +-
 src/backend/replication/logical/logical.c |  2 +-
 src/backend/replication/slot.c            |  2 +-
 src/backend/replication/walsender.c       |  2 +-
 src/backend/storage/ipc/procarray.c       | 38 ++++++++++++-----------
 src/backend/storage/lmgr/proc.c           |  8 +++--
 src/include/storage/proc.h                |  6 ++--
 9 files changed, 39 insertions(+), 33 deletions(-)

diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index dc5e32d86f3..6c941de5c99 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -85,7 +85,7 @@ GetNewTransactionId(bool isSubXact)
 	{
 		Assert(!isSubXact);
 		MyProc->xid = BootstrapTransactionId;
-		ProcGlobal->xids[MyProc->pgxactoff] = BootstrapTransactionId;
+		ProcGlobal->xids[GetMyXactOffPGProc()] = BootstrapTransactionId;
 		return FullTransactionIdFromEpochAndXid(0, BootstrapTransactionId);
 	}
 
@@ -244,18 +244,18 @@ GetNewTransactionId(bool isSubXact)
 	 */
 	if (!isSubXact)
 	{
-		Assert(ProcGlobal->subxidStates[MyProc->pgxactoff].count == 0);
-		Assert(!ProcGlobal->subxidStates[MyProc->pgxactoff].overflowed);
+		Assert(ProcGlobal->subxidStates[GetMyXactOffPGProc()].count == 0);
+		Assert(!ProcGlobal->subxidStates[GetMyXactOffPGProc()].overflowed);
 		Assert(MyProc->subxidStatus.count == 0);
 		Assert(!MyProc->subxidStatus.overflowed);
 
 		/* LWLockRelease acts as barrier */
 		MyProc->xid = xid;
-		ProcGlobal->xids[MyProc->pgxactoff] = xid;
+		ProcGlobal->xids[GetMyXactOffPGProc()] = xid;
 	}
 	else
 	{
-		XidCacheStatus *substat = &ProcGlobal->subxidStates[MyProc->pgxactoff];
+		XidCacheStatus *substat = &ProcGlobal->subxidStates[GetMyXactOffPGProc()];
 		int			nxids = MyProc->subxidStatus.count;
 
 		Assert(substat->count == MyProc->subxidStatus.count);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..03721601070 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -4654,6 +4654,6 @@ set_indexsafe_procflags(void)
 
 	LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
 	MyProc->statusFlags |= PROC_IN_SAFE_IC;
-	ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+	ProcGlobal->statusFlags[GetMyXactOffPGProc()] = MyProc->statusFlags;
 	LWLockRelease(ProcArrayLock);
 }
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 99d0db82ed7..10b712c8e78 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2054,7 +2054,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		MyProc->statusFlags |= PROC_IN_VACUUM;
 		if (params.is_wraparound)
 			MyProc->statusFlags |= PROC_VACUUM_FOR_WRAPAROUND;
-		ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+		ProcGlobal->statusFlags[GetMyXactOffPGProc()] = MyProc->statusFlags;
 		LWLockRelease(ProcArrayLock);
 	}
 
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index a33a685dcc6..91d6d83a5d8 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -188,7 +188,7 @@ StartupDecodingContext(List *output_plugin_options,
 	{
 		LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
 		MyProc->statusFlags |= PROC_IN_LOGICAL_DECODING;
-		ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+		ProcGlobal->statusFlags[GetMyXactOffPGProc()] = MyProc->statusFlags;
 		LWLockRelease(ProcArrayLock);
 	}
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 83fcde74718..180cc800825 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -839,7 +839,7 @@ ReplicationSlotRelease(void)
 	/* might not have been set when we've been a plain slot */
 	LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
 	MyProc->statusFlags &= ~PROC_IN_LOGICAL_DECODING;
-	ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+	ProcGlobal->statusFlags[GetMyXactOffPGProc()] = MyProc->statusFlags;
 	LWLockRelease(ProcArrayLock);
 
 	if (am_walsender)
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 3d4ab929f91..38c935e09a0 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -351,7 +351,7 @@ InitWalSender(void)
 		Assert(MyProc->xmin == InvalidTransactionId);
 		LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
 		MyProc->statusFlags |= PROC_AFFECTS_ALL_HORIZONS;
-		ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+		ProcGlobal->statusFlags[GetMyXactOffPGProc()] = MyProc->statusFlags;
 		LWLockRelease(ProcArrayLock);
 	}
 
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 9299bcebbda..35578e7222c 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -283,6 +283,7 @@ typedef enum KAXCompressReason
 } KAXCompressReason;
 
 static PGPROC *allProcs;
+static int *pgxactoffs;
 
 /*
  * Cache to reduce overhead of repeated calls to TransactionIdIsInProgress()
@@ -449,6 +450,7 @@ ProcArrayShmemInit(void *arg)
 	TransamVariables->xactCompletionCount = 1;
 
 	allProcs = ProcGlobal->allProcs;
+	pgxactoffs = ProcGlobal->pgxactoffs;
 }
 
 static void
@@ -498,7 +500,7 @@ ProcArrayAdd(PGPROC *proc)
 		int			this_procno = arrayP->pgprocnos[index];
 
 		Assert(this_procno >= 0 && this_procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
-		Assert(allProcs[this_procno].pgxactoff == index);
+		Assert(pgxactoffs[this_procno] == index);
 
 		/* If we have found our right position in the array, break */
 		if (this_procno > pgprocno)
@@ -520,7 +522,7 @@ ProcArrayAdd(PGPROC *proc)
 			movecount * sizeof(*ProcGlobal->statusFlags));
 
 	arrayP->pgprocnos[index] = GetNumberFromPGProc(proc);
-	proc->pgxactoff = index;
+	pgxactoffs[arrayP->pgprocnos[index]] = index;
 	ProcGlobal->xids[index] = proc->xid;
 	ProcGlobal->subxidStates[index] = proc->subxidStatus;
 	ProcGlobal->statusFlags[index] = proc->statusFlags;
@@ -534,9 +536,9 @@ ProcArrayAdd(PGPROC *proc)
 		int			procno = arrayP->pgprocnos[index];
 
 		Assert(procno >= 0 && procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
-		Assert(allProcs[procno].pgxactoff == index - 1);
+		Assert(pgxactoffs[procno] == index - 1);
 
-		allProcs[procno].pgxactoff = index;
+		pgxactoffs[procno] = index;
 	}
 
 	/*
@@ -574,10 +576,10 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid)
 	LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
 	LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
 
-	myoff = proc->pgxactoff;
+	myoff = GetXactOffPGProc(proc);
 
 	Assert(myoff >= 0 && myoff < arrayP->numProcs);
-	Assert(ProcGlobal->allProcs[arrayP->pgprocnos[myoff]].pgxactoff == myoff);
+	Assert(ProcGlobal->pgxactoffs[arrayP->pgprocnos[myoff]] == myoff);
 
 	if (TransactionIdIsValid(latestXid))
 	{
@@ -632,9 +634,9 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid)
 		int			procno = arrayP->pgprocnos[index];
 
 		Assert(procno >= 0 && procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
-		Assert(allProcs[procno].pgxactoff - 1 == index);
+		Assert(pgxactoffs[procno] - 1 == index);
 
-		allProcs[procno].pgxactoff = index;
+		pgxactoffs[procno] = index;
 	}
 
 	/*
@@ -708,9 +710,9 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid)
 		{
 			Assert(!LWLockHeldByMe(ProcArrayLock));
 			LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
-			Assert(proc->statusFlags == ProcGlobal->statusFlags[proc->pgxactoff]);
+			Assert(proc->statusFlags == ProcGlobal->statusFlags[GetXactOffPGProc(proc)]);
 			proc->statusFlags &= ~PROC_VACUUM_STATE_MASK;
-			ProcGlobal->statusFlags[proc->pgxactoff] = proc->statusFlags;
+			ProcGlobal->statusFlags[GetXactOffPGProc(proc)] = proc->statusFlags;
 			LWLockRelease(ProcArrayLock);
 		}
 	}
@@ -724,7 +726,7 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid)
 static inline void
 ProcArrayEndTransactionInternal(PGPROC *proc, TransactionId latestXid)
 {
-	int			pgxactoff = proc->pgxactoff;
+	int			pgxactoff = GetXactOffPGProc(proc);
 
 	/*
 	 * Note: we need exclusive lock here because we're going to change other
@@ -747,7 +749,7 @@ ProcArrayEndTransactionInternal(PGPROC *proc, TransactionId latestXid)
 	if (proc->statusFlags & PROC_VACUUM_STATE_MASK)
 	{
 		proc->statusFlags &= ~PROC_VACUUM_STATE_MASK;
-		ProcGlobal->statusFlags[proc->pgxactoff] = proc->statusFlags;
+		ProcGlobal->statusFlags[GetXactOffPGProc(proc)] = proc->statusFlags;
 	}
 
 	/* Clear the subtransaction-XID cache too while holding the lock */
@@ -916,7 +918,7 @@ ProcArrayClearTransaction(PGPROC *proc)
 	 */
 	LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
 
-	pgxactoff = proc->pgxactoff;
+	pgxactoff = GetXactOffPGProc(proc);
 
 	ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
 	proc->xid = InvalidTransactionId;
@@ -1475,7 +1477,7 @@ TransactionIdIsInProgress(TransactionId xid)
 	}
 
 	/* No shortcuts, gotta grovel through the array */
-	mypgxactoff = MyProc->pgxactoff;
+	mypgxactoff = GetMyXactOffPGProc();
 	numProcs = arrayP->numProcs;
 	for (int pgxactoff = 0; pgxactoff < numProcs; pgxactoff++)
 	{
@@ -2176,7 +2178,7 @@ GetSnapshotData(Snapshot snapshot)
 	}
 
 	latest_completed = TransamVariables->latestCompletedXid;
-	mypgxactoff = MyProc->pgxactoff;
+	mypgxactoff = GetMyXactOffPGProc();
 	myxid = other_xids[mypgxactoff];
 	Assert(myxid == MyProc->xid);
 
@@ -2215,7 +2217,7 @@ GetSnapshotData(Snapshot snapshot)
 			TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
 			uint8		statusFlags;
 
-			Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+			Assert(pgxactoffs[arrayP->pgprocnos[pgxactoff]] == pgxactoff);
 
 			/*
 			 * If the transaction has no XID assigned, we can skip it; it
@@ -2583,7 +2585,7 @@ ProcArrayInstallRestoredXmin(TransactionId xmin, PGPROC *proc)
 		MyProc->xmin = TransactionXmin = xmin;
 		MyProc->statusFlags = (MyProc->statusFlags & ~PROC_XMIN_FLAGS) |
 			(proc->statusFlags & PROC_XMIN_FLAGS);
-		ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+		ProcGlobal->statusFlags[GetMyXactOffPGProc()] = MyProc->statusFlags;
 
 		result = true;
 	}
@@ -4032,7 +4034,7 @@ XidCacheRemoveRunningXids(TransactionId xid,
 	 */
 	LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
 
-	mysubxidstat = &ProcGlobal->subxidStates[MyProc->pgxactoff];
+	mysubxidstat = &ProcGlobal->subxidStates[GetMyXactOffPGProc()];
 
 	/*
 	 * Under normal circumstances xid and xids[] will be in increasing order,
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 1ac25068d62..5ae4922128e 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -165,6 +165,7 @@ ProcGlobalShmemRequest(void *arg)
 	size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->xids)));
 	size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->subxidStates)));
 	size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->statusFlags)));
+	size = add_size(size, mul_size(TotalProcs, sizeof(int)));
 	ProcGlobalAllProcsShmemSize = size;
 	ShmemRequestStruct(.name = "PGPROC structures",
 					   .size = ProcGlobalAllProcsShmemSize,
@@ -273,7 +274,10 @@ ProcGlobalShmemInit(void *arg)
 	ProcGlobal->statusFlags = (uint8 *) ptr;
 	ptr = ptr + (TotalProcs * sizeof(*ProcGlobal->statusFlags));
 
-	/* make sure we didn't overflow */
+	ProcGlobal->pgxactoffs = (int *) ptr;
+	ptr = (char *) ptr + TotalProcs * sizeof(int);
+
+	/* make sure wer didn't overflow */
 	Assert((ptr > (char *) procs) && (ptr <= (char *) procs + requestSize));
 
 	/*
@@ -1498,7 +1502,7 @@ ProcSleep(LOCALLOCK *locallock)
 			 * the lock held, which is much more undesirable.
 			 */
 			LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
-			statusFlags = ProcGlobal->statusFlags[autovac->pgxactoff];
+			statusFlags = ProcGlobal->statusFlags[GetXactOffPGProc(autovac)];
 			lockmethod_copy = lock->tag.locktag_lockmethodid;
 			locktag_copy = lock->tag;
 			LWLockRelease(ProcArrayLock);
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 3e1d1fad5f9..fee9deb8f3a 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -210,9 +210,6 @@ typedef struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
-	int			pgxactoff;		/* offset into various ProcGlobal->arrays with
-								 * data mirrored from this PGPROC */
-
 	uint8		statusFlags;	/* this backend's status flags, see PROC_*
 								 * above. mirrored in
 								 * ProcGlobal->statusFlags[pgxactoff] */
@@ -445,6 +442,7 @@ typedef struct PROC_HDR
 {
 	/* Array of PGPROC structures (not including dummies for prepared txns) */
 	PGPROC	   *allProcs;
+	int		   *pgxactoffs;
 
 	/* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
 	TransactionId *xids;
@@ -509,6 +507,8 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs;
  */
 #define GetPGProcByNumber(n) (&ProcGlobal->allProcs[(n)])
 #define GetNumberFromPGProc(proc) ((proc) - &ProcGlobal->allProcs[0])
+#define GetXactOffPGProc(proc) (ProcGlobal->pgxactoffs[(proc) - &ProcGlobal->allProcs[0]])
+#define GetMyXactOffPGProc() (GetXactOffPGProc(MyProc))
 
 /*
  * We set aside some extra PGPROC structures for "special worker" processes,
-- 
2.43.0

