Commit 941697c3 authored by Andres Freund's avatar Andres Freund

snapshot scalability: Introduce dense array of in-progress xids.

The new array contains the xids for all connected backends / in-use
PGPROC entries in a dense manner (in contrast to the PGPROC/PGXACT
arrays which can have unused entries interspersed).

This improves performance because GetSnapshotData() always needs to
scan the xids of all live procarray entries and now there's no need to
go through the procArray->pgprocnos indirection anymore.

As the set of running top-level xids changes rarely, compared to the
number of snapshots taken, this substantially increases the likelihood
of most data required for a snapshot being in l2 cache.  In
read-mostly workloads scanning the xids[] array will sufficient to
build a snapshot, as most backends will not have an xid assigned.

To keep the xid array dense ProcArrayRemove() needs to move entries
behind the to-be-removed proc's one further up in the array. Obviously
moving array entries cannot happen while a backend sets it
xid. I.e. locking needs to prevent that array entries are moved while
a backend modifies its xid.

To avoid locking ProcArrayLock in GetNewTransactionId() - a fairly hot
spot already - ProcArrayAdd() / ProcArrayRemove() now needs to hold
XidGenLock in addition to ProcArrayLock. Adding / Removing a procarray
entry is not a very frequent operation, even taking 2PC into account.

Due to the above, the dense array entries can only be read or modified
while holding ProcArrayLock and/or XidGenLock. This prevents a
concurrent ProcArrayRemove() from shifting the dense array while it is
accessed concurrently.

While the new dense array is very good when needing to look at all
xids it is less suitable when accessing a single backend's xid. In
particular it would be problematic to have to acquire a lock to access
a backend's own xid. Therefore a backend's xid is not just stored in
the dense array, but also in PGPROC. This also allows a backend to
only access the shared xid value when the backend had acquired an
xid.

The infrastructure added in this commit will be used for the remaining
PGXACT fields in subsequent commits. They are kept separate to make
review easier.

