From d3ae09a783df085789fa4b899163a189eed889de Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Mon, 6 Apr 2020 21:28:55 -0700 Subject: [PATCH v7 07/11] snapshot scalability: Move in-progress xids to ProcGlobal->xids. This improves performance because GetSnapshotData() each time needs to access the xids of all procarray entries. As the set of running xids changes fairly rarely compared to the number of snapshots taken, this substantially increases the likelihood of most data required for a snapshot already being in l2 cache. --- src/include/storage/proc.h | 45 ++- src/backend/access/heap/heapam_visibility.c | 8 +- src/backend/access/transam/README | 33 +- src/backend/access/transam/clog.c | 8 +- src/backend/access/transam/twophase.c | 31 +- src/backend/access/transam/varsup.c | 20 +- src/backend/commands/vacuum.c | 4 +- src/backend/storage/ipc/procarray.c | 319 +++++++++++++------- src/backend/storage/ipc/sinvaladt.c | 4 +- src/backend/storage/lmgr/lock.c | 3 +- src/backend/storage/lmgr/proc.c | 33 +- 11 files changed, 333 insertions(+), 175 deletions(-) diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 3b3936249ab..60586e8be34 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -83,6 +83,10 @@ struct XidCache * distinguished from a real one at need by the fact that it has pid == 0. * The semaphore and lock-activity fields in a prepared-xact PGPROC are unused, * but its myProcLocks[] lists are valid. + * + * The various *Copy fields are copies of the data in ProcGlobal arrays that + * can be accessed without holding ProcArrayLock / XidGenLock (see PROC_HDR + * comments). */ struct PGPROC { @@ -95,6 +99,9 @@ struct PGPROC Latch procLatch; /* generic latch for process */ + TransactionId xidCopy; /* this backend's xid, a copy of this proc's + ProcGlobal->xids[] entry. */ + TransactionId xmin; /* minimal running XID as it was when we were * starting our xact, excluding LAZY VACUUM: * vacuum must not remove tuples deleted by @@ -104,6 +111,10 @@ struct PGPROC * being executed by this proc, if running; * else InvalidLocalTransactionId */ int pid; /* Backend's process ID; 0 if prepared xact */ + + int pgxactoff; /* offset into various ProcGlobal-> arrays + * NB: can change any time unless locks held! + */ int pgprocno; /* These fields are zero while a backend is still starting up: */ @@ -220,10 +231,6 @@ extern PGDLLIMPORT struct PGXACT *MyPgXact; */ typedef struct PGXACT { - TransactionId xid; /* id of top-level transaction currently being - * executed by this proc, if running and XID - * is assigned; else InvalidTransactionId */ - uint8 vacuumFlags; /* vacuum-related flags, see above */ bool overflowed; @@ -232,6 +239,13 @@ typedef struct PGXACT /* * There is one ProcGlobal struct for the whole database cluster. + * + * Adding/Removing an entry into the procarray requires holding *both* + * ProcArrayLock and XidGenLock in exclusive mode (in that order). Both are + * needed because the dense arrays (see below) are accessed from + * GetNewTransactionId() and GetSnapshotData(), and we don't want to add + * further contention by both using one lock. Adding/Removing a procarray + * entry is much less frequent. */ typedef struct PROC_HDR { @@ -239,6 +253,29 @@ typedef struct PROC_HDR PGPROC *allProcs; /* Array of PGXACT structures (not including dummies for prepared txns) */ PGXACT *allPgXact; + + /* + * Arrays with per-backend information that is hotly accessed, indexed by + * PGPROC->pgxactoff. These are in separate arrays for three reasons: + * First, to allow for as tight loops accessing the data as + * possible. Second, to prevent updates of frequently changing data from + * invalidating cachelines shared with less frequently changing + * data. Third to condense frequently accessed data into as few cachelines + * as possible. + * + * When entering a PGPROC for 2PC transactions with ProcArrayAdd(), those + * copies are used to provide the contents of the dense data, and will be + * transferred by ProcArrayAdd() while it already holds ProcArrayLock. + */ + + /* + * TransactionId of top-level transaction currently being executed by each + * proc, if running and XID is assigned; else InvalidTransactionId. + * + * Each PGPROC has a copy of its value in PGPROC.xidCopy. + */ + TransactionId *xids; + /* Length of allProcs array */ uint32 allProcCount; /* Head of list of free PGPROC structures */ diff --git a/src/backend/access/heap/heapam_visibility.c b/src/backend/access/heap/heapam_visibility.c index 793a8036331..ddd8f19bd10 100644 --- a/src/backend/access/heap/heapam_visibility.c +++ b/src/backend/access/heap/heapam_visibility.c @@ -11,12 +11,12 @@ * shared buffer content lock on the buffer containing the tuple. * * NOTE: When using a non-MVCC snapshot, we must check - * TransactionIdIsInProgress (which looks in the PGXACT array) + * TransactionIdIsInProgress (which looks in the PGPROC array) * before TransactionIdDidCommit/TransactionIdDidAbort (which look in * pg_xact). Otherwise we have a race condition: we might decide that a * just-committed transaction crashed, because none of the tests succeed. * xact.c is careful to record commit/abort in pg_xact before it unsets - * MyPgXact->xid in the PGXACT array. That fixes that problem, but it + * MyProc->xid in the PGPROC array. That fixes that problem, but it * also means there is a window where TransactionIdIsInProgress and * TransactionIdDidCommit will both return true. If we check only * TransactionIdDidCommit, we could consider a tuple committed when a @@ -956,7 +956,7 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot, * coding where we tried to set the hint bits as soon as possible, we instead * did TransactionIdIsInProgress in each call --- to no avail, as long as the * inserting/deleting transaction was still running --- which was more cycles - * and more contention on the PGXACT array. + * and more contention on ProcArrayLock. */ static bool HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot, @@ -1444,7 +1444,7 @@ HeapTupleSatisfiesNonVacuumable(HeapTuple htup, Snapshot snapshot, * HeapTupleSatisfiesMVCC) and, therefore, any hint bits that can be set * should already be set. We assume that if no hint bits are set, the xmin * or xmax transaction is still running. This is therefore faster than - * HeapTupleSatisfiesVacuum, because we don't consult PGXACT nor CLOG. + * HeapTupleSatisfiesVacuum, because we consult neither procarray nor CLOG. * It's okay to return false when in doubt, but we must return true only * if the tuple is removable. */ diff --git a/src/backend/access/transam/README b/src/backend/access/transam/README index 85c2625ec42..818f84d32aa 100644 --- a/src/backend/access/transam/README +++ b/src/backend/access/transam/README @@ -251,10 +251,10 @@ enforce, and it assists with some other issues as explained below.) The implementation of this is that GetSnapshotData takes the ProcArrayLock in shared mode (so that multiple backends can take snapshots in parallel), but ProcArrayEndTransaction must take the ProcArrayLock in exclusive mode -while clearing MyPgXact->xid at transaction end (either commit or abort). -(To reduce context switching, when multiple transactions commit nearly -simultaneously, we have one backend take ProcArrayLock and clear the XIDs -of multiple processes at once.) +while clearing the ProcGlobal->xids[] entry at transaction end (either +commit or abort). (To reduce context switching, when multiple transactions +commit nearly simultaneously, we have one backend take ProcArrayLock and +clear the XIDs of multiple processes at once.) ProcArrayEndTransaction also holds the lock while advancing the shared latestCompletedFullXid variable. This allows GetSnapshotData to use @@ -278,12 +278,13 @@ present in the ProcArray, or not running anymore. (This guarantee doesn't apply to subtransaction XIDs, because of the possibility that there's not room for them in the subxid array; instead we guarantee that they are present or the overflow flag is set.) If a backend released XidGenLock -before storing its XID into MyPgXact, then it would be possible for another -backend to allocate and commit a later XID, causing latestCompletedFullXid to -pass the first backend's XID, before that value became visible in the -ProcArray. That would break ComputeTransactionHorizons, as discussed below. +before storing its XID into ProcGlobal->xids[], then it would be possible for +another backend to allocate and commit a later XID, causing +latestCompletedFullXid to pass the first backend's XID, before that value +became visible in the ProcArray. That would break ComputeTransactionHorizons, +as discussed below. -We allow GetNewTransactionId to store the XID into MyPgXact->xid (or the +We allow GetNewTransactionId to store the XID into ProcGlobal->xids[] (or the subxid array) without taking ProcArrayLock. This was once necessary to avoid deadlock; while that is no longer the case, it's still beneficial for performance. We are thereby relying on fetch/store of an XID to be atomic, @@ -386,13 +387,13 @@ Top-level transactions do not have a parent, so they leave their pg_subtrans entries set to the default value of zero (InvalidTransactionId). pg_subtrans is used to check whether the transaction in question is still -running --- the main Xid of a transaction is recorded in the PGXACT struct, -but since we allow arbitrary nesting of subtransactions, we can't fit all Xids -in shared memory, so we have to store them on disk. Note, however, that for -each transaction we keep a "cache" of Xids that are known to be part of the -transaction tree, so we can skip looking at pg_subtrans unless we know the -cache has been overflowed. See storage/ipc/procarray.c for the gory details. - +running --- the main Xid of a transaction is recorded in ProcGlobal->xids[], +with a copy in PGPROC->xidCopy, but since we allow arbitrary nesting of +subtransactions, we can't fit all Xids in shared memory, so we have to store +them on disk. Note, however, that for each transaction we keep a "cache" of +Xids that are known to be part of the transaction tree, so we can skip looking +at pg_subtrans unless we know the cache has been overflowed. See +storage/ipc/procarray.c for the gory details. slru.c is the supporting mechanism for both pg_xact and pg_subtrans. It implements the LRU policy for in-memory buffer pages. The high-level routines for pg_xact are implemented in transam.c, while the low-level functions are in diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c index f8e7670f8da..8e9c211b02a 100644 --- a/src/backend/access/transam/clog.c +++ b/src/backend/access/transam/clog.c @@ -285,15 +285,15 @@ TransactionIdSetPageStatus(TransactionId xid, int nsubxids, * updates for multiple backends so that the number of times * CLogControlLock needs to be acquired is reduced. * - * For this optimization to be safe, the XID in MyPgXact and the subxids - * in MyProc must be the same as the ones for which we're setting the - * status. Check that this is the case. + * For this optimization to be safe, the XID in MyProc->xidCopy and the + * subxids in MyProc must be the same as the ones for which we're setting + * the status. Check that this is the case. * * For this optimization to be efficient, we shouldn't have too many * sub-XIDs and all of the XIDs for which we're adjusting clog should be * on the same page. Check those conditions, too. */ - if (all_xact_same_page && xid == MyPgXact->xid && + if (all_xact_same_page && xid == MyProc->xidCopy && nsubxids <= THRESHOLD_SUBTRANS_CLOG_OPT && nsubxids == MyPgXact->nxids && memcmp(subxids, MyProc->subxids.xids, diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c index 5867cc60f3e..8103c5cb71f 100644 --- a/src/backend/access/transam/twophase.c +++ b/src/backend/access/transam/twophase.c @@ -351,7 +351,7 @@ AtAbort_Twophase(void) /* * This is called after we have finished transferring state to the prepared - * PGXACT entry. + * PGPROC entry. */ void PostPrepare_Twophase(void) @@ -463,7 +463,7 @@ MarkAsPreparingGuts(GlobalTransaction gxact, TransactionId xid, const char *gid, proc->waitStatus = STATUS_OK; /* We set up the gxact's VXID as InvalidBackendId/XID */ proc->lxid = (LocalTransactionId) xid; - pgxact->xid = xid; + proc->xidCopy = xid; Assert(proc->xmin == InvalidTransactionId); proc->delayChkpt = false; pgxact->vacuumFlags = 0; @@ -768,7 +768,6 @@ pg_prepared_xact(PG_FUNCTION_ARGS) { GlobalTransaction gxact = &status->array[status->currIdx++]; PGPROC *proc = &ProcGlobal->allProcs[gxact->pgprocno]; - PGXACT *pgxact = &ProcGlobal->allPgXact[gxact->pgprocno]; Datum values[5]; bool nulls[5]; HeapTuple tuple; @@ -783,7 +782,7 @@ pg_prepared_xact(PG_FUNCTION_ARGS) MemSet(values, 0, sizeof(values)); MemSet(nulls, 0, sizeof(nulls)); - values[0] = TransactionIdGetDatum(pgxact->xid); + values[0] = TransactionIdGetDatum(proc->xidCopy); values[1] = CStringGetTextDatum(gxact->gid); values[2] = TimestampTzGetDatum(gxact->prepared_at); values[3] = ObjectIdGetDatum(gxact->owner); @@ -829,9 +828,8 @@ TwoPhaseGetGXact(TransactionId xid, bool lock_held) for (i = 0; i < TwoPhaseState->numPrepXacts; i++) { GlobalTransaction gxact = TwoPhaseState->prepXacts[i]; - PGXACT *pgxact = &ProcGlobal->allPgXact[gxact->pgprocno]; - if (pgxact->xid == xid) + if (gxact->xid == xid) { result = gxact; break; @@ -987,8 +985,7 @@ void StartPrepare(GlobalTransaction gxact) { PGPROC *proc = &ProcGlobal->allProcs[gxact->pgprocno]; - PGXACT *pgxact = &ProcGlobal->allPgXact[gxact->pgprocno]; - TransactionId xid = pgxact->xid; + TransactionId xid = gxact->xid; TwoPhaseFileHeader hdr; TransactionId *children; RelFileNode *commitrels; @@ -1140,15 +1137,15 @@ EndPrepare(GlobalTransaction gxact) /* * Mark the prepared transaction as valid. As soon as xact.c marks - * MyPgXact as not running our XID (which it will do immediately after + * MyProc as not running our XID (which it will do immediately after * this function returns), others can commit/rollback the xact. * * NB: a side effect of this is to make a dummy ProcArray entry for the - * prepared XID. This must happen before we clear the XID from MyPgXact, - * else there is a window where the XID is not running according to - * TransactionIdIsInProgress, and onlookers would be entitled to assume - * the xact crashed. Instead we have a window where the same XID appears - * twice in ProcArray, which is OK. + * prepared XID. This must happen before we clear the XID from + * ProcGlobal->xids[], else there is a window where the XID is not running + * according to TransactionIdIsInProgress, and onlookers would be entitled + * to assume the xact crashed. Instead we have a window where the same + * XID appears twice in ProcArray, which is OK. */ MarkAsPrepared(gxact, false); @@ -1401,7 +1398,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit) { GlobalTransaction gxact; PGPROC *proc; - PGXACT *pgxact; TransactionId xid; char *buf; char *bufptr; @@ -1420,8 +1416,7 @@ FinishPreparedTransaction(const char *gid, bool isCommit) */ gxact = LockGXact(gid, GetUserId()); proc = &ProcGlobal->allProcs[gxact->pgprocno]; - pgxact = &ProcGlobal->allPgXact[gxact->pgprocno]; - xid = pgxact->xid; + xid = gxact->xid; /* * Read and validate 2PC state data. State data will typically be stored @@ -1723,7 +1718,7 @@ CheckPointTwoPhase(XLogRecPtr redo_horizon) for (i = 0; i < TwoPhaseState->numPrepXacts; i++) { /* - * Note that we are using gxact not pgxact so this works in recovery + * Note that we are using gxact not pgproc so this works in recovery * also */ GlobalTransaction gxact = TwoPhaseState->prepXacts[i]; diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c index 43973130b7c..f703c229450 100644 --- a/src/backend/access/transam/varsup.c +++ b/src/backend/access/transam/varsup.c @@ -38,7 +38,8 @@ VariableCache ShmemVariableCache = NULL; * Allocate the next FullTransactionId for a new transaction or * subtransaction. * - * The new XID is also stored into MyPgXact before returning. + * The new XID is also stored into ProcGlobal->xids[]/MyProc->xidCopy before + * returning. * * Note: when this is called, we are actually already inside a valid * transaction, since XIDs are now not allocated until the transaction @@ -65,7 +66,8 @@ GetNewTransactionId(bool isSubXact) if (IsBootstrapProcessingMode()) { Assert(!isSubXact); - MyPgXact->xid = BootstrapTransactionId; + ProcGlobal->xids[MyProc->pgxactoff] = BootstrapTransactionId; + MyProc->xidCopy = BootstrapTransactionId; return FullTransactionIdFromEpochAndXid(0, BootstrapTransactionId); } @@ -190,10 +192,10 @@ GetNewTransactionId(bool isSubXact) * latestCompletedXid is present in the ProcArray, which is essential for * correct OldestXmin tracking; see src/backend/access/transam/README. * - * Note that readers of PGXACT xid fields should be careful to fetch the - * value only once, rather than assume they can read a value multiple - * times and get the same answer each time. Note we are assuming that - * TransactionId and int fetch/store are atomic. + * Note that readers of ProcGlobal->xids/PGPROC->xidCopy should be careful + * to fetch the value for each proc only once, rather than assume they can + * read a value multiple times and get the same answer each time. Note we + * are assuming that TransactionId and int fetch/store are atomic. * * The same comments apply to the subxact xid count and overflow fields. * @@ -219,7 +221,11 @@ GetNewTransactionId(bool isSubXact) * answer later on when someone does have a reason to inquire.) */ if (!isSubXact) - MyPgXact->xid = xid; /* LWLockRelease acts as barrier */ + { + /* LWLockRelease acts as barrier */ + ProcGlobal->xids[MyProc->pgxactoff] = xid; + MyProc->xidCopy = xid; + } else { int nxids = MyPgXact->nxids; diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 7055b237337..1cc220c2d56 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -1726,8 +1726,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params) * * Note: these flags remain set until CommitTransaction or * AbortTransaction. We don't want to clear them until we reset - * MyPgXact->xid/xmin, else OldestXmin might appear to go backwards, - * which is probably Not Good. + * MyProc->xidCopy/xmin, otherwise GetOldestVisibleTransactionId() + * might appear to go backwards, which is probably Not Good. */ LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); MyPgXact->vacuumFlags |= PROC_IN_VACUUM; diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c index 52822c74cff..7a6efaafe26 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -9,8 +9,9 @@ * one is as a means of determining the set of currently running transactions. * * Because of various subtle race conditions it is critical that a backend - * hold the correct locks while setting or clearing its MyPgXact->xid field. - * See notes in src/backend/access/transam/README. + * hold the correct locks while setting or clearing its xid (in + * ProcGlobal->xids[]/MyProc->xidCopy). See notes in + * src/backend/access/transam/README. * * The process arrays now also include structures representing prepared * transactions. The xid and subxids fields of these are valid, as are the @@ -61,6 +62,7 @@ #include "storage/spin.h" #include "utils/acl.h" #include "utils/builtins.h" +#include "utils/memutils.h" #include "utils/rel.h" #include "utils/snapmgr.h" @@ -288,6 +290,9 @@ static void MaintainLatestCompletedXid(TransactionId latestXid); static inline FullTransactionId FullXidViaRelative(FullTransactionId rel, TransactionId xid); +static TransactionId *snapshot_workspace_xid; +static ssize_t *snapshot_workspace_off; + /* * Report shared-memory space needed by CreateSharedProcArray. */ @@ -381,6 +386,11 @@ CreateSharedProcArray(void) } LWLockRegisterTranche(LWTRANCHE_PROC, "proc"); + + snapshot_workspace_xid = MemoryContextAllocZero(TopMemoryContext, + sizeof(*snapshot_workspace_xid) * PROCARRAY_MAXPROCS); + snapshot_workspace_off = MemoryContextAllocZero(TopMemoryContext, + sizeof(*snapshot_workspace_off) * PROCARRAY_MAXPROCS); } /* @@ -392,7 +402,9 @@ ProcArrayAdd(PGPROC *proc) ProcArrayStruct *arrayP = procArray; int index; + /* See ProcGlobal comment explaining why both locks are held */ LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); + LWLockAcquire(XidGenLock, LW_EXCLUSIVE); if (arrayP->numProcs >= arrayP->maxProcs) { @@ -401,7 +413,6 @@ ProcArrayAdd(PGPROC *proc) * fixed supply of PGPROC structs too, and so we should have failed * earlier.) */ - LWLockRelease(ProcArrayLock); ereport(FATAL, (errcode(ERRCODE_TOO_MANY_CONNECTIONS), errmsg("sorry, too many clients already"))); @@ -427,10 +438,25 @@ ProcArrayAdd(PGPROC *proc) } memmove(&arrayP->pgprocnos[index + 1], &arrayP->pgprocnos[index], - (arrayP->numProcs - index) * sizeof(int)); + (arrayP->numProcs - index) * sizeof(*arrayP->pgprocnos)); + memmove(&ProcGlobal->xids[index + 1], &ProcGlobal->xids[index], + (arrayP->numProcs - index) * sizeof(*ProcGlobal->xids)); + arrayP->pgprocnos[index] = proc->pgprocno; + ProcGlobal->xids[index] = proc->xidCopy; + arrayP->numProcs++; + for (; index < arrayP->numProcs; index++) + { + allProcs[arrayP->pgprocnos[index]].pgxactoff = index; + } + + /* + * Release in reversed acquisition order, to reduce frequency of having to + * wait for XidGenLock while holding ProcArrayLock. + */ + LWLockRelease(XidGenLock); LWLockRelease(ProcArrayLock); } @@ -456,36 +482,59 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid) DisplayXidCache(); #endif + /* See ProcGlobal comment explaining why both locks are held */ LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); + LWLockAcquire(XidGenLock, LW_EXCLUSIVE); + + Assert(ProcGlobal->allProcs[arrayP->pgprocnos[proc->pgxactoff]].pgxactoff == proc->pgxactoff); if (TransactionIdIsValid(latestXid)) { - Assert(TransactionIdIsValid(allPgXact[proc->pgprocno].xid)); + Assert(TransactionIdIsValid(ProcGlobal->xids[proc->pgxactoff])); /* Advance global latestCompletedXid while holding the lock */ MaintainLatestCompletedXid(latestXid); + + ProcGlobal->xids[proc->pgxactoff] = 0; } else { /* Shouldn't be trying to remove a live transaction here */ - Assert(!TransactionIdIsValid(allPgXact[proc->pgprocno].xid)); + Assert(!TransactionIdIsValid(ProcGlobal->xids[proc->pgxactoff])); } + Assert(TransactionIdIsValid(ProcGlobal->xids[proc->pgxactoff] == 0)); + for (index = 0; index < arrayP->numProcs; index++) { if (arrayP->pgprocnos[index] == proc->pgprocno) { /* Keep the PGPROC array sorted. See notes above */ memmove(&arrayP->pgprocnos[index], &arrayP->pgprocnos[index + 1], - (arrayP->numProcs - index - 1) * sizeof(int)); + (arrayP->numProcs - index - 1) * sizeof(*arrayP->pgprocnos)); + memmove(&ProcGlobal->xids[index], &ProcGlobal->xids[index + 1], + (arrayP->numProcs - index - 1) * sizeof(*ProcGlobal->xids)); + arrayP->pgprocnos[arrayP->numProcs - 1] = -1; /* for debugging */ arrayP->numProcs--; + + for (; index < arrayP->numProcs; index++) + { + allProcs[arrayP->pgprocnos[index]].pgxactoff--; + } + + /* + * Release in reversed acquisition order, to reduce frequency of + * having to wait for XidGenLock while holding ProcArrayLock. + */ + LWLockRelease(XidGenLock); LWLockRelease(ProcArrayLock); return; } } /* Oops */ + LWLockRelease(XidGenLock); LWLockRelease(ProcArrayLock); elog(LOG, "failed to find proc %p in ProcArray", proc); @@ -518,7 +567,7 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid) * else is taking a snapshot. See discussion in * src/backend/access/transam/README. */ - Assert(TransactionIdIsValid(allPgXact[proc->pgprocno].xid)); + Assert(TransactionIdIsValid(proc->xidCopy)); /* * If we can immediately acquire ProcArrayLock, we clear our own XID @@ -540,7 +589,7 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid) * anyone else's calculation of a snapshot. We might change their * estimate of global xmin, but that's OK. */ - Assert(!TransactionIdIsValid(allPgXact[proc->pgprocno].xid)); + Assert(!TransactionIdIsValid(proc->xidCopy)); proc->lxid = InvalidLocalTransactionId; /* must be cleared with xid/xmin: */ @@ -563,7 +612,13 @@ static inline void ProcArrayEndTransactionInternal(PGPROC *proc, PGXACT *pgxact, TransactionId latestXid) { - pgxact->xid = InvalidTransactionId; + size_t pgxactoff = proc->pgxactoff; + + Assert(TransactionIdIsValid(ProcGlobal->xids[pgxactoff])); + Assert(ProcGlobal->xids[pgxactoff] == proc->xidCopy); + + ProcGlobal->xids[pgxactoff] = InvalidTransactionId; + proc->xidCopy = InvalidTransactionId; proc->lxid = InvalidLocalTransactionId; /* must be cleared with xid/xmin: */ pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK; @@ -599,7 +654,7 @@ ProcArrayGroupClearXid(PGPROC *proc, TransactionId latestXid) uint32 wakeidx; /* We should definitely have an XID to clear. */ - Assert(TransactionIdIsValid(allPgXact[proc->pgprocno].xid)); + Assert(TransactionIdIsValid(proc->xidCopy)); /* Add ourselves to the list of processes needing a group XID clear. */ proc->procArrayGroupMember = true; @@ -704,20 +759,28 @@ ProcArrayGroupClearXid(PGPROC *proc, TransactionId latestXid) * This is used after successfully preparing a 2-phase transaction. We are * not actually reporting the transaction's XID as no longer running --- it * will still appear as running because the 2PC's gxact is in the ProcArray - * too. We just have to clear out our own PGXACT. + * too. We just have to clear out our own PGPROC. */ void ProcArrayClearTransaction(PGPROC *proc) { PGXACT *pgxact = &allPgXact[proc->pgprocno]; + size_t pgxactoff; /* - * We can skip locking ProcArrayLock here, because this action does not - * actually change anyone's view of the set of running XIDs: our entry is - * duplicate with the gxact that has already been inserted into the - * ProcArray. + * We can skip locking ProcArrayLock exclusively here, because this action + * does not actually change anyone's view of the set of running XIDs: our + * entry is duplicate with the gxact that has already been inserted into + * the ProcArray. But need it in shared mode for pgproc->pgxactoff to stay + * the same. */ - pgxact->xid = InvalidTransactionId; + LWLockAcquire(ProcArrayLock, LW_SHARED); + + pgxactoff = proc->pgxactoff; + + ProcGlobal->xids[pgxactoff] = InvalidTransactionId; + proc->xidCopy = InvalidTransactionId; + proc->lxid = InvalidLocalTransactionId; proc->xmin = InvalidTransactionId; proc->recoveryConflictPending = false; @@ -729,6 +792,8 @@ ProcArrayClearTransaction(PGPROC *proc) /* Clear the subtransaction-XID cache too */ pgxact->nxids = 0; pgxact->overflowed = false; + + LWLockRelease(ProcArrayLock); } /* @@ -1099,7 +1164,7 @@ ProcArrayApplyXidAssignment(TransactionId topxid, * there are four possibilities for finding a running transaction: * * 1. The given Xid is a main transaction Id. We will find this out cheaply - * by looking at the PGXACT struct for each backend. + * by looking at ProcGlobal->xids. * * 2. The given Xid is one of the cached subxact Xids in the PGPROC array. * We can find this out cheaply too. @@ -1108,25 +1173,27 @@ ProcArrayApplyXidAssignment(TransactionId topxid, * if the Xid is running on the master. * * 4. Search the SubTrans tree to find the Xid's topmost parent, and then see - * if that is running according to PGXACT or KnownAssignedXids. This is the - * slowest way, but sadly it has to be done always if the others failed, - * unless we see that the cached subxact sets are complete (none have + * if that is running according to ProcGlobal->xids[] or KnownAssignedXids. + * This is the slowest way, but sadly it has to be done always if the others + * failed, unless we see that the cached subxact sets are complete (none have * overflowed). * * ProcArrayLock has to be held while we do 1, 2, 3. If we save the top Xids * while doing 1 and 3, we can release the ProcArrayLock while we do 4. * This buys back some concurrency (and we can't retrieve the main Xids from - * PGXACT again anyway; see GetNewTransactionId). + * ProcGlobal->xids[] again anyway; see GetNewTransactionId). */ bool TransactionIdIsInProgress(TransactionId xid) { static TransactionId *xids = NULL; + static TransactionId *other_xids; int nxids = 0; ProcArrayStruct *arrayP = procArray; TransactionId topxid; - int i, - j; + int mypgxactoff; + size_t numProcs; + int j; /* * Don't bother checking a transaction older than RecentXmin; it could not @@ -1181,6 +1248,8 @@ TransactionIdIsInProgress(TransactionId xid) errmsg("out of memory"))); } + other_xids = ProcGlobal->xids; + LWLockAcquire(ProcArrayLock, LW_SHARED); /* @@ -1196,20 +1265,22 @@ TransactionIdIsInProgress(TransactionId xid) } /* No shortcuts, gotta grovel through the array */ - for (i = 0; i < arrayP->numProcs; i++) + mypgxactoff = MyProc->pgxactoff; + numProcs = arrayP->numProcs; + for (size_t pgxactoff = 0; pgxactoff < numProcs; pgxactoff++) { - int pgprocno = arrayP->pgprocnos[i]; - PGPROC *proc = &allProcs[pgprocno]; - PGXACT *pgxact = &allPgXact[pgprocno]; + int pgprocno; + PGXACT *pgxact; + PGPROC *proc; TransactionId pxid; int pxids; - /* Ignore my own proc --- dealt with it above */ - if (proc == MyProc) + /* Ignore ourselves --- dealt with it above */ + if (pgxactoff == mypgxactoff) continue; /* Fetch xid just once - see GetNewTransactionId */ - pxid = UINT32_ACCESS_ONCE(pgxact->xid); + pxid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]); if (!TransactionIdIsValid(pxid)) continue; @@ -1234,8 +1305,12 @@ TransactionIdIsInProgress(TransactionId xid) /* * Step 2: check the cached child-Xids arrays */ + pgprocno = arrayP->pgprocnos[pgxactoff]; + pgxact = &allPgXact[pgprocno]; pxids = pgxact->nxids; pg_read_barrier(); /* pairs with barrier in GetNewTransactionId() */ + pgprocno = arrayP->pgprocnos[pgxactoff]; + proc = &allProcs[pgprocno]; for (j = pxids - 1; j >= 0; j--) { /* Fetch xid just once - see GetNewTransactionId */ @@ -1266,7 +1341,7 @@ TransactionIdIsInProgress(TransactionId xid) */ if (RecoveryInProgress()) { - /* none of the PGXACT entries should have XIDs in hot standby mode */ + /* none of the PGPROC entries should have XIDs in hot standby mode */ Assert(nxids == 0); if (KnownAssignedXidExists(xid)) @@ -1321,7 +1396,7 @@ TransactionIdIsInProgress(TransactionId xid) Assert(TransactionIdIsValid(topxid)); if (!TransactionIdEquals(topxid, xid)) { - for (i = 0; i < nxids; i++) + for (int i = 0; i < nxids; i++) { if (TransactionIdEquals(xids[i], topxid)) return true; @@ -1344,6 +1419,7 @@ TransactionIdIsActive(TransactionId xid) { bool result = false; ProcArrayStruct *arrayP = procArray; + TransactionId *other_xids = ProcGlobal->xids; int i; /* @@ -1359,11 +1435,10 @@ TransactionIdIsActive(TransactionId xid) { int pgprocno = arrayP->pgprocnos[i]; PGPROC *proc = &allProcs[pgprocno]; - PGXACT *pgxact = &allPgXact[pgprocno]; TransactionId pxid; /* Fetch xid just once - see GetNewTransactionId */ - pxid = UINT32_ACCESS_ONCE(pgxact->xid); + pxid = UINT32_ACCESS_ONCE(other_xids[i]); if (!TransactionIdIsValid(pxid)) continue; @@ -1424,6 +1499,7 @@ ComputeTransactionHorizons(ComputedHorizons *h) ProcArrayStruct *arrayP = procArray; TransactionId kaxmin; bool in_recovery = RecoveryInProgress(); + TransactionId *other_xids = ProcGlobal->xids; /* inferred after ProcArrayLock is released */ h->catalog_oldest_visible = InvalidTransactionId; @@ -1467,7 +1543,7 @@ ComputeTransactionHorizons(ComputedHorizons *h) TransactionId xmin; /* Fetch xid just once - see GetNewTransactionId */ - xid = UINT32_ACCESS_ONCE(pgxact->xid); + xid = UINT32_ACCESS_ONCE(other_xids[pgprocno]); xmin = UINT32_ACCESS_ONCE(proc->xmin); /* @@ -1752,14 +1828,17 @@ Snapshot GetSnapshotData(Snapshot snapshot) { ProcArrayStruct *arrayP = procArray; + TransactionId *other_xids = ProcGlobal->xids; TransactionId xmin; TransactionId xmax; - int index; - int count = 0; + size_t count = 0; int subcount = 0; bool suboverflowed = false; FullTransactionId latest_completed; TransactionId oldestxid; + int mypgxactoff; + TransactionId myxid; + TransactionId replication_slot_xmin = InvalidTransactionId; TransactionId replication_slot_catalog_xmin = InvalidTransactionId; @@ -1804,6 +1883,10 @@ GetSnapshotData(Snapshot snapshot) LWLockAcquire(ProcArrayLock, LW_SHARED); latest_completed = ShmemVariableCache->latestCompletedFullXid; + mypgxactoff = MyProc->pgxactoff; + myxid = other_xids[mypgxactoff]; + Assert(myxid == MyProc->xidCopy); + oldestxid = ShmemVariableCache->oldestXid; /* xmax is always latestCompletedXid + 1 */ @@ -1814,57 +1897,82 @@ GetSnapshotData(Snapshot snapshot) /* initialize xmin calculation with xmax */ xmin = xmax; + /* take own xid into account, saves a check inside the loop */ + if (TransactionIdIsNormal(myxid) && NormalTransactionIdPrecedes(myxid, xmin)) + xmin = myxid; + snapshot->takenDuringRecovery = RecoveryInProgress(); if (!snapshot->takenDuringRecovery) { - int *pgprocnos = arrayP->pgprocnos; - int numProcs; + size_t numProcs = arrayP->numProcs; + TransactionId *xip = snapshot->xip; + int workspace_count = 0; /* - * Spin over procArray checking xid, xmin, and subxids. The goal is - * to gather all active xids, find the lowest xmin, and try to record - * subxids. + * First collect set of pgxactoff/xids that need to be included in the + * snapshot. */ - numProcs = arrayP->numProcs; - for (index = 0; index < numProcs; index++) + for (size_t pgxactoff = 0; pgxactoff < numProcs; pgxactoff++) { - int pgprocno = pgprocnos[index]; + /* Fetch xid just once - see GetNewTransactionId */ + TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]); + + if (unlikely(xid != 0)) + { + /* + * We don't include our own XIDs (if any) in the snapshot. It + * needs to be includeded in the xmin computation, but we did so + * outside the loop. + */ + if (pgxactoff == mypgxactoff) + continue; + + /* + * The only way we are able to get here with a non-normal xid + * is during bootstrap - with this backend using + * BootstrapTransactionId. But the above test should filter + * that out. + */ + Assert(TransactionIdIsNormal(xid)); + + /* + * If the XID is >= xmax, we can skip it; such transactions will + * be treated as running anyway (and any sub-XIDs will also be >= + * xmax). + */ + if (!NormalTransactionIdPrecedes(xid, xmax)) + continue; + + snapshot_workspace_xid[workspace_count] = xid; + snapshot_workspace_off[workspace_count] = pgxactoff; + workspace_count++; + } + } + + for (ssize_t i = 0; i < workspace_count; i++) + { + int pgxactoff = snapshot_workspace_off[i]; + TransactionId xid = snapshot_workspace_xid[i]; + int pgprocno = arrayP->pgprocnos[pgxactoff]; PGXACT *pgxact = &allPgXact[pgprocno]; - TransactionId xid; + uint8 vacuumFlags = pgxact->vacuumFlags; + int nsubxids; + + Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff); /* * Skip over backends doing logical decoding which manages xmin * separately (check below) and ones running LAZY VACUUM. */ - if (pgxact->vacuumFlags & - (PROC_IN_LOGICAL_DECODING | PROC_IN_VACUUM)) + if (vacuumFlags & (PROC_IN_LOGICAL_DECODING | PROC_IN_VACUUM)) continue; - /* Fetch xid just once - see GetNewTransactionId */ - xid = UINT32_ACCESS_ONCE(pgxact->xid); - - /* - * If the transaction has no XID assigned, we can skip it; it - * won't have sub-XIDs either. If the XID is >= xmax, we can also - * skip it; such transactions will be treated as running anyway - * (and any sub-XIDs will also be >= xmax). - */ - if (!TransactionIdIsNormal(xid) - || !NormalTransactionIdPrecedes(xid, xmax)) - continue; - - /* - * We don't include our own XIDs (if any) in the snapshot, but we - * must include them in xmin. - */ if (NormalTransactionIdPrecedes(xid, xmin)) xmin = xid; - if (pgxact == MyPgXact) - continue; /* Add XID to snapshot. */ - snapshot->xip[count++] = xid; + xip[count++] = xid; /* * Save subtransaction XIDs if possible (if we've already @@ -1881,26 +1989,25 @@ GetSnapshotData(Snapshot snapshot) * * Again, our own XIDs are not included in the snapshot. */ - if (!suboverflowed) + if (suboverflowed) + continue; + + suboverflowed = pgxact->overflowed; + nsubxids = pgxact->nxids; + + if (suboverflowed || nsubxids == 0) + continue; + else { - if (pgxact->overflowed) - suboverflowed = true; - else - { - int nxids = pgxact->nxids; + int pgprocno = arrayP->pgprocnos[pgxactoff]; + PGPROC *proc = &allProcs[pgprocno]; - if (nxids > 0) - { - PGPROC *proc = &allProcs[pgprocno]; + pg_read_barrier(); /* pairs with GetNewTransactionId */ - pg_read_barrier(); /* pairs with GetNewTransactionId */ - - memcpy(snapshot->subxip + subcount, - (void *) proc->subxids.xids, - nxids * sizeof(TransactionId)); - subcount += nxids; - } - } + memcpy(snapshot->subxip + subcount, + (void *) proc->subxids.xids, + nsubxids * sizeof(TransactionId)); + subcount += nsubxids; } } } @@ -2030,6 +2137,7 @@ GetSnapshotData(Snapshot snapshot) } RecentXmin = xmin; + Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin)); snapshot->xmin = xmin; snapshot->xmax = xmax; @@ -2192,7 +2300,7 @@ ProcArrayInstallRestoredXmin(TransactionId xmin, PGPROC *proc) * GetRunningTransactionData -- returns information about running transactions. * * Similar to GetSnapshotData but returns more information. We include - * all PGXACTs with an assigned TransactionId, even VACUUM processes and + * all PGPROCs with an assigned TransactionId, even VACUUM processes and * prepared transactions. * * We acquire XidGenLock and ProcArrayLock, but the caller is responsible for @@ -2207,7 +2315,7 @@ ProcArrayInstallRestoredXmin(TransactionId xmin, PGPROC *proc) * This is never executed during recovery so there is no need to look at * KnownAssignedXids. * - * Dummy PGXACTs from prepared transaction are included, meaning that this + * Dummy PGPROCs from prepared transaction are included, meaning that this * may return entries with duplicated TransactionId values coming from * transaction finishing to prepare. Nothing is done about duplicated * entries here to not hold on ProcArrayLock more than necessary. @@ -2226,6 +2334,7 @@ GetRunningTransactionData(void) static RunningTransactionsData CurrentRunningXactsData; ProcArrayStruct *arrayP = procArray; + TransactionId *other_xids = ProcGlobal->xids; RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData; TransactionId latestCompletedXid; TransactionId oldestRunningXid; @@ -2285,7 +2394,7 @@ GetRunningTransactionData(void) TransactionId xid; /* Fetch xid just once - see GetNewTransactionId */ - xid = UINT32_ACCESS_ONCE(pgxact->xid); + xid = UINT32_ACCESS_ONCE(other_xids[index]); /* * We don't need to store transactions that don't have a TransactionId @@ -2382,7 +2491,7 @@ GetRunningTransactionData(void) * GetOldestActiveTransactionId() * * Similar to GetSnapshotData but returns just oldestActiveXid. We include - * all PGXACTs with an assigned TransactionId, even VACUUM processes. + * all PGPROCs with an assigned TransactionId, even VACUUM processes. * We look at all databases, though there is no need to include WALSender * since this has no effect on hot standby conflicts. * @@ -2397,6 +2506,7 @@ TransactionId GetOldestActiveTransactionId(void) { ProcArrayStruct *arrayP = procArray; + TransactionId *other_xids = ProcGlobal->xids; TransactionId oldestRunningXid; int index; @@ -2419,12 +2529,10 @@ GetOldestActiveTransactionId(void) LWLockAcquire(ProcArrayLock, LW_SHARED); for (index = 0; index < arrayP->numProcs; index++) { - int pgprocno = arrayP->pgprocnos[index]; - PGXACT *pgxact = &allPgXact[pgprocno]; TransactionId xid; /* Fetch xid just once - see GetNewTransactionId */ - xid = UINT32_ACCESS_ONCE(pgxact->xid); + xid = UINT32_ACCESS_ONCE(other_xids[index]); if (!TransactionIdIsNormal(xid)) continue; @@ -2502,8 +2610,8 @@ GetOldestSafeDecodingTransactionId(bool catalogOnly) * If we're not in recovery, we walk over the procarray and collect the * lowest xid. Since we're called with ProcArrayLock held and have * acquired XidGenLock, no entries can vanish concurrently, since - * PGXACT->xid is only set with XidGenLock held and only cleared with - * ProcArrayLock held. + * ProcGlobal->xids[i] is only set with XidGenLock held and only cleared + * with ProcArrayLock held. * * In recovery we can't lower the safe value besides what we've computed * above, so we'll have to wait a bit longer there. We unfortunately can @@ -2512,17 +2620,17 @@ GetOldestSafeDecodingTransactionId(bool catalogOnly) */ if (!recovery_in_progress) { + TransactionId *other_xids = ProcGlobal->xids; + /* - * Spin over procArray collecting all min(PGXACT->xid) + * Spin over procArray collecting min(ProcGlobal->xids[i]) */ for (index = 0; index < arrayP->numProcs; index++) { - int pgprocno = arrayP->pgprocnos[index]; - PGXACT *pgxact = &allPgXact[pgprocno]; TransactionId xid; /* Fetch xid just once - see GetNewTransactionId */ - xid = UINT32_ACCESS_ONCE(pgxact->xid); + xid = UINT32_ACCESS_ONCE(other_xids[index]); if (!TransactionIdIsNormal(xid)) continue; @@ -2710,6 +2818,7 @@ BackendXidGetPid(TransactionId xid) { int result = 0; ProcArrayStruct *arrayP = procArray; + TransactionId *other_xids = ProcGlobal->xids; int index; if (xid == InvalidTransactionId) /* never match invalid xid */ @@ -2721,9 +2830,8 @@ BackendXidGetPid(TransactionId xid) { int pgprocno = arrayP->pgprocnos[index]; PGPROC *proc = &allProcs[pgprocno]; - PGXACT *pgxact = &allPgXact[pgprocno]; - if (pgxact->xid == xid) + if (other_xids[index] == xid) { result = proc->pid; break; @@ -3003,7 +3111,6 @@ MinimumActiveBackends(int min) { int pgprocno = arrayP->pgprocnos[index]; PGPROC *proc = &allProcs[pgprocno]; - PGXACT *pgxact = &allPgXact[pgprocno]; /* * Since we're not holding a lock, need to be prepared to deal with @@ -3020,7 +3127,7 @@ MinimumActiveBackends(int min) continue; /* do not count deleted entries */ if (proc == MyProc) continue; /* do not count myself */ - if (pgxact->xid == InvalidTransactionId) + if (proc->xidCopy == InvalidTransactionId) continue; /* do not count if no XID assigned */ if (proc->pid == 0) continue; /* do not count prepared xacts */ @@ -3446,8 +3553,8 @@ XidCacheRemoveRunningXids(TransactionId xid, * * Note that we do not have to be careful about memory ordering of our own * reads wrt. GetNewTransactionId() here - only this process can modify - * relevant fields of MyProc/MyPgXact. But we do have to be careful about - * our own writes being well ordered. + * relevant fields of MyProc/ProcGlobal->xids[]. But we do have to be + * careful about our own writes being well ordered. */ LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); @@ -3801,7 +3908,7 @@ FullXidViaRelative(FullTransactionId rel, TransactionId xid) * In Hot Standby mode, we maintain a list of transactions that are (or were) * running in the master at the current point in WAL. These XIDs must be * treated as running by standby transactions, even though they are not in - * the standby server's PGXACT array. + * the standby server's ProcGlobal->xids[] array. * * We record all XIDs that we know have been assigned. That includes all the * XIDs seen in WAL records, plus all unobserved XIDs that we can deduce have diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index ad048bc85fa..b353c2f8005 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -417,9 +417,7 @@ BackendIdGetTransactionIds(int backendID, TransactionId *xid, TransactionId *xmi if (proc != NULL) { - PGXACT *xact = &ProcGlobal->allPgXact[proc->pgprocno]; - - *xid = xact->xid; + *xid = proc->xidCopy; *xmin = proc->xmin; } } diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index efb44a25c42..5cd9a81bde8 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -3974,9 +3974,8 @@ GetRunningTransactionLocks(int *nlocks) proclock->tag.myLock->tag.locktag_type == LOCKTAG_RELATION) { PGPROC *proc = proclock->tag.myProc; - PGXACT *pgxact = &ProcGlobal->allPgXact[proc->pgprocno]; LOCK *lock = proclock->tag.myLock; - TransactionId xid = pgxact->xid; + TransactionId xid = proc->xidCopy; /* * Don't record locks for transactions if we know they have diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 66d25dba7f8..4dc588223c1 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -102,21 +102,17 @@ Size ProcGlobalShmemSize(void) { Size size = 0; + uint32 TotalProcs = MaxBackends + NUM_AUXILIARY_PROCS + max_prepared_xacts; /* ProcGlobal */ size = add_size(size, sizeof(PROC_HDR)); - /* MyProcs, including autovacuum workers and launcher */ - size = add_size(size, mul_size(MaxBackends, sizeof(PGPROC))); - /* AuxiliaryProcs */ - size = add_size(size, mul_size(NUM_AUXILIARY_PROCS, sizeof(PGPROC))); - /* Prepared xacts */ - size = add_size(size, mul_size(max_prepared_xacts, sizeof(PGPROC))); - /* ProcStructLock */ + size = add_size(size, mul_size(TotalProcs, sizeof(PGPROC))); size = add_size(size, sizeof(slock_t)); size = add_size(size, mul_size(MaxBackends, sizeof(PGXACT))); size = add_size(size, mul_size(NUM_AUXILIARY_PROCS, sizeof(PGXACT))); size = add_size(size, mul_size(max_prepared_xacts, sizeof(PGXACT))); + size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->xids))); return size; } @@ -216,6 +212,25 @@ InitProcGlobal(void) MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT)); ProcGlobal->allPgXact = pgxacts; + /* + * Also allocate a separate arrays for data that is frequently (e.g. by + * GetSnapshotData()) accessed from outside a backend. There is one entry + * in each for every *live* PGPROC entry, and they are densely packed so + * that the first procArray->numProc entries are all valid. The entries + * for a PGPROC in those arrays are at PGPROC->pgxactoff. + * + * Note that they may not be accessed without ProcArrayLock held! Upon + * ProcArrayRemove() later entries will be moved. + * + * These are separate from the main PGPROC array so that the most heavily + * accessed data is stored contiguously in memory in as few cache lines as + * possible. This provides significant performance benefits, especially on + * a multiprocessor system. + */ + // XXX: Pad to cacheline (or even page?)! + ProcGlobal->xids = (TransactionId *) ShmemAlloc(TotalProcs * sizeof(*ProcGlobal->xids)); + MemSet(ProcGlobal->xids, 0, TotalProcs * sizeof(*ProcGlobal->xids)); + for (i = 0; i < TotalProcs; i++) { /* Common initialization for all PGPROCs, regardless of type. */ @@ -387,7 +402,7 @@ InitProcess(void) MyProc->lxid = InvalidLocalTransactionId; MyProc->fpVXIDLock = false; MyProc->fpLocalTransactionId = InvalidLocalTransactionId; - MyPgXact->xid = InvalidTransactionId; + MyProc->xidCopy = InvalidTransactionId; MyProc->xmin = InvalidTransactionId; MyProc->pid = MyProcPid; /* backendId, databaseId and roleId will be filled in later */ @@ -571,7 +586,7 @@ InitAuxiliaryProcess(void) MyProc->lxid = InvalidLocalTransactionId; MyProc->fpVXIDLock = false; MyProc->fpLocalTransactionId = InvalidLocalTransactionId; - MyPgXact->xid = InvalidTransactionId; + MyProc->xidCopy = InvalidTransactionId; MyProc->xmin = InvalidTransactionId; MyProc->backendId = InvalidBackendId; MyProc->databaseId = InvalidOid; -- 2.25.0.114.g5b0ca878e0