From 0c8a2270fe42e0269b87b1d3abf78dd7c7200369 Mon Sep 17 00:00:00 2001 From: Matthias van de Meent Date: Fri, 9 Oct 2026 20:32:31 +0200 Subject: [PATCH v2 2/3] procarray: Implement optimistic lockless snapshot reuse If no other transaction has committed, and our current backend already had an xmin installed, we can reuse the snapshot just by testing the completion count of an existing snapshot against the shared counter. This avoids taking the ProcArrayLock in a common case, thus reducing write traffic on the lock's cache line. This has been hinted at in 623a9ba79b, but wasn't yet picked up after that was committed. --- src/backend/storage/ipc/procarray.c | 64 ++++++++++++++++++++++++----- src/include/access/varsup.h | 18 +++++++- 2 files changed, 70 insertions(+), 12 deletions(-) diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c index 742547ceb54..ffd584bffdb 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -447,7 +447,7 @@ ProcArrayShmemInit(void *arg) procArray->lastOverflowedXid = InvalidTransactionId; procArray->replication_slot_xmin = InvalidTransactionId; procArray->replication_slot_catalog_xmin = InvalidTransactionId; - TransamVariables->xactCompletionCount = 1; + pg_atomic_init_u64(&TransamVariables->xactCompletionCount, 1); allProcs = ProcGlobal->allProcs; } @@ -588,7 +588,7 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid) MaintainLatestCompletedXid(latestXid); /* Same with xactCompletionCount */ - TransamVariables->xactCompletionCount++; + IncrementXactCompletionCount(); ProcGlobal->xids[myoff] = InvalidTransactionId; ProcGlobal->subxidStates[myoff].overflowed = false; @@ -766,7 +766,7 @@ ProcArrayEndTransactionInternal(PGPROC *proc, TransactionId latestXid) MaintainLatestCompletedXid(latestXid); /* Same with xactCompletionCount */ - TransamVariables->xactCompletionCount++; + IncrementXactCompletionCount(); } /* @@ -935,7 +935,7 @@ ProcArrayClearTransaction(PGPROC *proc) * otherwise could end up reusing the snapshot later. Which would be bad, * because it might not count the prepared transaction as running. */ - TransamVariables->xactCompletionCount++; + IncrementXactCompletionCount(); /* Clear the subtransaction-XID cache too */ Assert(ProcGlobal->subxidStates[pgxactoff].count == proc->subxidStatus.count && @@ -2022,6 +2022,41 @@ GetMaxSnapshotSubxidCount(void) return TOTAL_MAX_CACHED_SUBXIDS; } +static inline bool +GetSnapshotDataReuseUnlocked(Snapshot snapshot, bool *try_reuse) +{ + uint64 curXactCompletionCount; + + if (!TransactionIdIsValid(MyProc->xmin)) + { + *try_reuse = true; + return false; + } + + if (unlikely(snapshot->snapXactCompletionCount == 0)) + { + *try_reuse = false; + return false; + } + + curXactCompletionCount = + pg_atomic_read_u64(&TransamVariables->xactCompletionCount); + + if (curXactCompletionCount != snapshot->snapXactCompletionCount) + { + *try_reuse = false; + return false; + } + + RecentXmin = snapshot->xmin; + Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin)); + snapshot->curcid = GetCurrentCommandId(false); + snapshot->active_count = 0; + snapshot->regd_count = 0; + snapshot->copied = false; + + return true; +} /* * Helper function for GetSnapshotData() that checks if the bulk of the * visibility information in the snapshot is still valid. If so, it updates @@ -2041,7 +2076,9 @@ GetSnapshotDataReuse(Snapshot snapshot) if (unlikely(snapshot->snapXactCompletionCount == 0)) return false; - curXactCompletionCount = TransamVariables->xactCompletionCount; + curXactCompletionCount = + pg_atomic_read_u64(&TransamVariables->xactCompletionCount); + if (curXactCompletionCount != snapshot->snapXactCompletionCount) return false; @@ -2126,6 +2163,7 @@ GetSnapshotData(Snapshot snapshot) int mypgxactoff; TransactionId myxid; uint64 curXactCompletionCount; + bool retry_reuse; TransactionId replication_slot_xmin = InvalidTransactionId; TransactionId replication_slot_catalog_xmin = InvalidTransactionId; @@ -2172,13 +2210,16 @@ GetSnapshotData(Snapshot snapshot) } } + if (GetSnapshotDataReuseUnlocked(snapshot, &retry_reuse)) + return snapshot; + /* * It is sufficient to get shared lock on ProcArrayLock, even if we are * going to set MyProc->xmin. */ LWLockAcquire(ProcArrayLock, LW_SHARED); - if (GetSnapshotDataReuse(snapshot)) + if (retry_reuse && GetSnapshotDataReuse(snapshot)) { LWLockRelease(ProcArrayLock); return snapshot; @@ -2190,7 +2231,8 @@ GetSnapshotData(Snapshot snapshot) Assert(myxid == MyProc->xid); oldestxid = TransamVariables->oldestXid; - curXactCompletionCount = TransamVariables->xactCompletionCount; + curXactCompletionCount = + pg_atomic_read_u64(&TransamVariables->xactCompletionCount); /* xmax is always latestCompletedXid + 1 */ xmax = XidFromFullTransactionId(latest_completed); @@ -4078,7 +4120,7 @@ XidCacheRemoveRunningXids(TransactionId xid, MaintainLatestCompletedXid(latestXid); /* ... and xactCompletionCount */ - TransamVariables->xactCompletionCount++; + IncrementXactCompletionCount(); LWLockRelease(ProcArrayLock); } @@ -4531,7 +4573,7 @@ ExpireTreeKnownAssignedTransactionIds(TransactionId xid, int nsubxids, MaintainLatestCompletedXidRecovery(max_xid); /* ... and xactCompletionCount */ - TransamVariables->xactCompletionCount++; + IncrementXactCompletionCount(); LWLockRelease(ProcArrayLock); } @@ -4558,7 +4600,7 @@ ExpireAllKnownAssignedTransactionIds(void) * Any transactions that were in-progress were effectively aborted, so * advance xactCompletionCount. */ - TransamVariables->xactCompletionCount++; + IncrementXactCompletionCount(); /* * Reset lastOverflowedXid. Currently, lastOverflowedXid has no use after @@ -4587,7 +4629,7 @@ ExpireOldKnownAssignedTransactionIds(TransactionId xid) MaintainLatestCompletedXidRecovery(latestXid); /* ... and xactCompletionCount */ - TransamVariables->xactCompletionCount++; + IncrementXactCompletionCount(); /* * Reset lastOverflowedXid if we know all transactions that have been diff --git a/src/include/access/varsup.h b/src/include/access/varsup.h index 0ade9e62ccf..9eec60d1008 100644 --- a/src/include/access/varsup.h +++ b/src/include/access/varsup.h @@ -14,6 +14,10 @@ #define VARSUP_H #include "access/transam.h" +#include "port/atomics.h" +#ifdef USE_ASSERT_CHECKING +#include "storage/lwlock.h" +#endif /* @@ -65,7 +69,7 @@ typedef struct TransamVariablesData * GetSnapshotData() needs to recompute the contents of the snapshot, or * not. There are likely other users of this. Always above 1. */ - uint64 xactCompletionCount; + pg_atomic_uint64 xactCompletionCount; /* * These fields are protected by XactTruncationLock @@ -77,4 +81,16 @@ typedef struct TransamVariablesData extern PGDLLIMPORT TransamVariablesData *TransamVariables; +static inline void +IncrementXactCompletionCount(void) +{ + uint64 xcc; + + Assert(LWLockHeldByMeInMode(ProcArrayLock, LW_EXCLUSIVE)); + + xcc = pg_atomic_read_u64(&TransamVariables->xactCompletionCount); + xcc += 1; + pg_atomic_write_u64(&TransamVariables->xactCompletionCount, xcc); +} + #endif /* VARSUP_H */ -- 2.54.0 (Apple Git-157)