Author: Andres Freund <andres@anarazel.de>
Reviewed-By: default avatarRobert Haas <robertmhaas@gmail.com>
Reviewed-By: default avatarThomas Munro <thomas.munro@gmail.com>
Reviewed-By: default avatarDavid Rowley <dgrowleyml@gmail.com>
Discussion: https://postgr.es/m/20200301083601.ews6hz5dduc3w2se@alap3.anarazel.de
parent 2ba5b2db
...@@ -11,12 +11,12 @@ ...@@ -11,12 +11,12 @@
* shared buffer content lock on the buffer containing the tuple. * shared buffer content lock on the buffer containing the tuple.
* *
* NOTE: When using a non-MVCC snapshot, we must check * 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 * before TransactionIdDidCommit/TransactionIdDidAbort (which look in
* pg_xact). Otherwise we have a race condition: we might decide that a * pg_xact). Otherwise we have a race condition: we might decide that a
* just-committed transaction crashed, because none of the tests succeed. * just-committed transaction crashed, because none of the tests succeed.
* xact.c is careful to record commit/abort in pg_xact before it unsets * 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 * also means there is a window where TransactionIdIsInProgress and
* TransactionIdDidCommit will both return true. If we check only * TransactionIdDidCommit will both return true. If we check only
* TransactionIdDidCommit, we could consider a tuple committed when a * TransactionIdDidCommit, we could consider a tuple committed when a
...@@ -956,7 +956,7 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot, ...@@ -956,7 +956,7 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
* coding where we tried to set the hint bits as soon as possible, we instead * 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 * did TransactionIdIsInProgress in each call --- to no avail, as long as the
* inserting/deleting transaction was still running --- which was more cycles * inserting/deleting transaction was still running --- which was more cycles
* and more contention on the PGXACT array. * and more contention on ProcArrayLock.
*/ */
static bool static bool
HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot, HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
...@@ -1459,7 +1459,7 @@ HeapTupleSatisfiesNonVacuumable(HeapTuple htup, Snapshot snapshot, ...@@ -1459,7 +1459,7 @@ HeapTupleSatisfiesNonVacuumable(HeapTuple htup, Snapshot snapshot,
* HeapTupleSatisfiesMVCC) and, therefore, any hint bits that can be set * 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 * 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 * 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 * It's okay to return false when in doubt, but we must return true only
* if the tuple is removable. * if the tuple is removable.
*/ */
......
...@@ -251,10 +251,10 @@ enforce, and it assists with some other issues as explained below.) The ...@@ -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 implementation of this is that GetSnapshotData takes the ProcArrayLock in
shared mode (so that multiple backends can take snapshots in parallel), shared mode (so that multiple backends can take snapshots in parallel),
but ProcArrayEndTransaction must take the ProcArrayLock in exclusive mode but ProcArrayEndTransaction must take the ProcArrayLock in exclusive mode
while clearing MyPgXact->xid at transaction end (either commit or abort). while clearing the ProcGlobal->xids[] entry at transaction end (either
(To reduce context switching, when multiple transactions commit nearly commit or abort). (To reduce context switching, when multiple transactions
simultaneously, we have one backend take ProcArrayLock and clear the XIDs commit nearly simultaneously, we have one backend take ProcArrayLock and
of multiple processes at once.) clear the XIDs of multiple processes at once.)
ProcArrayEndTransaction also holds the lock while advancing the shared ProcArrayEndTransaction also holds the lock while advancing the shared
latestCompletedXid variable. This allows GetSnapshotData to use latestCompletedXid variable. This allows GetSnapshotData to use
...@@ -278,12 +278,12 @@ present in the ProcArray, or not running anymore. (This guarantee doesn't ...@@ -278,12 +278,12 @@ present in the ProcArray, or not running anymore. (This guarantee doesn't
apply to subtransaction XIDs, because of the possibility that there's not 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 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 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 before storing its XID into ProcGlobal->xids[], then it would be possible for
backend to allocate and commit a later XID, causing latestCompletedXid to another backend to allocate and commit a later XID, causing latestCompletedXid
pass the first backend's XID, before that value became visible in the to pass the first backend's XID, before that value became visible in the
ProcArray. That would break ComputeXidHorizons, as discussed below. ProcArray. That would break ComputeXidHorizons, 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 subxid array) without taking ProcArrayLock. This was once necessary to
avoid deadlock; while that is no longer the case, it's still beneficial for 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, performance. We are thereby relying on fetch/store of an XID to be atomic,
...@@ -382,12 +382,13 @@ Top-level transactions do not have a parent, so they leave their pg_subtrans ...@@ -382,12 +382,13 @@ Top-level transactions do not have a parent, so they leave their pg_subtrans
entries set to the default value of zero (InvalidTransactionId). entries set to the default value of zero (InvalidTransactionId).
pg_subtrans is used to check whether the transaction in question is still 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, running --- the main Xid of a transaction is recorded in ProcGlobal->xids[],
but since we allow arbitrary nesting of subtransactions, we can't fit all Xids with a copy in PGPROC->xid, but since we allow arbitrary nesting of
in shared memory, so we have to store them on disk. Note, however, that for subtransactions, we can't fit all Xids in shared memory, so we have to store
each transaction we keep a "cache" of Xids that are known to be part of the them on disk. Note, however, that for each transaction we keep a "cache" of
transaction tree, so we can skip looking at pg_subtrans unless we know the Xids that are known to be part of the transaction tree, so we can skip looking
cache has been overflowed. See storage/ipc/procarray.c for the gory details. 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 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 implements the LRU policy for in-memory buffer pages. The high-level routines
......
...@@ -285,15 +285,15 @@ TransactionIdSetPageStatus(TransactionId xid, int nsubxids, ...@@ -285,15 +285,15 @@ TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
* updates for multiple backends so that the number of times XactSLRULock * updates for multiple backends so that the number of times XactSLRULock
* needs to be acquired is reduced. * needs to be acquired is reduced.
* *
* For this optimization to be safe, the XID in MyPgXact and the subxids * For this optimization to be safe, the XID and subxids in MyProc must be
* in MyProc must be the same as the ones for which we're setting the * the same as the ones for which we're setting the status. Check that
* status. Check that this is the case. * this is the case.
* *
* For this optimization to be efficient, we shouldn't have too many * 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 * sub-XIDs and all of the XIDs for which we're adjusting clog should be
* on the same page. Check those conditions, too. * on the same page. Check those conditions, too.
*/ */
if (all_xact_same_page && xid == MyPgXact->xid && if (all_xact_same_page && xid == MyProc->xid &&
nsubxids <= THRESHOLD_SUBTRANS_CLOG_OPT && nsubxids <= THRESHOLD_SUBTRANS_CLOG_OPT &&
nsubxids == MyPgXact->nxids && nsubxids == MyPgXact->nxids &&
memcmp(subxids, MyProc->subxids.xids, memcmp(subxids, MyProc->subxids.xids,
......
...@@ -351,7 +351,7 @@ AtAbort_Twophase(void) ...@@ -351,7 +351,7 @@ AtAbort_Twophase(void)
/* /*
* This is called after we have finished transferring state to the prepared * This is called after we have finished transferring state to the prepared
* PGXACT entry. * PGPROC entry.
*/ */
void void
PostPrepare_Twophase(void) PostPrepare_Twophase(void)
...@@ -463,7 +463,7 @@ MarkAsPreparingGuts(GlobalTransaction gxact, TransactionId xid, const char *gid, ...@@ -463,7 +463,7 @@ MarkAsPreparingGuts(GlobalTransaction gxact, TransactionId xid, const char *gid,
proc->waitStatus = PROC_WAIT_STATUS_OK; proc->waitStatus = PROC_WAIT_STATUS_OK;
/* We set up the gxact's VXID as InvalidBackendId/XID */ /* We set up the gxact's VXID as InvalidBackendId/XID */
proc->lxid = (LocalTransactionId) xid; proc->lxid = (LocalTransactionId) xid;
pgxact->xid = xid; proc->xid = xid;
Assert(proc->xmin == InvalidTransactionId); Assert(proc->xmin == InvalidTransactionId);
proc->delayChkpt = false; proc->delayChkpt = false;
pgxact->vacuumFlags = 0; pgxact->vacuumFlags = 0;
...@@ -768,7 +768,6 @@ pg_prepared_xact(PG_FUNCTION_ARGS) ...@@ -768,7 +768,6 @@ pg_prepared_xact(PG_FUNCTION_ARGS)
{ {
GlobalTransaction gxact = &status->array[status->currIdx++]; GlobalTransaction gxact = &status->array[status->currIdx++];
PGPROC *proc = &ProcGlobal->allProcs[gxact->pgprocno]; PGPROC *proc = &ProcGlobal->allProcs[gxact->pgprocno];
PGXACT *pgxact = &ProcGlobal->allPgXact[gxact->pgprocno];
Datum values[5]; Datum values[5];
bool nulls[5]; bool nulls[5];
HeapTuple tuple; HeapTuple tuple;
...@@ -783,7 +782,7 @@ pg_prepared_xact(PG_FUNCTION_ARGS) ...@@ -783,7 +782,7 @@ pg_prepared_xact(PG_FUNCTION_ARGS)
MemSet(values, 0, sizeof(values)); MemSet(values, 0, sizeof(values));
MemSet(nulls, 0, sizeof(nulls)); MemSet(nulls, 0, sizeof(nulls));
values[0] = TransactionIdGetDatum(pgxact->xid); values[0] = TransactionIdGetDatum(proc->xid);
values[1] = CStringGetTextDatum(gxact->gid); values[1] = CStringGetTextDatum(gxact->gid);
values[2] = TimestampTzGetDatum(gxact->prepared_at); values[2] = TimestampTzGetDatum(gxact->prepared_at);
values[3] = ObjectIdGetDatum(gxact->owner); values[3] = ObjectIdGetDatum(gxact->owner);
...@@ -829,9 +828,8 @@ TwoPhaseGetGXact(TransactionId xid, bool lock_held) ...@@ -829,9 +828,8 @@ TwoPhaseGetGXact(TransactionId xid, bool lock_held)
for (i = 0; i < TwoPhaseState->numPrepXacts; i++) for (i = 0; i < TwoPhaseState->numPrepXacts; i++)
{ {
GlobalTransaction gxact = TwoPhaseState->prepXacts[i]; GlobalTransaction gxact = TwoPhaseState->prepXacts[i];
PGXACT *pgxact = &ProcGlobal->allPgXact[gxact->pgprocno];
if (pgxact->xid == xid) if (gxact->xid == xid)
{ {
result = gxact; result = gxact;
break; break;
...@@ -987,8 +985,7 @@ void ...@@ -987,8 +985,7 @@ void
StartPrepare(GlobalTransaction gxact) StartPrepare(GlobalTransaction gxact)
{ {
PGPROC *proc = &ProcGlobal->allProcs[gxact->pgprocno]; PGPROC *proc = &ProcGlobal->allProcs[gxact->pgprocno];
PGXACT *pgxact = &ProcGlobal->allPgXact[gxact->pgprocno]; TransactionId xid = gxact->xid;
TransactionId xid = pgxact->xid;
TwoPhaseFileHeader hdr; TwoPhaseFileHeader hdr;
TransactionId *children; TransactionId *children;
RelFileNode *commitrels; RelFileNode *commitrels;
...@@ -1140,15 +1137,15 @@ EndPrepare(GlobalTransaction gxact) ...@@ -1140,15 +1137,15 @@ EndPrepare(GlobalTransaction gxact)
/* /*
* Mark the prepared transaction as valid. As soon as xact.c marks * 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. * this function returns), others can commit/rollback the xact.
* *
* NB: a side effect of this is to make a dummy ProcArray entry for the * 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, * prepared XID. This must happen before we clear the XID from MyProc /
* else there is a window where the XID is not running according to * ProcGlobal->xids[], else there is a window where the XID is not running
* TransactionIdIsInProgress, and onlookers would be entitled to assume * according to TransactionIdIsInProgress, and onlookers would be entitled
* the xact crashed. Instead we have a window where the same XID appears * to assume the xact crashed. Instead we have a window where the same
* twice in ProcArray, which is OK. * XID appears twice in ProcArray, which is OK.
*/ */
MarkAsPrepared(gxact, false); MarkAsPrepared(gxact, false);
...@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit) ...@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
{ {
GlobalTransaction gxact; GlobalTransaction gxact;
PGPROC *proc; PGPROC *proc;
PGXACT *pgxact;
TransactionId xid; TransactionId xid;
char *buf; char *buf;
char *bufptr; char *bufptr;
...@@ -1423,8 +1419,7 @@ FinishPreparedTransaction(const char *gid, bool isCommit) ...@@ -1423,8 +1419,7 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
*/ */
gxact = LockGXact(gid, GetUserId()); gxact = LockGXact(gid, GetUserId());
proc = &ProcGlobal->allProcs[gxact->pgprocno]; proc = &ProcGlobal->allProcs[gxact->pgprocno];
pgxact = &ProcGlobal->allPgXact[gxact->pgprocno]; xid = gxact->xid;
xid = pgxact->xid;
/* /*
* Read and validate 2PC state data. State data will typically be stored * Read and validate 2PC state data. State data will typically be stored
...@@ -1726,7 +1721,7 @@ CheckPointTwoPhase(XLogRecPtr redo_horizon) ...@@ -1726,7 +1721,7 @@ CheckPointTwoPhase(XLogRecPtr redo_horizon)
for (i = 0; i < TwoPhaseState->numPrepXacts; i++) 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 * also
*/ */
GlobalTransaction gxact = TwoPhaseState->prepXacts[i]; GlobalTransaction gxact = TwoPhaseState->prepXacts[i];
......
...@@ -38,7 +38,8 @@ VariableCache ShmemVariableCache = NULL; ...@@ -38,7 +38,8 @@ VariableCache ShmemVariableCache = NULL;
* Allocate the next FullTransactionId for a new transaction or * Allocate the next FullTransactionId for a new transaction or
* subtransaction. * subtransaction.
* *
* The new XID is also stored into MyPgXact before returning. * The new XID is also stored into MyProc->xid/ProcGlobal->xids[] before
* returning.
* *
* Note: when this is called, we are actually already inside a valid * Note: when this is called, we are actually already inside a valid
* transaction, since XIDs are now not allocated until the transaction * transaction, since XIDs are now not allocated until the transaction
...@@ -65,7 +66,8 @@ GetNewTransactionId(bool isSubXact) ...@@ -65,7 +66,8 @@ GetNewTransactionId(bool isSubXact)
if (IsBootstrapProcessingMode()) if (IsBootstrapProcessingMode())
{ {
Assert(!isSubXact); Assert(!isSubXact);
MyPgXact->xid = BootstrapTransactionId; MyProc->xid = BootstrapTransactionId;
ProcGlobal->xids[MyProc->pgxactoff] = BootstrapTransactionId;
return FullTransactionIdFromEpochAndXid(0, BootstrapTransactionId); return FullTransactionIdFromEpochAndXid(0, BootstrapTransactionId);
} }
...@@ -190,10 +192,10 @@ GetNewTransactionId(bool isSubXact) ...@@ -190,10 +192,10 @@ GetNewTransactionId(bool isSubXact)
* latestCompletedXid is present in the ProcArray, which is essential for * latestCompletedXid is present in the ProcArray, which is essential for
* correct OldestXmin tracking; see src/backend/access/transam/README. * correct OldestXmin tracking; see src/backend/access/transam/README.
* *
* Note that readers of PGXACT xid fields should be careful to fetch the * Note that readers of ProcGlobal->xids/PGPROC->xid should be careful
* value only once, rather than assume they can read a value multiple * to fetch the value for each proc only once, rather than assume they can
* times and get the same answer each time. Note we are assuming that * read a value multiple times and get the same answer each time. Note we
* TransactionId and int fetch/store are atomic. * are assuming that TransactionId and int fetch/store are atomic.
* *
* The same comments apply to the subxact xid count and overflow fields. * The same comments apply to the subxact xid count and overflow fields.
* *
...@@ -219,7 +221,11 @@ GetNewTransactionId(bool isSubXact) ...@@ -219,7 +221,11 @@ GetNewTransactionId(bool isSubXact)
* answer later on when someone does have a reason to inquire.) * answer later on when someone does have a reason to inquire.)
*/ */
if (!isSubXact) if (!isSubXact)
MyPgXact->xid = xid; /* LWLockRelease acts as barrier */ {
/* LWLockRelease acts as barrier */
MyProc->xid = xid;
ProcGlobal->xids[MyProc->pgxactoff] = xid;
}
else else
{ {
int nxids = MyPgXact->nxids; int nxids = MyPgXact->nxids;
......
...@@ -1724,7 +1724,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params) ...@@ -1724,7 +1724,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
* *
* Note: these flags remain set until CommitTransaction or * Note: these flags remain set until CommitTransaction or
* AbortTransaction. We don't want to clear them until we reset * AbortTransaction. We don't want to clear them until we reset
* MyPgXact->xid/xmin, otherwise GetOldestNonRemovableTransactionId() * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
* might appear to go backwards, which is probably Not Good. * might appear to go backwards, which is probably Not Good.
*/ */
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
......
...@@ -9,8 +9,9 @@ ...@@ -9,8 +9,9 @@
* one is as a means of determining the set of currently running transactions. * 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 * Because of various subtle race conditions it is critical that a backend
* hold the correct locks while setting or clearing its MyPgXact->xid field. * hold the correct locks while setting or clearing its xid (in
* See notes in src/backend/access/transam/README. * ProcGlobal->xids[]/MyProc->xid). See notes in
* src/backend/access/transam/README.
* *
* The process arrays now also include structures representing prepared * The process arrays now also include structures representing prepared
* transactions. The xid and subxids fields of these are valid, as are the * transactions. The xid and subxids fields of these are valid, as are the
...@@ -436,7 +437,9 @@ ProcArrayAdd(PGPROC *proc) ...@@ -436,7 +437,9 @@ ProcArrayAdd(PGPROC *proc)
ProcArrayStruct *arrayP = procArray; ProcArrayStruct *arrayP = procArray;
int index; int index;
/* See ProcGlobal comment explaining why both locks are held */
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
if (arrayP->numProcs >= arrayP->maxProcs) if (arrayP->numProcs >= arrayP->maxProcs)
{ {
...@@ -445,7 +448,6 @@ ProcArrayAdd(PGPROC *proc) ...@@ -445,7 +448,6 @@ ProcArrayAdd(PGPROC *proc)
* fixed supply of PGPROC structs too, and so we should have failed * fixed supply of PGPROC structs too, and so we should have failed
* earlier.) * earlier.)
*/ */
LWLockRelease(ProcArrayLock);
ereport(FATAL, ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS), (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
errmsg("sorry, too many clients already"))); errmsg("sorry, too many clients already")));
...@@ -471,10 +473,25 @@ ProcArrayAdd(PGPROC *proc) ...@@ -471,10 +473,25 @@ ProcArrayAdd(PGPROC *proc)
} }
memmove(&arrayP->pgprocnos[index + 1], &arrayP->pgprocnos[index], 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; arrayP->pgprocnos[index] = proc->pgprocno;
ProcGlobal->xids[index] = proc->xid;
arrayP->numProcs++; 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); LWLockRelease(ProcArrayLock);
} }
...@@ -500,36 +517,58 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid) ...@@ -500,36 +517,58 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid)
DisplayXidCache(); DisplayXidCache();
#endif #endif
/* See ProcGlobal comment explaining why both locks are held */
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
Assert(ProcGlobal->allProcs[arrayP->pgprocnos[proc->pgxactoff]].pgxactoff == proc->pgxactoff);
if (TransactionIdIsValid(latestXid)) if (TransactionIdIsValid(latestXid))
{ {
Assert(TransactionIdIsValid(allPgXact[proc->pgprocno].xid)); Assert(TransactionIdIsValid(ProcGlobal->xids[proc->pgxactoff]));
/* Advance global latestCompletedXid while holding the lock */ /* Advance global latestCompletedXid while holding the lock */
MaintainLatestCompletedXid(latestXid); MaintainLatestCompletedXid(latestXid);
ProcGlobal->xids[proc->pgxactoff] = 0;
} }
else else
{ {
/* Shouldn't be trying to remove a live transaction here */ /* 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++) for (index = 0; index < arrayP->numProcs; index++)
{ {
if (arrayP->pgprocnos[index] == proc->pgprocno) if (arrayP->pgprocnos[index] == proc->pgprocno)
{ {
/* Keep the PGPROC array sorted. See notes above */ /* Keep the PGPROC array sorted. See notes above */
memmove(&arrayP->pgprocnos[index], &arrayP->pgprocnos[index + 1], 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->pgprocnos[arrayP->numProcs - 1] = -1; /* for debugging */
arrayP->numProcs--; arrayP->numProcs--;
/* adjust for removed PGPROC */
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); LWLockRelease(ProcArrayLock);
return; return;
} }
} }
/* Oops */ /* Oops */
LWLockRelease(XidGenLock);
LWLockRelease(ProcArrayLock); LWLockRelease(ProcArrayLock);
elog(LOG, "failed to find proc %p in ProcArray", proc); elog(LOG, "failed to find proc %p in ProcArray", proc);
...@@ -562,7 +601,7 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid) ...@@ -562,7 +601,7 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid)
* else is taking a snapshot. See discussion in * else is taking a snapshot. See discussion in
* src/backend/access/transam/README. * src/backend/access/transam/README.
*/ */
Assert(TransactionIdIsValid(allPgXact[proc->pgprocno].xid)); Assert(TransactionIdIsValid(proc->xid));
/* /*
* If we can immediately acquire ProcArrayLock, we clear our own XID * If we can immediately acquire ProcArrayLock, we clear our own XID
...@@ -584,7 +623,7 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid) ...@@ -584,7 +623,7 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid)
* anyone else's calculation of a snapshot. We might change their * anyone else's calculation of a snapshot. We might change their
* estimate of global xmin, but that's OK. * estimate of global xmin, but that's OK.
*/ */
Assert(!TransactionIdIsValid(allPgXact[proc->pgprocno].xid)); Assert(!TransactionIdIsValid(proc->xid));
proc->lxid = InvalidLocalTransactionId; proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */ /* must be cleared with xid/xmin: */
...@@ -607,7 +646,13 @@ static inline void ...@@ -607,7 +646,13 @@ static inline void
ProcArrayEndTransactionInternal(PGPROC *proc, PGXACT *pgxact, ProcArrayEndTransactionInternal(PGPROC *proc, PGXACT *pgxact,
TransactionId latestXid) TransactionId latestXid)
{ {
pgxact->xid = InvalidTransactionId; size_t pgxactoff = proc->pgxactoff;
Assert(TransactionIdIsValid(ProcGlobal->xids[pgxactoff]));
Assert(ProcGlobal->xids[pgxactoff] == proc->xid);
ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId; proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */ /* must be cleared with xid/xmin: */
pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK; pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
...@@ -643,7 +688,7 @@ ProcArrayGroupClearXid(PGPROC *proc, TransactionId latestXid) ...@@ -643,7 +688,7 @@ ProcArrayGroupClearXid(PGPROC *proc, TransactionId latestXid)
uint32 wakeidx; uint32 wakeidx;
/* We should definitely have an XID to clear. */ /* We should definitely have an XID to clear. */
Assert(TransactionIdIsValid(allPgXact[proc->pgprocno].xid)); Assert(TransactionIdIsValid(proc->xid));
/* Add ourselves to the list of processes needing a group XID clear. */ /* Add ourselves to the list of processes needing a group XID clear. */
proc->procArrayGroupMember = true; proc->procArrayGroupMember = true;
...@@ -748,20 +793,28 @@ ProcArrayGroupClearXid(PGPROC *proc, TransactionId latestXid) ...@@ -748,20 +793,28 @@ ProcArrayGroupClearXid(PGPROC *proc, TransactionId latestXid)
* This is used after successfully preparing a 2-phase transaction. We are * This is used after successfully preparing a 2-phase transaction. We are
* not actually reporting the transaction's XID as no longer running --- it * 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 * 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 void
ProcArrayClearTransaction(PGPROC *proc) ProcArrayClearTransaction(PGPROC *proc)
{ {
PGXACT *pgxact = &allPgXact[proc->pgprocno]; PGXACT *pgxact = &allPgXact[proc->pgprocno];
size_t pgxactoff;
/* /*
* We can skip locking ProcArrayLock here, because this action does not * We can skip locking ProcArrayLock exclusively here, because this action
* actually change anyone's view of the set of running XIDs: our entry is * does not actually change anyone's view of the set of running XIDs: our
* duplicate with the gxact that has already been inserted into the * entry is duplicate with the gxact that has already been inserted into
* ProcArray. * 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->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId; proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId; proc->xmin = InvalidTransactionId;
proc->recoveryConflictPending = false; proc->recoveryConflictPending = false;
...@@ -773,6 +826,8 @@ ProcArrayClearTransaction(PGPROC *proc) ...@@ -773,6 +826,8 @@ ProcArrayClearTransaction(PGPROC *proc)
/* Clear the subtransaction-XID cache too */ /* Clear the subtransaction-XID cache too */
pgxact->nxids = 0; pgxact->nxids = 0;
pgxact->overflowed = false; pgxact->overflowed = false;
LWLockRelease(ProcArrayLock);
} }
/* /*
...@@ -1167,7 +1222,7 @@ ProcArrayApplyXidAssignment(TransactionId topxid, ...@@ -1167,7 +1222,7 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* there are four possibilities for finding a running transaction: * there are four possibilities for finding a running transaction:
* *
* 1. The given Xid is a main transaction Id. We will find this out cheaply * 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. * 2. The given Xid is one of the cached subxact Xids in the PGPROC array.
* We can find this out cheaply too. * We can find this out cheaply too.
...@@ -1176,26 +1231,28 @@ ProcArrayApplyXidAssignment(TransactionId topxid, ...@@ -1176,26 +1231,28 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* if the Xid is running on the primary. * if the Xid is running on the primary.
* *
* 4. Search the SubTrans tree to find the Xid's topmost parent, and then see * 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 * if that is running according to ProcGlobal->xids[] or KnownAssignedXids.
* slowest way, but sadly it has to be done always if the others failed, * This is the slowest way, but sadly it has to be done always if the others
* unless we see that the cached subxact sets are complete (none have * failed, unless we see that the cached subxact sets are complete (none have
* overflowed). * overflowed).
* *
* ProcArrayLock has to be held while we do 1, 2, 3. If we save the top Xids * 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. * 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 * 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 bool
TransactionIdIsInProgress(TransactionId xid) TransactionIdIsInProgress(TransactionId xid)
{ {
static TransactionId *xids = NULL; static TransactionId *xids = NULL;
static TransactionId *other_xids;
int nxids = 0; int nxids = 0;
ProcArrayStruct *arrayP = procArray; ProcArrayStruct *arrayP = procArray;
TransactionId topxid; TransactionId topxid;
TransactionId latestCompletedXid; TransactionId latestCompletedXid;
int i, int mypgxactoff;
j; size_t numProcs;
int j;
/* /*
* Don't bother checking a transaction older than RecentXmin; it could not * Don't bother checking a transaction older than RecentXmin; it could not
...@@ -1250,6 +1307,8 @@ TransactionIdIsInProgress(TransactionId xid) ...@@ -1250,6 +1307,8 @@ TransactionIdIsInProgress(TransactionId xid)
errmsg("out of memory"))); errmsg("out of memory")));
} }
other_xids = ProcGlobal->xids;
LWLockAcquire(ProcArrayLock, LW_SHARED); LWLockAcquire(ProcArrayLock, LW_SHARED);
/* /*
...@@ -1266,20 +1325,22 @@ TransactionIdIsInProgress(TransactionId xid) ...@@ -1266,20 +1325,22 @@ TransactionIdIsInProgress(TransactionId xid)
} }
/* No shortcuts, gotta grovel through the array */ /* 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]; int pgprocno;
PGPROC *proc = &allProcs[pgprocno]; PGXACT *pgxact;
PGXACT *pgxact = &allPgXact[pgprocno]; PGPROC *proc;
TransactionId pxid; TransactionId pxid;
int pxids; int pxids;
/* Ignore my own proc --- dealt with it above */ /* Ignore ourselves --- dealt with it above */
if (proc == MyProc) if (pgxactoff == mypgxactoff)
continue; continue;
/* Fetch xid just once - see GetNewTransactionId */ /* Fetch xid just once - see GetNewTransactionId */
pxid = UINT32_ACCESS_ONCE(pgxact->xid); pxid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
if (!TransactionIdIsValid(pxid)) if (!TransactionIdIsValid(pxid))
continue; continue;
...@@ -1304,8 +1365,12 @@ TransactionIdIsInProgress(TransactionId xid) ...@@ -1304,8 +1365,12 @@ TransactionIdIsInProgress(TransactionId xid)
/* /*
* Step 2: check the cached child-Xids arrays * Step 2: check the cached child-Xids arrays
*/ */
pgprocno = arrayP->pgprocnos[pgxactoff];
pgxact = &allPgXact[pgprocno];
pxids = pgxact->nxids; pxids = pgxact->nxids;
pg_read_barrier(); /* pairs with barrier in GetNewTransactionId() */ pg_read_barrier(); /* pairs with barrier in GetNewTransactionId() */
pgprocno = arrayP->pgprocnos[pgxactoff];
proc = &allProcs[pgprocno];
for (j = pxids - 1; j >= 0; j--) for (j = pxids - 1; j >= 0; j--)
{ {
/* Fetch xid just once - see GetNewTransactionId */ /* Fetch xid just once - see GetNewTransactionId */
...@@ -1336,7 +1401,7 @@ TransactionIdIsInProgress(TransactionId xid) ...@@ -1336,7 +1401,7 @@ TransactionIdIsInProgress(TransactionId xid)
*/ */
if (RecoveryInProgress()) 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); Assert(nxids == 0);
if (KnownAssignedXidExists(xid)) if (KnownAssignedXidExists(xid))
...@@ -1391,7 +1456,7 @@ TransactionIdIsInProgress(TransactionId xid) ...@@ -1391,7 +1456,7 @@ TransactionIdIsInProgress(TransactionId xid)
Assert(TransactionIdIsValid(topxid)); Assert(TransactionIdIsValid(topxid));
if (!TransactionIdEquals(topxid, xid)) if (!TransactionIdEquals(topxid, xid))
{ {
for (i = 0; i < nxids; i++) for (int i = 0; i < nxids; i++)
{ {
if (TransactionIdEquals(xids[i], topxid)) if (TransactionIdEquals(xids[i], topxid))
return true; return true;
...@@ -1414,6 +1479,7 @@ TransactionIdIsActive(TransactionId xid) ...@@ -1414,6 +1479,7 @@ TransactionIdIsActive(TransactionId xid)
{ {
bool result = false; bool result = false;
ProcArrayStruct *arrayP = procArray; ProcArrayStruct *arrayP = procArray;
TransactionId *other_xids = ProcGlobal->xids;
int i; int i;
/* /*
...@@ -1429,11 +1495,10 @@ TransactionIdIsActive(TransactionId xid) ...@@ -1429,11 +1495,10 @@ TransactionIdIsActive(TransactionId xid)
{ {
int pgprocno = arrayP->pgprocnos[i]; int pgprocno = arrayP->pgprocnos[i];
PGPROC *proc = &allProcs[pgprocno]; PGPROC *proc = &allProcs[pgprocno];
PGXACT *pgxact = &allPgXact[pgprocno];
TransactionId pxid; TransactionId pxid;
/* Fetch xid just once - see GetNewTransactionId */ /* Fetch xid just once - see GetNewTransactionId */
pxid = UINT32_ACCESS_ONCE(pgxact->xid); pxid = UINT32_ACCESS_ONCE(other_xids[i]);
if (!TransactionIdIsValid(pxid)) if (!TransactionIdIsValid(pxid))
continue; continue;
...@@ -1519,6 +1584,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h) ...@@ -1519,6 +1584,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
ProcArrayStruct *arrayP = procArray; ProcArrayStruct *arrayP = procArray;
TransactionId kaxmin; TransactionId kaxmin;
bool in_recovery = RecoveryInProgress(); bool in_recovery = RecoveryInProgress();
TransactionId *other_xids = ProcGlobal->xids;
/* inferred after ProcArrayLock is released */ /* inferred after ProcArrayLock is released */
h->catalog_oldest_nonremovable = InvalidTransactionId; h->catalog_oldest_nonremovable = InvalidTransactionId;
...@@ -1562,7 +1628,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h) ...@@ -1562,7 +1628,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
TransactionId xmin; TransactionId xmin;
/* Fetch xid just once - see GetNewTransactionId */ /* 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); xmin = UINT32_ACCESS_ONCE(proc->xmin);
/* /*
...@@ -1852,14 +1918,17 @@ Snapshot ...@@ -1852,14 +1918,17 @@ Snapshot
GetSnapshotData(Snapshot snapshot) GetSnapshotData(Snapshot snapshot)
{ {
ProcArrayStruct *arrayP = procArray; ProcArrayStruct *arrayP = procArray;
TransactionId *other_xids = ProcGlobal->xids;
TransactionId xmin; TransactionId xmin;
TransactionId xmax; TransactionId xmax;
int index; size_t count = 0;
int count = 0;
int subcount = 0; int subcount = 0;
bool suboverflowed = false; bool suboverflowed = false;
FullTransactionId latest_completed; FullTransactionId latest_completed;
TransactionId oldestxid; TransactionId oldestxid;
int mypgxactoff;
TransactionId myxid;
TransactionId replication_slot_xmin = InvalidTransactionId; TransactionId replication_slot_xmin = InvalidTransactionId;
TransactionId replication_slot_catalog_xmin = InvalidTransactionId; TransactionId replication_slot_catalog_xmin = InvalidTransactionId;
...@@ -1904,6 +1973,10 @@ GetSnapshotData(Snapshot snapshot) ...@@ -1904,6 +1973,10 @@ GetSnapshotData(Snapshot snapshot)
LWLockAcquire(ProcArrayLock, LW_SHARED); LWLockAcquire(ProcArrayLock, LW_SHARED);
latest_completed = ShmemVariableCache->latestCompletedXid; latest_completed = ShmemVariableCache->latestCompletedXid;
mypgxactoff = MyProc->pgxactoff;
myxid = other_xids[mypgxactoff];
Assert(myxid == MyProc->xid);
oldestxid = ShmemVariableCache->oldestXid; oldestxid = ShmemVariableCache->oldestXid;
/* xmax is always latestCompletedXid + 1 */ /* xmax is always latestCompletedXid + 1 */
...@@ -1914,57 +1987,79 @@ GetSnapshotData(Snapshot snapshot) ...@@ -1914,57 +1987,79 @@ GetSnapshotData(Snapshot snapshot)
/* initialize xmin calculation with xmax */ /* initialize xmin calculation with xmax */
xmin = 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(); snapshot->takenDuringRecovery = RecoveryInProgress();
if (!snapshot->takenDuringRecovery) if (!snapshot->takenDuringRecovery)
{ {
size_t numProcs = arrayP->numProcs;
TransactionId *xip = snapshot->xip;
int *pgprocnos = arrayP->pgprocnos; int *pgprocnos = arrayP->pgprocnos;
int numProcs;
/* /*
* Spin over procArray checking xid, xmin, and subxids. The goal is * First collect set of pgxactoff/xids that need to be included in the
* to gather all active xids, find the lowest xmin, and try to record * snapshot.
* subxids.
*/ */
numProcs = arrayP->numProcs; for (size_t pgxactoff = 0; pgxactoff < numProcs; pgxactoff++)
for (index = 0; index < numProcs; index++)
{ {
int pgprocno = pgprocnos[index]; /* Fetch xid just once - see GetNewTransactionId */
PGXACT *pgxact = &allPgXact[pgprocno]; TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
TransactionId xid; int pgprocno;
PGXACT *pgxact;
uint8 vacuumFlags;
Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
/* /*
* Skip over backends doing logical decoding which manages xmin * If the transaction has no XID assigned, we can skip it; it
* separately (check below) and ones running LAZY VACUUM. * won't have sub-XIDs either.
*/ */
if (pgxact->vacuumFlags & if (likely(xid == InvalidTransactionId))
(PROC_IN_LOGICAL_DECODING | PROC_IN_VACUUM))
continue; continue;
/* Fetch xid just once - see GetNewTransactionId */ /*
xid = UINT32_ACCESS_ONCE(pgxact->xid); * 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;
/* /*
* If the transaction has no XID assigned, we can skip it; it * The only way we are able to get here with a non-normal xid
* won't have sub-XIDs either. If the XID is >= xmax, we can also * is during bootstrap - with this backend using
* skip it; such transactions will be treated as running anyway * BootstrapTransactionId. But the above test should filter
* (and any sub-XIDs will also be >= xmax). * that out.
*/ */
if (!TransactionIdIsNormal(xid) Assert(TransactionIdIsNormal(xid));
|| !NormalTransactionIdPrecedes(xid, xmax))
/*
* 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; continue;
pgprocno = pgprocnos[pgxactoff];
pgxact = &allPgXact[pgprocno];
vacuumFlags = pgxact->vacuumFlags;
/* /*
* We don't include our own XIDs (if any) in the snapshot, but we * Skip over backends doing logical decoding which manages xmin
* must include them in xmin. * separately (check below) and ones running LAZY VACUUM.
*/ */
if (vacuumFlags & (PROC_IN_LOGICAL_DECODING | PROC_IN_VACUUM))
continue;
if (NormalTransactionIdPrecedes(xid, xmin)) if (NormalTransactionIdPrecedes(xid, xmin))
xmin = xid; xmin = xid;
if (pgxact == MyPgXact)
continue;
/* Add XID to snapshot. */ /* Add XID to snapshot. */
snapshot->xip[count++] = xid; xip[count++] = xid;
/* /*
* Save subtransaction XIDs if possible (if we've already * Save subtransaction XIDs if possible (if we've already
...@@ -1987,9 +2082,9 @@ GetSnapshotData(Snapshot snapshot) ...@@ -1987,9 +2082,9 @@ GetSnapshotData(Snapshot snapshot)
suboverflowed = true; suboverflowed = true;
else else
{ {
int nxids = pgxact->nxids; int nsubxids = pgxact->nxids;
if (nxids > 0) if (nsubxids > 0)
{ {
PGPROC *proc = &allProcs[pgprocno]; PGPROC *proc = &allProcs[pgprocno];
...@@ -1997,8 +2092,8 @@ GetSnapshotData(Snapshot snapshot) ...@@ -1997,8 +2092,8 @@ GetSnapshotData(Snapshot snapshot)
memcpy(snapshot->subxip + subcount, memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids, (void *) proc->subxids.xids,
nxids * sizeof(TransactionId)); nsubxids * sizeof(TransactionId));
subcount += nxids; subcount += nsubxids;
} }
} }
} }
...@@ -2130,6 +2225,7 @@ GetSnapshotData(Snapshot snapshot) ...@@ -2130,6 +2225,7 @@ GetSnapshotData(Snapshot snapshot)
} }
RecentXmin = xmin; RecentXmin = xmin;
Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
snapshot->xmin = xmin; snapshot->xmin = xmin;
snapshot->xmax = xmax; snapshot->xmax = xmax;
...@@ -2292,7 +2388,7 @@ ProcArrayInstallRestoredXmin(TransactionId xmin, PGPROC *proc) ...@@ -2292,7 +2388,7 @@ ProcArrayInstallRestoredXmin(TransactionId xmin, PGPROC *proc)
* GetRunningTransactionData -- returns information about running transactions. * GetRunningTransactionData -- returns information about running transactions.
* *
* Similar to GetSnapshotData but returns more information. We include * 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. * prepared transactions.
* *
* We acquire XidGenLock and ProcArrayLock, but the caller is responsible for * We acquire XidGenLock and ProcArrayLock, but the caller is responsible for
...@@ -2307,7 +2403,7 @@ ProcArrayInstallRestoredXmin(TransactionId xmin, PGPROC *proc) ...@@ -2307,7 +2403,7 @@ ProcArrayInstallRestoredXmin(TransactionId xmin, PGPROC *proc)
* This is never executed during recovery so there is no need to look at * This is never executed during recovery so there is no need to look at
* KnownAssignedXids. * 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 * may return entries with duplicated TransactionId values coming from
* transaction finishing to prepare. Nothing is done about duplicated * transaction finishing to prepare. Nothing is done about duplicated
* entries here to not hold on ProcArrayLock more than necessary. * entries here to not hold on ProcArrayLock more than necessary.
...@@ -2326,6 +2422,7 @@ GetRunningTransactionData(void) ...@@ -2326,6 +2422,7 @@ GetRunningTransactionData(void)
static RunningTransactionsData CurrentRunningXactsData; static RunningTransactionsData CurrentRunningXactsData;
ProcArrayStruct *arrayP = procArray; ProcArrayStruct *arrayP = procArray;
TransactionId *other_xids = ProcGlobal->xids;
RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData; RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
TransactionId latestCompletedXid; TransactionId latestCompletedXid;
TransactionId oldestRunningXid; TransactionId oldestRunningXid;
...@@ -2386,7 +2483,7 @@ GetRunningTransactionData(void) ...@@ -2386,7 +2483,7 @@ GetRunningTransactionData(void)
TransactionId xid; TransactionId xid;
/* Fetch xid just once - see GetNewTransactionId */ /* 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 * We don't need to store transactions that don't have a TransactionId
...@@ -2483,7 +2580,7 @@ GetRunningTransactionData(void) ...@@ -2483,7 +2580,7 @@ GetRunningTransactionData(void)
* GetOldestActiveTransactionId() * GetOldestActiveTransactionId()
* *
* Similar to GetSnapshotData but returns just oldestActiveXid. We include * 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 * We look at all databases, though there is no need to include WALSender
* since this has no effect on hot standby conflicts. * since this has no effect on hot standby conflicts.
* *
...@@ -2498,6 +2595,7 @@ TransactionId ...@@ -2498,6 +2595,7 @@ TransactionId
GetOldestActiveTransactionId(void) GetOldestActiveTransactionId(void)
{ {
ProcArrayStruct *arrayP = procArray; ProcArrayStruct *arrayP = procArray;
TransactionId *other_xids = ProcGlobal->xids;
TransactionId oldestRunningXid; TransactionId oldestRunningXid;
int index; int index;
...@@ -2520,12 +2618,10 @@ GetOldestActiveTransactionId(void) ...@@ -2520,12 +2618,10 @@ GetOldestActiveTransactionId(void)
LWLockAcquire(ProcArrayLock, LW_SHARED); LWLockAcquire(ProcArrayLock, LW_SHARED);
for (index = 0; index < arrayP->numProcs; index++) for (index = 0; index < arrayP->numProcs; index++)
{ {
int pgprocno = arrayP->pgprocnos[index];
PGXACT *pgxact = &allPgXact[pgprocno];
TransactionId xid; TransactionId xid;
/* Fetch xid just once - see GetNewTransactionId */ /* Fetch xid just once - see GetNewTransactionId */
xid = UINT32_ACCESS_ONCE(pgxact->xid); xid = UINT32_ACCESS_ONCE(other_xids[index]);
if (!TransactionIdIsNormal(xid)) if (!TransactionIdIsNormal(xid))
continue; continue;
...@@ -2603,8 +2699,8 @@ GetOldestSafeDecodingTransactionId(bool catalogOnly) ...@@ -2603,8 +2699,8 @@ GetOldestSafeDecodingTransactionId(bool catalogOnly)
* If we're not in recovery, we walk over the procarray and collect the * 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 * lowest xid. Since we're called with ProcArrayLock held and have
* acquired XidGenLock, no entries can vanish concurrently, since * acquired XidGenLock, no entries can vanish concurrently, since
* PGXACT->xid is only set with XidGenLock held and only cleared with * ProcGlobal->xids[i] is only set with XidGenLock held and only cleared
* ProcArrayLock held. * with ProcArrayLock held.
* *
* In recovery we can't lower the safe value besides what we've computed * 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 * above, so we'll have to wait a bit longer there. We unfortunately can
...@@ -2613,17 +2709,17 @@ GetOldestSafeDecodingTransactionId(bool catalogOnly) ...@@ -2613,17 +2709,17 @@ GetOldestSafeDecodingTransactionId(bool catalogOnly)
*/ */
if (!recovery_in_progress) 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++) for (index = 0; index < arrayP->numProcs; index++)
{ {
int pgprocno = arrayP->pgprocnos[index];
PGXACT *pgxact = &allPgXact[pgprocno];
TransactionId xid; TransactionId xid;
/* Fetch xid just once - see GetNewTransactionId */ /* Fetch xid just once - see GetNewTransactionId */
xid = UINT32_ACCESS_ONCE(pgxact->xid); xid = UINT32_ACCESS_ONCE(other_xids[index]);
if (!TransactionIdIsNormal(xid)) if (!TransactionIdIsNormal(xid))
continue; continue;
...@@ -2811,6 +2907,7 @@ BackendXidGetPid(TransactionId xid) ...@@ -2811,6 +2907,7 @@ BackendXidGetPid(TransactionId xid)
{ {
int result = 0; int result = 0;
ProcArrayStruct *arrayP = procArray; ProcArrayStruct *arrayP = procArray;
TransactionId *other_xids = ProcGlobal->xids;
int index; int index;
if (xid == InvalidTransactionId) /* never match invalid xid */ if (xid == InvalidTransactionId) /* never match invalid xid */
...@@ -2822,9 +2919,8 @@ BackendXidGetPid(TransactionId xid) ...@@ -2822,9 +2919,8 @@ BackendXidGetPid(TransactionId xid)
{ {
int pgprocno = arrayP->pgprocnos[index]; int pgprocno = arrayP->pgprocnos[index];
PGPROC *proc = &allProcs[pgprocno]; PGPROC *proc = &allProcs[pgprocno];
PGXACT *pgxact = &allPgXact[pgprocno];
if (pgxact->xid == xid) if (other_xids[index] == xid)
{ {
result = proc->pid; result = proc->pid;
break; break;
...@@ -3104,7 +3200,6 @@ MinimumActiveBackends(int min) ...@@ -3104,7 +3200,6 @@ MinimumActiveBackends(int min)
{ {
int pgprocno = arrayP->pgprocnos[index]; int pgprocno = arrayP->pgprocnos[index];
PGPROC *proc = &allProcs[pgprocno]; PGPROC *proc = &allProcs[pgprocno];
PGXACT *pgxact = &allPgXact[pgprocno];
/* /*
* Since we're not holding a lock, need to be prepared to deal with * Since we're not holding a lock, need to be prepared to deal with
...@@ -3121,7 +3216,7 @@ MinimumActiveBackends(int min) ...@@ -3121,7 +3216,7 @@ MinimumActiveBackends(int min)
continue; /* do not count deleted entries */ continue; /* do not count deleted entries */
if (proc == MyProc) if (proc == MyProc)
continue; /* do not count myself */ continue; /* do not count myself */
if (pgxact->xid == InvalidTransactionId) if (proc->xid == InvalidTransactionId)
continue; /* do not count if no XID assigned */ continue; /* do not count if no XID assigned */
if (proc->pid == 0) if (proc->pid == 0)
continue; /* do not count prepared xacts */ continue; /* do not count prepared xacts */
...@@ -3547,8 +3642,8 @@ XidCacheRemoveRunningXids(TransactionId xid, ...@@ -3547,8 +3642,8 @@ XidCacheRemoveRunningXids(TransactionId xid,
* *
* Note that we do not have to be careful about memory ordering of our own * Note that we do not have to be careful about memory ordering of our own
* reads wrt. GetNewTransactionId() here - only this process can modify * reads wrt. GetNewTransactionId() here - only this process can modify
* relevant fields of MyProc/MyPgXact. But we do have to be careful about * relevant fields of MyProc/ProcGlobal->xids[]. But we do have to be
* our own writes being well ordered. * careful about our own writes being well ordered.
*/ */
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
...@@ -3906,7 +4001,7 @@ FullXidRelativeTo(FullTransactionId rel, TransactionId xid) ...@@ -3906,7 +4001,7 @@ FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
* In Hot Standby mode, we maintain a list of transactions that are (or were) * In Hot Standby mode, we maintain a list of transactions that are (or were)
* running on the primary at the current point in WAL. These XIDs must be * running on the primary at the current point in WAL. These XIDs must be
* treated as running by standby transactions, even though they are not in * treated as running by standby transactions, even though they are not in
* the standby server's PGXACT array. * the standby server's PGPROC array.
* *
* We record all XIDs that we know have been assigned. That includes all the * 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 * XIDs seen in WAL records, plus all unobserved XIDs that we can deduce have
......
...@@ -417,9 +417,7 @@ BackendIdGetTransactionIds(int backendID, TransactionId *xid, TransactionId *xmi ...@@ -417,9 +417,7 @@ BackendIdGetTransactionIds(int backendID, TransactionId *xid, TransactionId *xmi
if (proc != NULL) if (proc != NULL)
{ {
PGXACT *xact = &ProcGlobal->allPgXact[proc->pgprocno]; *xid = proc->xid;
*xid = xact->xid;
*xmin = proc->xmin; *xmin = proc->xmin;
} }
} }
......
...@@ -3974,9 +3974,8 @@ GetRunningTransactionLocks(int *nlocks) ...@@ -3974,9 +3974,8 @@ GetRunningTransactionLocks(int *nlocks)
proclock->tag.myLock->tag.locktag_type == LOCKTAG_RELATION) proclock->tag.myLock->tag.locktag_type == LOCKTAG_RELATION)
{ {
PGPROC *proc = proclock->tag.myProc; PGPROC *proc = proclock->tag.myProc;
PGXACT *pgxact = &ProcGlobal->allPgXact[proc->pgprocno];
LOCK *lock = proclock->tag.myLock; LOCK *lock = proclock->tag.myLock;
TransactionId xid = pgxact->xid; TransactionId xid = proc->xid;
/* /*
* Don't record locks for transactions if we know they have * Don't record locks for transactions if we know they have
......
...@@ -102,21 +102,18 @@ Size ...@@ -102,21 +102,18 @@ Size
ProcGlobalShmemSize(void) ProcGlobalShmemSize(void)
{ {
Size size = 0; Size size = 0;
Size TotalProcs =
add_size(MaxBackends, add_size(NUM_AUXILIARY_PROCS, max_prepared_xacts));
/* ProcGlobal */ /* ProcGlobal */
size = add_size(size, sizeof(PROC_HDR)); size = add_size(size, sizeof(PROC_HDR));
/* MyProcs, including autovacuum workers and launcher */ size = add_size(size, mul_size(TotalProcs, sizeof(PGPROC)));
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, sizeof(slock_t)); size = add_size(size, sizeof(slock_t));
size = add_size(size, mul_size(MaxBackends, sizeof(PGXACT))); 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(NUM_AUXILIARY_PROCS, sizeof(PGXACT)));
size = add_size(size, mul_size(max_prepared_xacts, 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; return size;
} }
...@@ -216,6 +213,17 @@ InitProcGlobal(void) ...@@ -216,6 +213,17 @@ InitProcGlobal(void)
MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT)); MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT));
ProcGlobal->allPgXact = pgxacts; ProcGlobal->allPgXact = pgxacts;
/*
* Allocate arrays mirroring PGPROC fields in a dense manner. See
* PROC_HDR.
*
* XXX: It might make sense to increase padding for these arrays, given
* how hotly they are accessed.
*/
ProcGlobal->xids =
(TransactionId *) ShmemAlloc(TotalProcs * sizeof(*ProcGlobal->xids));
MemSet(ProcGlobal->xids, 0, TotalProcs * sizeof(*ProcGlobal->xids));
for (i = 0; i < TotalProcs; i++) for (i = 0; i < TotalProcs; i++)
{ {
/* Common initialization for all PGPROCs, regardless of type. */ /* Common initialization for all PGPROCs, regardless of type. */
...@@ -387,7 +395,7 @@ InitProcess(void) ...@@ -387,7 +395,7 @@ InitProcess(void)
MyProc->lxid = InvalidLocalTransactionId; MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false; MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId; MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
MyPgXact->xid = InvalidTransactionId; MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId; MyProc->xmin = InvalidTransactionId;
MyProc->pid = MyProcPid; MyProc->pid = MyProcPid;
/* backendId, databaseId and roleId will be filled in later */ /* backendId, databaseId and roleId will be filled in later */
...@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void) ...@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void)
MyProc->lxid = InvalidLocalTransactionId; MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false; MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId; MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
MyPgXact->xid = InvalidTransactionId; MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId; MyProc->xmin = InvalidTransactionId;
MyProc->backendId = InvalidBackendId; MyProc->backendId = InvalidBackendId;
MyProc->databaseId = InvalidOid; MyProc->databaseId = InvalidOid;
......
...@@ -89,6 +89,17 @@ typedef enum ...@@ -89,6 +89,17 @@ typedef enum
* distinguished from a real one at need by the fact that it has pid == 0. * 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, * The semaphore and lock-activity fields in a prepared-xact PGPROC are unused,
* but its myProcLocks[] lists are valid. * but its myProcLocks[] lists are valid.
*
* Mirrored fields:
*
* Some fields in PGPROC (see "mirrored in ..." comment) are mirrored into an
* element of more densely packed ProcGlobal arrays. These arrays are indexed
* by PGPROC->pgxactoff. Both copies need to be maintained coherently.
*
* NB: The pgxactoff indexed value can *never* be accessed without holding
* locks.
*
* See PROC_HDR for details.
*/ */
struct PGPROC struct PGPROC
{ {
...@@ -101,6 +112,12 @@ struct PGPROC ...@@ -101,6 +112,12 @@ struct PGPROC
Latch procLatch; /* generic latch for process */ Latch procLatch; /* generic latch for process */
TransactionId xid; /* id of top-level transaction currently being
* executed by this proc, if running and XID
* is assigned; else InvalidTransactionId.
* mirrored in ProcGlobal->xids[pgxactoff] */
TransactionId xmin; /* minimal running XID as it was when we were TransactionId xmin; /* minimal running XID as it was when we were
* starting our xact, excluding LAZY VACUUM: * starting our xact, excluding LAZY VACUUM:
* vacuum must not remove tuples deleted by * vacuum must not remove tuples deleted by
...@@ -110,6 +127,9 @@ struct PGPROC ...@@ -110,6 +127,9 @@ struct PGPROC
* being executed by this proc, if running; * being executed by this proc, if running;
* else InvalidLocalTransactionId */ * else InvalidLocalTransactionId */
int pid; /* Backend's process ID; 0 if prepared xact */ int pid; /* Backend's process ID; 0 if prepared xact */
int pgxactoff; /* offset into various ProcGlobal->arrays
* with data mirrored from this PGPROC */
int pgprocno; int pgprocno;
/* These fields are zero while a backend is still starting up: */ /* These fields are zero while a backend is still starting up: */
...@@ -224,10 +244,6 @@ extern PGDLLIMPORT struct PGXACT *MyPgXact; ...@@ -224,10 +244,6 @@ extern PGDLLIMPORT struct PGXACT *MyPgXact;
*/ */
typedef struct PGXACT 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 */ uint8 vacuumFlags; /* vacuum-related flags, see above */
bool overflowed; bool overflowed;
...@@ -236,6 +252,57 @@ typedef struct PGXACT ...@@ -236,6 +252,57 @@ typedef struct PGXACT
/* /*
* There is one ProcGlobal struct for the whole database cluster. * 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 the same lock. Adding/Removing a procarray
* entry is much less frequent.
*
* Some fields in PGPROC are mirrored into more densely packed arrays (e.g.
* xids), with one entry for each backend. These arrays only contain entries
* for PGPROCs that have been added to the shared array with ProcArrayAdd()
* (in contrast to PGPROC array which has unused PGPROCs interspersed).
*
* The dense arrays are indexed by PGPROC->pgxactoff. Any concurrent
* ProcArrayAdd() / ProcArrayRemove() can lead to pgxactoff of a procarray
* member to change. Therefore it is only safe to use PGPROC->pgxactoff to
* access the dense array while holding either ProcArrayLock or XidGenLock.
*
* As long as a PGPROC is in the procarray, the mirrored values need to be
* maintained in both places in a coherent manner.
*
* The denser separate arrays are beneficial for three main reasons: First, to
* allow for as tight loops accessing the data as possible. Second, to prevent
* updates of frequently changing data (e.g. xmin) from invalidating
* cachelines also containing less frequently changing data (e.g. xid,
* vacuumFlags). Third to condense frequently accessed data into as few
* cachelines as possible.
*
* There are two main reasons to have the data mirrored between these dense
* arrays and PGPROC. First, as explained above, a PGPROC's array entries can
* only be accessed with either ProcArrayLock or XidGenLock held, whereas the
* PGPROC entries do not require that (obviously there may still be locking
* requirements around the individual field, separate from the concerns
* here). That is particularly important for a backend to efficiently checks
* it own values, which it often can safely do without locking. Second, the
* PGPROC fields allow to avoid unnecessary accesses and modification to the
* dense arrays. A backend's own PGPROC is more likely to be in a local cache,
* whereas the cachelines for the dense array will be modified by other
* backends (often removing it from the cache for other cores/sockets). At
* commit/abort time a check of the PGPROC value can avoid accessing/dirtying
* the corresponding array value.
*
* Basically it makes sense to access the PGPROC variable when checking a
* single backend's data, especially when already looking at the PGPROC for
* other reasons already. It makes sense to look at the "dense" arrays if we
* need to look at many / most entries, because we then benefit from the
* reduced indirection and better cross-process cache-ability.
*
* When entering a PGPROC for 2PC transactions with ProcArrayAdd(), the data
* in the dense arrays is initialized from the PGPROC while it already holds
* ProcArrayLock.
*/ */
typedef struct PROC_HDR typedef struct PROC_HDR
{ {
...@@ -243,6 +310,10 @@ typedef struct PROC_HDR ...@@ -243,6 +310,10 @@ typedef struct PROC_HDR
PGPROC *allProcs; PGPROC *allProcs;
/* Array of PGXACT structures (not including dummies for prepared txns) */ /* Array of PGXACT structures (not including dummies for prepared txns) */
PGXACT *allPgXact; PGXACT *allPgXact;
/* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
TransactionId *xids;
/* Length of allProcs array */ /* Length of allProcs array */
uint32 allProcCount; uint32 allProcCount;
/* Head of list of free PGPROC structures */ /* Head of list of free PGPROC structures */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment