agora inbox for [email protected]
help / color / mirror / Atom feed[PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids.
87+ messages / 10 participants
[nested] [flat]
* [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids.
@ 2020-07-15 22:35 Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2020-07-15 22:35 UTC (permalink / raw)
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
Reviewed-By: Robert Haas, Thomas Munro, David Rowley
Discussion: https://postgr.es/m/[email protected]
---
src/include/storage/proc.h | 79 +++++-
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 | 2 +-
src/backend/storage/ipc/procarray.c | 282 +++++++++++++-------
src/backend/storage/ipc/sinvaladt.c | 4 +-
src/backend/storage/lmgr/lock.c | 3 +-
src/backend/storage/lmgr/proc.c | 26 +-
11 files changed, 335 insertions(+), 161 deletions(-)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5e4b028a5f9..146bca84bd6 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -89,6 +89,17 @@ typedef enum
* 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.
+ *
+ * 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
{
@@ -101,6 +112,12 @@ struct PGPROC
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
* starting our xact, excluding LAZY VACUUM:
* vacuum must not remove tuples deleted by
@@ -110,6 +127,9 @@ 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
+ * with data mirrored from this PGPROC */
int pgprocno;
/* These fields are zero while a backend is still starting up: */
@@ -224,10 +244,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;
@@ -236,6 +252,57 @@ 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 the same lock. Adding/Removing a procarray
+ * entry is much less frequent.
+ *
+ * Some fields in PGPROC are mirrored into more densely packed arrays (like
+ * 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 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
{
@@ -243,6 +310,10 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/* Array of PGXACT structures (not including dummies for prepared txns) */
PGXACT *allPgXact;
+
+ /* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
+ 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 f117ee160a3..6dec0c8311b 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,
@@ -1445,7 +1445,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 c15b5540a09..2d7979011ce 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
latestCompletedXid 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 latestCompletedXid to
-pass the first backend's XID, before that value became visible in the
-ProcArray. That would break GetOldestXmin, 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
+latestCompletedXid to pass the first backend's XID, before that value
+became visible in the 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
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,
@@ -382,13 +383,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->xid, 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 dd2f4d5bc7e..a4599e96610 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 XactSLRULock
* 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 and 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->xid &&
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 eb5f4680a3d..a0398bf3a3e 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 = PROC_WAIT_STATUS_OK;
/* We set up the gxact's VXID as InvalidBackendId/XID */
proc->lxid = (LocalTransactionId) xid;
- pgxact->xid = xid;
+ proc->xid = 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->xid);
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 MyProc /
+ * 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);
@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
{
GlobalTransaction gxact;
PGPROC *proc;
- PGXACT *pgxact;
TransactionId xid;
char *buf;
char *bufptr;
@@ -1423,8 +1419,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
@@ -1726,7 +1721,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 2ef0f4991ca..4c91b343ecd 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 MyProc->xid/ProcGlobal->xids[] 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;
+ MyProc->xid = BootstrapTransactionId;
+ ProcGlobal->xids[MyProc->pgxactoff] = 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->xid 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 */
+ MyProc->xid = xid;
+ ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ }
else
{
int nxids = MyPgXact->nxids;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 22228f5684f..648e12c78d8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1724,7 +1724,7 @@ 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, otherwise GetOldestNonRemovableTransactionId()
+ * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
* might appear to go backwards, which is probably Not Good.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a016816ae86..e5617ddfb41 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->xid). 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
@@ -436,7 +437,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)
{
@@ -445,7 +448,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")));
@@ -471,10 +473,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->xid;
+
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);
}
@@ -500,36 +517,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);
@@ -562,7 +602,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->xid));
/*
* If we can immediately acquire ProcArrayLock, we clear our own XID
@@ -584,7 +624,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->xid));
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
@@ -607,7 +647,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->xid);
+
+ ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
+ proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
@@ -643,7 +689,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->xid));
/* Add ourselves to the list of processes needing a group XID clear. */
proc->procArrayGroupMember = true;
@@ -748,20 +794,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->xid = InvalidTransactionId;
+
proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId;
proc->recoveryConflictPending = false;
@@ -773,6 +827,8 @@ ProcArrayClearTransaction(PGPROC *proc)
/* Clear the subtransaction-XID cache too */
pgxact->nxids = 0;
pgxact->overflowed = false;
+
+ LWLockRelease(ProcArrayLock);
}
/*
@@ -1167,7 +1223,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.
@@ -1176,26 +1232,28 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* if the Xid is running on the primary.
*
* 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;
TransactionId latestCompletedXid;
- int i,
- j;
+ int mypgxactoff;
+ size_t numProcs;
+ int j;
/*
* Don't bother checking a transaction older than RecentXmin; it could not
@@ -1250,6 +1308,8 @@ TransactionIdIsInProgress(TransactionId xid)
errmsg("out of memory")));
}
+ other_xids = ProcGlobal->xids;
+
LWLockAcquire(ProcArrayLock, LW_SHARED);
/*
@@ -1266,20 +1326,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;
@@ -1304,8 +1366,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 */
@@ -1336,7 +1402,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))
@@ -1391,7 +1457,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;
@@ -1414,6 +1480,7 @@ TransactionIdIsActive(TransactionId xid)
{
bool result = false;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int i;
/*
@@ -1429,11 +1496,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;
@@ -1519,6 +1585,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
ProcArrayStruct *arrayP = procArray;
TransactionId kaxmin;
bool in_recovery = RecoveryInProgress();
+ TransactionId *other_xids = ProcGlobal->xids;
/* inferred after ProcArrayLock is released */
h->catalog_oldest_nonremovable = InvalidTransactionId;
@@ -1562,7 +1629,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *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);
/*
@@ -1853,14 +1920,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;
@@ -1905,6 +1975,10 @@ GetSnapshotData(Snapshot snapshot)
LWLockAcquire(ProcArrayLock, LW_SHARED);
latest_completed = ShmemVariableCache->latestCompletedXid;
+ mypgxactoff = MyProc->pgxactoff;
+ myxid = other_xids[mypgxactoff];
+ Assert(myxid == MyProc->xid);
+
oldestxid = ShmemVariableCache->oldestXid;
/* xmax is always latestCompletedXid + 1 */
@@ -1915,57 +1989,79 @@ 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)
{
+ size_t numProcs = arrayP->numProcs;
+ TransactionId *xip = snapshot->xip;
int *pgprocnos = arrayP->pgprocnos;
- int numProcs;
/*
- * 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];
- PGXACT *pgxact = &allPgXact[pgprocno];
- TransactionId xid;
+ /* Fetch xid just once - see GetNewTransactionId */
+ TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ int pgprocno;
+ PGXACT *pgxact;
+ uint8 vacuumFlags;
+
+ Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+
+ /*
+ * If the transaction has no XID assigned, we can skip it; it
+ * won't have sub-XIDs either.
+ */
+ if (likely(xid == InvalidTransactionId))
+ continue;
+
+ /*
+ * 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;
+
+ pgprocno = pgprocnos[pgxactoff];
+ pgxact = &allPgXact[pgprocno];
+ vacuumFlags = pgxact->vacuumFlags;
/*
* 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
@@ -1988,9 +2084,9 @@ GetSnapshotData(Snapshot snapshot)
suboverflowed = true;
else
{
- int nxids = pgxact->nxids;
+ int nsubxids = pgxact->nxids;
- if (nxids > 0)
+ if (nsubxids > 0)
{
PGPROC *proc = &allProcs[pgprocno];
@@ -1998,8 +2094,8 @@ GetSnapshotData(Snapshot snapshot)
memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids,
- nxids * sizeof(TransactionId));
- subcount += nxids;
+ nsubxids * sizeof(TransactionId));
+ subcount += nsubxids;
}
}
}
@@ -2131,6 +2227,7 @@ GetSnapshotData(Snapshot snapshot)
}
RecentXmin = xmin;
+ Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
snapshot->xmin = xmin;
snapshot->xmax = xmax;
@@ -2293,7 +2390,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
@@ -2308,7 +2405,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.
@@ -2327,6 +2424,7 @@ GetRunningTransactionData(void)
static RunningTransactionsData CurrentRunningXactsData;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
TransactionId latestCompletedXid;
TransactionId oldestRunningXid;
@@ -2387,7 +2485,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
@@ -2484,7 +2582,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.
*
@@ -2499,6 +2597,7 @@ TransactionId
GetOldestActiveTransactionId(void)
{
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
TransactionId oldestRunningXid;
int index;
@@ -2521,12 +2620,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;
@@ -2604,8 +2701,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
@@ -2614,17 +2711,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;
@@ -2812,6 +2909,7 @@ BackendXidGetPid(TransactionId xid)
{
int result = 0;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int index;
if (xid == InvalidTransactionId) /* never match invalid xid */
@@ -2823,9 +2921,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;
@@ -3105,7 +3202,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
@@ -3122,7 +3218,7 @@ MinimumActiveBackends(int min)
continue; /* do not count deleted entries */
if (proc == MyProc)
continue; /* do not count myself */
- if (pgxact->xid == InvalidTransactionId)
+ if (proc->xid == InvalidTransactionId)
continue; /* do not count if no XID assigned */
if (proc->pid == 0)
continue; /* do not count prepared xacts */
@@ -3548,8 +3644,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);
@@ -3907,7 +4003,7 @@ FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
* 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
* 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
* 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..a9477ccb4a3 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->xid;
*xmin = proc->xmin;
}
}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 95989ce79bd..d86566f4554 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->xid;
/*
* 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 de346cd87fc..7fad49544ce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -102,21 +102,18 @@ Size
ProcGlobalShmemSize(void)
{
Size size = 0;
+ Size TotalProcs =
+ add_size(MaxBackends, add_size(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 +213,17 @@ InitProcGlobal(void)
MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT));
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++)
{
/* Common initialization for all PGPROCs, regardless of type. */
@@ -387,7 +395,7 @@ InitProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->pid = MyProcPid;
/* backendId, databaseId and roleId will be filled in later */
@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->backendId = InvalidBackendId;
MyProc->databaseId = InvalidOid;
--
2.25.0.114.g5b0ca878e0
--4il4azyv6rmablac
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v13-0004-snapshot-scalability-Move-PGXACT-vacuumFlags-to-.patch"
^ permalink raw reply [nested|flat] 87+ messages in thread
* [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids.
@ 2020-07-15 22:35 Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2020-07-15 22:35 UTC (permalink / raw)
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
Reviewed-By: Robert Haas, Thomas Munro, David Rowley
Discussion: https://postgr.es/m/[email protected]
---
src/include/storage/proc.h | 79 +++++-
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 | 2 +-
src/backend/storage/ipc/procarray.c | 282 +++++++++++++-------
src/backend/storage/ipc/sinvaladt.c | 4 +-
src/backend/storage/lmgr/lock.c | 3 +-
src/backend/storage/lmgr/proc.c | 26 +-
11 files changed, 335 insertions(+), 161 deletions(-)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5e4b028a5f9..146bca84bd6 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -89,6 +89,17 @@ typedef enum
* 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.
+ *
+ * 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
{
@@ -101,6 +112,12 @@ struct PGPROC
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
* starting our xact, excluding LAZY VACUUM:
* vacuum must not remove tuples deleted by
@@ -110,6 +127,9 @@ 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
+ * with data mirrored from this PGPROC */
int pgprocno;
/* These fields are zero while a backend is still starting up: */
@@ -224,10 +244,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;
@@ -236,6 +252,57 @@ 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 the same lock. Adding/Removing a procarray
+ * entry is much less frequent.
+ *
+ * Some fields in PGPROC are mirrored into more densely packed arrays (like
+ * 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 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
{
@@ -243,6 +310,10 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/* Array of PGXACT structures (not including dummies for prepared txns) */
PGXACT *allPgXact;
+
+ /* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
+ 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 f117ee160a3..6dec0c8311b 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,
@@ -1445,7 +1445,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 c15b5540a09..2d7979011ce 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
latestCompletedXid 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 latestCompletedXid to
-pass the first backend's XID, before that value became visible in the
-ProcArray. That would break GetOldestXmin, 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
+latestCompletedXid to pass the first backend's XID, before that value
+became visible in the 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
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,
@@ -382,13 +383,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->xid, 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 dd2f4d5bc7e..a4599e96610 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 XactSLRULock
* 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 and 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->xid &&
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 eb5f4680a3d..a0398bf3a3e 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 = PROC_WAIT_STATUS_OK;
/* We set up the gxact's VXID as InvalidBackendId/XID */
proc->lxid = (LocalTransactionId) xid;
- pgxact->xid = xid;
+ proc->xid = 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->xid);
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 MyProc /
+ * 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);
@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
{
GlobalTransaction gxact;
PGPROC *proc;
- PGXACT *pgxact;
TransactionId xid;
char *buf;
char *bufptr;
@@ -1423,8 +1419,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
@@ -1726,7 +1721,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 2ef0f4991ca..4c91b343ecd 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 MyProc->xid/ProcGlobal->xids[] 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;
+ MyProc->xid = BootstrapTransactionId;
+ ProcGlobal->xids[MyProc->pgxactoff] = 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->xid 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 */
+ MyProc->xid = xid;
+ ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ }
else
{
int nxids = MyPgXact->nxids;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 22228f5684f..648e12c78d8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1724,7 +1724,7 @@ 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, otherwise GetOldestNonRemovableTransactionId()
+ * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
* might appear to go backwards, which is probably Not Good.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a016816ae86..e5617ddfb41 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->xid). 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
@@ -436,7 +437,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)
{
@@ -445,7 +448,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")));
@@ -471,10 +473,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->xid;
+
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);
}
@@ -500,36 +517,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);
@@ -562,7 +602,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->xid));
/*
* If we can immediately acquire ProcArrayLock, we clear our own XID
@@ -584,7 +624,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->xid));
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
@@ -607,7 +647,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->xid);
+
+ ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
+ proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
@@ -643,7 +689,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->xid));
/* Add ourselves to the list of processes needing a group XID clear. */
proc->procArrayGroupMember = true;
@@ -748,20 +794,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->xid = InvalidTransactionId;
+
proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId;
proc->recoveryConflictPending = false;
@@ -773,6 +827,8 @@ ProcArrayClearTransaction(PGPROC *proc)
/* Clear the subtransaction-XID cache too */
pgxact->nxids = 0;
pgxact->overflowed = false;
+
+ LWLockRelease(ProcArrayLock);
}
/*
@@ -1167,7 +1223,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.
@@ -1176,26 +1232,28 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* if the Xid is running on the primary.
*
* 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;
TransactionId latestCompletedXid;
- int i,
- j;
+ int mypgxactoff;
+ size_t numProcs;
+ int j;
/*
* Don't bother checking a transaction older than RecentXmin; it could not
@@ -1250,6 +1308,8 @@ TransactionIdIsInProgress(TransactionId xid)
errmsg("out of memory")));
}
+ other_xids = ProcGlobal->xids;
+
LWLockAcquire(ProcArrayLock, LW_SHARED);
/*
@@ -1266,20 +1326,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;
@@ -1304,8 +1366,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 */
@@ -1336,7 +1402,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))
@@ -1391,7 +1457,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;
@@ -1414,6 +1480,7 @@ TransactionIdIsActive(TransactionId xid)
{
bool result = false;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int i;
/*
@@ -1429,11 +1496,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;
@@ -1519,6 +1585,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
ProcArrayStruct *arrayP = procArray;
TransactionId kaxmin;
bool in_recovery = RecoveryInProgress();
+ TransactionId *other_xids = ProcGlobal->xids;
/* inferred after ProcArrayLock is released */
h->catalog_oldest_nonremovable = InvalidTransactionId;
@@ -1562,7 +1629,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *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);
/*
@@ -1853,14 +1920,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;
@@ -1905,6 +1975,10 @@ GetSnapshotData(Snapshot snapshot)
LWLockAcquire(ProcArrayLock, LW_SHARED);
latest_completed = ShmemVariableCache->latestCompletedXid;
+ mypgxactoff = MyProc->pgxactoff;
+ myxid = other_xids[mypgxactoff];
+ Assert(myxid == MyProc->xid);
+
oldestxid = ShmemVariableCache->oldestXid;
/* xmax is always latestCompletedXid + 1 */
@@ -1915,57 +1989,79 @@ 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)
{
+ size_t numProcs = arrayP->numProcs;
+ TransactionId *xip = snapshot->xip;
int *pgprocnos = arrayP->pgprocnos;
- int numProcs;
/*
- * 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];
- PGXACT *pgxact = &allPgXact[pgprocno];
- TransactionId xid;
+ /* Fetch xid just once - see GetNewTransactionId */
+ TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ int pgprocno;
+ PGXACT *pgxact;
+ uint8 vacuumFlags;
+
+ Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+
+ /*
+ * If the transaction has no XID assigned, we can skip it; it
+ * won't have sub-XIDs either.
+ */
+ if (likely(xid == InvalidTransactionId))
+ continue;
+
+ /*
+ * 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;
+
+ pgprocno = pgprocnos[pgxactoff];
+ pgxact = &allPgXact[pgprocno];
+ vacuumFlags = pgxact->vacuumFlags;
/*
* 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
@@ -1988,9 +2084,9 @@ GetSnapshotData(Snapshot snapshot)
suboverflowed = true;
else
{
- int nxids = pgxact->nxids;
+ int nsubxids = pgxact->nxids;
- if (nxids > 0)
+ if (nsubxids > 0)
{
PGPROC *proc = &allProcs[pgprocno];
@@ -1998,8 +2094,8 @@ GetSnapshotData(Snapshot snapshot)
memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids,
- nxids * sizeof(TransactionId));
- subcount += nxids;
+ nsubxids * sizeof(TransactionId));
+ subcount += nsubxids;
}
}
}
@@ -2131,6 +2227,7 @@ GetSnapshotData(Snapshot snapshot)
}
RecentXmin = xmin;
+ Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
snapshot->xmin = xmin;
snapshot->xmax = xmax;
@@ -2293,7 +2390,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
@@ -2308,7 +2405,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.
@@ -2327,6 +2424,7 @@ GetRunningTransactionData(void)
static RunningTransactionsData CurrentRunningXactsData;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
TransactionId latestCompletedXid;
TransactionId oldestRunningXid;
@@ -2387,7 +2485,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
@@ -2484,7 +2582,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.
*
@@ -2499,6 +2597,7 @@ TransactionId
GetOldestActiveTransactionId(void)
{
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
TransactionId oldestRunningXid;
int index;
@@ -2521,12 +2620,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;
@@ -2604,8 +2701,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
@@ -2614,17 +2711,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;
@@ -2812,6 +2909,7 @@ BackendXidGetPid(TransactionId xid)
{
int result = 0;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int index;
if (xid == InvalidTransactionId) /* never match invalid xid */
@@ -2823,9 +2921,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;
@@ -3105,7 +3202,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
@@ -3122,7 +3218,7 @@ MinimumActiveBackends(int min)
continue; /* do not count deleted entries */
if (proc == MyProc)
continue; /* do not count myself */
- if (pgxact->xid == InvalidTransactionId)
+ if (proc->xid == InvalidTransactionId)
continue; /* do not count if no XID assigned */
if (proc->pid == 0)
continue; /* do not count prepared xacts */
@@ -3548,8 +3644,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);
@@ -3907,7 +4003,7 @@ FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
* 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
* 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
* 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..a9477ccb4a3 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->xid;
*xmin = proc->xmin;
}
}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 95989ce79bd..d86566f4554 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->xid;
/*
* 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 de346cd87fc..7fad49544ce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -102,21 +102,18 @@ Size
ProcGlobalShmemSize(void)
{
Size size = 0;
+ Size TotalProcs =
+ add_size(MaxBackends, add_size(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 +213,17 @@ InitProcGlobal(void)
MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT));
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++)
{
/* Common initialization for all PGPROCs, regardless of type. */
@@ -387,7 +395,7 @@ InitProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->pid = MyProcPid;
/* backendId, databaseId and roleId will be filled in later */
@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->backendId = InvalidBackendId;
MyProc->databaseId = InvalidOid;
--
2.25.0.114.g5b0ca878e0
--4il4azyv6rmablac
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v13-0004-snapshot-scalability-Move-PGXACT-vacuumFlags-to-.patch"
^ permalink raw reply [nested|flat] 87+ messages in thread
* [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids.
@ 2020-07-15 22:35 Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2020-07-15 22:35 UTC (permalink / raw)
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
Reviewed-By: Robert Haas, Thomas Munro, David Rowley
Discussion: https://postgr.es/m/[email protected]
---
src/include/storage/proc.h | 79 +++++-
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 | 2 +-
src/backend/storage/ipc/procarray.c | 282 +++++++++++++-------
src/backend/storage/ipc/sinvaladt.c | 4 +-
src/backend/storage/lmgr/lock.c | 3 +-
src/backend/storage/lmgr/proc.c | 26 +-
11 files changed, 335 insertions(+), 161 deletions(-)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5e4b028a5f9..146bca84bd6 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -89,6 +89,17 @@ typedef enum
* 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.
+ *
+ * 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
{
@@ -101,6 +112,12 @@ struct PGPROC
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
* starting our xact, excluding LAZY VACUUM:
* vacuum must not remove tuples deleted by
@@ -110,6 +127,9 @@ 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
+ * with data mirrored from this PGPROC */
int pgprocno;
/* These fields are zero while a backend is still starting up: */
@@ -224,10 +244,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;
@@ -236,6 +252,57 @@ 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 the same lock. Adding/Removing a procarray
+ * entry is much less frequent.
+ *
+ * Some fields in PGPROC are mirrored into more densely packed arrays (like
+ * 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 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
{
@@ -243,6 +310,10 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/* Array of PGXACT structures (not including dummies for prepared txns) */
PGXACT *allPgXact;
+
+ /* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
+ 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 f117ee160a3..6dec0c8311b 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,
@@ -1445,7 +1445,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 c15b5540a09..2d7979011ce 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
latestCompletedXid 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 latestCompletedXid to
-pass the first backend's XID, before that value became visible in the
-ProcArray. That would break GetOldestXmin, 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
+latestCompletedXid to pass the first backend's XID, before that value
+became visible in the 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
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,
@@ -382,13 +383,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->xid, 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 dd2f4d5bc7e..a4599e96610 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 XactSLRULock
* 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 and 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->xid &&
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 eb5f4680a3d..a0398bf3a3e 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 = PROC_WAIT_STATUS_OK;
/* We set up the gxact's VXID as InvalidBackendId/XID */
proc->lxid = (LocalTransactionId) xid;
- pgxact->xid = xid;
+ proc->xid = 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->xid);
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 MyProc /
+ * 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);
@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
{
GlobalTransaction gxact;
PGPROC *proc;
- PGXACT *pgxact;
TransactionId xid;
char *buf;
char *bufptr;
@@ -1423,8 +1419,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
@@ -1726,7 +1721,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 2ef0f4991ca..4c91b343ecd 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 MyProc->xid/ProcGlobal->xids[] 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;
+ MyProc->xid = BootstrapTransactionId;
+ ProcGlobal->xids[MyProc->pgxactoff] = 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->xid 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 */
+ MyProc->xid = xid;
+ ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ }
else
{
int nxids = MyPgXact->nxids;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 22228f5684f..648e12c78d8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1724,7 +1724,7 @@ 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, otherwise GetOldestNonRemovableTransactionId()
+ * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
* might appear to go backwards, which is probably Not Good.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a016816ae86..e5617ddfb41 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->xid). 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
@@ -436,7 +437,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)
{
@@ -445,7 +448,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")));
@@ -471,10 +473,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->xid;
+
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);
}
@@ -500,36 +517,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);
@@ -562,7 +602,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->xid));
/*
* If we can immediately acquire ProcArrayLock, we clear our own XID
@@ -584,7 +624,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->xid));
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
@@ -607,7 +647,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->xid);
+
+ ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
+ proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
@@ -643,7 +689,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->xid));
/* Add ourselves to the list of processes needing a group XID clear. */
proc->procArrayGroupMember = true;
@@ -748,20 +794,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->xid = InvalidTransactionId;
+
proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId;
proc->recoveryConflictPending = false;
@@ -773,6 +827,8 @@ ProcArrayClearTransaction(PGPROC *proc)
/* Clear the subtransaction-XID cache too */
pgxact->nxids = 0;
pgxact->overflowed = false;
+
+ LWLockRelease(ProcArrayLock);
}
/*
@@ -1167,7 +1223,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.
@@ -1176,26 +1232,28 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* if the Xid is running on the primary.
*
* 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;
TransactionId latestCompletedXid;
- int i,
- j;
+ int mypgxactoff;
+ size_t numProcs;
+ int j;
/*
* Don't bother checking a transaction older than RecentXmin; it could not
@@ -1250,6 +1308,8 @@ TransactionIdIsInProgress(TransactionId xid)
errmsg("out of memory")));
}
+ other_xids = ProcGlobal->xids;
+
LWLockAcquire(ProcArrayLock, LW_SHARED);
/*
@@ -1266,20 +1326,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;
@@ -1304,8 +1366,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 */
@@ -1336,7 +1402,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))
@@ -1391,7 +1457,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;
@@ -1414,6 +1480,7 @@ TransactionIdIsActive(TransactionId xid)
{
bool result = false;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int i;
/*
@@ -1429,11 +1496,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;
@@ -1519,6 +1585,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
ProcArrayStruct *arrayP = procArray;
TransactionId kaxmin;
bool in_recovery = RecoveryInProgress();
+ TransactionId *other_xids = ProcGlobal->xids;
/* inferred after ProcArrayLock is released */
h->catalog_oldest_nonremovable = InvalidTransactionId;
@@ -1562,7 +1629,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *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);
/*
@@ -1853,14 +1920,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;
@@ -1905,6 +1975,10 @@ GetSnapshotData(Snapshot snapshot)
LWLockAcquire(ProcArrayLock, LW_SHARED);
latest_completed = ShmemVariableCache->latestCompletedXid;
+ mypgxactoff = MyProc->pgxactoff;
+ myxid = other_xids[mypgxactoff];
+ Assert(myxid == MyProc->xid);
+
oldestxid = ShmemVariableCache->oldestXid;
/* xmax is always latestCompletedXid + 1 */
@@ -1915,57 +1989,79 @@ 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)
{
+ size_t numProcs = arrayP->numProcs;
+ TransactionId *xip = snapshot->xip;
int *pgprocnos = arrayP->pgprocnos;
- int numProcs;
/*
- * 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];
- PGXACT *pgxact = &allPgXact[pgprocno];
- TransactionId xid;
+ /* Fetch xid just once - see GetNewTransactionId */
+ TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ int pgprocno;
+ PGXACT *pgxact;
+ uint8 vacuumFlags;
+
+ Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+
+ /*
+ * If the transaction has no XID assigned, we can skip it; it
+ * won't have sub-XIDs either.
+ */
+ if (likely(xid == InvalidTransactionId))
+ continue;
+
+ /*
+ * 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;
+
+ pgprocno = pgprocnos[pgxactoff];
+ pgxact = &allPgXact[pgprocno];
+ vacuumFlags = pgxact->vacuumFlags;
/*
* 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
@@ -1988,9 +2084,9 @@ GetSnapshotData(Snapshot snapshot)
suboverflowed = true;
else
{
- int nxids = pgxact->nxids;
+ int nsubxids = pgxact->nxids;
- if (nxids > 0)
+ if (nsubxids > 0)
{
PGPROC *proc = &allProcs[pgprocno];
@@ -1998,8 +2094,8 @@ GetSnapshotData(Snapshot snapshot)
memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids,
- nxids * sizeof(TransactionId));
- subcount += nxids;
+ nsubxids * sizeof(TransactionId));
+ subcount += nsubxids;
}
}
}
@@ -2131,6 +2227,7 @@ GetSnapshotData(Snapshot snapshot)
}
RecentXmin = xmin;
+ Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
snapshot->xmin = xmin;
snapshot->xmax = xmax;
@@ -2293,7 +2390,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
@@ -2308,7 +2405,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.
@@ -2327,6 +2424,7 @@ GetRunningTransactionData(void)
static RunningTransactionsData CurrentRunningXactsData;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
TransactionId latestCompletedXid;
TransactionId oldestRunningXid;
@@ -2387,7 +2485,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
@@ -2484,7 +2582,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.
*
@@ -2499,6 +2597,7 @@ TransactionId
GetOldestActiveTransactionId(void)
{
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
TransactionId oldestRunningXid;
int index;
@@ -2521,12 +2620,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;
@@ -2604,8 +2701,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
@@ -2614,17 +2711,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;
@@ -2812,6 +2909,7 @@ BackendXidGetPid(TransactionId xid)
{
int result = 0;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int index;
if (xid == InvalidTransactionId) /* never match invalid xid */
@@ -2823,9 +2921,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;
@@ -3105,7 +3202,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
@@ -3122,7 +3218,7 @@ MinimumActiveBackends(int min)
continue; /* do not count deleted entries */
if (proc == MyProc)
continue; /* do not count myself */
- if (pgxact->xid == InvalidTransactionId)
+ if (proc->xid == InvalidTransactionId)
continue; /* do not count if no XID assigned */
if (proc->pid == 0)
continue; /* do not count prepared xacts */
@@ -3548,8 +3644,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);
@@ -3907,7 +4003,7 @@ FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
* 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
* 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
* 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..a9477ccb4a3 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->xid;
*xmin = proc->xmin;
}
}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 95989ce79bd..d86566f4554 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->xid;
/*
* 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 de346cd87fc..7fad49544ce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -102,21 +102,18 @@ Size
ProcGlobalShmemSize(void)
{
Size size = 0;
+ Size TotalProcs =
+ add_size(MaxBackends, add_size(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 +213,17 @@ InitProcGlobal(void)
MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT));
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++)
{
/* Common initialization for all PGPROCs, regardless of type. */
@@ -387,7 +395,7 @@ InitProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->pid = MyProcPid;
/* backendId, databaseId and roleId will be filled in later */
@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->backendId = InvalidBackendId;
MyProc->databaseId = InvalidOid;
--
2.25.0.114.g5b0ca878e0
--4il4azyv6rmablac
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v13-0004-snapshot-scalability-Move-PGXACT-vacuumFlags-to-.patch"
^ permalink raw reply [nested|flat] 87+ messages in thread
* [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids.
@ 2020-07-15 22:35 Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2020-07-15 22:35 UTC (permalink / raw)
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
Reviewed-By: Robert Haas, Thomas Munro, David Rowley
Discussion: https://postgr.es/m/[email protected]
---
src/include/storage/proc.h | 79 +++++-
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 | 2 +-
src/backend/storage/ipc/procarray.c | 282 +++++++++++++-------
src/backend/storage/ipc/sinvaladt.c | 4 +-
src/backend/storage/lmgr/lock.c | 3 +-
src/backend/storage/lmgr/proc.c | 26 +-
11 files changed, 335 insertions(+), 161 deletions(-)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5e4b028a5f9..146bca84bd6 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -89,6 +89,17 @@ typedef enum
* 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.
+ *
+ * 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
{
@@ -101,6 +112,12 @@ struct PGPROC
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
* starting our xact, excluding LAZY VACUUM:
* vacuum must not remove tuples deleted by
@@ -110,6 +127,9 @@ 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
+ * with data mirrored from this PGPROC */
int pgprocno;
/* These fields are zero while a backend is still starting up: */
@@ -224,10 +244,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;
@@ -236,6 +252,57 @@ 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 the same lock. Adding/Removing a procarray
+ * entry is much less frequent.
+ *
+ * Some fields in PGPROC are mirrored into more densely packed arrays (like
+ * 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 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
{
@@ -243,6 +310,10 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/* Array of PGXACT structures (not including dummies for prepared txns) */
PGXACT *allPgXact;
+
+ /* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
+ 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 f117ee160a3..6dec0c8311b 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,
@@ -1445,7 +1445,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 c15b5540a09..2d7979011ce 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
latestCompletedXid 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 latestCompletedXid to
-pass the first backend's XID, before that value became visible in the
-ProcArray. That would break GetOldestXmin, 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
+latestCompletedXid to pass the first backend's XID, before that value
+became visible in the 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
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,
@@ -382,13 +383,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->xid, 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 dd2f4d5bc7e..a4599e96610 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 XactSLRULock
* 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 and 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->xid &&
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 eb5f4680a3d..a0398bf3a3e 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 = PROC_WAIT_STATUS_OK;
/* We set up the gxact's VXID as InvalidBackendId/XID */
proc->lxid = (LocalTransactionId) xid;
- pgxact->xid = xid;
+ proc->xid = 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->xid);
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 MyProc /
+ * 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);
@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
{
GlobalTransaction gxact;
PGPROC *proc;
- PGXACT *pgxact;
TransactionId xid;
char *buf;
char *bufptr;
@@ -1423,8 +1419,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
@@ -1726,7 +1721,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 2ef0f4991ca..4c91b343ecd 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 MyProc->xid/ProcGlobal->xids[] 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;
+ MyProc->xid = BootstrapTransactionId;
+ ProcGlobal->xids[MyProc->pgxactoff] = 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->xid 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 */
+ MyProc->xid = xid;
+ ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ }
else
{
int nxids = MyPgXact->nxids;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 22228f5684f..648e12c78d8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1724,7 +1724,7 @@ 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, otherwise GetOldestNonRemovableTransactionId()
+ * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
* might appear to go backwards, which is probably Not Good.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a016816ae86..e5617ddfb41 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->xid). 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
@@ -436,7 +437,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)
{
@@ -445,7 +448,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")));
@@ -471,10 +473,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->xid;
+
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);
}
@@ -500,36 +517,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);
@@ -562,7 +602,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->xid));
/*
* If we can immediately acquire ProcArrayLock, we clear our own XID
@@ -584,7 +624,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->xid));
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
@@ -607,7 +647,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->xid);
+
+ ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
+ proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
@@ -643,7 +689,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->xid));
/* Add ourselves to the list of processes needing a group XID clear. */
proc->procArrayGroupMember = true;
@@ -748,20 +794,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->xid = InvalidTransactionId;
+
proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId;
proc->recoveryConflictPending = false;
@@ -773,6 +827,8 @@ ProcArrayClearTransaction(PGPROC *proc)
/* Clear the subtransaction-XID cache too */
pgxact->nxids = 0;
pgxact->overflowed = false;
+
+ LWLockRelease(ProcArrayLock);
}
/*
@@ -1167,7 +1223,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.
@@ -1176,26 +1232,28 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* if the Xid is running on the primary.
*
* 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;
TransactionId latestCompletedXid;
- int i,
- j;
+ int mypgxactoff;
+ size_t numProcs;
+ int j;
/*
* Don't bother checking a transaction older than RecentXmin; it could not
@@ -1250,6 +1308,8 @@ TransactionIdIsInProgress(TransactionId xid)
errmsg("out of memory")));
}
+ other_xids = ProcGlobal->xids;
+
LWLockAcquire(ProcArrayLock, LW_SHARED);
/*
@@ -1266,20 +1326,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;
@@ -1304,8 +1366,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 */
@@ -1336,7 +1402,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))
@@ -1391,7 +1457,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;
@@ -1414,6 +1480,7 @@ TransactionIdIsActive(TransactionId xid)
{
bool result = false;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int i;
/*
@@ -1429,11 +1496,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;
@@ -1519,6 +1585,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
ProcArrayStruct *arrayP = procArray;
TransactionId kaxmin;
bool in_recovery = RecoveryInProgress();
+ TransactionId *other_xids = ProcGlobal->xids;
/* inferred after ProcArrayLock is released */
h->catalog_oldest_nonremovable = InvalidTransactionId;
@@ -1562,7 +1629,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *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);
/*
@@ -1853,14 +1920,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;
@@ -1905,6 +1975,10 @@ GetSnapshotData(Snapshot snapshot)
LWLockAcquire(ProcArrayLock, LW_SHARED);
latest_completed = ShmemVariableCache->latestCompletedXid;
+ mypgxactoff = MyProc->pgxactoff;
+ myxid = other_xids[mypgxactoff];
+ Assert(myxid == MyProc->xid);
+
oldestxid = ShmemVariableCache->oldestXid;
/* xmax is always latestCompletedXid + 1 */
@@ -1915,57 +1989,79 @@ 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)
{
+ size_t numProcs = arrayP->numProcs;
+ TransactionId *xip = snapshot->xip;
int *pgprocnos = arrayP->pgprocnos;
- int numProcs;
/*
- * 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];
- PGXACT *pgxact = &allPgXact[pgprocno];
- TransactionId xid;
+ /* Fetch xid just once - see GetNewTransactionId */
+ TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ int pgprocno;
+ PGXACT *pgxact;
+ uint8 vacuumFlags;
+
+ Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+
+ /*
+ * If the transaction has no XID assigned, we can skip it; it
+ * won't have sub-XIDs either.
+ */
+ if (likely(xid == InvalidTransactionId))
+ continue;
+
+ /*
+ * 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;
+
+ pgprocno = pgprocnos[pgxactoff];
+ pgxact = &allPgXact[pgprocno];
+ vacuumFlags = pgxact->vacuumFlags;
/*
* 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
@@ -1988,9 +2084,9 @@ GetSnapshotData(Snapshot snapshot)
suboverflowed = true;
else
{
- int nxids = pgxact->nxids;
+ int nsubxids = pgxact->nxids;
- if (nxids > 0)
+ if (nsubxids > 0)
{
PGPROC *proc = &allProcs[pgprocno];
@@ -1998,8 +2094,8 @@ GetSnapshotData(Snapshot snapshot)
memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids,
- nxids * sizeof(TransactionId));
- subcount += nxids;
+ nsubxids * sizeof(TransactionId));
+ subcount += nsubxids;
}
}
}
@@ -2131,6 +2227,7 @@ GetSnapshotData(Snapshot snapshot)
}
RecentXmin = xmin;
+ Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
snapshot->xmin = xmin;
snapshot->xmax = xmax;
@@ -2293,7 +2390,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
@@ -2308,7 +2405,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.
@@ -2327,6 +2424,7 @@ GetRunningTransactionData(void)
static RunningTransactionsData CurrentRunningXactsData;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
TransactionId latestCompletedXid;
TransactionId oldestRunningXid;
@@ -2387,7 +2485,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
@@ -2484,7 +2582,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.
*
@@ -2499,6 +2597,7 @@ TransactionId
GetOldestActiveTransactionId(void)
{
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
TransactionId oldestRunningXid;
int index;
@@ -2521,12 +2620,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;
@@ -2604,8 +2701,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
@@ -2614,17 +2711,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;
@@ -2812,6 +2909,7 @@ BackendXidGetPid(TransactionId xid)
{
int result = 0;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int index;
if (xid == InvalidTransactionId) /* never match invalid xid */
@@ -2823,9 +2921,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;
@@ -3105,7 +3202,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
@@ -3122,7 +3218,7 @@ MinimumActiveBackends(int min)
continue; /* do not count deleted entries */
if (proc == MyProc)
continue; /* do not count myself */
- if (pgxact->xid == InvalidTransactionId)
+ if (proc->xid == InvalidTransactionId)
continue; /* do not count if no XID assigned */
if (proc->pid == 0)
continue; /* do not count prepared xacts */
@@ -3548,8 +3644,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);
@@ -3907,7 +4003,7 @@ FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
* 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
* 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
* 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..a9477ccb4a3 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->xid;
*xmin = proc->xmin;
}
}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 95989ce79bd..d86566f4554 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->xid;
/*
* 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 de346cd87fc..7fad49544ce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -102,21 +102,18 @@ Size
ProcGlobalShmemSize(void)
{
Size size = 0;
+ Size TotalProcs =
+ add_size(MaxBackends, add_size(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 +213,17 @@ InitProcGlobal(void)
MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT));
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++)
{
/* Common initialization for all PGPROCs, regardless of type. */
@@ -387,7 +395,7 @@ InitProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->pid = MyProcPid;
/* backendId, databaseId and roleId will be filled in later */
@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->backendId = InvalidBackendId;
MyProc->databaseId = InvalidOid;
--
2.25.0.114.g5b0ca878e0
--4il4azyv6rmablac
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v13-0004-snapshot-scalability-Move-PGXACT-vacuumFlags-to-.patch"
^ permalink raw reply [nested|flat] 87+ messages in thread
* [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids.
@ 2020-07-15 22:35 Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2020-07-15 22:35 UTC (permalink / raw)
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
Reviewed-By: Robert Haas, Thomas Munro, David Rowley
Discussion: https://postgr.es/m/[email protected]
---
src/include/storage/proc.h | 79 +++++-
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 | 2 +-
src/backend/storage/ipc/procarray.c | 282 +++++++++++++-------
src/backend/storage/ipc/sinvaladt.c | 4 +-
src/backend/storage/lmgr/lock.c | 3 +-
src/backend/storage/lmgr/proc.c | 26 +-
11 files changed, 335 insertions(+), 161 deletions(-)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5e4b028a5f9..146bca84bd6 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -89,6 +89,17 @@ typedef enum
* 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.
+ *
+ * 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
{
@@ -101,6 +112,12 @@ struct PGPROC
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
* starting our xact, excluding LAZY VACUUM:
* vacuum must not remove tuples deleted by
@@ -110,6 +127,9 @@ 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
+ * with data mirrored from this PGPROC */
int pgprocno;
/* These fields are zero while a backend is still starting up: */
@@ -224,10 +244,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;
@@ -236,6 +252,57 @@ 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 the same lock. Adding/Removing a procarray
+ * entry is much less frequent.
+ *
+ * Some fields in PGPROC are mirrored into more densely packed arrays (like
+ * 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 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
{
@@ -243,6 +310,10 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/* Array of PGXACT structures (not including dummies for prepared txns) */
PGXACT *allPgXact;
+
+ /* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
+ 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 f117ee160a3..6dec0c8311b 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,
@@ -1445,7 +1445,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 c15b5540a09..2d7979011ce 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
latestCompletedXid 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 latestCompletedXid to
-pass the first backend's XID, before that value became visible in the
-ProcArray. That would break GetOldestXmin, 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
+latestCompletedXid to pass the first backend's XID, before that value
+became visible in the 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
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,
@@ -382,13 +383,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->xid, 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 dd2f4d5bc7e..a4599e96610 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 XactSLRULock
* 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 and 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->xid &&
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 eb5f4680a3d..a0398bf3a3e 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 = PROC_WAIT_STATUS_OK;
/* We set up the gxact's VXID as InvalidBackendId/XID */
proc->lxid = (LocalTransactionId) xid;
- pgxact->xid = xid;
+ proc->xid = 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->xid);
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 MyProc /
+ * 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);
@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
{
GlobalTransaction gxact;
PGPROC *proc;
- PGXACT *pgxact;
TransactionId xid;
char *buf;
char *bufptr;
@@ -1423,8 +1419,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
@@ -1726,7 +1721,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 2ef0f4991ca..4c91b343ecd 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 MyProc->xid/ProcGlobal->xids[] 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;
+ MyProc->xid = BootstrapTransactionId;
+ ProcGlobal->xids[MyProc->pgxactoff] = 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->xid 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 */
+ MyProc->xid = xid;
+ ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ }
else
{
int nxids = MyPgXact->nxids;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 22228f5684f..648e12c78d8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1724,7 +1724,7 @@ 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, otherwise GetOldestNonRemovableTransactionId()
+ * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
* might appear to go backwards, which is probably Not Good.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a016816ae86..e5617ddfb41 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->xid). 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
@@ -436,7 +437,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)
{
@@ -445,7 +448,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")));
@@ -471,10 +473,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->xid;
+
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);
}
@@ -500,36 +517,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);
@@ -562,7 +602,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->xid));
/*
* If we can immediately acquire ProcArrayLock, we clear our own XID
@@ -584,7 +624,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->xid));
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
@@ -607,7 +647,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->xid);
+
+ ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
+ proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
@@ -643,7 +689,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->xid));
/* Add ourselves to the list of processes needing a group XID clear. */
proc->procArrayGroupMember = true;
@@ -748,20 +794,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->xid = InvalidTransactionId;
+
proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId;
proc->recoveryConflictPending = false;
@@ -773,6 +827,8 @@ ProcArrayClearTransaction(PGPROC *proc)
/* Clear the subtransaction-XID cache too */
pgxact->nxids = 0;
pgxact->overflowed = false;
+
+ LWLockRelease(ProcArrayLock);
}
/*
@@ -1167,7 +1223,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.
@@ -1176,26 +1232,28 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* if the Xid is running on the primary.
*
* 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;
TransactionId latestCompletedXid;
- int i,
- j;
+ int mypgxactoff;
+ size_t numProcs;
+ int j;
/*
* Don't bother checking a transaction older than RecentXmin; it could not
@@ -1250,6 +1308,8 @@ TransactionIdIsInProgress(TransactionId xid)
errmsg("out of memory")));
}
+ other_xids = ProcGlobal->xids;
+
LWLockAcquire(ProcArrayLock, LW_SHARED);
/*
@@ -1266,20 +1326,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;
@@ -1304,8 +1366,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 */
@@ -1336,7 +1402,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))
@@ -1391,7 +1457,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;
@@ -1414,6 +1480,7 @@ TransactionIdIsActive(TransactionId xid)
{
bool result = false;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int i;
/*
@@ -1429,11 +1496,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;
@@ -1519,6 +1585,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
ProcArrayStruct *arrayP = procArray;
TransactionId kaxmin;
bool in_recovery = RecoveryInProgress();
+ TransactionId *other_xids = ProcGlobal->xids;
/* inferred after ProcArrayLock is released */
h->catalog_oldest_nonremovable = InvalidTransactionId;
@@ -1562,7 +1629,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *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);
/*
@@ -1853,14 +1920,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;
@@ -1905,6 +1975,10 @@ GetSnapshotData(Snapshot snapshot)
LWLockAcquire(ProcArrayLock, LW_SHARED);
latest_completed = ShmemVariableCache->latestCompletedXid;
+ mypgxactoff = MyProc->pgxactoff;
+ myxid = other_xids[mypgxactoff];
+ Assert(myxid == MyProc->xid);
+
oldestxid = ShmemVariableCache->oldestXid;
/* xmax is always latestCompletedXid + 1 */
@@ -1915,57 +1989,79 @@ 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)
{
+ size_t numProcs = arrayP->numProcs;
+ TransactionId *xip = snapshot->xip;
int *pgprocnos = arrayP->pgprocnos;
- int numProcs;
/*
- * 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];
- PGXACT *pgxact = &allPgXact[pgprocno];
- TransactionId xid;
+ /* Fetch xid just once - see GetNewTransactionId */
+ TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ int pgprocno;
+ PGXACT *pgxact;
+ uint8 vacuumFlags;
+
+ Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+
+ /*
+ * If the transaction has no XID assigned, we can skip it; it
+ * won't have sub-XIDs either.
+ */
+ if (likely(xid == InvalidTransactionId))
+ continue;
+
+ /*
+ * 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;
+
+ pgprocno = pgprocnos[pgxactoff];
+ pgxact = &allPgXact[pgprocno];
+ vacuumFlags = pgxact->vacuumFlags;
/*
* 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
@@ -1988,9 +2084,9 @@ GetSnapshotData(Snapshot snapshot)
suboverflowed = true;
else
{
- int nxids = pgxact->nxids;
+ int nsubxids = pgxact->nxids;
- if (nxids > 0)
+ if (nsubxids > 0)
{
PGPROC *proc = &allProcs[pgprocno];
@@ -1998,8 +2094,8 @@ GetSnapshotData(Snapshot snapshot)
memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids,
- nxids * sizeof(TransactionId));
- subcount += nxids;
+ nsubxids * sizeof(TransactionId));
+ subcount += nsubxids;
}
}
}
@@ -2131,6 +2227,7 @@ GetSnapshotData(Snapshot snapshot)
}
RecentXmin = xmin;
+ Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
snapshot->xmin = xmin;
snapshot->xmax = xmax;
@@ -2293,7 +2390,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
@@ -2308,7 +2405,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.
@@ -2327,6 +2424,7 @@ GetRunningTransactionData(void)
static RunningTransactionsData CurrentRunningXactsData;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
TransactionId latestCompletedXid;
TransactionId oldestRunningXid;
@@ -2387,7 +2485,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
@@ -2484,7 +2582,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.
*
@@ -2499,6 +2597,7 @@ TransactionId
GetOldestActiveTransactionId(void)
{
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
TransactionId oldestRunningXid;
int index;
@@ -2521,12 +2620,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;
@@ -2604,8 +2701,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
@@ -2614,17 +2711,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;
@@ -2812,6 +2909,7 @@ BackendXidGetPid(TransactionId xid)
{
int result = 0;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int index;
if (xid == InvalidTransactionId) /* never match invalid xid */
@@ -2823,9 +2921,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;
@@ -3105,7 +3202,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
@@ -3122,7 +3218,7 @@ MinimumActiveBackends(int min)
continue; /* do not count deleted entries */
if (proc == MyProc)
continue; /* do not count myself */
- if (pgxact->xid == InvalidTransactionId)
+ if (proc->xid == InvalidTransactionId)
continue; /* do not count if no XID assigned */
if (proc->pid == 0)
continue; /* do not count prepared xacts */
@@ -3548,8 +3644,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);
@@ -3907,7 +4003,7 @@ FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
* 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
* 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
* 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..a9477ccb4a3 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->xid;
*xmin = proc->xmin;
}
}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 95989ce79bd..d86566f4554 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->xid;
/*
* 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 de346cd87fc..7fad49544ce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -102,21 +102,18 @@ Size
ProcGlobalShmemSize(void)
{
Size size = 0;
+ Size TotalProcs =
+ add_size(MaxBackends, add_size(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 +213,17 @@ InitProcGlobal(void)
MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT));
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++)
{
/* Common initialization for all PGPROCs, regardless of type. */
@@ -387,7 +395,7 @@ InitProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->pid = MyProcPid;
/* backendId, databaseId and roleId will be filled in later */
@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->backendId = InvalidBackendId;
MyProc->databaseId = InvalidOid;
--
2.25.0.114.g5b0ca878e0
--4il4azyv6rmablac
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v13-0004-snapshot-scalability-Move-PGXACT-vacuumFlags-to-.patch"
^ permalink raw reply [nested|flat] 87+ messages in thread
* [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids.
@ 2020-07-15 22:35 Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2020-07-15 22:35 UTC (permalink / raw)
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
Reviewed-By: Robert Haas, Thomas Munro, David Rowley
Discussion: https://postgr.es/m/[email protected]
---
src/include/storage/proc.h | 79 +++++-
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 | 2 +-
src/backend/storage/ipc/procarray.c | 282 +++++++++++++-------
src/backend/storage/ipc/sinvaladt.c | 4 +-
src/backend/storage/lmgr/lock.c | 3 +-
src/backend/storage/lmgr/proc.c | 26 +-
11 files changed, 335 insertions(+), 161 deletions(-)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5e4b028a5f9..146bca84bd6 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -89,6 +89,17 @@ typedef enum
* 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.
+ *
+ * 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
{
@@ -101,6 +112,12 @@ struct PGPROC
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
* starting our xact, excluding LAZY VACUUM:
* vacuum must not remove tuples deleted by
@@ -110,6 +127,9 @@ 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
+ * with data mirrored from this PGPROC */
int pgprocno;
/* These fields are zero while a backend is still starting up: */
@@ -224,10 +244,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;
@@ -236,6 +252,57 @@ 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 the same lock. Adding/Removing a procarray
+ * entry is much less frequent.
+ *
+ * Some fields in PGPROC are mirrored into more densely packed arrays (like
+ * 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 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
{
@@ -243,6 +310,10 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/* Array of PGXACT structures (not including dummies for prepared txns) */
PGXACT *allPgXact;
+
+ /* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
+ 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 f117ee160a3..6dec0c8311b 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,
@@ -1445,7 +1445,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 c15b5540a09..2d7979011ce 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
latestCompletedXid 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 latestCompletedXid to
-pass the first backend's XID, before that value became visible in the
-ProcArray. That would break GetOldestXmin, 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
+latestCompletedXid to pass the first backend's XID, before that value
+became visible in the 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
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,
@@ -382,13 +383,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->xid, 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 dd2f4d5bc7e..a4599e96610 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 XactSLRULock
* 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 and 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->xid &&
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 eb5f4680a3d..a0398bf3a3e 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 = PROC_WAIT_STATUS_OK;
/* We set up the gxact's VXID as InvalidBackendId/XID */
proc->lxid = (LocalTransactionId) xid;
- pgxact->xid = xid;
+ proc->xid = 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->xid);
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 MyProc /
+ * 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);
@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
{
GlobalTransaction gxact;
PGPROC *proc;
- PGXACT *pgxact;
TransactionId xid;
char *buf;
char *bufptr;
@@ -1423,8 +1419,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
@@ -1726,7 +1721,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 2ef0f4991ca..4c91b343ecd 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 MyProc->xid/ProcGlobal->xids[] 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;
+ MyProc->xid = BootstrapTransactionId;
+ ProcGlobal->xids[MyProc->pgxactoff] = 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->xid 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 */
+ MyProc->xid = xid;
+ ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ }
else
{
int nxids = MyPgXact->nxids;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 22228f5684f..648e12c78d8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1724,7 +1724,7 @@ 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, otherwise GetOldestNonRemovableTransactionId()
+ * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
* might appear to go backwards, which is probably Not Good.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a016816ae86..e5617ddfb41 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->xid). 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
@@ -436,7 +437,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)
{
@@ -445,7 +448,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")));
@@ -471,10 +473,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->xid;
+
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);
}
@@ -500,36 +517,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);
@@ -562,7 +602,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->xid));
/*
* If we can immediately acquire ProcArrayLock, we clear our own XID
@@ -584,7 +624,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->xid));
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
@@ -607,7 +647,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->xid);
+
+ ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
+ proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
@@ -643,7 +689,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->xid));
/* Add ourselves to the list of processes needing a group XID clear. */
proc->procArrayGroupMember = true;
@@ -748,20 +794,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->xid = InvalidTransactionId;
+
proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId;
proc->recoveryConflictPending = false;
@@ -773,6 +827,8 @@ ProcArrayClearTransaction(PGPROC *proc)
/* Clear the subtransaction-XID cache too */
pgxact->nxids = 0;
pgxact->overflowed = false;
+
+ LWLockRelease(ProcArrayLock);
}
/*
@@ -1167,7 +1223,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.
@@ -1176,26 +1232,28 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* if the Xid is running on the primary.
*
* 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;
TransactionId latestCompletedXid;
- int i,
- j;
+ int mypgxactoff;
+ size_t numProcs;
+ int j;
/*
* Don't bother checking a transaction older than RecentXmin; it could not
@@ -1250,6 +1308,8 @@ TransactionIdIsInProgress(TransactionId xid)
errmsg("out of memory")));
}
+ other_xids = ProcGlobal->xids;
+
LWLockAcquire(ProcArrayLock, LW_SHARED);
/*
@@ -1266,20 +1326,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;
@@ -1304,8 +1366,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 */
@@ -1336,7 +1402,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))
@@ -1391,7 +1457,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;
@@ -1414,6 +1480,7 @@ TransactionIdIsActive(TransactionId xid)
{
bool result = false;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int i;
/*
@@ -1429,11 +1496,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;
@@ -1519,6 +1585,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
ProcArrayStruct *arrayP = procArray;
TransactionId kaxmin;
bool in_recovery = RecoveryInProgress();
+ TransactionId *other_xids = ProcGlobal->xids;
/* inferred after ProcArrayLock is released */
h->catalog_oldest_nonremovable = InvalidTransactionId;
@@ -1562,7 +1629,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *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);
/*
@@ -1853,14 +1920,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;
@@ -1905,6 +1975,10 @@ GetSnapshotData(Snapshot snapshot)
LWLockAcquire(ProcArrayLock, LW_SHARED);
latest_completed = ShmemVariableCache->latestCompletedXid;
+ mypgxactoff = MyProc->pgxactoff;
+ myxid = other_xids[mypgxactoff];
+ Assert(myxid == MyProc->xid);
+
oldestxid = ShmemVariableCache->oldestXid;
/* xmax is always latestCompletedXid + 1 */
@@ -1915,57 +1989,79 @@ 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)
{
+ size_t numProcs = arrayP->numProcs;
+ TransactionId *xip = snapshot->xip;
int *pgprocnos = arrayP->pgprocnos;
- int numProcs;
/*
- * 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];
- PGXACT *pgxact = &allPgXact[pgprocno];
- TransactionId xid;
+ /* Fetch xid just once - see GetNewTransactionId */
+ TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ int pgprocno;
+ PGXACT *pgxact;
+ uint8 vacuumFlags;
+
+ Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+
+ /*
+ * If the transaction has no XID assigned, we can skip it; it
+ * won't have sub-XIDs either.
+ */
+ if (likely(xid == InvalidTransactionId))
+ continue;
+
+ /*
+ * 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;
+
+ pgprocno = pgprocnos[pgxactoff];
+ pgxact = &allPgXact[pgprocno];
+ vacuumFlags = pgxact->vacuumFlags;
/*
* 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
@@ -1988,9 +2084,9 @@ GetSnapshotData(Snapshot snapshot)
suboverflowed = true;
else
{
- int nxids = pgxact->nxids;
+ int nsubxids = pgxact->nxids;
- if (nxids > 0)
+ if (nsubxids > 0)
{
PGPROC *proc = &allProcs[pgprocno];
@@ -1998,8 +2094,8 @@ GetSnapshotData(Snapshot snapshot)
memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids,
- nxids * sizeof(TransactionId));
- subcount += nxids;
+ nsubxids * sizeof(TransactionId));
+ subcount += nsubxids;
}
}
}
@@ -2131,6 +2227,7 @@ GetSnapshotData(Snapshot snapshot)
}
RecentXmin = xmin;
+ Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
snapshot->xmin = xmin;
snapshot->xmax = xmax;
@@ -2293,7 +2390,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
@@ -2308,7 +2405,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.
@@ -2327,6 +2424,7 @@ GetRunningTransactionData(void)
static RunningTransactionsData CurrentRunningXactsData;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
TransactionId latestCompletedXid;
TransactionId oldestRunningXid;
@@ -2387,7 +2485,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
@@ -2484,7 +2582,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.
*
@@ -2499,6 +2597,7 @@ TransactionId
GetOldestActiveTransactionId(void)
{
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
TransactionId oldestRunningXid;
int index;
@@ -2521,12 +2620,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;
@@ -2604,8 +2701,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
@@ -2614,17 +2711,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;
@@ -2812,6 +2909,7 @@ BackendXidGetPid(TransactionId xid)
{
int result = 0;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int index;
if (xid == InvalidTransactionId) /* never match invalid xid */
@@ -2823,9 +2921,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;
@@ -3105,7 +3202,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
@@ -3122,7 +3218,7 @@ MinimumActiveBackends(int min)
continue; /* do not count deleted entries */
if (proc == MyProc)
continue; /* do not count myself */
- if (pgxact->xid == InvalidTransactionId)
+ if (proc->xid == InvalidTransactionId)
continue; /* do not count if no XID assigned */
if (proc->pid == 0)
continue; /* do not count prepared xacts */
@@ -3548,8 +3644,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);
@@ -3907,7 +4003,7 @@ FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
* 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
* 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
* 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..a9477ccb4a3 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->xid;
*xmin = proc->xmin;
}
}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 95989ce79bd..d86566f4554 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->xid;
/*
* 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 de346cd87fc..7fad49544ce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -102,21 +102,18 @@ Size
ProcGlobalShmemSize(void)
{
Size size = 0;
+ Size TotalProcs =
+ add_size(MaxBackends, add_size(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 +213,17 @@ InitProcGlobal(void)
MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT));
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++)
{
/* Common initialization for all PGPROCs, regardless of type. */
@@ -387,7 +395,7 @@ InitProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->pid = MyProcPid;
/* backendId, databaseId and roleId will be filled in later */
@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->backendId = InvalidBackendId;
MyProc->databaseId = InvalidOid;
--
2.25.0.114.g5b0ca878e0
--4il4azyv6rmablac
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v13-0004-snapshot-scalability-Move-PGXACT-vacuumFlags-to-.patch"
^ permalink raw reply [nested|flat] 87+ messages in thread
* [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids.
@ 2020-07-15 22:35 Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2020-07-15 22:35 UTC (permalink / raw)
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
Reviewed-By: Robert Haas, Thomas Munro, David Rowley
Discussion: https://postgr.es/m/[email protected]
---
src/include/storage/proc.h | 79 +++++-
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 | 2 +-
src/backend/storage/ipc/procarray.c | 282 +++++++++++++-------
src/backend/storage/ipc/sinvaladt.c | 4 +-
src/backend/storage/lmgr/lock.c | 3 +-
src/backend/storage/lmgr/proc.c | 26 +-
11 files changed, 335 insertions(+), 161 deletions(-)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5e4b028a5f9..146bca84bd6 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -89,6 +89,17 @@ typedef enum
* 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.
+ *
+ * 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
{
@@ -101,6 +112,12 @@ struct PGPROC
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
* starting our xact, excluding LAZY VACUUM:
* vacuum must not remove tuples deleted by
@@ -110,6 +127,9 @@ 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
+ * with data mirrored from this PGPROC */
int pgprocno;
/* These fields are zero while a backend is still starting up: */
@@ -224,10 +244,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;
@@ -236,6 +252,57 @@ 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 the same lock. Adding/Removing a procarray
+ * entry is much less frequent.
+ *
+ * Some fields in PGPROC are mirrored into more densely packed arrays (like
+ * 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 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
{
@@ -243,6 +310,10 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/* Array of PGXACT structures (not including dummies for prepared txns) */
PGXACT *allPgXact;
+
+ /* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
+ 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 f117ee160a3..6dec0c8311b 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,
@@ -1445,7 +1445,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 c15b5540a09..2d7979011ce 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
latestCompletedXid 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 latestCompletedXid to
-pass the first backend's XID, before that value became visible in the
-ProcArray. That would break GetOldestXmin, 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
+latestCompletedXid to pass the first backend's XID, before that value
+became visible in the 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
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,
@@ -382,13 +383,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->xid, 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 dd2f4d5bc7e..a4599e96610 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 XactSLRULock
* 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 and 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->xid &&
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 eb5f4680a3d..a0398bf3a3e 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 = PROC_WAIT_STATUS_OK;
/* We set up the gxact's VXID as InvalidBackendId/XID */
proc->lxid = (LocalTransactionId) xid;
- pgxact->xid = xid;
+ proc->xid = 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->xid);
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 MyProc /
+ * 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);
@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
{
GlobalTransaction gxact;
PGPROC *proc;
- PGXACT *pgxact;
TransactionId xid;
char *buf;
char *bufptr;
@@ -1423,8 +1419,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
@@ -1726,7 +1721,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 2ef0f4991ca..4c91b343ecd 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 MyProc->xid/ProcGlobal->xids[] 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;
+ MyProc->xid = BootstrapTransactionId;
+ ProcGlobal->xids[MyProc->pgxactoff] = 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->xid 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 */
+ MyProc->xid = xid;
+ ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ }
else
{
int nxids = MyPgXact->nxids;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 22228f5684f..648e12c78d8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1724,7 +1724,7 @@ 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, otherwise GetOldestNonRemovableTransactionId()
+ * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
* might appear to go backwards, which is probably Not Good.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a016816ae86..e5617ddfb41 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->xid). 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
@@ -436,7 +437,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)
{
@@ -445,7 +448,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")));
@@ -471,10 +473,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->xid;
+
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);
}
@@ -500,36 +517,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);
@@ -562,7 +602,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->xid));
/*
* If we can immediately acquire ProcArrayLock, we clear our own XID
@@ -584,7 +624,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->xid));
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
@@ -607,7 +647,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->xid);
+
+ ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
+ proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
@@ -643,7 +689,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->xid));
/* Add ourselves to the list of processes needing a group XID clear. */
proc->procArrayGroupMember = true;
@@ -748,20 +794,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->xid = InvalidTransactionId;
+
proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId;
proc->recoveryConflictPending = false;
@@ -773,6 +827,8 @@ ProcArrayClearTransaction(PGPROC *proc)
/* Clear the subtransaction-XID cache too */
pgxact->nxids = 0;
pgxact->overflowed = false;
+
+ LWLockRelease(ProcArrayLock);
}
/*
@@ -1167,7 +1223,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.
@@ -1176,26 +1232,28 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* if the Xid is running on the primary.
*
* 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;
TransactionId latestCompletedXid;
- int i,
- j;
+ int mypgxactoff;
+ size_t numProcs;
+ int j;
/*
* Don't bother checking a transaction older than RecentXmin; it could not
@@ -1250,6 +1308,8 @@ TransactionIdIsInProgress(TransactionId xid)
errmsg("out of memory")));
}
+ other_xids = ProcGlobal->xids;
+
LWLockAcquire(ProcArrayLock, LW_SHARED);
/*
@@ -1266,20 +1326,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;
@@ -1304,8 +1366,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 */
@@ -1336,7 +1402,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))
@@ -1391,7 +1457,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;
@@ -1414,6 +1480,7 @@ TransactionIdIsActive(TransactionId xid)
{
bool result = false;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int i;
/*
@@ -1429,11 +1496,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;
@@ -1519,6 +1585,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
ProcArrayStruct *arrayP = procArray;
TransactionId kaxmin;
bool in_recovery = RecoveryInProgress();
+ TransactionId *other_xids = ProcGlobal->xids;
/* inferred after ProcArrayLock is released */
h->catalog_oldest_nonremovable = InvalidTransactionId;
@@ -1562,7 +1629,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *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);
/*
@@ -1853,14 +1920,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;
@@ -1905,6 +1975,10 @@ GetSnapshotData(Snapshot snapshot)
LWLockAcquire(ProcArrayLock, LW_SHARED);
latest_completed = ShmemVariableCache->latestCompletedXid;
+ mypgxactoff = MyProc->pgxactoff;
+ myxid = other_xids[mypgxactoff];
+ Assert(myxid == MyProc->xid);
+
oldestxid = ShmemVariableCache->oldestXid;
/* xmax is always latestCompletedXid + 1 */
@@ -1915,57 +1989,79 @@ 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)
{
+ size_t numProcs = arrayP->numProcs;
+ TransactionId *xip = snapshot->xip;
int *pgprocnos = arrayP->pgprocnos;
- int numProcs;
/*
- * 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];
- PGXACT *pgxact = &allPgXact[pgprocno];
- TransactionId xid;
+ /* Fetch xid just once - see GetNewTransactionId */
+ TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ int pgprocno;
+ PGXACT *pgxact;
+ uint8 vacuumFlags;
+
+ Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+
+ /*
+ * If the transaction has no XID assigned, we can skip it; it
+ * won't have sub-XIDs either.
+ */
+ if (likely(xid == InvalidTransactionId))
+ continue;
+
+ /*
+ * 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;
+
+ pgprocno = pgprocnos[pgxactoff];
+ pgxact = &allPgXact[pgprocno];
+ vacuumFlags = pgxact->vacuumFlags;
/*
* 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
@@ -1988,9 +2084,9 @@ GetSnapshotData(Snapshot snapshot)
suboverflowed = true;
else
{
- int nxids = pgxact->nxids;
+ int nsubxids = pgxact->nxids;
- if (nxids > 0)
+ if (nsubxids > 0)
{
PGPROC *proc = &allProcs[pgprocno];
@@ -1998,8 +2094,8 @@ GetSnapshotData(Snapshot snapshot)
memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids,
- nxids * sizeof(TransactionId));
- subcount += nxids;
+ nsubxids * sizeof(TransactionId));
+ subcount += nsubxids;
}
}
}
@@ -2131,6 +2227,7 @@ GetSnapshotData(Snapshot snapshot)
}
RecentXmin = xmin;
+ Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
snapshot->xmin = xmin;
snapshot->xmax = xmax;
@@ -2293,7 +2390,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
@@ -2308,7 +2405,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.
@@ -2327,6 +2424,7 @@ GetRunningTransactionData(void)
static RunningTransactionsData CurrentRunningXactsData;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
TransactionId latestCompletedXid;
TransactionId oldestRunningXid;
@@ -2387,7 +2485,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
@@ -2484,7 +2582,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.
*
@@ -2499,6 +2597,7 @@ TransactionId
GetOldestActiveTransactionId(void)
{
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
TransactionId oldestRunningXid;
int index;
@@ -2521,12 +2620,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;
@@ -2604,8 +2701,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
@@ -2614,17 +2711,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;
@@ -2812,6 +2909,7 @@ BackendXidGetPid(TransactionId xid)
{
int result = 0;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int index;
if (xid == InvalidTransactionId) /* never match invalid xid */
@@ -2823,9 +2921,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;
@@ -3105,7 +3202,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
@@ -3122,7 +3218,7 @@ MinimumActiveBackends(int min)
continue; /* do not count deleted entries */
if (proc == MyProc)
continue; /* do not count myself */
- if (pgxact->xid == InvalidTransactionId)
+ if (proc->xid == InvalidTransactionId)
continue; /* do not count if no XID assigned */
if (proc->pid == 0)
continue; /* do not count prepared xacts */
@@ -3548,8 +3644,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);
@@ -3907,7 +4003,7 @@ FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
* 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
* 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
* 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..a9477ccb4a3 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->xid;
*xmin = proc->xmin;
}
}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 95989ce79bd..d86566f4554 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->xid;
/*
* 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 de346cd87fc..7fad49544ce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -102,21 +102,18 @@ Size
ProcGlobalShmemSize(void)
{
Size size = 0;
+ Size TotalProcs =
+ add_size(MaxBackends, add_size(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 +213,17 @@ InitProcGlobal(void)
MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT));
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++)
{
/* Common initialization for all PGPROCs, regardless of type. */
@@ -387,7 +395,7 @@ InitProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->pid = MyProcPid;
/* backendId, databaseId and roleId will be filled in later */
@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->backendId = InvalidBackendId;
MyProc->databaseId = InvalidOid;
--
2.25.0.114.g5b0ca878e0
--4il4azyv6rmablac
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v13-0004-snapshot-scalability-Move-PGXACT-vacuumFlags-to-.patch"
^ permalink raw reply [nested|flat] 87+ messages in thread
* [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids.
@ 2020-07-15 22:35 Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2020-07-15 22:35 UTC (permalink / raw)
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
Reviewed-By: Robert Haas, Thomas Munro, David Rowley
Discussion: https://postgr.es/m/[email protected]
---
src/include/storage/proc.h | 79 +++++-
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 | 2 +-
src/backend/storage/ipc/procarray.c | 282 +++++++++++++-------
src/backend/storage/ipc/sinvaladt.c | 4 +-
src/backend/storage/lmgr/lock.c | 3 +-
src/backend/storage/lmgr/proc.c | 26 +-
11 files changed, 335 insertions(+), 161 deletions(-)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5e4b028a5f9..146bca84bd6 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -89,6 +89,17 @@ typedef enum
* 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.
+ *
+ * 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
{
@@ -101,6 +112,12 @@ struct PGPROC
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
* starting our xact, excluding LAZY VACUUM:
* vacuum must not remove tuples deleted by
@@ -110,6 +127,9 @@ 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
+ * with data mirrored from this PGPROC */
int pgprocno;
/* These fields are zero while a backend is still starting up: */
@@ -224,10 +244,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;
@@ -236,6 +252,57 @@ 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 the same lock. Adding/Removing a procarray
+ * entry is much less frequent.
+ *
+ * Some fields in PGPROC are mirrored into more densely packed arrays (like
+ * 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 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
{
@@ -243,6 +310,10 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/* Array of PGXACT structures (not including dummies for prepared txns) */
PGXACT *allPgXact;
+
+ /* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
+ 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 f117ee160a3..6dec0c8311b 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,
@@ -1445,7 +1445,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 c15b5540a09..2d7979011ce 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
latestCompletedXid 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 latestCompletedXid to
-pass the first backend's XID, before that value became visible in the
-ProcArray. That would break GetOldestXmin, 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
+latestCompletedXid to pass the first backend's XID, before that value
+became visible in the 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
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,
@@ -382,13 +383,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->xid, 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 dd2f4d5bc7e..a4599e96610 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 XactSLRULock
* 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 and 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->xid &&
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 eb5f4680a3d..a0398bf3a3e 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 = PROC_WAIT_STATUS_OK;
/* We set up the gxact's VXID as InvalidBackendId/XID */
proc->lxid = (LocalTransactionId) xid;
- pgxact->xid = xid;
+ proc->xid = 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->xid);
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 MyProc /
+ * 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);
@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
{
GlobalTransaction gxact;
PGPROC *proc;
- PGXACT *pgxact;
TransactionId xid;
char *buf;
char *bufptr;
@@ -1423,8 +1419,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
@@ -1726,7 +1721,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 2ef0f4991ca..4c91b343ecd 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 MyProc->xid/ProcGlobal->xids[] 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;
+ MyProc->xid = BootstrapTransactionId;
+ ProcGlobal->xids[MyProc->pgxactoff] = 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->xid 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 */
+ MyProc->xid = xid;
+ ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ }
else
{
int nxids = MyPgXact->nxids;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 22228f5684f..648e12c78d8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1724,7 +1724,7 @@ 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, otherwise GetOldestNonRemovableTransactionId()
+ * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
* might appear to go backwards, which is probably Not Good.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a016816ae86..e5617ddfb41 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->xid). 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
@@ -436,7 +437,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)
{
@@ -445,7 +448,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")));
@@ -471,10 +473,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->xid;
+
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);
}
@@ -500,36 +517,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);
@@ -562,7 +602,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->xid));
/*
* If we can immediately acquire ProcArrayLock, we clear our own XID
@@ -584,7 +624,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->xid));
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
@@ -607,7 +647,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->xid);
+
+ ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
+ proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
@@ -643,7 +689,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->xid));
/* Add ourselves to the list of processes needing a group XID clear. */
proc->procArrayGroupMember = true;
@@ -748,20 +794,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->xid = InvalidTransactionId;
+
proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId;
proc->recoveryConflictPending = false;
@@ -773,6 +827,8 @@ ProcArrayClearTransaction(PGPROC *proc)
/* Clear the subtransaction-XID cache too */
pgxact->nxids = 0;
pgxact->overflowed = false;
+
+ LWLockRelease(ProcArrayLock);
}
/*
@@ -1167,7 +1223,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.
@@ -1176,26 +1232,28 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* if the Xid is running on the primary.
*
* 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;
TransactionId latestCompletedXid;
- int i,
- j;
+ int mypgxactoff;
+ size_t numProcs;
+ int j;
/*
* Don't bother checking a transaction older than RecentXmin; it could not
@@ -1250,6 +1308,8 @@ TransactionIdIsInProgress(TransactionId xid)
errmsg("out of memory")));
}
+ other_xids = ProcGlobal->xids;
+
LWLockAcquire(ProcArrayLock, LW_SHARED);
/*
@@ -1266,20 +1326,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;
@@ -1304,8 +1366,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 */
@@ -1336,7 +1402,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))
@@ -1391,7 +1457,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;
@@ -1414,6 +1480,7 @@ TransactionIdIsActive(TransactionId xid)
{
bool result = false;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int i;
/*
@@ -1429,11 +1496,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;
@@ -1519,6 +1585,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
ProcArrayStruct *arrayP = procArray;
TransactionId kaxmin;
bool in_recovery = RecoveryInProgress();
+ TransactionId *other_xids = ProcGlobal->xids;
/* inferred after ProcArrayLock is released */
h->catalog_oldest_nonremovable = InvalidTransactionId;
@@ -1562,7 +1629,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *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);
/*
@@ -1853,14 +1920,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;
@@ -1905,6 +1975,10 @@ GetSnapshotData(Snapshot snapshot)
LWLockAcquire(ProcArrayLock, LW_SHARED);
latest_completed = ShmemVariableCache->latestCompletedXid;
+ mypgxactoff = MyProc->pgxactoff;
+ myxid = other_xids[mypgxactoff];
+ Assert(myxid == MyProc->xid);
+
oldestxid = ShmemVariableCache->oldestXid;
/* xmax is always latestCompletedXid + 1 */
@@ -1915,57 +1989,79 @@ 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)
{
+ size_t numProcs = arrayP->numProcs;
+ TransactionId *xip = snapshot->xip;
int *pgprocnos = arrayP->pgprocnos;
- int numProcs;
/*
- * 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];
- PGXACT *pgxact = &allPgXact[pgprocno];
- TransactionId xid;
+ /* Fetch xid just once - see GetNewTransactionId */
+ TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ int pgprocno;
+ PGXACT *pgxact;
+ uint8 vacuumFlags;
+
+ Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+
+ /*
+ * If the transaction has no XID assigned, we can skip it; it
+ * won't have sub-XIDs either.
+ */
+ if (likely(xid == InvalidTransactionId))
+ continue;
+
+ /*
+ * 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;
+
+ pgprocno = pgprocnos[pgxactoff];
+ pgxact = &allPgXact[pgprocno];
+ vacuumFlags = pgxact->vacuumFlags;
/*
* 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
@@ -1988,9 +2084,9 @@ GetSnapshotData(Snapshot snapshot)
suboverflowed = true;
else
{
- int nxids = pgxact->nxids;
+ int nsubxids = pgxact->nxids;
- if (nxids > 0)
+ if (nsubxids > 0)
{
PGPROC *proc = &allProcs[pgprocno];
@@ -1998,8 +2094,8 @@ GetSnapshotData(Snapshot snapshot)
memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids,
- nxids * sizeof(TransactionId));
- subcount += nxids;
+ nsubxids * sizeof(TransactionId));
+ subcount += nsubxids;
}
}
}
@@ -2131,6 +2227,7 @@ GetSnapshotData(Snapshot snapshot)
}
RecentXmin = xmin;
+ Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
snapshot->xmin = xmin;
snapshot->xmax = xmax;
@@ -2293,7 +2390,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
@@ -2308,7 +2405,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.
@@ -2327,6 +2424,7 @@ GetRunningTransactionData(void)
static RunningTransactionsData CurrentRunningXactsData;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
TransactionId latestCompletedXid;
TransactionId oldestRunningXid;
@@ -2387,7 +2485,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
@@ -2484,7 +2582,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.
*
@@ -2499,6 +2597,7 @@ TransactionId
GetOldestActiveTransactionId(void)
{
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
TransactionId oldestRunningXid;
int index;
@@ -2521,12 +2620,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;
@@ -2604,8 +2701,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
@@ -2614,17 +2711,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;
@@ -2812,6 +2909,7 @@ BackendXidGetPid(TransactionId xid)
{
int result = 0;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int index;
if (xid == InvalidTransactionId) /* never match invalid xid */
@@ -2823,9 +2921,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;
@@ -3105,7 +3202,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
@@ -3122,7 +3218,7 @@ MinimumActiveBackends(int min)
continue; /* do not count deleted entries */
if (proc == MyProc)
continue; /* do not count myself */
- if (pgxact->xid == InvalidTransactionId)
+ if (proc->xid == InvalidTransactionId)
continue; /* do not count if no XID assigned */
if (proc->pid == 0)
continue; /* do not count prepared xacts */
@@ -3548,8 +3644,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);
@@ -3907,7 +4003,7 @@ FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
* 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
* 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
* 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..a9477ccb4a3 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->xid;
*xmin = proc->xmin;
}
}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 95989ce79bd..d86566f4554 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->xid;
/*
* 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 de346cd87fc..7fad49544ce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -102,21 +102,18 @@ Size
ProcGlobalShmemSize(void)
{
Size size = 0;
+ Size TotalProcs =
+ add_size(MaxBackends, add_size(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 +213,17 @@ InitProcGlobal(void)
MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT));
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++)
{
/* Common initialization for all PGPROCs, regardless of type. */
@@ -387,7 +395,7 @@ InitProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->pid = MyProcPid;
/* backendId, databaseId and roleId will be filled in later */
@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->backendId = InvalidBackendId;
MyProc->databaseId = InvalidOid;
--
2.25.0.114.g5b0ca878e0
--4il4azyv6rmablac
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v13-0004-snapshot-scalability-Move-PGXACT-vacuumFlags-to-.patch"
^ permalink raw reply [nested|flat] 87+ messages in thread
* [PATCH v12 4/7] snapshot scalability: Introduce dense array of in-progress xids.
@ 2020-07-15 22:35 Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2020-07-15 22:35 UTC (permalink / raw)
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
Reviewed-By: Robert Haas, Thomas Munro, David Rowley
Discussion: https://postgr.es/m/[email protected]
---
src/include/storage/proc.h | 79 +++++-
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 | 2 +-
src/backend/storage/ipc/procarray.c | 282 +++++++++++++-------
src/backend/storage/ipc/sinvaladt.c | 4 +-
src/backend/storage/lmgr/lock.c | 3 +-
src/backend/storage/lmgr/proc.c | 26 +-
11 files changed, 335 insertions(+), 161 deletions(-)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 286c9a9aec3..b828cecd185 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -90,6 +90,17 @@ typedef enum
* 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.
+ *
+ * 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
{
@@ -102,6 +113,12 @@ struct PGPROC
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
* starting our xact, excluding LAZY VACUUM:
* vacuum must not remove tuples deleted by
@@ -111,6 +128,9 @@ 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
+ * with data mirrored from this PGPROC */
int pgprocno;
/* These fields are zero while a backend is still starting up: */
@@ -225,10 +245,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;
@@ -237,6 +253,57 @@ 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 the same lock. Adding/Removing a procarray
+ * entry is much less frequent.
+ *
+ * Some fields in PGPROC are mirrored into more densely packed arrays (like
+ * 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 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
{
@@ -244,6 +311,10 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/* Array of PGXACT structures (not including dummies for prepared txns) */
PGXACT *allPgXact;
+
+ /* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
+ 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 b25b3e429ed..10848649c0c 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,
@@ -1445,7 +1445,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 94d8f3fd0a2..c46fc3cc194 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 ComputeXidHorizons, 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 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
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,
@@ -382,13 +383,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->xid, 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 f3da40ae017..5198a0cef68 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 XactSLRULock
* 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 and 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->xid &&
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 ae7c1a4c172..d073eb07d23 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 = PROC_WAIT_STATUS_OK;
/* We set up the gxact's VXID as InvalidBackendId/XID */
proc->lxid = (LocalTransactionId) xid;
- pgxact->xid = xid;
+ proc->xid = 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->xid);
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 MyProc /
+ * 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);
@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
{
GlobalTransaction gxact;
PGPROC *proc;
- PGXACT *pgxact;
TransactionId xid;
char *buf;
char *bufptr;
@@ -1423,8 +1419,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
@@ -1726,7 +1721,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 66eb74aa9f8..73167054e61 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 MyProc->xid/ProcGlobal->xids[] 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;
+ MyProc->xid = BootstrapTransactionId;
+ ProcGlobal->xids[MyProc->pgxactoff] = BootstrapTransactionId;
return FullTransactionIdFromEpochAndXid(0, BootstrapTransactionId);
}
@@ -190,10 +192,10 @@ GetNewTransactionId(bool isSubXact)
* latestCompletedFullXid 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->xid 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 */
+ MyProc->xid = xid;
+ ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ }
else
{
int nxids = MyPgXact->nxids;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 22228f5684f..648e12c78d8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1724,7 +1724,7 @@ 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, otherwise GetOldestNonRemovableTransactionId()
+ * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
* might appear to go backwards, which is probably Not Good.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 164cf0cabc2..eeccb2eac7f 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->xid). 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
@@ -435,7 +436,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)
{
@@ -444,7 +447,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")));
@@ -470,10 +472,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->xid;
+
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);
}
@@ -499,36 +516,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);
@@ -561,7 +601,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->xid));
/*
* If we can immediately acquire ProcArrayLock, we clear our own XID
@@ -583,7 +623,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->xid));
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
@@ -606,7 +646,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->xid);
+
+ ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
+ proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
@@ -642,7 +688,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->xid));
/* Add ourselves to the list of processes needing a group XID clear. */
proc->procArrayGroupMember = true;
@@ -747,20 +793,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->xid = InvalidTransactionId;
+
proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId;
proc->recoveryConflictPending = false;
@@ -772,6 +826,8 @@ ProcArrayClearTransaction(PGPROC *proc)
/* Clear the subtransaction-XID cache too */
pgxact->nxids = 0;
pgxact->overflowed = false;
+
+ LWLockRelease(ProcArrayLock);
}
/*
@@ -1166,7 +1222,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.
@@ -1175,25 +1231,27 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* if the Xid is running on the primary.
*
* 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
@@ -1248,6 +1306,8 @@ TransactionIdIsInProgress(TransactionId xid)
errmsg("out of memory")));
}
+ other_xids = ProcGlobal->xids;
+
LWLockAcquire(ProcArrayLock, LW_SHARED);
/*
@@ -1263,20 +1323,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;
@@ -1301,8 +1363,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 */
@@ -1333,7 +1399,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))
@@ -1388,7 +1454,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;
@@ -1411,6 +1477,7 @@ TransactionIdIsActive(TransactionId xid)
{
bool result = false;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int i;
/*
@@ -1426,11 +1493,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;
@@ -1516,6 +1582,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
ProcArrayStruct *arrayP = procArray;
TransactionId kaxmin;
bool in_recovery = RecoveryInProgress();
+ TransactionId *other_xids = ProcGlobal->xids;
/* inferred after ProcArrayLock is released */
h->catalog_oldest_nonremovable = InvalidTransactionId;
@@ -1559,7 +1626,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *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);
/*
@@ -1850,14 +1917,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;
@@ -1902,6 +1972,10 @@ GetSnapshotData(Snapshot snapshot)
LWLockAcquire(ProcArrayLock, LW_SHARED);
latest_completed = ShmemVariableCache->latestCompletedFullXid;
+ mypgxactoff = MyProc->pgxactoff;
+ myxid = other_xids[mypgxactoff];
+ Assert(myxid == MyProc->xid);
+
oldestxid = ShmemVariableCache->oldestXid;
/* xmax is always latestCompletedFullXid + 1 */
@@ -1912,57 +1986,79 @@ 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)
{
+ size_t numProcs = arrayP->numProcs;
+ TransactionId *xip = snapshot->xip;
int *pgprocnos = arrayP->pgprocnos;
- int numProcs;
/*
- * 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];
- PGXACT *pgxact = &allPgXact[pgprocno];
- TransactionId xid;
+ /* Fetch xid just once - see GetNewTransactionId */
+ TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ int pgprocno;
+ PGXACT *pgxact;
+ uint8 vacuumFlags;
+
+ Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+
+ /*
+ * If the transaction has no XID assigned, we can skip it; it
+ * won't have sub-XIDs either.
+ */
+ if (likely(xid == InvalidTransactionId))
+ continue;
+
+ /*
+ * 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;
+
+ pgprocno = pgprocnos[pgxactoff];
+ pgxact = &allPgXact[pgprocno];
+ vacuumFlags = pgxact->vacuumFlags;
/*
* 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
@@ -1985,9 +2081,9 @@ GetSnapshotData(Snapshot snapshot)
suboverflowed = true;
else
{
- int nxids = pgxact->nxids;
+ int nsubxids = pgxact->nxids;
- if (nxids > 0)
+ if (nsubxids > 0)
{
PGPROC *proc = &allProcs[pgprocno];
@@ -1995,8 +2091,8 @@ GetSnapshotData(Snapshot snapshot)
memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids,
- nxids * sizeof(TransactionId));
- subcount += nxids;
+ nsubxids * sizeof(TransactionId));
+ subcount += nsubxids;
}
}
}
@@ -2128,6 +2224,7 @@ GetSnapshotData(Snapshot snapshot)
}
RecentXmin = xmin;
+ Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
snapshot->xmin = xmin;
snapshot->xmax = xmax;
@@ -2290,7 +2387,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
@@ -2305,7 +2402,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.
@@ -2324,6 +2421,7 @@ GetRunningTransactionData(void)
static RunningTransactionsData CurrentRunningXactsData;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
TransactionId latestCompletedXid;
TransactionId oldestRunningXid;
@@ -2384,7 +2482,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
@@ -2481,7 +2579,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.
*
@@ -2496,6 +2594,7 @@ TransactionId
GetOldestActiveTransactionId(void)
{
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
TransactionId oldestRunningXid;
int index;
@@ -2518,12 +2617,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;
@@ -2601,8 +2698,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
@@ -2611,17 +2708,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;
@@ -2809,6 +2906,7 @@ BackendXidGetPid(TransactionId xid)
{
int result = 0;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int index;
if (xid == InvalidTransactionId) /* never match invalid xid */
@@ -2820,9 +2918,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;
@@ -3102,7 +3199,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
@@ -3119,7 +3215,7 @@ MinimumActiveBackends(int min)
continue; /* do not count deleted entries */
if (proc == MyProc)
continue; /* do not count myself */
- if (pgxact->xid == InvalidTransactionId)
+ if (proc->xid == InvalidTransactionId)
continue; /* do not count if no XID assigned */
if (proc->pid == 0)
continue; /* do not count prepared xacts */
@@ -3545,8 +3641,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);
@@ -3904,7 +4000,7 @@ FullXidViaRelative(FullTransactionId rel, TransactionId xid)
* 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
* 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
* 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..a9477ccb4a3 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->xid;
*xmin = proc->xmin;
}
}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 95989ce79bd..d86566f4554 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->xid;
/*
* 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 de346cd87fc..7fad49544ce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -102,21 +102,18 @@ Size
ProcGlobalShmemSize(void)
{
Size size = 0;
+ Size TotalProcs =
+ add_size(MaxBackends, add_size(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 +213,17 @@ InitProcGlobal(void)
MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT));
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++)
{
/* Common initialization for all PGPROCs, regardless of type. */
@@ -387,7 +395,7 @@ InitProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->pid = MyProcPid;
/* backendId, databaseId and roleId will be filled in later */
@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->backendId = InvalidBackendId;
MyProc->databaseId = InvalidOid;
--
2.25.0.114.g5b0ca878e0
--pyx7ptyhkbjgwa43
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v12-0005-snapshot-scalability-Move-PGXACT-vacuumFlags-to-.patch"
^ permalink raw reply [nested|flat] 87+ messages in thread
* [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids.
@ 2020-07-15 22:35 Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2020-07-15 22:35 UTC (permalink / raw)
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
Reviewed-By: Robert Haas, Thomas Munro, David Rowley
Discussion: https://postgr.es/m/[email protected]
---
src/include/storage/proc.h | 79 +++++-
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 | 2 +-
src/backend/storage/ipc/procarray.c | 282 +++++++++++++-------
src/backend/storage/ipc/sinvaladt.c | 4 +-
src/backend/storage/lmgr/lock.c | 3 +-
src/backend/storage/lmgr/proc.c | 26 +-
11 files changed, 335 insertions(+), 161 deletions(-)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5e4b028a5f9..146bca84bd6 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -89,6 +89,17 @@ typedef enum
* 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.
+ *
+ * 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
{
@@ -101,6 +112,12 @@ struct PGPROC
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
* starting our xact, excluding LAZY VACUUM:
* vacuum must not remove tuples deleted by
@@ -110,6 +127,9 @@ 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
+ * with data mirrored from this PGPROC */
int pgprocno;
/* These fields are zero while a backend is still starting up: */
@@ -224,10 +244,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;
@@ -236,6 +252,57 @@ 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 the same lock. Adding/Removing a procarray
+ * entry is much less frequent.
+ *
+ * Some fields in PGPROC are mirrored into more densely packed arrays (like
+ * 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 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
{
@@ -243,6 +310,10 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/* Array of PGXACT structures (not including dummies for prepared txns) */
PGXACT *allPgXact;
+
+ /* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
+ 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 f117ee160a3..6dec0c8311b 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,
@@ -1445,7 +1445,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 c15b5540a09..2d7979011ce 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
latestCompletedXid 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 latestCompletedXid to
-pass the first backend's XID, before that value became visible in the
-ProcArray. That would break GetOldestXmin, 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
+latestCompletedXid to pass the first backend's XID, before that value
+became visible in the 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
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,
@@ -382,13 +383,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->xid, 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 dd2f4d5bc7e..a4599e96610 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 XactSLRULock
* 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 and 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->xid &&
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 eb5f4680a3d..a0398bf3a3e 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 = PROC_WAIT_STATUS_OK;
/* We set up the gxact's VXID as InvalidBackendId/XID */
proc->lxid = (LocalTransactionId) xid;
- pgxact->xid = xid;
+ proc->xid = 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->xid);
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 MyProc /
+ * 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);
@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
{
GlobalTransaction gxact;
PGPROC *proc;
- PGXACT *pgxact;
TransactionId xid;
char *buf;
char *bufptr;
@@ -1423,8 +1419,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
@@ -1726,7 +1721,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 2ef0f4991ca..4c91b343ecd 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 MyProc->xid/ProcGlobal->xids[] 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;
+ MyProc->xid = BootstrapTransactionId;
+ ProcGlobal->xids[MyProc->pgxactoff] = 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->xid 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 */
+ MyProc->xid = xid;
+ ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ }
else
{
int nxids = MyPgXact->nxids;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 22228f5684f..648e12c78d8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1724,7 +1724,7 @@ 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, otherwise GetOldestNonRemovableTransactionId()
+ * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
* might appear to go backwards, which is probably Not Good.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a016816ae86..e5617ddfb41 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->xid). 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
@@ -436,7 +437,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)
{
@@ -445,7 +448,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")));
@@ -471,10 +473,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->xid;
+
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);
}
@@ -500,36 +517,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);
@@ -562,7 +602,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->xid));
/*
* If we can immediately acquire ProcArrayLock, we clear our own XID
@@ -584,7 +624,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->xid));
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
@@ -607,7 +647,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->xid);
+
+ ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
+ proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
@@ -643,7 +689,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->xid));
/* Add ourselves to the list of processes needing a group XID clear. */
proc->procArrayGroupMember = true;
@@ -748,20 +794,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->xid = InvalidTransactionId;
+
proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId;
proc->recoveryConflictPending = false;
@@ -773,6 +827,8 @@ ProcArrayClearTransaction(PGPROC *proc)
/* Clear the subtransaction-XID cache too */
pgxact->nxids = 0;
pgxact->overflowed = false;
+
+ LWLockRelease(ProcArrayLock);
}
/*
@@ -1167,7 +1223,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.
@@ -1176,26 +1232,28 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* if the Xid is running on the primary.
*
* 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;
TransactionId latestCompletedXid;
- int i,
- j;
+ int mypgxactoff;
+ size_t numProcs;
+ int j;
/*
* Don't bother checking a transaction older than RecentXmin; it could not
@@ -1250,6 +1308,8 @@ TransactionIdIsInProgress(TransactionId xid)
errmsg("out of memory")));
}
+ other_xids = ProcGlobal->xids;
+
LWLockAcquire(ProcArrayLock, LW_SHARED);
/*
@@ -1266,20 +1326,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;
@@ -1304,8 +1366,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 */
@@ -1336,7 +1402,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))
@@ -1391,7 +1457,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;
@@ -1414,6 +1480,7 @@ TransactionIdIsActive(TransactionId xid)
{
bool result = false;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int i;
/*
@@ -1429,11 +1496,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;
@@ -1519,6 +1585,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
ProcArrayStruct *arrayP = procArray;
TransactionId kaxmin;
bool in_recovery = RecoveryInProgress();
+ TransactionId *other_xids = ProcGlobal->xids;
/* inferred after ProcArrayLock is released */
h->catalog_oldest_nonremovable = InvalidTransactionId;
@@ -1562,7 +1629,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *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);
/*
@@ -1853,14 +1920,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;
@@ -1905,6 +1975,10 @@ GetSnapshotData(Snapshot snapshot)
LWLockAcquire(ProcArrayLock, LW_SHARED);
latest_completed = ShmemVariableCache->latestCompletedXid;
+ mypgxactoff = MyProc->pgxactoff;
+ myxid = other_xids[mypgxactoff];
+ Assert(myxid == MyProc->xid);
+
oldestxid = ShmemVariableCache->oldestXid;
/* xmax is always latestCompletedXid + 1 */
@@ -1915,57 +1989,79 @@ 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)
{
+ size_t numProcs = arrayP->numProcs;
+ TransactionId *xip = snapshot->xip;
int *pgprocnos = arrayP->pgprocnos;
- int numProcs;
/*
- * 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];
- PGXACT *pgxact = &allPgXact[pgprocno];
- TransactionId xid;
+ /* Fetch xid just once - see GetNewTransactionId */
+ TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ int pgprocno;
+ PGXACT *pgxact;
+ uint8 vacuumFlags;
+
+ Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+
+ /*
+ * If the transaction has no XID assigned, we can skip it; it
+ * won't have sub-XIDs either.
+ */
+ if (likely(xid == InvalidTransactionId))
+ continue;
+
+ /*
+ * 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;
+
+ pgprocno = pgprocnos[pgxactoff];
+ pgxact = &allPgXact[pgprocno];
+ vacuumFlags = pgxact->vacuumFlags;
/*
* 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
@@ -1988,9 +2084,9 @@ GetSnapshotData(Snapshot snapshot)
suboverflowed = true;
else
{
- int nxids = pgxact->nxids;
+ int nsubxids = pgxact->nxids;
- if (nxids > 0)
+ if (nsubxids > 0)
{
PGPROC *proc = &allProcs[pgprocno];
@@ -1998,8 +2094,8 @@ GetSnapshotData(Snapshot snapshot)
memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids,
- nxids * sizeof(TransactionId));
- subcount += nxids;
+ nsubxids * sizeof(TransactionId));
+ subcount += nsubxids;
}
}
}
@@ -2131,6 +2227,7 @@ GetSnapshotData(Snapshot snapshot)
}
RecentXmin = xmin;
+ Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
snapshot->xmin = xmin;
snapshot->xmax = xmax;
@@ -2293,7 +2390,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
@@ -2308,7 +2405,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.
@@ -2327,6 +2424,7 @@ GetRunningTransactionData(void)
static RunningTransactionsData CurrentRunningXactsData;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
TransactionId latestCompletedXid;
TransactionId oldestRunningXid;
@@ -2387,7 +2485,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
@@ -2484,7 +2582,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.
*
@@ -2499,6 +2597,7 @@ TransactionId
GetOldestActiveTransactionId(void)
{
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
TransactionId oldestRunningXid;
int index;
@@ -2521,12 +2620,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;
@@ -2604,8 +2701,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
@@ -2614,17 +2711,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;
@@ -2812,6 +2909,7 @@ BackendXidGetPid(TransactionId xid)
{
int result = 0;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int index;
if (xid == InvalidTransactionId) /* never match invalid xid */
@@ -2823,9 +2921,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;
@@ -3105,7 +3202,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
@@ -3122,7 +3218,7 @@ MinimumActiveBackends(int min)
continue; /* do not count deleted entries */
if (proc == MyProc)
continue; /* do not count myself */
- if (pgxact->xid == InvalidTransactionId)
+ if (proc->xid == InvalidTransactionId)
continue; /* do not count if no XID assigned */
if (proc->pid == 0)
continue; /* do not count prepared xacts */
@@ -3548,8 +3644,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);
@@ -3907,7 +4003,7 @@ FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
* 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
* 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
* 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..a9477ccb4a3 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->xid;
*xmin = proc->xmin;
}
}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 95989ce79bd..d86566f4554 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->xid;
/*
* 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 de346cd87fc..7fad49544ce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -102,21 +102,18 @@ Size
ProcGlobalShmemSize(void)
{
Size size = 0;
+ Size TotalProcs =
+ add_size(MaxBackends, add_size(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 +213,17 @@ InitProcGlobal(void)
MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT));
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++)
{
/* Common initialization for all PGPROCs, regardless of type. */
@@ -387,7 +395,7 @@ InitProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->pid = MyProcPid;
/* backendId, databaseId and roleId will be filled in later */
@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->backendId = InvalidBackendId;
MyProc->databaseId = InvalidOid;
--
2.25.0.114.g5b0ca878e0
--4il4azyv6rmablac
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v13-0004-snapshot-scalability-Move-PGXACT-vacuumFlags-to-.patch"
^ permalink raw reply [nested|flat] 87+ messages in thread
* [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids.
@ 2020-07-15 22:35 Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2020-07-15 22:35 UTC (permalink / raw)
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
Reviewed-By: Robert Haas, Thomas Munro, David Rowley
Discussion: https://postgr.es/m/[email protected]
---
src/include/storage/proc.h | 79 +++++-
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 | 2 +-
src/backend/storage/ipc/procarray.c | 282 +++++++++++++-------
src/backend/storage/ipc/sinvaladt.c | 4 +-
src/backend/storage/lmgr/lock.c | 3 +-
src/backend/storage/lmgr/proc.c | 26 +-
11 files changed, 335 insertions(+), 161 deletions(-)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5e4b028a5f9..146bca84bd6 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -89,6 +89,17 @@ typedef enum
* 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.
+ *
+ * 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
{
@@ -101,6 +112,12 @@ struct PGPROC
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
* starting our xact, excluding LAZY VACUUM:
* vacuum must not remove tuples deleted by
@@ -110,6 +127,9 @@ 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
+ * with data mirrored from this PGPROC */
int pgprocno;
/* These fields are zero while a backend is still starting up: */
@@ -224,10 +244,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;
@@ -236,6 +252,57 @@ 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 the same lock. Adding/Removing a procarray
+ * entry is much less frequent.
+ *
+ * Some fields in PGPROC are mirrored into more densely packed arrays (like
+ * 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 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
{
@@ -243,6 +310,10 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/* Array of PGXACT structures (not including dummies for prepared txns) */
PGXACT *allPgXact;
+
+ /* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
+ 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 f117ee160a3..6dec0c8311b 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,
@@ -1445,7 +1445,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 c15b5540a09..2d7979011ce 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
latestCompletedXid 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 latestCompletedXid to
-pass the first backend's XID, before that value became visible in the
-ProcArray. That would break GetOldestXmin, 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
+latestCompletedXid to pass the first backend's XID, before that value
+became visible in the 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
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,
@@ -382,13 +383,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->xid, 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 dd2f4d5bc7e..a4599e96610 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 XactSLRULock
* 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 and 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->xid &&
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 eb5f4680a3d..a0398bf3a3e 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 = PROC_WAIT_STATUS_OK;
/* We set up the gxact's VXID as InvalidBackendId/XID */
proc->lxid = (LocalTransactionId) xid;
- pgxact->xid = xid;
+ proc->xid = 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->xid);
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 MyProc /
+ * 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);
@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
{
GlobalTransaction gxact;
PGPROC *proc;
- PGXACT *pgxact;
TransactionId xid;
char *buf;
char *bufptr;
@@ -1423,8 +1419,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
@@ -1726,7 +1721,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 2ef0f4991ca..4c91b343ecd 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 MyProc->xid/ProcGlobal->xids[] 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;
+ MyProc->xid = BootstrapTransactionId;
+ ProcGlobal->xids[MyProc->pgxactoff] = 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->xid 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 */
+ MyProc->xid = xid;
+ ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ }
else
{
int nxids = MyPgXact->nxids;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 22228f5684f..648e12c78d8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1724,7 +1724,7 @@ 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, otherwise GetOldestNonRemovableTransactionId()
+ * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
* might appear to go backwards, which is probably Not Good.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a016816ae86..e5617ddfb41 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->xid). 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
@@ -436,7 +437,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)
{
@@ -445,7 +448,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")));
@@ -471,10 +473,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->xid;
+
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);
}
@@ -500,36 +517,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);
@@ -562,7 +602,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->xid));
/*
* If we can immediately acquire ProcArrayLock, we clear our own XID
@@ -584,7 +624,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->xid));
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
@@ -607,7 +647,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->xid);
+
+ ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
+ proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
@@ -643,7 +689,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->xid));
/* Add ourselves to the list of processes needing a group XID clear. */
proc->procArrayGroupMember = true;
@@ -748,20 +794,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->xid = InvalidTransactionId;
+
proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId;
proc->recoveryConflictPending = false;
@@ -773,6 +827,8 @@ ProcArrayClearTransaction(PGPROC *proc)
/* Clear the subtransaction-XID cache too */
pgxact->nxids = 0;
pgxact->overflowed = false;
+
+ LWLockRelease(ProcArrayLock);
}
/*
@@ -1167,7 +1223,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.
@@ -1176,26 +1232,28 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* if the Xid is running on the primary.
*
* 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;
TransactionId latestCompletedXid;
- int i,
- j;
+ int mypgxactoff;
+ size_t numProcs;
+ int j;
/*
* Don't bother checking a transaction older than RecentXmin; it could not
@@ -1250,6 +1308,8 @@ TransactionIdIsInProgress(TransactionId xid)
errmsg("out of memory")));
}
+ other_xids = ProcGlobal->xids;
+
LWLockAcquire(ProcArrayLock, LW_SHARED);
/*
@@ -1266,20 +1326,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;
@@ -1304,8 +1366,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 */
@@ -1336,7 +1402,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))
@@ -1391,7 +1457,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;
@@ -1414,6 +1480,7 @@ TransactionIdIsActive(TransactionId xid)
{
bool result = false;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int i;
/*
@@ -1429,11 +1496,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;
@@ -1519,6 +1585,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
ProcArrayStruct *arrayP = procArray;
TransactionId kaxmin;
bool in_recovery = RecoveryInProgress();
+ TransactionId *other_xids = ProcGlobal->xids;
/* inferred after ProcArrayLock is released */
h->catalog_oldest_nonremovable = InvalidTransactionId;
@@ -1562,7 +1629,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *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);
/*
@@ -1853,14 +1920,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;
@@ -1905,6 +1975,10 @@ GetSnapshotData(Snapshot snapshot)
LWLockAcquire(ProcArrayLock, LW_SHARED);
latest_completed = ShmemVariableCache->latestCompletedXid;
+ mypgxactoff = MyProc->pgxactoff;
+ myxid = other_xids[mypgxactoff];
+ Assert(myxid == MyProc->xid);
+
oldestxid = ShmemVariableCache->oldestXid;
/* xmax is always latestCompletedXid + 1 */
@@ -1915,57 +1989,79 @@ 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)
{
+ size_t numProcs = arrayP->numProcs;
+ TransactionId *xip = snapshot->xip;
int *pgprocnos = arrayP->pgprocnos;
- int numProcs;
/*
- * 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];
- PGXACT *pgxact = &allPgXact[pgprocno];
- TransactionId xid;
+ /* Fetch xid just once - see GetNewTransactionId */
+ TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ int pgprocno;
+ PGXACT *pgxact;
+ uint8 vacuumFlags;
+
+ Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+
+ /*
+ * If the transaction has no XID assigned, we can skip it; it
+ * won't have sub-XIDs either.
+ */
+ if (likely(xid == InvalidTransactionId))
+ continue;
+
+ /*
+ * 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;
+
+ pgprocno = pgprocnos[pgxactoff];
+ pgxact = &allPgXact[pgprocno];
+ vacuumFlags = pgxact->vacuumFlags;
/*
* 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
@@ -1988,9 +2084,9 @@ GetSnapshotData(Snapshot snapshot)
suboverflowed = true;
else
{
- int nxids = pgxact->nxids;
+ int nsubxids = pgxact->nxids;
- if (nxids > 0)
+ if (nsubxids > 0)
{
PGPROC *proc = &allProcs[pgprocno];
@@ -1998,8 +2094,8 @@ GetSnapshotData(Snapshot snapshot)
memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids,
- nxids * sizeof(TransactionId));
- subcount += nxids;
+ nsubxids * sizeof(TransactionId));
+ subcount += nsubxids;
}
}
}
@@ -2131,6 +2227,7 @@ GetSnapshotData(Snapshot snapshot)
}
RecentXmin = xmin;
+ Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
snapshot->xmin = xmin;
snapshot->xmax = xmax;
@@ -2293,7 +2390,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
@@ -2308,7 +2405,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.
@@ -2327,6 +2424,7 @@ GetRunningTransactionData(void)
static RunningTransactionsData CurrentRunningXactsData;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
TransactionId latestCompletedXid;
TransactionId oldestRunningXid;
@@ -2387,7 +2485,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
@@ -2484,7 +2582,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.
*
@@ -2499,6 +2597,7 @@ TransactionId
GetOldestActiveTransactionId(void)
{
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
TransactionId oldestRunningXid;
int index;
@@ -2521,12 +2620,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;
@@ -2604,8 +2701,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
@@ -2614,17 +2711,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;
@@ -2812,6 +2909,7 @@ BackendXidGetPid(TransactionId xid)
{
int result = 0;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int index;
if (xid == InvalidTransactionId) /* never match invalid xid */
@@ -2823,9 +2921,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;
@@ -3105,7 +3202,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
@@ -3122,7 +3218,7 @@ MinimumActiveBackends(int min)
continue; /* do not count deleted entries */
if (proc == MyProc)
continue; /* do not count myself */
- if (pgxact->xid == InvalidTransactionId)
+ if (proc->xid == InvalidTransactionId)
continue; /* do not count if no XID assigned */
if (proc->pid == 0)
continue; /* do not count prepared xacts */
@@ -3548,8 +3644,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);
@@ -3907,7 +4003,7 @@ FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
* 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
* 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
* 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..a9477ccb4a3 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->xid;
*xmin = proc->xmin;
}
}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 95989ce79bd..d86566f4554 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->xid;
/*
* 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 de346cd87fc..7fad49544ce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -102,21 +102,18 @@ Size
ProcGlobalShmemSize(void)
{
Size size = 0;
+ Size TotalProcs =
+ add_size(MaxBackends, add_size(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 +213,17 @@ InitProcGlobal(void)
MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT));
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++)
{
/* Common initialization for all PGPROCs, regardless of type. */
@@ -387,7 +395,7 @@ InitProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->pid = MyProcPid;
/* backendId, databaseId and roleId will be filled in later */
@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->backendId = InvalidBackendId;
MyProc->databaseId = InvalidOid;
--
2.25.0.114.g5b0ca878e0
--4il4azyv6rmablac
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v13-0004-snapshot-scalability-Move-PGXACT-vacuumFlags-to-.patch"
^ permalink raw reply [nested|flat] 87+ messages in thread
* [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids.
@ 2020-07-15 22:35 Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2020-07-15 22:35 UTC (permalink / raw)
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
Reviewed-By: Robert Haas, Thomas Munro, David Rowley
Discussion: https://postgr.es/m/[email protected]
---
src/include/storage/proc.h | 79 +++++-
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 | 2 +-
src/backend/storage/ipc/procarray.c | 282 +++++++++++++-------
src/backend/storage/ipc/sinvaladt.c | 4 +-
src/backend/storage/lmgr/lock.c | 3 +-
src/backend/storage/lmgr/proc.c | 26 +-
11 files changed, 335 insertions(+), 161 deletions(-)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5e4b028a5f9..146bca84bd6 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -89,6 +89,17 @@ typedef enum
* 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.
+ *
+ * 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
{
@@ -101,6 +112,12 @@ struct PGPROC
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
* starting our xact, excluding LAZY VACUUM:
* vacuum must not remove tuples deleted by
@@ -110,6 +127,9 @@ 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
+ * with data mirrored from this PGPROC */
int pgprocno;
/* These fields are zero while a backend is still starting up: */
@@ -224,10 +244,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;
@@ -236,6 +252,57 @@ 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 the same lock. Adding/Removing a procarray
+ * entry is much less frequent.
+ *
+ * Some fields in PGPROC are mirrored into more densely packed arrays (like
+ * 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 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
{
@@ -243,6 +310,10 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/* Array of PGXACT structures (not including dummies for prepared txns) */
PGXACT *allPgXact;
+
+ /* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
+ 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 f117ee160a3..6dec0c8311b 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,
@@ -1445,7 +1445,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 c15b5540a09..2d7979011ce 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
latestCompletedXid 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 latestCompletedXid to
-pass the first backend's XID, before that value became visible in the
-ProcArray. That would break GetOldestXmin, 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
+latestCompletedXid to pass the first backend's XID, before that value
+became visible in the 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
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,
@@ -382,13 +383,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->xid, 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 dd2f4d5bc7e..a4599e96610 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 XactSLRULock
* 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 and 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->xid &&
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 eb5f4680a3d..a0398bf3a3e 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 = PROC_WAIT_STATUS_OK;
/* We set up the gxact's VXID as InvalidBackendId/XID */
proc->lxid = (LocalTransactionId) xid;
- pgxact->xid = xid;
+ proc->xid = 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->xid);
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 MyProc /
+ * 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);
@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
{
GlobalTransaction gxact;
PGPROC *proc;
- PGXACT *pgxact;
TransactionId xid;
char *buf;
char *bufptr;
@@ -1423,8 +1419,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
@@ -1726,7 +1721,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 2ef0f4991ca..4c91b343ecd 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 MyProc->xid/ProcGlobal->xids[] 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;
+ MyProc->xid = BootstrapTransactionId;
+ ProcGlobal->xids[MyProc->pgxactoff] = 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->xid 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 */
+ MyProc->xid = xid;
+ ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ }
else
{
int nxids = MyPgXact->nxids;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 22228f5684f..648e12c78d8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1724,7 +1724,7 @@ 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, otherwise GetOldestNonRemovableTransactionId()
+ * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
* might appear to go backwards, which is probably Not Good.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a016816ae86..e5617ddfb41 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->xid). 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
@@ -436,7 +437,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)
{
@@ -445,7 +448,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")));
@@ -471,10 +473,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->xid;
+
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);
}
@@ -500,36 +517,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);
@@ -562,7 +602,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->xid));
/*
* If we can immediately acquire ProcArrayLock, we clear our own XID
@@ -584,7 +624,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->xid));
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
@@ -607,7 +647,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->xid);
+
+ ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
+ proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
@@ -643,7 +689,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->xid));
/* Add ourselves to the list of processes needing a group XID clear. */
proc->procArrayGroupMember = true;
@@ -748,20 +794,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->xid = InvalidTransactionId;
+
proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId;
proc->recoveryConflictPending = false;
@@ -773,6 +827,8 @@ ProcArrayClearTransaction(PGPROC *proc)
/* Clear the subtransaction-XID cache too */
pgxact->nxids = 0;
pgxact->overflowed = false;
+
+ LWLockRelease(ProcArrayLock);
}
/*
@@ -1167,7 +1223,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.
@@ -1176,26 +1232,28 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* if the Xid is running on the primary.
*
* 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;
TransactionId latestCompletedXid;
- int i,
- j;
+ int mypgxactoff;
+ size_t numProcs;
+ int j;
/*
* Don't bother checking a transaction older than RecentXmin; it could not
@@ -1250,6 +1308,8 @@ TransactionIdIsInProgress(TransactionId xid)
errmsg("out of memory")));
}
+ other_xids = ProcGlobal->xids;
+
LWLockAcquire(ProcArrayLock, LW_SHARED);
/*
@@ -1266,20 +1326,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;
@@ -1304,8 +1366,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 */
@@ -1336,7 +1402,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))
@@ -1391,7 +1457,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;
@@ -1414,6 +1480,7 @@ TransactionIdIsActive(TransactionId xid)
{
bool result = false;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int i;
/*
@@ -1429,11 +1496,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;
@@ -1519,6 +1585,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
ProcArrayStruct *arrayP = procArray;
TransactionId kaxmin;
bool in_recovery = RecoveryInProgress();
+ TransactionId *other_xids = ProcGlobal->xids;
/* inferred after ProcArrayLock is released */
h->catalog_oldest_nonremovable = InvalidTransactionId;
@@ -1562,7 +1629,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *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);
/*
@@ -1853,14 +1920,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;
@@ -1905,6 +1975,10 @@ GetSnapshotData(Snapshot snapshot)
LWLockAcquire(ProcArrayLock, LW_SHARED);
latest_completed = ShmemVariableCache->latestCompletedXid;
+ mypgxactoff = MyProc->pgxactoff;
+ myxid = other_xids[mypgxactoff];
+ Assert(myxid == MyProc->xid);
+
oldestxid = ShmemVariableCache->oldestXid;
/* xmax is always latestCompletedXid + 1 */
@@ -1915,57 +1989,79 @@ 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)
{
+ size_t numProcs = arrayP->numProcs;
+ TransactionId *xip = snapshot->xip;
int *pgprocnos = arrayP->pgprocnos;
- int numProcs;
/*
- * 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];
- PGXACT *pgxact = &allPgXact[pgprocno];
- TransactionId xid;
+ /* Fetch xid just once - see GetNewTransactionId */
+ TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ int pgprocno;
+ PGXACT *pgxact;
+ uint8 vacuumFlags;
+
+ Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+
+ /*
+ * If the transaction has no XID assigned, we can skip it; it
+ * won't have sub-XIDs either.
+ */
+ if (likely(xid == InvalidTransactionId))
+ continue;
+
+ /*
+ * 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;
+
+ pgprocno = pgprocnos[pgxactoff];
+ pgxact = &allPgXact[pgprocno];
+ vacuumFlags = pgxact->vacuumFlags;
/*
* 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
@@ -1988,9 +2084,9 @@ GetSnapshotData(Snapshot snapshot)
suboverflowed = true;
else
{
- int nxids = pgxact->nxids;
+ int nsubxids = pgxact->nxids;
- if (nxids > 0)
+ if (nsubxids > 0)
{
PGPROC *proc = &allProcs[pgprocno];
@@ -1998,8 +2094,8 @@ GetSnapshotData(Snapshot snapshot)
memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids,
- nxids * sizeof(TransactionId));
- subcount += nxids;
+ nsubxids * sizeof(TransactionId));
+ subcount += nsubxids;
}
}
}
@@ -2131,6 +2227,7 @@ GetSnapshotData(Snapshot snapshot)
}
RecentXmin = xmin;
+ Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
snapshot->xmin = xmin;
snapshot->xmax = xmax;
@@ -2293,7 +2390,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
@@ -2308,7 +2405,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.
@@ -2327,6 +2424,7 @@ GetRunningTransactionData(void)
static RunningTransactionsData CurrentRunningXactsData;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
TransactionId latestCompletedXid;
TransactionId oldestRunningXid;
@@ -2387,7 +2485,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
@@ -2484,7 +2582,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.
*
@@ -2499,6 +2597,7 @@ TransactionId
GetOldestActiveTransactionId(void)
{
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
TransactionId oldestRunningXid;
int index;
@@ -2521,12 +2620,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;
@@ -2604,8 +2701,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
@@ -2614,17 +2711,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;
@@ -2812,6 +2909,7 @@ BackendXidGetPid(TransactionId xid)
{
int result = 0;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int index;
if (xid == InvalidTransactionId) /* never match invalid xid */
@@ -2823,9 +2921,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;
@@ -3105,7 +3202,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
@@ -3122,7 +3218,7 @@ MinimumActiveBackends(int min)
continue; /* do not count deleted entries */
if (proc == MyProc)
continue; /* do not count myself */
- if (pgxact->xid == InvalidTransactionId)
+ if (proc->xid == InvalidTransactionId)
continue; /* do not count if no XID assigned */
if (proc->pid == 0)
continue; /* do not count prepared xacts */
@@ -3548,8 +3644,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);
@@ -3907,7 +4003,7 @@ FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
* 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
* 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
* 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..a9477ccb4a3 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->xid;
*xmin = proc->xmin;
}
}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 95989ce79bd..d86566f4554 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->xid;
/*
* 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 de346cd87fc..7fad49544ce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -102,21 +102,18 @@ Size
ProcGlobalShmemSize(void)
{
Size size = 0;
+ Size TotalProcs =
+ add_size(MaxBackends, add_size(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 +213,17 @@ InitProcGlobal(void)
MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT));
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++)
{
/* Common initialization for all PGPROCs, regardless of type. */
@@ -387,7 +395,7 @@ InitProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->pid = MyProcPid;
/* backendId, databaseId and roleId will be filled in later */
@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->backendId = InvalidBackendId;
MyProc->databaseId = InvalidOid;
--
2.25.0.114.g5b0ca878e0
--4il4azyv6rmablac
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v13-0004-snapshot-scalability-Move-PGXACT-vacuumFlags-to-.patch"
^ permalink raw reply [nested|flat] 87+ messages in thread
* [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids.
@ 2020-07-15 22:35 Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2020-07-15 22:35 UTC (permalink / raw)
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
Reviewed-By: Robert Haas, Thomas Munro, David Rowley
Discussion: https://postgr.es/m/[email protected]
---
src/include/storage/proc.h | 79 +++++-
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 | 2 +-
src/backend/storage/ipc/procarray.c | 282 +++++++++++++-------
src/backend/storage/ipc/sinvaladt.c | 4 +-
src/backend/storage/lmgr/lock.c | 3 +-
src/backend/storage/lmgr/proc.c | 26 +-
11 files changed, 335 insertions(+), 161 deletions(-)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5e4b028a5f9..146bca84bd6 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -89,6 +89,17 @@ typedef enum
* 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.
+ *
+ * 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
{
@@ -101,6 +112,12 @@ struct PGPROC
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
* starting our xact, excluding LAZY VACUUM:
* vacuum must not remove tuples deleted by
@@ -110,6 +127,9 @@ 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
+ * with data mirrored from this PGPROC */
int pgprocno;
/* These fields are zero while a backend is still starting up: */
@@ -224,10 +244,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;
@@ -236,6 +252,57 @@ 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 the same lock. Adding/Removing a procarray
+ * entry is much less frequent.
+ *
+ * Some fields in PGPROC are mirrored into more densely packed arrays (like
+ * 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 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
{
@@ -243,6 +310,10 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/* Array of PGXACT structures (not including dummies for prepared txns) */
PGXACT *allPgXact;
+
+ /* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
+ 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 f117ee160a3..6dec0c8311b 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,
@@ -1445,7 +1445,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 c15b5540a09..2d7979011ce 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
latestCompletedXid 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 latestCompletedXid to
-pass the first backend's XID, before that value became visible in the
-ProcArray. That would break GetOldestXmin, 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
+latestCompletedXid to pass the first backend's XID, before that value
+became visible in the 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
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,
@@ -382,13 +383,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->xid, 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 dd2f4d5bc7e..a4599e96610 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 XactSLRULock
* 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 and 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->xid &&
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 eb5f4680a3d..a0398bf3a3e 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 = PROC_WAIT_STATUS_OK;
/* We set up the gxact's VXID as InvalidBackendId/XID */
proc->lxid = (LocalTransactionId) xid;
- pgxact->xid = xid;
+ proc->xid = 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->xid);
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 MyProc /
+ * 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);
@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
{
GlobalTransaction gxact;
PGPROC *proc;
- PGXACT *pgxact;
TransactionId xid;
char *buf;
char *bufptr;
@@ -1423,8 +1419,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
@@ -1726,7 +1721,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 2ef0f4991ca..4c91b343ecd 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 MyProc->xid/ProcGlobal->xids[] 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;
+ MyProc->xid = BootstrapTransactionId;
+ ProcGlobal->xids[MyProc->pgxactoff] = 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->xid 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 */
+ MyProc->xid = xid;
+ ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ }
else
{
int nxids = MyPgXact->nxids;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 22228f5684f..648e12c78d8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1724,7 +1724,7 @@ 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, otherwise GetOldestNonRemovableTransactionId()
+ * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
* might appear to go backwards, which is probably Not Good.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a016816ae86..e5617ddfb41 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->xid). 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
@@ -436,7 +437,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)
{
@@ -445,7 +448,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")));
@@ -471,10 +473,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->xid;
+
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);
}
@@ -500,36 +517,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);
@@ -562,7 +602,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->xid));
/*
* If we can immediately acquire ProcArrayLock, we clear our own XID
@@ -584,7 +624,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->xid));
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
@@ -607,7 +647,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->xid);
+
+ ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
+ proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
@@ -643,7 +689,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->xid));
/* Add ourselves to the list of processes needing a group XID clear. */
proc->procArrayGroupMember = true;
@@ -748,20 +794,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->xid = InvalidTransactionId;
+
proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId;
proc->recoveryConflictPending = false;
@@ -773,6 +827,8 @@ ProcArrayClearTransaction(PGPROC *proc)
/* Clear the subtransaction-XID cache too */
pgxact->nxids = 0;
pgxact->overflowed = false;
+
+ LWLockRelease(ProcArrayLock);
}
/*
@@ -1167,7 +1223,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.
@@ -1176,26 +1232,28 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* if the Xid is running on the primary.
*
* 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;
TransactionId latestCompletedXid;
- int i,
- j;
+ int mypgxactoff;
+ size_t numProcs;
+ int j;
/*
* Don't bother checking a transaction older than RecentXmin; it could not
@@ -1250,6 +1308,8 @@ TransactionIdIsInProgress(TransactionId xid)
errmsg("out of memory")));
}
+ other_xids = ProcGlobal->xids;
+
LWLockAcquire(ProcArrayLock, LW_SHARED);
/*
@@ -1266,20 +1326,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;
@@ -1304,8 +1366,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 */
@@ -1336,7 +1402,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))
@@ -1391,7 +1457,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;
@@ -1414,6 +1480,7 @@ TransactionIdIsActive(TransactionId xid)
{
bool result = false;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int i;
/*
@@ -1429,11 +1496,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;
@@ -1519,6 +1585,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
ProcArrayStruct *arrayP = procArray;
TransactionId kaxmin;
bool in_recovery = RecoveryInProgress();
+ TransactionId *other_xids = ProcGlobal->xids;
/* inferred after ProcArrayLock is released */
h->catalog_oldest_nonremovable = InvalidTransactionId;
@@ -1562,7 +1629,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *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);
/*
@@ -1853,14 +1920,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;
@@ -1905,6 +1975,10 @@ GetSnapshotData(Snapshot snapshot)
LWLockAcquire(ProcArrayLock, LW_SHARED);
latest_completed = ShmemVariableCache->latestCompletedXid;
+ mypgxactoff = MyProc->pgxactoff;
+ myxid = other_xids[mypgxactoff];
+ Assert(myxid == MyProc->xid);
+
oldestxid = ShmemVariableCache->oldestXid;
/* xmax is always latestCompletedXid + 1 */
@@ -1915,57 +1989,79 @@ 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)
{
+ size_t numProcs = arrayP->numProcs;
+ TransactionId *xip = snapshot->xip;
int *pgprocnos = arrayP->pgprocnos;
- int numProcs;
/*
- * 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];
- PGXACT *pgxact = &allPgXact[pgprocno];
- TransactionId xid;
+ /* Fetch xid just once - see GetNewTransactionId */
+ TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ int pgprocno;
+ PGXACT *pgxact;
+ uint8 vacuumFlags;
+
+ Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+
+ /*
+ * If the transaction has no XID assigned, we can skip it; it
+ * won't have sub-XIDs either.
+ */
+ if (likely(xid == InvalidTransactionId))
+ continue;
+
+ /*
+ * 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;
+
+ pgprocno = pgprocnos[pgxactoff];
+ pgxact = &allPgXact[pgprocno];
+ vacuumFlags = pgxact->vacuumFlags;
/*
* 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
@@ -1988,9 +2084,9 @@ GetSnapshotData(Snapshot snapshot)
suboverflowed = true;
else
{
- int nxids = pgxact->nxids;
+ int nsubxids = pgxact->nxids;
- if (nxids > 0)
+ if (nsubxids > 0)
{
PGPROC *proc = &allProcs[pgprocno];
@@ -1998,8 +2094,8 @@ GetSnapshotData(Snapshot snapshot)
memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids,
- nxids * sizeof(TransactionId));
- subcount += nxids;
+ nsubxids * sizeof(TransactionId));
+ subcount += nsubxids;
}
}
}
@@ -2131,6 +2227,7 @@ GetSnapshotData(Snapshot snapshot)
}
RecentXmin = xmin;
+ Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
snapshot->xmin = xmin;
snapshot->xmax = xmax;
@@ -2293,7 +2390,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
@@ -2308,7 +2405,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.
@@ -2327,6 +2424,7 @@ GetRunningTransactionData(void)
static RunningTransactionsData CurrentRunningXactsData;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
TransactionId latestCompletedXid;
TransactionId oldestRunningXid;
@@ -2387,7 +2485,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
@@ -2484,7 +2582,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.
*
@@ -2499,6 +2597,7 @@ TransactionId
GetOldestActiveTransactionId(void)
{
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
TransactionId oldestRunningXid;
int index;
@@ -2521,12 +2620,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;
@@ -2604,8 +2701,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
@@ -2614,17 +2711,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;
@@ -2812,6 +2909,7 @@ BackendXidGetPid(TransactionId xid)
{
int result = 0;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int index;
if (xid == InvalidTransactionId) /* never match invalid xid */
@@ -2823,9 +2921,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;
@@ -3105,7 +3202,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
@@ -3122,7 +3218,7 @@ MinimumActiveBackends(int min)
continue; /* do not count deleted entries */
if (proc == MyProc)
continue; /* do not count myself */
- if (pgxact->xid == InvalidTransactionId)
+ if (proc->xid == InvalidTransactionId)
continue; /* do not count if no XID assigned */
if (proc->pid == 0)
continue; /* do not count prepared xacts */
@@ -3548,8 +3644,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);
@@ -3907,7 +4003,7 @@ FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
* 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
* 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
* 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..a9477ccb4a3 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->xid;
*xmin = proc->xmin;
}
}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 95989ce79bd..d86566f4554 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->xid;
/*
* 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 de346cd87fc..7fad49544ce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -102,21 +102,18 @@ Size
ProcGlobalShmemSize(void)
{
Size size = 0;
+ Size TotalProcs =
+ add_size(MaxBackends, add_size(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 +213,17 @@ InitProcGlobal(void)
MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT));
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++)
{
/* Common initialization for all PGPROCs, regardless of type. */
@@ -387,7 +395,7 @@ InitProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->pid = MyProcPid;
/* backendId, databaseId and roleId will be filled in later */
@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->backendId = InvalidBackendId;
MyProc->databaseId = InvalidOid;
--
2.25.0.114.g5b0ca878e0
--4il4azyv6rmablac
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v13-0004-snapshot-scalability-Move-PGXACT-vacuumFlags-to-.patch"
^ permalink raw reply [nested|flat] 87+ messages in thread
* [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids.
@ 2020-07-15 22:35 Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2020-07-15 22:35 UTC (permalink / raw)
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
Reviewed-By: Robert Haas, Thomas Munro, David Rowley
Discussion: https://postgr.es/m/[email protected]
---
src/include/storage/proc.h | 79 +++++-
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 | 2 +-
src/backend/storage/ipc/procarray.c | 282 +++++++++++++-------
src/backend/storage/ipc/sinvaladt.c | 4 +-
src/backend/storage/lmgr/lock.c | 3 +-
src/backend/storage/lmgr/proc.c | 26 +-
11 files changed, 335 insertions(+), 161 deletions(-)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5e4b028a5f9..146bca84bd6 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -89,6 +89,17 @@ typedef enum
* 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.
+ *
+ * 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
{
@@ -101,6 +112,12 @@ struct PGPROC
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
* starting our xact, excluding LAZY VACUUM:
* vacuum must not remove tuples deleted by
@@ -110,6 +127,9 @@ 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
+ * with data mirrored from this PGPROC */
int pgprocno;
/* These fields are zero while a backend is still starting up: */
@@ -224,10 +244,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;
@@ -236,6 +252,57 @@ 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 the same lock. Adding/Removing a procarray
+ * entry is much less frequent.
+ *
+ * Some fields in PGPROC are mirrored into more densely packed arrays (like
+ * 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 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
{
@@ -243,6 +310,10 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/* Array of PGXACT structures (not including dummies for prepared txns) */
PGXACT *allPgXact;
+
+ /* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
+ 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 f117ee160a3..6dec0c8311b 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,
@@ -1445,7 +1445,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 c15b5540a09..2d7979011ce 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
latestCompletedXid 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 latestCompletedXid to
-pass the first backend's XID, before that value became visible in the
-ProcArray. That would break GetOldestXmin, 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
+latestCompletedXid to pass the first backend's XID, before that value
+became visible in the 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
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,
@@ -382,13 +383,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->xid, 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 dd2f4d5bc7e..a4599e96610 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 XactSLRULock
* 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 and 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->xid &&
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 eb5f4680a3d..a0398bf3a3e 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 = PROC_WAIT_STATUS_OK;
/* We set up the gxact's VXID as InvalidBackendId/XID */
proc->lxid = (LocalTransactionId) xid;
- pgxact->xid = xid;
+ proc->xid = 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->xid);
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 MyProc /
+ * 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);
@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
{
GlobalTransaction gxact;
PGPROC *proc;
- PGXACT *pgxact;
TransactionId xid;
char *buf;
char *bufptr;
@@ -1423,8 +1419,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
@@ -1726,7 +1721,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 2ef0f4991ca..4c91b343ecd 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 MyProc->xid/ProcGlobal->xids[] 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;
+ MyProc->xid = BootstrapTransactionId;
+ ProcGlobal->xids[MyProc->pgxactoff] = 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->xid 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 */
+ MyProc->xid = xid;
+ ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ }
else
{
int nxids = MyPgXact->nxids;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 22228f5684f..648e12c78d8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1724,7 +1724,7 @@ 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, otherwise GetOldestNonRemovableTransactionId()
+ * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
* might appear to go backwards, which is probably Not Good.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a016816ae86..e5617ddfb41 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->xid). 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
@@ -436,7 +437,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)
{
@@ -445,7 +448,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")));
@@ -471,10 +473,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->xid;
+
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);
}
@@ -500,36 +517,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);
@@ -562,7 +602,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->xid));
/*
* If we can immediately acquire ProcArrayLock, we clear our own XID
@@ -584,7 +624,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->xid));
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
@@ -607,7 +647,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->xid);
+
+ ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
+ proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
@@ -643,7 +689,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->xid));
/* Add ourselves to the list of processes needing a group XID clear. */
proc->procArrayGroupMember = true;
@@ -748,20 +794,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->xid = InvalidTransactionId;
+
proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId;
proc->recoveryConflictPending = false;
@@ -773,6 +827,8 @@ ProcArrayClearTransaction(PGPROC *proc)
/* Clear the subtransaction-XID cache too */
pgxact->nxids = 0;
pgxact->overflowed = false;
+
+ LWLockRelease(ProcArrayLock);
}
/*
@@ -1167,7 +1223,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.
@@ -1176,26 +1232,28 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* if the Xid is running on the primary.
*
* 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;
TransactionId latestCompletedXid;
- int i,
- j;
+ int mypgxactoff;
+ size_t numProcs;
+ int j;
/*
* Don't bother checking a transaction older than RecentXmin; it could not
@@ -1250,6 +1308,8 @@ TransactionIdIsInProgress(TransactionId xid)
errmsg("out of memory")));
}
+ other_xids = ProcGlobal->xids;
+
LWLockAcquire(ProcArrayLock, LW_SHARED);
/*
@@ -1266,20 +1326,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;
@@ -1304,8 +1366,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 */
@@ -1336,7 +1402,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))
@@ -1391,7 +1457,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;
@@ -1414,6 +1480,7 @@ TransactionIdIsActive(TransactionId xid)
{
bool result = false;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int i;
/*
@@ -1429,11 +1496,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;
@@ -1519,6 +1585,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
ProcArrayStruct *arrayP = procArray;
TransactionId kaxmin;
bool in_recovery = RecoveryInProgress();
+ TransactionId *other_xids = ProcGlobal->xids;
/* inferred after ProcArrayLock is released */
h->catalog_oldest_nonremovable = InvalidTransactionId;
@@ -1562,7 +1629,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *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);
/*
@@ -1853,14 +1920,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;
@@ -1905,6 +1975,10 @@ GetSnapshotData(Snapshot snapshot)
LWLockAcquire(ProcArrayLock, LW_SHARED);
latest_completed = ShmemVariableCache->latestCompletedXid;
+ mypgxactoff = MyProc->pgxactoff;
+ myxid = other_xids[mypgxactoff];
+ Assert(myxid == MyProc->xid);
+
oldestxid = ShmemVariableCache->oldestXid;
/* xmax is always latestCompletedXid + 1 */
@@ -1915,57 +1989,79 @@ 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)
{
+ size_t numProcs = arrayP->numProcs;
+ TransactionId *xip = snapshot->xip;
int *pgprocnos = arrayP->pgprocnos;
- int numProcs;
/*
- * 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];
- PGXACT *pgxact = &allPgXact[pgprocno];
- TransactionId xid;
+ /* Fetch xid just once - see GetNewTransactionId */
+ TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ int pgprocno;
+ PGXACT *pgxact;
+ uint8 vacuumFlags;
+
+ Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+
+ /*
+ * If the transaction has no XID assigned, we can skip it; it
+ * won't have sub-XIDs either.
+ */
+ if (likely(xid == InvalidTransactionId))
+ continue;
+
+ /*
+ * 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;
+
+ pgprocno = pgprocnos[pgxactoff];
+ pgxact = &allPgXact[pgprocno];
+ vacuumFlags = pgxact->vacuumFlags;
/*
* 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
@@ -1988,9 +2084,9 @@ GetSnapshotData(Snapshot snapshot)
suboverflowed = true;
else
{
- int nxids = pgxact->nxids;
+ int nsubxids = pgxact->nxids;
- if (nxids > 0)
+ if (nsubxids > 0)
{
PGPROC *proc = &allProcs[pgprocno];
@@ -1998,8 +2094,8 @@ GetSnapshotData(Snapshot snapshot)
memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids,
- nxids * sizeof(TransactionId));
- subcount += nxids;
+ nsubxids * sizeof(TransactionId));
+ subcount += nsubxids;
}
}
}
@@ -2131,6 +2227,7 @@ GetSnapshotData(Snapshot snapshot)
}
RecentXmin = xmin;
+ Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
snapshot->xmin = xmin;
snapshot->xmax = xmax;
@@ -2293,7 +2390,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
@@ -2308,7 +2405,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.
@@ -2327,6 +2424,7 @@ GetRunningTransactionData(void)
static RunningTransactionsData CurrentRunningXactsData;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
TransactionId latestCompletedXid;
TransactionId oldestRunningXid;
@@ -2387,7 +2485,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
@@ -2484,7 +2582,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.
*
@@ -2499,6 +2597,7 @@ TransactionId
GetOldestActiveTransactionId(void)
{
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
TransactionId oldestRunningXid;
int index;
@@ -2521,12 +2620,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;
@@ -2604,8 +2701,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
@@ -2614,17 +2711,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;
@@ -2812,6 +2909,7 @@ BackendXidGetPid(TransactionId xid)
{
int result = 0;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int index;
if (xid == InvalidTransactionId) /* never match invalid xid */
@@ -2823,9 +2921,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;
@@ -3105,7 +3202,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
@@ -3122,7 +3218,7 @@ MinimumActiveBackends(int min)
continue; /* do not count deleted entries */
if (proc == MyProc)
continue; /* do not count myself */
- if (pgxact->xid == InvalidTransactionId)
+ if (proc->xid == InvalidTransactionId)
continue; /* do not count if no XID assigned */
if (proc->pid == 0)
continue; /* do not count prepared xacts */
@@ -3548,8 +3644,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);
@@ -3907,7 +4003,7 @@ FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
* 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
* 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
* 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..a9477ccb4a3 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->xid;
*xmin = proc->xmin;
}
}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 95989ce79bd..d86566f4554 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->xid;
/*
* 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 de346cd87fc..7fad49544ce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -102,21 +102,18 @@ Size
ProcGlobalShmemSize(void)
{
Size size = 0;
+ Size TotalProcs =
+ add_size(MaxBackends, add_size(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 +213,17 @@ InitProcGlobal(void)
MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT));
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++)
{
/* Common initialization for all PGPROCs, regardless of type. */
@@ -387,7 +395,7 @@ InitProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->pid = MyProcPid;
/* backendId, databaseId and roleId will be filled in later */
@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->backendId = InvalidBackendId;
MyProc->databaseId = InvalidOid;
--
2.25.0.114.g5b0ca878e0
--4il4azyv6rmablac
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v13-0004-snapshot-scalability-Move-PGXACT-vacuumFlags-to-.patch"
^ permalink raw reply [nested|flat] 87+ messages in thread
* [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids.
@ 2020-07-15 22:35 Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2020-07-15 22:35 UTC (permalink / raw)
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
Reviewed-By: Robert Haas, Thomas Munro, David Rowley
Discussion: https://postgr.es/m/[email protected]
---
src/include/storage/proc.h | 79 +++++-
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 | 2 +-
src/backend/storage/ipc/procarray.c | 282 +++++++++++++-------
src/backend/storage/ipc/sinvaladt.c | 4 +-
src/backend/storage/lmgr/lock.c | 3 +-
src/backend/storage/lmgr/proc.c | 26 +-
11 files changed, 335 insertions(+), 161 deletions(-)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5e4b028a5f9..146bca84bd6 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -89,6 +89,17 @@ typedef enum
* 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.
+ *
+ * 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
{
@@ -101,6 +112,12 @@ struct PGPROC
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
* starting our xact, excluding LAZY VACUUM:
* vacuum must not remove tuples deleted by
@@ -110,6 +127,9 @@ 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
+ * with data mirrored from this PGPROC */
int pgprocno;
/* These fields are zero while a backend is still starting up: */
@@ -224,10 +244,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;
@@ -236,6 +252,57 @@ 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 the same lock. Adding/Removing a procarray
+ * entry is much less frequent.
+ *
+ * Some fields in PGPROC are mirrored into more densely packed arrays (like
+ * 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 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
{
@@ -243,6 +310,10 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/* Array of PGXACT structures (not including dummies for prepared txns) */
PGXACT *allPgXact;
+
+ /* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
+ 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 f117ee160a3..6dec0c8311b 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,
@@ -1445,7 +1445,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 c15b5540a09..2d7979011ce 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
latestCompletedXid 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 latestCompletedXid to
-pass the first backend's XID, before that value became visible in the
-ProcArray. That would break GetOldestXmin, 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
+latestCompletedXid to pass the first backend's XID, before that value
+became visible in the 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
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,
@@ -382,13 +383,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->xid, 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 dd2f4d5bc7e..a4599e96610 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 XactSLRULock
* 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 and 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->xid &&
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 eb5f4680a3d..a0398bf3a3e 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 = PROC_WAIT_STATUS_OK;
/* We set up the gxact's VXID as InvalidBackendId/XID */
proc->lxid = (LocalTransactionId) xid;
- pgxact->xid = xid;
+ proc->xid = 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->xid);
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 MyProc /
+ * 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);
@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
{
GlobalTransaction gxact;
PGPROC *proc;
- PGXACT *pgxact;
TransactionId xid;
char *buf;
char *bufptr;
@@ -1423,8 +1419,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
@@ -1726,7 +1721,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 2ef0f4991ca..4c91b343ecd 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 MyProc->xid/ProcGlobal->xids[] 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;
+ MyProc->xid = BootstrapTransactionId;
+ ProcGlobal->xids[MyProc->pgxactoff] = 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->xid 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 */
+ MyProc->xid = xid;
+ ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ }
else
{
int nxids = MyPgXact->nxids;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 22228f5684f..648e12c78d8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1724,7 +1724,7 @@ 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, otherwise GetOldestNonRemovableTransactionId()
+ * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
* might appear to go backwards, which is probably Not Good.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a016816ae86..e5617ddfb41 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->xid). 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
@@ -436,7 +437,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)
{
@@ -445,7 +448,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")));
@@ -471,10 +473,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->xid;
+
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);
}
@@ -500,36 +517,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);
@@ -562,7 +602,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->xid));
/*
* If we can immediately acquire ProcArrayLock, we clear our own XID
@@ -584,7 +624,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->xid));
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
@@ -607,7 +647,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->xid);
+
+ ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
+ proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
@@ -643,7 +689,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->xid));
/* Add ourselves to the list of processes needing a group XID clear. */
proc->procArrayGroupMember = true;
@@ -748,20 +794,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->xid = InvalidTransactionId;
+
proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId;
proc->recoveryConflictPending = false;
@@ -773,6 +827,8 @@ ProcArrayClearTransaction(PGPROC *proc)
/* Clear the subtransaction-XID cache too */
pgxact->nxids = 0;
pgxact->overflowed = false;
+
+ LWLockRelease(ProcArrayLock);
}
/*
@@ -1167,7 +1223,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.
@@ -1176,26 +1232,28 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* if the Xid is running on the primary.
*
* 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;
TransactionId latestCompletedXid;
- int i,
- j;
+ int mypgxactoff;
+ size_t numProcs;
+ int j;
/*
* Don't bother checking a transaction older than RecentXmin; it could not
@@ -1250,6 +1308,8 @@ TransactionIdIsInProgress(TransactionId xid)
errmsg("out of memory")));
}
+ other_xids = ProcGlobal->xids;
+
LWLockAcquire(ProcArrayLock, LW_SHARED);
/*
@@ -1266,20 +1326,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;
@@ -1304,8 +1366,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 */
@@ -1336,7 +1402,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))
@@ -1391,7 +1457,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;
@@ -1414,6 +1480,7 @@ TransactionIdIsActive(TransactionId xid)
{
bool result = false;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int i;
/*
@@ -1429,11 +1496,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;
@@ -1519,6 +1585,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
ProcArrayStruct *arrayP = procArray;
TransactionId kaxmin;
bool in_recovery = RecoveryInProgress();
+ TransactionId *other_xids = ProcGlobal->xids;
/* inferred after ProcArrayLock is released */
h->catalog_oldest_nonremovable = InvalidTransactionId;
@@ -1562,7 +1629,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *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);
/*
@@ -1853,14 +1920,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;
@@ -1905,6 +1975,10 @@ GetSnapshotData(Snapshot snapshot)
LWLockAcquire(ProcArrayLock, LW_SHARED);
latest_completed = ShmemVariableCache->latestCompletedXid;
+ mypgxactoff = MyProc->pgxactoff;
+ myxid = other_xids[mypgxactoff];
+ Assert(myxid == MyProc->xid);
+
oldestxid = ShmemVariableCache->oldestXid;
/* xmax is always latestCompletedXid + 1 */
@@ -1915,57 +1989,79 @@ 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)
{
+ size_t numProcs = arrayP->numProcs;
+ TransactionId *xip = snapshot->xip;
int *pgprocnos = arrayP->pgprocnos;
- int numProcs;
/*
- * 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];
- PGXACT *pgxact = &allPgXact[pgprocno];
- TransactionId xid;
+ /* Fetch xid just once - see GetNewTransactionId */
+ TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ int pgprocno;
+ PGXACT *pgxact;
+ uint8 vacuumFlags;
+
+ Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+
+ /*
+ * If the transaction has no XID assigned, we can skip it; it
+ * won't have sub-XIDs either.
+ */
+ if (likely(xid == InvalidTransactionId))
+ continue;
+
+ /*
+ * 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;
+
+ pgprocno = pgprocnos[pgxactoff];
+ pgxact = &allPgXact[pgprocno];
+ vacuumFlags = pgxact->vacuumFlags;
/*
* 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
@@ -1988,9 +2084,9 @@ GetSnapshotData(Snapshot snapshot)
suboverflowed = true;
else
{
- int nxids = pgxact->nxids;
+ int nsubxids = pgxact->nxids;
- if (nxids > 0)
+ if (nsubxids > 0)
{
PGPROC *proc = &allProcs[pgprocno];
@@ -1998,8 +2094,8 @@ GetSnapshotData(Snapshot snapshot)
memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids,
- nxids * sizeof(TransactionId));
- subcount += nxids;
+ nsubxids * sizeof(TransactionId));
+ subcount += nsubxids;
}
}
}
@@ -2131,6 +2227,7 @@ GetSnapshotData(Snapshot snapshot)
}
RecentXmin = xmin;
+ Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
snapshot->xmin = xmin;
snapshot->xmax = xmax;
@@ -2293,7 +2390,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
@@ -2308,7 +2405,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.
@@ -2327,6 +2424,7 @@ GetRunningTransactionData(void)
static RunningTransactionsData CurrentRunningXactsData;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
TransactionId latestCompletedXid;
TransactionId oldestRunningXid;
@@ -2387,7 +2485,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
@@ -2484,7 +2582,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.
*
@@ -2499,6 +2597,7 @@ TransactionId
GetOldestActiveTransactionId(void)
{
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
TransactionId oldestRunningXid;
int index;
@@ -2521,12 +2620,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;
@@ -2604,8 +2701,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
@@ -2614,17 +2711,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;
@@ -2812,6 +2909,7 @@ BackendXidGetPid(TransactionId xid)
{
int result = 0;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int index;
if (xid == InvalidTransactionId) /* never match invalid xid */
@@ -2823,9 +2921,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;
@@ -3105,7 +3202,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
@@ -3122,7 +3218,7 @@ MinimumActiveBackends(int min)
continue; /* do not count deleted entries */
if (proc == MyProc)
continue; /* do not count myself */
- if (pgxact->xid == InvalidTransactionId)
+ if (proc->xid == InvalidTransactionId)
continue; /* do not count if no XID assigned */
if (proc->pid == 0)
continue; /* do not count prepared xacts */
@@ -3548,8 +3644,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);
@@ -3907,7 +4003,7 @@ FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
* 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
* 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
* 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..a9477ccb4a3 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->xid;
*xmin = proc->xmin;
}
}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 95989ce79bd..d86566f4554 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->xid;
/*
* 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 de346cd87fc..7fad49544ce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -102,21 +102,18 @@ Size
ProcGlobalShmemSize(void)
{
Size size = 0;
+ Size TotalProcs =
+ add_size(MaxBackends, add_size(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 +213,17 @@ InitProcGlobal(void)
MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT));
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++)
{
/* Common initialization for all PGPROCs, regardless of type. */
@@ -387,7 +395,7 @@ InitProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->pid = MyProcPid;
/* backendId, databaseId and roleId will be filled in later */
@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->backendId = InvalidBackendId;
MyProc->databaseId = InvalidOid;
--
2.25.0.114.g5b0ca878e0
--4il4azyv6rmablac
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v13-0004-snapshot-scalability-Move-PGXACT-vacuumFlags-to-.patch"
^ permalink raw reply [nested|flat] 87+ messages in thread
* [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids.
@ 2020-07-15 22:35 Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2020-07-15 22:35 UTC (permalink / raw)
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
Reviewed-By: Robert Haas, Thomas Munro, David Rowley
Discussion: https://postgr.es/m/[email protected]
---
src/include/storage/proc.h | 79 +++++-
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 | 2 +-
src/backend/storage/ipc/procarray.c | 282 +++++++++++++-------
src/backend/storage/ipc/sinvaladt.c | 4 +-
src/backend/storage/lmgr/lock.c | 3 +-
src/backend/storage/lmgr/proc.c | 26 +-
11 files changed, 335 insertions(+), 161 deletions(-)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5e4b028a5f9..146bca84bd6 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -89,6 +89,17 @@ typedef enum
* 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.
+ *
+ * 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
{
@@ -101,6 +112,12 @@ struct PGPROC
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
* starting our xact, excluding LAZY VACUUM:
* vacuum must not remove tuples deleted by
@@ -110,6 +127,9 @@ 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
+ * with data mirrored from this PGPROC */
int pgprocno;
/* These fields are zero while a backend is still starting up: */
@@ -224,10 +244,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;
@@ -236,6 +252,57 @@ 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 the same lock. Adding/Removing a procarray
+ * entry is much less frequent.
+ *
+ * Some fields in PGPROC are mirrored into more densely packed arrays (like
+ * 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 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
{
@@ -243,6 +310,10 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/* Array of PGXACT structures (not including dummies for prepared txns) */
PGXACT *allPgXact;
+
+ /* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
+ 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 f117ee160a3..6dec0c8311b 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,
@@ -1445,7 +1445,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 c15b5540a09..2d7979011ce 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
latestCompletedXid 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 latestCompletedXid to
-pass the first backend's XID, before that value became visible in the
-ProcArray. That would break GetOldestXmin, 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
+latestCompletedXid to pass the first backend's XID, before that value
+became visible in the 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
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,
@@ -382,13 +383,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->xid, 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 dd2f4d5bc7e..a4599e96610 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 XactSLRULock
* 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 and 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->xid &&
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 eb5f4680a3d..a0398bf3a3e 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 = PROC_WAIT_STATUS_OK;
/* We set up the gxact's VXID as InvalidBackendId/XID */
proc->lxid = (LocalTransactionId) xid;
- pgxact->xid = xid;
+ proc->xid = 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->xid);
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 MyProc /
+ * 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);
@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
{
GlobalTransaction gxact;
PGPROC *proc;
- PGXACT *pgxact;
TransactionId xid;
char *buf;
char *bufptr;
@@ -1423,8 +1419,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
@@ -1726,7 +1721,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 2ef0f4991ca..4c91b343ecd 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 MyProc->xid/ProcGlobal->xids[] 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;
+ MyProc->xid = BootstrapTransactionId;
+ ProcGlobal->xids[MyProc->pgxactoff] = 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->xid 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 */
+ MyProc->xid = xid;
+ ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ }
else
{
int nxids = MyPgXact->nxids;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 22228f5684f..648e12c78d8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1724,7 +1724,7 @@ 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, otherwise GetOldestNonRemovableTransactionId()
+ * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
* might appear to go backwards, which is probably Not Good.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a016816ae86..e5617ddfb41 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->xid). 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
@@ -436,7 +437,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)
{
@@ -445,7 +448,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")));
@@ -471,10 +473,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->xid;
+
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);
}
@@ -500,36 +517,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);
@@ -562,7 +602,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->xid));
/*
* If we can immediately acquire ProcArrayLock, we clear our own XID
@@ -584,7 +624,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->xid));
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
@@ -607,7 +647,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->xid);
+
+ ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
+ proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
@@ -643,7 +689,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->xid));
/* Add ourselves to the list of processes needing a group XID clear. */
proc->procArrayGroupMember = true;
@@ -748,20 +794,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->xid = InvalidTransactionId;
+
proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId;
proc->recoveryConflictPending = false;
@@ -773,6 +827,8 @@ ProcArrayClearTransaction(PGPROC *proc)
/* Clear the subtransaction-XID cache too */
pgxact->nxids = 0;
pgxact->overflowed = false;
+
+ LWLockRelease(ProcArrayLock);
}
/*
@@ -1167,7 +1223,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.
@@ -1176,26 +1232,28 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* if the Xid is running on the primary.
*
* 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;
TransactionId latestCompletedXid;
- int i,
- j;
+ int mypgxactoff;
+ size_t numProcs;
+ int j;
/*
* Don't bother checking a transaction older than RecentXmin; it could not
@@ -1250,6 +1308,8 @@ TransactionIdIsInProgress(TransactionId xid)
errmsg("out of memory")));
}
+ other_xids = ProcGlobal->xids;
+
LWLockAcquire(ProcArrayLock, LW_SHARED);
/*
@@ -1266,20 +1326,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;
@@ -1304,8 +1366,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 */
@@ -1336,7 +1402,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))
@@ -1391,7 +1457,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;
@@ -1414,6 +1480,7 @@ TransactionIdIsActive(TransactionId xid)
{
bool result = false;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int i;
/*
@@ -1429,11 +1496,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;
@@ -1519,6 +1585,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
ProcArrayStruct *arrayP = procArray;
TransactionId kaxmin;
bool in_recovery = RecoveryInProgress();
+ TransactionId *other_xids = ProcGlobal->xids;
/* inferred after ProcArrayLock is released */
h->catalog_oldest_nonremovable = InvalidTransactionId;
@@ -1562,7 +1629,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *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);
/*
@@ -1853,14 +1920,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;
@@ -1905,6 +1975,10 @@ GetSnapshotData(Snapshot snapshot)
LWLockAcquire(ProcArrayLock, LW_SHARED);
latest_completed = ShmemVariableCache->latestCompletedXid;
+ mypgxactoff = MyProc->pgxactoff;
+ myxid = other_xids[mypgxactoff];
+ Assert(myxid == MyProc->xid);
+
oldestxid = ShmemVariableCache->oldestXid;
/* xmax is always latestCompletedXid + 1 */
@@ -1915,57 +1989,79 @@ 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)
{
+ size_t numProcs = arrayP->numProcs;
+ TransactionId *xip = snapshot->xip;
int *pgprocnos = arrayP->pgprocnos;
- int numProcs;
/*
- * 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];
- PGXACT *pgxact = &allPgXact[pgprocno];
- TransactionId xid;
+ /* Fetch xid just once - see GetNewTransactionId */
+ TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ int pgprocno;
+ PGXACT *pgxact;
+ uint8 vacuumFlags;
+
+ Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+
+ /*
+ * If the transaction has no XID assigned, we can skip it; it
+ * won't have sub-XIDs either.
+ */
+ if (likely(xid == InvalidTransactionId))
+ continue;
+
+ /*
+ * 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;
+
+ pgprocno = pgprocnos[pgxactoff];
+ pgxact = &allPgXact[pgprocno];
+ vacuumFlags = pgxact->vacuumFlags;
/*
* 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
@@ -1988,9 +2084,9 @@ GetSnapshotData(Snapshot snapshot)
suboverflowed = true;
else
{
- int nxids = pgxact->nxids;
+ int nsubxids = pgxact->nxids;
- if (nxids > 0)
+ if (nsubxids > 0)
{
PGPROC *proc = &allProcs[pgprocno];
@@ -1998,8 +2094,8 @@ GetSnapshotData(Snapshot snapshot)
memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids,
- nxids * sizeof(TransactionId));
- subcount += nxids;
+ nsubxids * sizeof(TransactionId));
+ subcount += nsubxids;
}
}
}
@@ -2131,6 +2227,7 @@ GetSnapshotData(Snapshot snapshot)
}
RecentXmin = xmin;
+ Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
snapshot->xmin = xmin;
snapshot->xmax = xmax;
@@ -2293,7 +2390,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
@@ -2308,7 +2405,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.
@@ -2327,6 +2424,7 @@ GetRunningTransactionData(void)
static RunningTransactionsData CurrentRunningXactsData;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
TransactionId latestCompletedXid;
TransactionId oldestRunningXid;
@@ -2387,7 +2485,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
@@ -2484,7 +2582,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.
*
@@ -2499,6 +2597,7 @@ TransactionId
GetOldestActiveTransactionId(void)
{
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
TransactionId oldestRunningXid;
int index;
@@ -2521,12 +2620,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;
@@ -2604,8 +2701,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
@@ -2614,17 +2711,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;
@@ -2812,6 +2909,7 @@ BackendXidGetPid(TransactionId xid)
{
int result = 0;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int index;
if (xid == InvalidTransactionId) /* never match invalid xid */
@@ -2823,9 +2921,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;
@@ -3105,7 +3202,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
@@ -3122,7 +3218,7 @@ MinimumActiveBackends(int min)
continue; /* do not count deleted entries */
if (proc == MyProc)
continue; /* do not count myself */
- if (pgxact->xid == InvalidTransactionId)
+ if (proc->xid == InvalidTransactionId)
continue; /* do not count if no XID assigned */
if (proc->pid == 0)
continue; /* do not count prepared xacts */
@@ -3548,8 +3644,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);
@@ -3907,7 +4003,7 @@ FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
* 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
* 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
* 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..a9477ccb4a3 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->xid;
*xmin = proc->xmin;
}
}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 95989ce79bd..d86566f4554 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->xid;
/*
* 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 de346cd87fc..7fad49544ce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -102,21 +102,18 @@ Size
ProcGlobalShmemSize(void)
{
Size size = 0;
+ Size TotalProcs =
+ add_size(MaxBackends, add_size(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 +213,17 @@ InitProcGlobal(void)
MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT));
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++)
{
/* Common initialization for all PGPROCs, regardless of type. */
@@ -387,7 +395,7 @@ InitProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->pid = MyProcPid;
/* backendId, databaseId and roleId will be filled in later */
@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->backendId = InvalidBackendId;
MyProc->databaseId = InvalidOid;
--
2.25.0.114.g5b0ca878e0
--4il4azyv6rmablac
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v13-0004-snapshot-scalability-Move-PGXACT-vacuumFlags-to-.patch"
^ permalink raw reply [nested|flat] 87+ messages in thread
* [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids.
@ 2020-07-15 22:35 Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2020-07-15 22:35 UTC (permalink / raw)
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
Reviewed-By: Robert Haas, Thomas Munro, David Rowley
Discussion: https://postgr.es/m/[email protected]
---
src/include/storage/proc.h | 79 +++++-
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 | 2 +-
src/backend/storage/ipc/procarray.c | 282 +++++++++++++-------
src/backend/storage/ipc/sinvaladt.c | 4 +-
src/backend/storage/lmgr/lock.c | 3 +-
src/backend/storage/lmgr/proc.c | 26 +-
11 files changed, 335 insertions(+), 161 deletions(-)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5e4b028a5f9..146bca84bd6 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -89,6 +89,17 @@ typedef enum
* 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.
+ *
+ * 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
{
@@ -101,6 +112,12 @@ struct PGPROC
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
* starting our xact, excluding LAZY VACUUM:
* vacuum must not remove tuples deleted by
@@ -110,6 +127,9 @@ 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
+ * with data mirrored from this PGPROC */
int pgprocno;
/* These fields are zero while a backend is still starting up: */
@@ -224,10 +244,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;
@@ -236,6 +252,57 @@ 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 the same lock. Adding/Removing a procarray
+ * entry is much less frequent.
+ *
+ * Some fields in PGPROC are mirrored into more densely packed arrays (like
+ * 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 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
{
@@ -243,6 +310,10 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/* Array of PGXACT structures (not including dummies for prepared txns) */
PGXACT *allPgXact;
+
+ /* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
+ 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 f117ee160a3..6dec0c8311b 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,
@@ -1445,7 +1445,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 c15b5540a09..2d7979011ce 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
latestCompletedXid 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 latestCompletedXid to
-pass the first backend's XID, before that value became visible in the
-ProcArray. That would break GetOldestXmin, 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
+latestCompletedXid to pass the first backend's XID, before that value
+became visible in the 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
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,
@@ -382,13 +383,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->xid, 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 dd2f4d5bc7e..a4599e96610 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 XactSLRULock
* 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 and 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->xid &&
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 eb5f4680a3d..a0398bf3a3e 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 = PROC_WAIT_STATUS_OK;
/* We set up the gxact's VXID as InvalidBackendId/XID */
proc->lxid = (LocalTransactionId) xid;
- pgxact->xid = xid;
+ proc->xid = 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->xid);
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 MyProc /
+ * 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);
@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
{
GlobalTransaction gxact;
PGPROC *proc;
- PGXACT *pgxact;
TransactionId xid;
char *buf;
char *bufptr;
@@ -1423,8 +1419,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
@@ -1726,7 +1721,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 2ef0f4991ca..4c91b343ecd 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 MyProc->xid/ProcGlobal->xids[] 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;
+ MyProc->xid = BootstrapTransactionId;
+ ProcGlobal->xids[MyProc->pgxactoff] = 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->xid 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 */
+ MyProc->xid = xid;
+ ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ }
else
{
int nxids = MyPgXact->nxids;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 22228f5684f..648e12c78d8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1724,7 +1724,7 @@ 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, otherwise GetOldestNonRemovableTransactionId()
+ * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
* might appear to go backwards, which is probably Not Good.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a016816ae86..e5617ddfb41 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->xid). 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
@@ -436,7 +437,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)
{
@@ -445,7 +448,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")));
@@ -471,10 +473,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->xid;
+
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);
}
@@ -500,36 +517,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);
@@ -562,7 +602,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->xid));
/*
* If we can immediately acquire ProcArrayLock, we clear our own XID
@@ -584,7 +624,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->xid));
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
@@ -607,7 +647,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->xid);
+
+ ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
+ proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
@@ -643,7 +689,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->xid));
/* Add ourselves to the list of processes needing a group XID clear. */
proc->procArrayGroupMember = true;
@@ -748,20 +794,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->xid = InvalidTransactionId;
+
proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId;
proc->recoveryConflictPending = false;
@@ -773,6 +827,8 @@ ProcArrayClearTransaction(PGPROC *proc)
/* Clear the subtransaction-XID cache too */
pgxact->nxids = 0;
pgxact->overflowed = false;
+
+ LWLockRelease(ProcArrayLock);
}
/*
@@ -1167,7 +1223,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.
@@ -1176,26 +1232,28 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* if the Xid is running on the primary.
*
* 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;
TransactionId latestCompletedXid;
- int i,
- j;
+ int mypgxactoff;
+ size_t numProcs;
+ int j;
/*
* Don't bother checking a transaction older than RecentXmin; it could not
@@ -1250,6 +1308,8 @@ TransactionIdIsInProgress(TransactionId xid)
errmsg("out of memory")));
}
+ other_xids = ProcGlobal->xids;
+
LWLockAcquire(ProcArrayLock, LW_SHARED);
/*
@@ -1266,20 +1326,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;
@@ -1304,8 +1366,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 */
@@ -1336,7 +1402,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))
@@ -1391,7 +1457,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;
@@ -1414,6 +1480,7 @@ TransactionIdIsActive(TransactionId xid)
{
bool result = false;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int i;
/*
@@ -1429,11 +1496,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;
@@ -1519,6 +1585,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
ProcArrayStruct *arrayP = procArray;
TransactionId kaxmin;
bool in_recovery = RecoveryInProgress();
+ TransactionId *other_xids = ProcGlobal->xids;
/* inferred after ProcArrayLock is released */
h->catalog_oldest_nonremovable = InvalidTransactionId;
@@ -1562,7 +1629,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *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);
/*
@@ -1853,14 +1920,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;
@@ -1905,6 +1975,10 @@ GetSnapshotData(Snapshot snapshot)
LWLockAcquire(ProcArrayLock, LW_SHARED);
latest_completed = ShmemVariableCache->latestCompletedXid;
+ mypgxactoff = MyProc->pgxactoff;
+ myxid = other_xids[mypgxactoff];
+ Assert(myxid == MyProc->xid);
+
oldestxid = ShmemVariableCache->oldestXid;
/* xmax is always latestCompletedXid + 1 */
@@ -1915,57 +1989,79 @@ 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)
{
+ size_t numProcs = arrayP->numProcs;
+ TransactionId *xip = snapshot->xip;
int *pgprocnos = arrayP->pgprocnos;
- int numProcs;
/*
- * 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];
- PGXACT *pgxact = &allPgXact[pgprocno];
- TransactionId xid;
+ /* Fetch xid just once - see GetNewTransactionId */
+ TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ int pgprocno;
+ PGXACT *pgxact;
+ uint8 vacuumFlags;
+
+ Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+
+ /*
+ * If the transaction has no XID assigned, we can skip it; it
+ * won't have sub-XIDs either.
+ */
+ if (likely(xid == InvalidTransactionId))
+ continue;
+
+ /*
+ * 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;
+
+ pgprocno = pgprocnos[pgxactoff];
+ pgxact = &allPgXact[pgprocno];
+ vacuumFlags = pgxact->vacuumFlags;
/*
* 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
@@ -1988,9 +2084,9 @@ GetSnapshotData(Snapshot snapshot)
suboverflowed = true;
else
{
- int nxids = pgxact->nxids;
+ int nsubxids = pgxact->nxids;
- if (nxids > 0)
+ if (nsubxids > 0)
{
PGPROC *proc = &allProcs[pgprocno];
@@ -1998,8 +2094,8 @@ GetSnapshotData(Snapshot snapshot)
memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids,
- nxids * sizeof(TransactionId));
- subcount += nxids;
+ nsubxids * sizeof(TransactionId));
+ subcount += nsubxids;
}
}
}
@@ -2131,6 +2227,7 @@ GetSnapshotData(Snapshot snapshot)
}
RecentXmin = xmin;
+ Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
snapshot->xmin = xmin;
snapshot->xmax = xmax;
@@ -2293,7 +2390,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
@@ -2308,7 +2405,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.
@@ -2327,6 +2424,7 @@ GetRunningTransactionData(void)
static RunningTransactionsData CurrentRunningXactsData;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
TransactionId latestCompletedXid;
TransactionId oldestRunningXid;
@@ -2387,7 +2485,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
@@ -2484,7 +2582,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.
*
@@ -2499,6 +2597,7 @@ TransactionId
GetOldestActiveTransactionId(void)
{
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
TransactionId oldestRunningXid;
int index;
@@ -2521,12 +2620,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;
@@ -2604,8 +2701,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
@@ -2614,17 +2711,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;
@@ -2812,6 +2909,7 @@ BackendXidGetPid(TransactionId xid)
{
int result = 0;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int index;
if (xid == InvalidTransactionId) /* never match invalid xid */
@@ -2823,9 +2921,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;
@@ -3105,7 +3202,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
@@ -3122,7 +3218,7 @@ MinimumActiveBackends(int min)
continue; /* do not count deleted entries */
if (proc == MyProc)
continue; /* do not count myself */
- if (pgxact->xid == InvalidTransactionId)
+ if (proc->xid == InvalidTransactionId)
continue; /* do not count if no XID assigned */
if (proc->pid == 0)
continue; /* do not count prepared xacts */
@@ -3548,8 +3644,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);
@@ -3907,7 +4003,7 @@ FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
* 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
* 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
* 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..a9477ccb4a3 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->xid;
*xmin = proc->xmin;
}
}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 95989ce79bd..d86566f4554 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->xid;
/*
* 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 de346cd87fc..7fad49544ce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -102,21 +102,18 @@ Size
ProcGlobalShmemSize(void)
{
Size size = 0;
+ Size TotalProcs =
+ add_size(MaxBackends, add_size(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 +213,17 @@ InitProcGlobal(void)
MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT));
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++)
{
/* Common initialization for all PGPROCs, regardless of type. */
@@ -387,7 +395,7 @@ InitProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->pid = MyProcPid;
/* backendId, databaseId and roleId will be filled in later */
@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->backendId = InvalidBackendId;
MyProc->databaseId = InvalidOid;
--
2.25.0.114.g5b0ca878e0
--4il4azyv6rmablac
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v13-0004-snapshot-scalability-Move-PGXACT-vacuumFlags-to-.patch"
^ permalink raw reply [nested|flat] 87+ messages in thread
* [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids.
@ 2020-07-15 22:35 Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2020-07-15 22:35 UTC (permalink / raw)
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
Reviewed-By: Robert Haas, Thomas Munro, David Rowley
Discussion: https://postgr.es/m/[email protected]
---
src/include/storage/proc.h | 79 +++++-
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 | 2 +-
src/backend/storage/ipc/procarray.c | 282 +++++++++++++-------
src/backend/storage/ipc/sinvaladt.c | 4 +-
src/backend/storage/lmgr/lock.c | 3 +-
src/backend/storage/lmgr/proc.c | 26 +-
11 files changed, 335 insertions(+), 161 deletions(-)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5e4b028a5f9..146bca84bd6 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -89,6 +89,17 @@ typedef enum
* 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.
+ *
+ * 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
{
@@ -101,6 +112,12 @@ struct PGPROC
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
* starting our xact, excluding LAZY VACUUM:
* vacuum must not remove tuples deleted by
@@ -110,6 +127,9 @@ 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
+ * with data mirrored from this PGPROC */
int pgprocno;
/* These fields are zero while a backend is still starting up: */
@@ -224,10 +244,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;
@@ -236,6 +252,57 @@ 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 the same lock. Adding/Removing a procarray
+ * entry is much less frequent.
+ *
+ * Some fields in PGPROC are mirrored into more densely packed arrays (like
+ * 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 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
{
@@ -243,6 +310,10 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/* Array of PGXACT structures (not including dummies for prepared txns) */
PGXACT *allPgXact;
+
+ /* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
+ 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 f117ee160a3..6dec0c8311b 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,
@@ -1445,7 +1445,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 c15b5540a09..2d7979011ce 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
latestCompletedXid 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 latestCompletedXid to
-pass the first backend's XID, before that value became visible in the
-ProcArray. That would break GetOldestXmin, 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
+latestCompletedXid to pass the first backend's XID, before that value
+became visible in the 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
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,
@@ -382,13 +383,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->xid, 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 dd2f4d5bc7e..a4599e96610 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 XactSLRULock
* 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 and 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->xid &&
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 eb5f4680a3d..a0398bf3a3e 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 = PROC_WAIT_STATUS_OK;
/* We set up the gxact's VXID as InvalidBackendId/XID */
proc->lxid = (LocalTransactionId) xid;
- pgxact->xid = xid;
+ proc->xid = 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->xid);
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 MyProc /
+ * 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);
@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
{
GlobalTransaction gxact;
PGPROC *proc;
- PGXACT *pgxact;
TransactionId xid;
char *buf;
char *bufptr;
@@ -1423,8 +1419,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
@@ -1726,7 +1721,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 2ef0f4991ca..4c91b343ecd 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 MyProc->xid/ProcGlobal->xids[] 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;
+ MyProc->xid = BootstrapTransactionId;
+ ProcGlobal->xids[MyProc->pgxactoff] = 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->xid 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 */
+ MyProc->xid = xid;
+ ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ }
else
{
int nxids = MyPgXact->nxids;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 22228f5684f..648e12c78d8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1724,7 +1724,7 @@ 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, otherwise GetOldestNonRemovableTransactionId()
+ * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
* might appear to go backwards, which is probably Not Good.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a016816ae86..e5617ddfb41 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->xid). 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
@@ -436,7 +437,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)
{
@@ -445,7 +448,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")));
@@ -471,10 +473,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->xid;
+
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);
}
@@ -500,36 +517,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);
@@ -562,7 +602,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->xid));
/*
* If we can immediately acquire ProcArrayLock, we clear our own XID
@@ -584,7 +624,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->xid));
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
@@ -607,7 +647,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->xid);
+
+ ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
+ proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
@@ -643,7 +689,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->xid));
/* Add ourselves to the list of processes needing a group XID clear. */
proc->procArrayGroupMember = true;
@@ -748,20 +794,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->xid = InvalidTransactionId;
+
proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId;
proc->recoveryConflictPending = false;
@@ -773,6 +827,8 @@ ProcArrayClearTransaction(PGPROC *proc)
/* Clear the subtransaction-XID cache too */
pgxact->nxids = 0;
pgxact->overflowed = false;
+
+ LWLockRelease(ProcArrayLock);
}
/*
@@ -1167,7 +1223,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.
@@ -1176,26 +1232,28 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* if the Xid is running on the primary.
*
* 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;
TransactionId latestCompletedXid;
- int i,
- j;
+ int mypgxactoff;
+ size_t numProcs;
+ int j;
/*
* Don't bother checking a transaction older than RecentXmin; it could not
@@ -1250,6 +1308,8 @@ TransactionIdIsInProgress(TransactionId xid)
errmsg("out of memory")));
}
+ other_xids = ProcGlobal->xids;
+
LWLockAcquire(ProcArrayLock, LW_SHARED);
/*
@@ -1266,20 +1326,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;
@@ -1304,8 +1366,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 */
@@ -1336,7 +1402,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))
@@ -1391,7 +1457,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;
@@ -1414,6 +1480,7 @@ TransactionIdIsActive(TransactionId xid)
{
bool result = false;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int i;
/*
@@ -1429,11 +1496,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;
@@ -1519,6 +1585,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
ProcArrayStruct *arrayP = procArray;
TransactionId kaxmin;
bool in_recovery = RecoveryInProgress();
+ TransactionId *other_xids = ProcGlobal->xids;
/* inferred after ProcArrayLock is released */
h->catalog_oldest_nonremovable = InvalidTransactionId;
@@ -1562,7 +1629,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *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);
/*
@@ -1853,14 +1920,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;
@@ -1905,6 +1975,10 @@ GetSnapshotData(Snapshot snapshot)
LWLockAcquire(ProcArrayLock, LW_SHARED);
latest_completed = ShmemVariableCache->latestCompletedXid;
+ mypgxactoff = MyProc->pgxactoff;
+ myxid = other_xids[mypgxactoff];
+ Assert(myxid == MyProc->xid);
+
oldestxid = ShmemVariableCache->oldestXid;
/* xmax is always latestCompletedXid + 1 */
@@ -1915,57 +1989,79 @@ 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)
{
+ size_t numProcs = arrayP->numProcs;
+ TransactionId *xip = snapshot->xip;
int *pgprocnos = arrayP->pgprocnos;
- int numProcs;
/*
- * 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];
- PGXACT *pgxact = &allPgXact[pgprocno];
- TransactionId xid;
+ /* Fetch xid just once - see GetNewTransactionId */
+ TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ int pgprocno;
+ PGXACT *pgxact;
+ uint8 vacuumFlags;
+
+ Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+
+ /*
+ * If the transaction has no XID assigned, we can skip it; it
+ * won't have sub-XIDs either.
+ */
+ if (likely(xid == InvalidTransactionId))
+ continue;
+
+ /*
+ * 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;
+
+ pgprocno = pgprocnos[pgxactoff];
+ pgxact = &allPgXact[pgprocno];
+ vacuumFlags = pgxact->vacuumFlags;
/*
* 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
@@ -1988,9 +2084,9 @@ GetSnapshotData(Snapshot snapshot)
suboverflowed = true;
else
{
- int nxids = pgxact->nxids;
+ int nsubxids = pgxact->nxids;
- if (nxids > 0)
+ if (nsubxids > 0)
{
PGPROC *proc = &allProcs[pgprocno];
@@ -1998,8 +2094,8 @@ GetSnapshotData(Snapshot snapshot)
memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids,
- nxids * sizeof(TransactionId));
- subcount += nxids;
+ nsubxids * sizeof(TransactionId));
+ subcount += nsubxids;
}
}
}
@@ -2131,6 +2227,7 @@ GetSnapshotData(Snapshot snapshot)
}
RecentXmin = xmin;
+ Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
snapshot->xmin = xmin;
snapshot->xmax = xmax;
@@ -2293,7 +2390,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
@@ -2308,7 +2405,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.
@@ -2327,6 +2424,7 @@ GetRunningTransactionData(void)
static RunningTransactionsData CurrentRunningXactsData;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
TransactionId latestCompletedXid;
TransactionId oldestRunningXid;
@@ -2387,7 +2485,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
@@ -2484,7 +2582,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.
*
@@ -2499,6 +2597,7 @@ TransactionId
GetOldestActiveTransactionId(void)
{
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
TransactionId oldestRunningXid;
int index;
@@ -2521,12 +2620,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;
@@ -2604,8 +2701,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
@@ -2614,17 +2711,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;
@@ -2812,6 +2909,7 @@ BackendXidGetPid(TransactionId xid)
{
int result = 0;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int index;
if (xid == InvalidTransactionId) /* never match invalid xid */
@@ -2823,9 +2921,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;
@@ -3105,7 +3202,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
@@ -3122,7 +3218,7 @@ MinimumActiveBackends(int min)
continue; /* do not count deleted entries */
if (proc == MyProc)
continue; /* do not count myself */
- if (pgxact->xid == InvalidTransactionId)
+ if (proc->xid == InvalidTransactionId)
continue; /* do not count if no XID assigned */
if (proc->pid == 0)
continue; /* do not count prepared xacts */
@@ -3548,8 +3644,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);
@@ -3907,7 +4003,7 @@ FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
* 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
* 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
* 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..a9477ccb4a3 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->xid;
*xmin = proc->xmin;
}
}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 95989ce79bd..d86566f4554 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->xid;
/*
* 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 de346cd87fc..7fad49544ce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -102,21 +102,18 @@ Size
ProcGlobalShmemSize(void)
{
Size size = 0;
+ Size TotalProcs =
+ add_size(MaxBackends, add_size(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 +213,17 @@ InitProcGlobal(void)
MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT));
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++)
{
/* Common initialization for all PGPROCs, regardless of type. */
@@ -387,7 +395,7 @@ InitProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->pid = MyProcPid;
/* backendId, databaseId and roleId will be filled in later */
@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->backendId = InvalidBackendId;
MyProc->databaseId = InvalidOid;
--
2.25.0.114.g5b0ca878e0
--4il4azyv6rmablac
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v13-0004-snapshot-scalability-Move-PGXACT-vacuumFlags-to-.patch"
^ permalink raw reply [nested|flat] 87+ messages in thread
* [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids.
@ 2020-07-15 22:35 Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2020-07-15 22:35 UTC (permalink / raw)
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
Reviewed-By: Robert Haas, Thomas Munro, David Rowley
Discussion: https://postgr.es/m/[email protected]
---
src/include/storage/proc.h | 79 +++++-
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 | 2 +-
src/backend/storage/ipc/procarray.c | 282 +++++++++++++-------
src/backend/storage/ipc/sinvaladt.c | 4 +-
src/backend/storage/lmgr/lock.c | 3 +-
src/backend/storage/lmgr/proc.c | 26 +-
11 files changed, 335 insertions(+), 161 deletions(-)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5e4b028a5f9..146bca84bd6 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -89,6 +89,17 @@ typedef enum
* 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.
+ *
+ * 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
{
@@ -101,6 +112,12 @@ struct PGPROC
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
* starting our xact, excluding LAZY VACUUM:
* vacuum must not remove tuples deleted by
@@ -110,6 +127,9 @@ 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
+ * with data mirrored from this PGPROC */
int pgprocno;
/* These fields are zero while a backend is still starting up: */
@@ -224,10 +244,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;
@@ -236,6 +252,57 @@ 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 the same lock. Adding/Removing a procarray
+ * entry is much less frequent.
+ *
+ * Some fields in PGPROC are mirrored into more densely packed arrays (like
+ * 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 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
{
@@ -243,6 +310,10 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/* Array of PGXACT structures (not including dummies for prepared txns) */
PGXACT *allPgXact;
+
+ /* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
+ 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 f117ee160a3..6dec0c8311b 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,
@@ -1445,7 +1445,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 c15b5540a09..2d7979011ce 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
latestCompletedXid 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 latestCompletedXid to
-pass the first backend's XID, before that value became visible in the
-ProcArray. That would break GetOldestXmin, 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
+latestCompletedXid to pass the first backend's XID, before that value
+became visible in the 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
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,
@@ -382,13 +383,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->xid, 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 dd2f4d5bc7e..a4599e96610 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 XactSLRULock
* 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 and 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->xid &&
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 eb5f4680a3d..a0398bf3a3e 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 = PROC_WAIT_STATUS_OK;
/* We set up the gxact's VXID as InvalidBackendId/XID */
proc->lxid = (LocalTransactionId) xid;
- pgxact->xid = xid;
+ proc->xid = 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->xid);
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 MyProc /
+ * 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);
@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
{
GlobalTransaction gxact;
PGPROC *proc;
- PGXACT *pgxact;
TransactionId xid;
char *buf;
char *bufptr;
@@ -1423,8 +1419,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
@@ -1726,7 +1721,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 2ef0f4991ca..4c91b343ecd 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 MyProc->xid/ProcGlobal->xids[] 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;
+ MyProc->xid = BootstrapTransactionId;
+ ProcGlobal->xids[MyProc->pgxactoff] = 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->xid 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 */
+ MyProc->xid = xid;
+ ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ }
else
{
int nxids = MyPgXact->nxids;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 22228f5684f..648e12c78d8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1724,7 +1724,7 @@ 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, otherwise GetOldestNonRemovableTransactionId()
+ * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
* might appear to go backwards, which is probably Not Good.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a016816ae86..e5617ddfb41 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->xid). 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
@@ -436,7 +437,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)
{
@@ -445,7 +448,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")));
@@ -471,10 +473,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->xid;
+
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);
}
@@ -500,36 +517,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);
@@ -562,7 +602,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->xid));
/*
* If we can immediately acquire ProcArrayLock, we clear our own XID
@@ -584,7 +624,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->xid));
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
@@ -607,7 +647,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->xid);
+
+ ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
+ proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
@@ -643,7 +689,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->xid));
/* Add ourselves to the list of processes needing a group XID clear. */
proc->procArrayGroupMember = true;
@@ -748,20 +794,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->xid = InvalidTransactionId;
+
proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId;
proc->recoveryConflictPending = false;
@@ -773,6 +827,8 @@ ProcArrayClearTransaction(PGPROC *proc)
/* Clear the subtransaction-XID cache too */
pgxact->nxids = 0;
pgxact->overflowed = false;
+
+ LWLockRelease(ProcArrayLock);
}
/*
@@ -1167,7 +1223,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.
@@ -1176,26 +1232,28 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* if the Xid is running on the primary.
*
* 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;
TransactionId latestCompletedXid;
- int i,
- j;
+ int mypgxactoff;
+ size_t numProcs;
+ int j;
/*
* Don't bother checking a transaction older than RecentXmin; it could not
@@ -1250,6 +1308,8 @@ TransactionIdIsInProgress(TransactionId xid)
errmsg("out of memory")));
}
+ other_xids = ProcGlobal->xids;
+
LWLockAcquire(ProcArrayLock, LW_SHARED);
/*
@@ -1266,20 +1326,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;
@@ -1304,8 +1366,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 */
@@ -1336,7 +1402,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))
@@ -1391,7 +1457,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;
@@ -1414,6 +1480,7 @@ TransactionIdIsActive(TransactionId xid)
{
bool result = false;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int i;
/*
@@ -1429,11 +1496,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;
@@ -1519,6 +1585,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
ProcArrayStruct *arrayP = procArray;
TransactionId kaxmin;
bool in_recovery = RecoveryInProgress();
+ TransactionId *other_xids = ProcGlobal->xids;
/* inferred after ProcArrayLock is released */
h->catalog_oldest_nonremovable = InvalidTransactionId;
@@ -1562,7 +1629,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *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);
/*
@@ -1853,14 +1920,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;
@@ -1905,6 +1975,10 @@ GetSnapshotData(Snapshot snapshot)
LWLockAcquire(ProcArrayLock, LW_SHARED);
latest_completed = ShmemVariableCache->latestCompletedXid;
+ mypgxactoff = MyProc->pgxactoff;
+ myxid = other_xids[mypgxactoff];
+ Assert(myxid == MyProc->xid);
+
oldestxid = ShmemVariableCache->oldestXid;
/* xmax is always latestCompletedXid + 1 */
@@ -1915,57 +1989,79 @@ 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)
{
+ size_t numProcs = arrayP->numProcs;
+ TransactionId *xip = snapshot->xip;
int *pgprocnos = arrayP->pgprocnos;
- int numProcs;
/*
- * 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];
- PGXACT *pgxact = &allPgXact[pgprocno];
- TransactionId xid;
+ /* Fetch xid just once - see GetNewTransactionId */
+ TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ int pgprocno;
+ PGXACT *pgxact;
+ uint8 vacuumFlags;
+
+ Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+
+ /*
+ * If the transaction has no XID assigned, we can skip it; it
+ * won't have sub-XIDs either.
+ */
+ if (likely(xid == InvalidTransactionId))
+ continue;
+
+ /*
+ * 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;
+
+ pgprocno = pgprocnos[pgxactoff];
+ pgxact = &allPgXact[pgprocno];
+ vacuumFlags = pgxact->vacuumFlags;
/*
* 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
@@ -1988,9 +2084,9 @@ GetSnapshotData(Snapshot snapshot)
suboverflowed = true;
else
{
- int nxids = pgxact->nxids;
+ int nsubxids = pgxact->nxids;
- if (nxids > 0)
+ if (nsubxids > 0)
{
PGPROC *proc = &allProcs[pgprocno];
@@ -1998,8 +2094,8 @@ GetSnapshotData(Snapshot snapshot)
memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids,
- nxids * sizeof(TransactionId));
- subcount += nxids;
+ nsubxids * sizeof(TransactionId));
+ subcount += nsubxids;
}
}
}
@@ -2131,6 +2227,7 @@ GetSnapshotData(Snapshot snapshot)
}
RecentXmin = xmin;
+ Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
snapshot->xmin = xmin;
snapshot->xmax = xmax;
@@ -2293,7 +2390,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
@@ -2308,7 +2405,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.
@@ -2327,6 +2424,7 @@ GetRunningTransactionData(void)
static RunningTransactionsData CurrentRunningXactsData;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
TransactionId latestCompletedXid;
TransactionId oldestRunningXid;
@@ -2387,7 +2485,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
@@ -2484,7 +2582,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.
*
@@ -2499,6 +2597,7 @@ TransactionId
GetOldestActiveTransactionId(void)
{
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
TransactionId oldestRunningXid;
int index;
@@ -2521,12 +2620,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;
@@ -2604,8 +2701,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
@@ -2614,17 +2711,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;
@@ -2812,6 +2909,7 @@ BackendXidGetPid(TransactionId xid)
{
int result = 0;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int index;
if (xid == InvalidTransactionId) /* never match invalid xid */
@@ -2823,9 +2921,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;
@@ -3105,7 +3202,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
@@ -3122,7 +3218,7 @@ MinimumActiveBackends(int min)
continue; /* do not count deleted entries */
if (proc == MyProc)
continue; /* do not count myself */
- if (pgxact->xid == InvalidTransactionId)
+ if (proc->xid == InvalidTransactionId)
continue; /* do not count if no XID assigned */
if (proc->pid == 0)
continue; /* do not count prepared xacts */
@@ -3548,8 +3644,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);
@@ -3907,7 +4003,7 @@ FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
* 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
* 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
* 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..a9477ccb4a3 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->xid;
*xmin = proc->xmin;
}
}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 95989ce79bd..d86566f4554 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->xid;
/*
* 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 de346cd87fc..7fad49544ce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -102,21 +102,18 @@ Size
ProcGlobalShmemSize(void)
{
Size size = 0;
+ Size TotalProcs =
+ add_size(MaxBackends, add_size(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 +213,17 @@ InitProcGlobal(void)
MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT));
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++)
{
/* Common initialization for all PGPROCs, regardless of type. */
@@ -387,7 +395,7 @@ InitProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->pid = MyProcPid;
/* backendId, databaseId and roleId will be filled in later */
@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->backendId = InvalidBackendId;
MyProc->databaseId = InvalidOid;
--
2.25.0.114.g5b0ca878e0
--4il4azyv6rmablac
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v13-0004-snapshot-scalability-Move-PGXACT-vacuumFlags-to-.patch"
^ permalink raw reply [nested|flat] 87+ messages in thread
* [PATCH v11 3/6] snapshot scalability: Introduce dense array of in-progress xids.
@ 2020-07-15 22:35 Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2020-07-15 22:35 UTC (permalink / raw)
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
Reviewed-By: Robert Haas, Thomas Munro, David Rowley
Discussion: https://postgr.es/m/[email protected]
---
src/include/storage/proc.h | 79 +++++-
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 | 2 +-
src/backend/storage/ipc/procarray.c | 282 +++++++++++++-------
src/backend/storage/ipc/sinvaladt.c | 4 +-
src/backend/storage/lmgr/lock.c | 3 +-
src/backend/storage/lmgr/proc.c | 26 +-
11 files changed, 335 insertions(+), 161 deletions(-)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 286c9a9aec3..b828cecd185 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -90,6 +90,17 @@ typedef enum
* 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.
+ *
+ * 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
{
@@ -102,6 +113,12 @@ struct PGPROC
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
* starting our xact, excluding LAZY VACUUM:
* vacuum must not remove tuples deleted by
@@ -111,6 +128,9 @@ 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
+ * with data mirrored from this PGPROC */
int pgprocno;
/* These fields are zero while a backend is still starting up: */
@@ -225,10 +245,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;
@@ -237,6 +253,57 @@ 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 the same lock. Adding/Removing a procarray
+ * entry is much less frequent.
+ *
+ * Some fields in PGPROC are mirrored into more densely packed arrays (like
+ * 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 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
{
@@ -244,6 +311,10 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/* Array of PGXACT structures (not including dummies for prepared txns) */
PGXACT *allPgXact;
+
+ /* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
+ 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 b25b3e429ed..10848649c0c 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,
@@ -1445,7 +1445,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 94d8f3fd0a2..c46fc3cc194 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 ComputeXidHorizons, 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 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
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,
@@ -382,13 +383,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->xid, 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 f3da40ae017..5198a0cef68 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 XactSLRULock
* 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 and 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->xid &&
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 ae7c1a4c172..d073eb07d23 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 = PROC_WAIT_STATUS_OK;
/* We set up the gxact's VXID as InvalidBackendId/XID */
proc->lxid = (LocalTransactionId) xid;
- pgxact->xid = xid;
+ proc->xid = 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->xid);
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 MyProc /
+ * 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);
@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
{
GlobalTransaction gxact;
PGPROC *proc;
- PGXACT *pgxact;
TransactionId xid;
char *buf;
char *bufptr;
@@ -1423,8 +1419,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
@@ -1726,7 +1721,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 00b8e4e50d7..ab376f2fe22 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 MyProc->xid/ProcGlobal->xids[] 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;
+ MyProc->xid = BootstrapTransactionId;
+ ProcGlobal->xids[MyProc->pgxactoff] = 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->xid 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 */
+ MyProc->xid = xid;
+ ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ }
else
{
int nxids = MyPgXact->nxids;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 22228f5684f..648e12c78d8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1724,7 +1724,7 @@ 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, otherwise GetOldestNonRemovableTransactionId()
+ * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
* might appear to go backwards, which is probably Not Good.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 980ca2cc2af..a9b32565367 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->xid). 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
@@ -434,7 +435,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)
{
@@ -443,7 +446,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")));
@@ -469,10 +471,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->xid;
+
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);
}
@@ -498,36 +515,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);
@@ -560,7 +600,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->xid));
/*
* If we can immediately acquire ProcArrayLock, we clear our own XID
@@ -582,7 +622,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->xid));
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
@@ -605,7 +645,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->xid);
+
+ ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
+ proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
@@ -641,7 +687,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->xid));
/* Add ourselves to the list of processes needing a group XID clear. */
proc->procArrayGroupMember = true;
@@ -746,20 +792,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->xid = InvalidTransactionId;
+
proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId;
proc->recoveryConflictPending = false;
@@ -771,6 +825,8 @@ ProcArrayClearTransaction(PGPROC *proc)
/* Clear the subtransaction-XID cache too */
pgxact->nxids = 0;
pgxact->overflowed = false;
+
+ LWLockRelease(ProcArrayLock);
}
/*
@@ -1164,7 +1220,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.
@@ -1173,25 +1229,27 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* if the Xid is running on the primary.
*
* 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
@@ -1246,6 +1304,8 @@ TransactionIdIsInProgress(TransactionId xid)
errmsg("out of memory")));
}
+ other_xids = ProcGlobal->xids;
+
LWLockAcquire(ProcArrayLock, LW_SHARED);
/*
@@ -1261,20 +1321,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;
@@ -1299,8 +1361,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 */
@@ -1331,7 +1397,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))
@@ -1386,7 +1452,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;
@@ -1409,6 +1475,7 @@ TransactionIdIsActive(TransactionId xid)
{
bool result = false;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int i;
/*
@@ -1424,11 +1491,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;
@@ -1506,6 +1572,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
ProcArrayStruct *arrayP = procArray;
TransactionId kaxmin;
bool in_recovery = RecoveryInProgress();
+ TransactionId *other_xids = ProcGlobal->xids;
/* inferred after ProcArrayLock is released */
h->catalog_oldest_nonremovable = InvalidTransactionId;
@@ -1549,7 +1616,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *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);
/*
@@ -1837,14 +1904,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;
@@ -1889,6 +1959,10 @@ GetSnapshotData(Snapshot snapshot)
LWLockAcquire(ProcArrayLock, LW_SHARED);
latest_completed = ShmemVariableCache->latestCompletedFullXid;
+ mypgxactoff = MyProc->pgxactoff;
+ myxid = other_xids[mypgxactoff];
+ Assert(myxid == MyProc->xid);
+
oldestxid = ShmemVariableCache->oldestXid;
/* xmax is always latestCompletedXid + 1 */
@@ -1899,57 +1973,79 @@ 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)
{
+ size_t numProcs = arrayP->numProcs;
+ TransactionId *xip = snapshot->xip;
int *pgprocnos = arrayP->pgprocnos;
- int numProcs;
/*
- * 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];
- PGXACT *pgxact = &allPgXact[pgprocno];
- TransactionId xid;
+ /* Fetch xid just once - see GetNewTransactionId */
+ TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ int pgprocno;
+ PGXACT *pgxact;
+ uint8 vacuumFlags;
+
+ Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+
+ /*
+ * If the transaction has no XID assigned, we can skip it; it
+ * won't have sub-XIDs either.
+ */
+ if (likely(xid == InvalidTransactionId))
+ continue;
+
+ /*
+ * 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;
+
+ pgprocno = pgprocnos[pgxactoff];
+ pgxact = &allPgXact[pgprocno];
+ vacuumFlags = pgxact->vacuumFlags;
/*
* 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
@@ -1972,9 +2068,9 @@ GetSnapshotData(Snapshot snapshot)
suboverflowed = true;
else
{
- int nxids = pgxact->nxids;
+ int nsubxids = pgxact->nxids;
- if (nxids > 0)
+ if (nsubxids > 0)
{
PGPROC *proc = &allProcs[pgprocno];
@@ -1982,8 +2078,8 @@ GetSnapshotData(Snapshot snapshot)
memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids,
- nxids * sizeof(TransactionId));
- subcount += nxids;
+ nsubxids * sizeof(TransactionId));
+ subcount += nsubxids;
}
}
}
@@ -2115,6 +2211,7 @@ GetSnapshotData(Snapshot snapshot)
}
RecentXmin = xmin;
+ Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
snapshot->xmin = xmin;
snapshot->xmax = xmax;
@@ -2277,7 +2374,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
@@ -2292,7 +2389,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.
@@ -2311,6 +2408,7 @@ GetRunningTransactionData(void)
static RunningTransactionsData CurrentRunningXactsData;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
TransactionId latestCompletedXid;
TransactionId oldestRunningXid;
@@ -2370,7 +2468,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
@@ -2467,7 +2565,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.
*
@@ -2482,6 +2580,7 @@ TransactionId
GetOldestActiveTransactionId(void)
{
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
TransactionId oldestRunningXid;
int index;
@@ -2504,12 +2603,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;
@@ -2587,8 +2684,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
@@ -2597,17 +2694,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;
@@ -2795,6 +2892,7 @@ BackendXidGetPid(TransactionId xid)
{
int result = 0;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int index;
if (xid == InvalidTransactionId) /* never match invalid xid */
@@ -2806,9 +2904,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;
@@ -3088,7 +3185,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
@@ -3105,7 +3201,7 @@ MinimumActiveBackends(int min)
continue; /* do not count deleted entries */
if (proc == MyProc)
continue; /* do not count myself */
- if (pgxact->xid == InvalidTransactionId)
+ if (proc->xid == InvalidTransactionId)
continue; /* do not count if no XID assigned */
if (proc->pid == 0)
continue; /* do not count prepared xacts */
@@ -3531,8 +3627,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);
@@ -3885,7 +3981,7 @@ FullXidViaRelative(FullTransactionId rel, TransactionId xid)
* 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
* 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
* 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..a9477ccb4a3 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->xid;
*xmin = proc->xmin;
}
}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 95989ce79bd..d86566f4554 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->xid;
/*
* 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 de346cd87fc..7fad49544ce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -102,21 +102,18 @@ Size
ProcGlobalShmemSize(void)
{
Size size = 0;
+ Size TotalProcs =
+ add_size(MaxBackends, add_size(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 +213,17 @@ InitProcGlobal(void)
MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT));
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++)
{
/* Common initialization for all PGPROCs, regardless of type. */
@@ -387,7 +395,7 @@ InitProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->pid = MyProcPid;
/* backendId, databaseId and roleId will be filled in later */
@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->backendId = InvalidBackendId;
MyProc->databaseId = InvalidOid;
--
2.25.0.114.g5b0ca878e0
--kdyesixubxkl3b7r
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v11-0004-snapshot-scalability-Move-PGXACT-vacuumFlags-to-.patch"
^ permalink raw reply [nested|flat] 87+ messages in thread
* [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids.
@ 2020-07-15 22:35 Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2020-07-15 22:35 UTC (permalink / raw)
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
Reviewed-By: Robert Haas, Thomas Munro, David Rowley
Discussion: https://postgr.es/m/[email protected]
---
src/include/storage/proc.h | 79 +++++-
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 | 2 +-
src/backend/storage/ipc/procarray.c | 282 +++++++++++++-------
src/backend/storage/ipc/sinvaladt.c | 4 +-
src/backend/storage/lmgr/lock.c | 3 +-
src/backend/storage/lmgr/proc.c | 26 +-
11 files changed, 335 insertions(+), 161 deletions(-)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5e4b028a5f9..146bca84bd6 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -89,6 +89,17 @@ typedef enum
* 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.
+ *
+ * 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
{
@@ -101,6 +112,12 @@ struct PGPROC
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
* starting our xact, excluding LAZY VACUUM:
* vacuum must not remove tuples deleted by
@@ -110,6 +127,9 @@ 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
+ * with data mirrored from this PGPROC */
int pgprocno;
/* These fields are zero while a backend is still starting up: */
@@ -224,10 +244,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;
@@ -236,6 +252,57 @@ 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 the same lock. Adding/Removing a procarray
+ * entry is much less frequent.
+ *
+ * Some fields in PGPROC are mirrored into more densely packed arrays (like
+ * 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 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
{
@@ -243,6 +310,10 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/* Array of PGXACT structures (not including dummies for prepared txns) */
PGXACT *allPgXact;
+
+ /* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
+ 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 f117ee160a3..6dec0c8311b 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,
@@ -1445,7 +1445,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 c15b5540a09..2d7979011ce 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
latestCompletedXid 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 latestCompletedXid to
-pass the first backend's XID, before that value became visible in the
-ProcArray. That would break GetOldestXmin, 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
+latestCompletedXid to pass the first backend's XID, before that value
+became visible in the 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
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,
@@ -382,13 +383,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->xid, 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 dd2f4d5bc7e..a4599e96610 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 XactSLRULock
* 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 and 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->xid &&
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 eb5f4680a3d..a0398bf3a3e 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 = PROC_WAIT_STATUS_OK;
/* We set up the gxact's VXID as InvalidBackendId/XID */
proc->lxid = (LocalTransactionId) xid;
- pgxact->xid = xid;
+ proc->xid = 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->xid);
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 MyProc /
+ * 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);
@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
{
GlobalTransaction gxact;
PGPROC *proc;
- PGXACT *pgxact;
TransactionId xid;
char *buf;
char *bufptr;
@@ -1423,8 +1419,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
@@ -1726,7 +1721,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 2ef0f4991ca..4c91b343ecd 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 MyProc->xid/ProcGlobal->xids[] 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;
+ MyProc->xid = BootstrapTransactionId;
+ ProcGlobal->xids[MyProc->pgxactoff] = 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->xid 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 */
+ MyProc->xid = xid;
+ ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ }
else
{
int nxids = MyPgXact->nxids;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 22228f5684f..648e12c78d8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1724,7 +1724,7 @@ 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, otherwise GetOldestNonRemovableTransactionId()
+ * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
* might appear to go backwards, which is probably Not Good.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a016816ae86..e5617ddfb41 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->xid). 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
@@ -436,7 +437,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)
{
@@ -445,7 +448,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")));
@@ -471,10 +473,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->xid;
+
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);
}
@@ -500,36 +517,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);
@@ -562,7 +602,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->xid));
/*
* If we can immediately acquire ProcArrayLock, we clear our own XID
@@ -584,7 +624,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->xid));
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
@@ -607,7 +647,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->xid);
+
+ ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
+ proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
@@ -643,7 +689,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->xid));
/* Add ourselves to the list of processes needing a group XID clear. */
proc->procArrayGroupMember = true;
@@ -748,20 +794,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->xid = InvalidTransactionId;
+
proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId;
proc->recoveryConflictPending = false;
@@ -773,6 +827,8 @@ ProcArrayClearTransaction(PGPROC *proc)
/* Clear the subtransaction-XID cache too */
pgxact->nxids = 0;
pgxact->overflowed = false;
+
+ LWLockRelease(ProcArrayLock);
}
/*
@@ -1167,7 +1223,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.
@@ -1176,26 +1232,28 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* if the Xid is running on the primary.
*
* 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;
TransactionId latestCompletedXid;
- int i,
- j;
+ int mypgxactoff;
+ size_t numProcs;
+ int j;
/*
* Don't bother checking a transaction older than RecentXmin; it could not
@@ -1250,6 +1308,8 @@ TransactionIdIsInProgress(TransactionId xid)
errmsg("out of memory")));
}
+ other_xids = ProcGlobal->xids;
+
LWLockAcquire(ProcArrayLock, LW_SHARED);
/*
@@ -1266,20 +1326,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;
@@ -1304,8 +1366,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 */
@@ -1336,7 +1402,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))
@@ -1391,7 +1457,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;
@@ -1414,6 +1480,7 @@ TransactionIdIsActive(TransactionId xid)
{
bool result = false;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int i;
/*
@@ -1429,11 +1496,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;
@@ -1519,6 +1585,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
ProcArrayStruct *arrayP = procArray;
TransactionId kaxmin;
bool in_recovery = RecoveryInProgress();
+ TransactionId *other_xids = ProcGlobal->xids;
/* inferred after ProcArrayLock is released */
h->catalog_oldest_nonremovable = InvalidTransactionId;
@@ -1562,7 +1629,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *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);
/*
@@ -1853,14 +1920,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;
@@ -1905,6 +1975,10 @@ GetSnapshotData(Snapshot snapshot)
LWLockAcquire(ProcArrayLock, LW_SHARED);
latest_completed = ShmemVariableCache->latestCompletedXid;
+ mypgxactoff = MyProc->pgxactoff;
+ myxid = other_xids[mypgxactoff];
+ Assert(myxid == MyProc->xid);
+
oldestxid = ShmemVariableCache->oldestXid;
/* xmax is always latestCompletedXid + 1 */
@@ -1915,57 +1989,79 @@ 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)
{
+ size_t numProcs = arrayP->numProcs;
+ TransactionId *xip = snapshot->xip;
int *pgprocnos = arrayP->pgprocnos;
- int numProcs;
/*
- * 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];
- PGXACT *pgxact = &allPgXact[pgprocno];
- TransactionId xid;
+ /* Fetch xid just once - see GetNewTransactionId */
+ TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ int pgprocno;
+ PGXACT *pgxact;
+ uint8 vacuumFlags;
+
+ Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+
+ /*
+ * If the transaction has no XID assigned, we can skip it; it
+ * won't have sub-XIDs either.
+ */
+ if (likely(xid == InvalidTransactionId))
+ continue;
+
+ /*
+ * 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;
+
+ pgprocno = pgprocnos[pgxactoff];
+ pgxact = &allPgXact[pgprocno];
+ vacuumFlags = pgxact->vacuumFlags;
/*
* 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
@@ -1988,9 +2084,9 @@ GetSnapshotData(Snapshot snapshot)
suboverflowed = true;
else
{
- int nxids = pgxact->nxids;
+ int nsubxids = pgxact->nxids;
- if (nxids > 0)
+ if (nsubxids > 0)
{
PGPROC *proc = &allProcs[pgprocno];
@@ -1998,8 +2094,8 @@ GetSnapshotData(Snapshot snapshot)
memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids,
- nxids * sizeof(TransactionId));
- subcount += nxids;
+ nsubxids * sizeof(TransactionId));
+ subcount += nsubxids;
}
}
}
@@ -2131,6 +2227,7 @@ GetSnapshotData(Snapshot snapshot)
}
RecentXmin = xmin;
+ Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
snapshot->xmin = xmin;
snapshot->xmax = xmax;
@@ -2293,7 +2390,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
@@ -2308,7 +2405,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.
@@ -2327,6 +2424,7 @@ GetRunningTransactionData(void)
static RunningTransactionsData CurrentRunningXactsData;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
TransactionId latestCompletedXid;
TransactionId oldestRunningXid;
@@ -2387,7 +2485,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
@@ -2484,7 +2582,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.
*
@@ -2499,6 +2597,7 @@ TransactionId
GetOldestActiveTransactionId(void)
{
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
TransactionId oldestRunningXid;
int index;
@@ -2521,12 +2620,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;
@@ -2604,8 +2701,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
@@ -2614,17 +2711,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;
@@ -2812,6 +2909,7 @@ BackendXidGetPid(TransactionId xid)
{
int result = 0;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int index;
if (xid == InvalidTransactionId) /* never match invalid xid */
@@ -2823,9 +2921,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;
@@ -3105,7 +3202,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
@@ -3122,7 +3218,7 @@ MinimumActiveBackends(int min)
continue; /* do not count deleted entries */
if (proc == MyProc)
continue; /* do not count myself */
- if (pgxact->xid == InvalidTransactionId)
+ if (proc->xid == InvalidTransactionId)
continue; /* do not count if no XID assigned */
if (proc->pid == 0)
continue; /* do not count prepared xacts */
@@ -3548,8 +3644,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);
@@ -3907,7 +4003,7 @@ FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
* 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
* 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
* 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..a9477ccb4a3 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->xid;
*xmin = proc->xmin;
}
}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 95989ce79bd..d86566f4554 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->xid;
/*
* 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 de346cd87fc..7fad49544ce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -102,21 +102,18 @@ Size
ProcGlobalShmemSize(void)
{
Size size = 0;
+ Size TotalProcs =
+ add_size(MaxBackends, add_size(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 +213,17 @@ InitProcGlobal(void)
MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT));
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++)
{
/* Common initialization for all PGPROCs, regardless of type. */
@@ -387,7 +395,7 @@ InitProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->pid = MyProcPid;
/* backendId, databaseId and roleId will be filled in later */
@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->backendId = InvalidBackendId;
MyProc->databaseId = InvalidOid;
--
2.25.0.114.g5b0ca878e0
--4il4azyv6rmablac
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v13-0004-snapshot-scalability-Move-PGXACT-vacuumFlags-to-.patch"
^ permalink raw reply [nested|flat] 87+ messages in thread
* [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids.
@ 2020-07-15 22:35 Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2020-07-15 22:35 UTC (permalink / raw)
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
Reviewed-By: Robert Haas, Thomas Munro, David Rowley
Discussion: https://postgr.es/m/[email protected]
---
src/include/storage/proc.h | 79 +++++-
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 | 2 +-
src/backend/storage/ipc/procarray.c | 282 +++++++++++++-------
src/backend/storage/ipc/sinvaladt.c | 4 +-
src/backend/storage/lmgr/lock.c | 3 +-
src/backend/storage/lmgr/proc.c | 26 +-
11 files changed, 335 insertions(+), 161 deletions(-)
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5e4b028a5f9..146bca84bd6 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -89,6 +89,17 @@ typedef enum
* 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.
+ *
+ * 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
{
@@ -101,6 +112,12 @@ struct PGPROC
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
* starting our xact, excluding LAZY VACUUM:
* vacuum must not remove tuples deleted by
@@ -110,6 +127,9 @@ 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
+ * with data mirrored from this PGPROC */
int pgprocno;
/* These fields are zero while a backend is still starting up: */
@@ -224,10 +244,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;
@@ -236,6 +252,57 @@ 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 the same lock. Adding/Removing a procarray
+ * entry is much less frequent.
+ *
+ * Some fields in PGPROC are mirrored into more densely packed arrays (like
+ * 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 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
{
@@ -243,6 +310,10 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/* Array of PGXACT structures (not including dummies for prepared txns) */
PGXACT *allPgXact;
+
+ /* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
+ 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 f117ee160a3..6dec0c8311b 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,
@@ -1445,7 +1445,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 c15b5540a09..2d7979011ce 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
latestCompletedXid 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 latestCompletedXid to
-pass the first backend's XID, before that value became visible in the
-ProcArray. That would break GetOldestXmin, 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
+latestCompletedXid to pass the first backend's XID, before that value
+became visible in the 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
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,
@@ -382,13 +383,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->xid, 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 dd2f4d5bc7e..a4599e96610 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 XactSLRULock
* 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 and 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->xid &&
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 eb5f4680a3d..a0398bf3a3e 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 = PROC_WAIT_STATUS_OK;
/* We set up the gxact's VXID as InvalidBackendId/XID */
proc->lxid = (LocalTransactionId) xid;
- pgxact->xid = xid;
+ proc->xid = 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->xid);
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 MyProc /
+ * 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);
@@ -1404,7 +1401,6 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
{
GlobalTransaction gxact;
PGPROC *proc;
- PGXACT *pgxact;
TransactionId xid;
char *buf;
char *bufptr;
@@ -1423,8 +1419,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
@@ -1726,7 +1721,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 2ef0f4991ca..4c91b343ecd 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 MyProc->xid/ProcGlobal->xids[] 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;
+ MyProc->xid = BootstrapTransactionId;
+ ProcGlobal->xids[MyProc->pgxactoff] = 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->xid 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 */
+ MyProc->xid = xid;
+ ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ }
else
{
int nxids = MyPgXact->nxids;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 22228f5684f..648e12c78d8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1724,7 +1724,7 @@ 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, otherwise GetOldestNonRemovableTransactionId()
+ * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId()
* might appear to go backwards, which is probably Not Good.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a016816ae86..e5617ddfb41 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->xid). 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
@@ -436,7 +437,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)
{
@@ -445,7 +448,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")));
@@ -471,10 +473,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->xid;
+
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);
}
@@ -500,36 +517,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);
@@ -562,7 +602,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->xid));
/*
* If we can immediately acquire ProcArrayLock, we clear our own XID
@@ -584,7 +624,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->xid));
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
@@ -607,7 +647,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->xid);
+
+ ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
+ proc->xid = InvalidTransactionId;
proc->lxid = InvalidLocalTransactionId;
/* must be cleared with xid/xmin: */
pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK;
@@ -643,7 +689,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->xid));
/* Add ourselves to the list of processes needing a group XID clear. */
proc->procArrayGroupMember = true;
@@ -748,20 +794,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->xid = InvalidTransactionId;
+
proc->lxid = InvalidLocalTransactionId;
proc->xmin = InvalidTransactionId;
proc->recoveryConflictPending = false;
@@ -773,6 +827,8 @@ ProcArrayClearTransaction(PGPROC *proc)
/* Clear the subtransaction-XID cache too */
pgxact->nxids = 0;
pgxact->overflowed = false;
+
+ LWLockRelease(ProcArrayLock);
}
/*
@@ -1167,7 +1223,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.
@@ -1176,26 +1232,28 @@ ProcArrayApplyXidAssignment(TransactionId topxid,
* if the Xid is running on the primary.
*
* 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;
TransactionId latestCompletedXid;
- int i,
- j;
+ int mypgxactoff;
+ size_t numProcs;
+ int j;
/*
* Don't bother checking a transaction older than RecentXmin; it could not
@@ -1250,6 +1308,8 @@ TransactionIdIsInProgress(TransactionId xid)
errmsg("out of memory")));
}
+ other_xids = ProcGlobal->xids;
+
LWLockAcquire(ProcArrayLock, LW_SHARED);
/*
@@ -1266,20 +1326,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;
@@ -1304,8 +1366,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 */
@@ -1336,7 +1402,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))
@@ -1391,7 +1457,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;
@@ -1414,6 +1480,7 @@ TransactionIdIsActive(TransactionId xid)
{
bool result = false;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int i;
/*
@@ -1429,11 +1496,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;
@@ -1519,6 +1585,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
ProcArrayStruct *arrayP = procArray;
TransactionId kaxmin;
bool in_recovery = RecoveryInProgress();
+ TransactionId *other_xids = ProcGlobal->xids;
/* inferred after ProcArrayLock is released */
h->catalog_oldest_nonremovable = InvalidTransactionId;
@@ -1562,7 +1629,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *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);
/*
@@ -1853,14 +1920,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;
@@ -1905,6 +1975,10 @@ GetSnapshotData(Snapshot snapshot)
LWLockAcquire(ProcArrayLock, LW_SHARED);
latest_completed = ShmemVariableCache->latestCompletedXid;
+ mypgxactoff = MyProc->pgxactoff;
+ myxid = other_xids[mypgxactoff];
+ Assert(myxid == MyProc->xid);
+
oldestxid = ShmemVariableCache->oldestXid;
/* xmax is always latestCompletedXid + 1 */
@@ -1915,57 +1989,79 @@ 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)
{
+ size_t numProcs = arrayP->numProcs;
+ TransactionId *xip = snapshot->xip;
int *pgprocnos = arrayP->pgprocnos;
- int numProcs;
/*
- * 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];
- PGXACT *pgxact = &allPgXact[pgprocno];
- TransactionId xid;
+ /* Fetch xid just once - see GetNewTransactionId */
+ TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
+ int pgprocno;
+ PGXACT *pgxact;
+ uint8 vacuumFlags;
+
+ Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+
+ /*
+ * If the transaction has no XID assigned, we can skip it; it
+ * won't have sub-XIDs either.
+ */
+ if (likely(xid == InvalidTransactionId))
+ continue;
+
+ /*
+ * 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;
+
+ pgprocno = pgprocnos[pgxactoff];
+ pgxact = &allPgXact[pgprocno];
+ vacuumFlags = pgxact->vacuumFlags;
/*
* 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
@@ -1988,9 +2084,9 @@ GetSnapshotData(Snapshot snapshot)
suboverflowed = true;
else
{
- int nxids = pgxact->nxids;
+ int nsubxids = pgxact->nxids;
- if (nxids > 0)
+ if (nsubxids > 0)
{
PGPROC *proc = &allProcs[pgprocno];
@@ -1998,8 +2094,8 @@ GetSnapshotData(Snapshot snapshot)
memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids,
- nxids * sizeof(TransactionId));
- subcount += nxids;
+ nsubxids * sizeof(TransactionId));
+ subcount += nsubxids;
}
}
}
@@ -2131,6 +2227,7 @@ GetSnapshotData(Snapshot snapshot)
}
RecentXmin = xmin;
+ Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
snapshot->xmin = xmin;
snapshot->xmax = xmax;
@@ -2293,7 +2390,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
@@ -2308,7 +2405,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.
@@ -2327,6 +2424,7 @@ GetRunningTransactionData(void)
static RunningTransactionsData CurrentRunningXactsData;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
TransactionId latestCompletedXid;
TransactionId oldestRunningXid;
@@ -2387,7 +2485,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
@@ -2484,7 +2582,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.
*
@@ -2499,6 +2597,7 @@ TransactionId
GetOldestActiveTransactionId(void)
{
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
TransactionId oldestRunningXid;
int index;
@@ -2521,12 +2620,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;
@@ -2604,8 +2701,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
@@ -2614,17 +2711,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;
@@ -2812,6 +2909,7 @@ BackendXidGetPid(TransactionId xid)
{
int result = 0;
ProcArrayStruct *arrayP = procArray;
+ TransactionId *other_xids = ProcGlobal->xids;
int index;
if (xid == InvalidTransactionId) /* never match invalid xid */
@@ -2823,9 +2921,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;
@@ -3105,7 +3202,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
@@ -3122,7 +3218,7 @@ MinimumActiveBackends(int min)
continue; /* do not count deleted entries */
if (proc == MyProc)
continue; /* do not count myself */
- if (pgxact->xid == InvalidTransactionId)
+ if (proc->xid == InvalidTransactionId)
continue; /* do not count if no XID assigned */
if (proc->pid == 0)
continue; /* do not count prepared xacts */
@@ -3548,8 +3644,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);
@@ -3907,7 +4003,7 @@ FullXidRelativeTo(FullTransactionId rel, TransactionId xid)
* 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
* 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
* 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..a9477ccb4a3 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->xid;
*xmin = proc->xmin;
}
}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 95989ce79bd..d86566f4554 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->xid;
/*
* 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 de346cd87fc..7fad49544ce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -102,21 +102,18 @@ Size
ProcGlobalShmemSize(void)
{
Size size = 0;
+ Size TotalProcs =
+ add_size(MaxBackends, add_size(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 +213,17 @@ InitProcGlobal(void)
MemSet(pgxacts, 0, TotalProcs * sizeof(PGXACT));
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++)
{
/* Common initialization for all PGPROCs, regardless of type. */
@@ -387,7 +395,7 @@ InitProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->pid = MyProcPid;
/* backendId, databaseId and roleId will be filled in later */
@@ -571,7 +579,7 @@ InitAuxiliaryProcess(void)
MyProc->lxid = InvalidLocalTransactionId;
MyProc->fpVXIDLock = false;
MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
- MyPgXact->xid = InvalidTransactionId;
+ MyProc->xid = InvalidTransactionId;
MyProc->xmin = InvalidTransactionId;
MyProc->backendId = InvalidBackendId;
MyProc->databaseId = InvalidOid;
--
2.25.0.114.g5b0ca878e0
--4il4azyv6rmablac
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v13-0004-snapshot-scalability-Move-PGXACT-vacuumFlags-to-.patch"
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v66
@ 2022-04-02 08:21 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: Andres Freund @ 2022-04-02 08:21 UTC (permalink / raw)
To: Kyotaro Horiguchi <[email protected]>; +Cc: [email protected]; [email protected]; [email protected]; [email protected]; [email protected]
Hi,
On 2022-03-25 17:24:18 +0900, Kyotaro Horiguchi wrote:
> > * AFIXME: Should all the stats drop code be moved into pgstat_drop.c?
>
> Or pgstat_xact.c?
I wasn't initially happy with that suggestion, but after running with it, it
looks pretty good.
I also moved a fair bit of code into pgstat_shm.c, which to me improved code
navigation a lot. I'm wondering about splitting it further even, into
pgstat_shm.c and pgstat_entry.c.
What do you think?
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v68
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
@ 2022-04-04 04:15 ` Andres Freund <[email protected]>
2022-04-04 13:16 ` Re: shared-memory based stats collector - v68 Thomas Munro <[email protected]>
2022-04-04 19:08 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 20:45 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
0 siblings, 4 replies; 87+ messages in thread
From: Andres Freund @ 2022-04-04 04:15 UTC (permalink / raw)
To: Kyotaro Horiguchi <[email protected]>; +Cc: [email protected]; [email protected]; [email protected]; [email protected]; [email protected]
Hi,
New version of the shared memory stats patchset. Most important changes:
- It's now "cumulative statistics system", as discussed at [1]. This basically
is now the term that all the references to the "stats collector" are
replaced with. Looks much better than "activity statistics" imo. The
STATS_COLLECTOR is now named STATS_CUMULATIVE. I tried to find all
references to either collector or "activity statistics", but in all
likelihood I didn't get them all.
- updated docs (significantly edited version of the version Kyotaro posted a
few days ago)
- significantly improved test coverage - pgstat*.c are nearly completely
covered. While pgstatsfuncs.c coverage has increased, it is not great - but
there's already so much more coverage, that I think it's good enough for
now. Thanks to Melanie for help with this!
- largely cleaned up inconsisten function / type naming. Everything now /
again is under the PgStats_ prefix, except for statistics in shared memory,
which is prefixed with PgStatsShared_. I think we should go further and
add at least a PgStatsPending_ namespace, but that requires touching plenty
code that didn't need to be touched so far, so it'll have to be task for
another release.
- As discussed in [2] I added a patch at the start of the queue to clean up
the inconsistent function header comments conventions.
- pgstat.c is further split. Two new files: pgstat_xact.c and pgstat_shmem.c
(wrote an email about this a few days ago, without sending the patches)
- Split out as much as I could into separate commits.
- Cleaned up autovacuum.c changes - mostly removing more obsolted code
- code, comment polishing
Still todo:
- docs need review
- finish writing architectural comment atop pgstat.c
- fix the bug around pgstat_report_stat() I wrote about at [3]
- collect who reviewed earlier revisions
- choose what conditions for stats file reset we want
- I'm wondering if the solution for replication slot names on disk is too
narrow, and instead we should have a more general "serialize" /
"deserialize" callback. But we can easily do that later as well...
There's a bit more inconsistency around function naming. Right now all
callbacks are pgstat_$kind_$action_cb, but most of the rest of pgstat is
pgstat_$action_$kind. But somehow it "feels" wrong for the callbacks -
there's also a bunch of functions already named similarly, but that's
partially my fault in commits in the past.
There are a lot of copies of "Permission checking for this function is managed
through the normal GRANT system." in the pre-existing code. Aren't they
completely bogus? None of the functions commented upon like that is actually
exposed to SQL!
Please take a look!
Greetings,
Andres Freund
[1] https://www.postgresql.org/message-id/20220308205351.2xcn6k4x5yivcxyd%40alap3.anarazel.de
[2] https://www.postgresql.org/message-id/20220329191727.mzzwbl7udhpq7pmf%40alap3.anarazel.de
[3] https://www.postgresql.org/message-id/[email protected]
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v68
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
@ 2022-04-04 13:16 ` Thomas Munro <[email protected]>
2022-04-04 17:54 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
3 siblings, 1 reply; 87+ messages in thread
From: Thomas Munro @ 2022-04-04 13:16 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Ibrar Ahmed <[email protected]>; Fujii Masao <[email protected]>; Georgios <[email protected]>; PostgreSQL Hackers <[email protected]>
On Mon, Apr 4, 2022 at 4:16 PM Andres Freund <[email protected]> wrote:
> Please take a look!
A few superficial comments:
> [PATCH v68 01/31] pgstat: consistent function header formatting.
> [PATCH v68 02/31] pgstat: remove some superflous comments from pgstat.h.
+1
> [PATCH v68 03/31] dshash: revise sequential scan support.
Logic looks good. That is,
lock-0-and-ensure_valid_bucket_pointers()-only-once makes sense. Just
some comment trivia:
+ * dshash_seq_term needs to be called when a scan finished. The caller may
+ * delete returned elements midst of a scan by using dshash_delete_current()
+ * if exclusive = true.
s/scan finished/scan is finished/
s/midst of/during/ (or /in the middle of/, ...)
> [PATCH v68 04/31] dsm: allow use in single user mode.
LGTM.
+ Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
(Not this patch's fault, but I wish we had a more explicit way to say "am
single user".)
> [PATCH v68 05/31] pgstat: stats collector references in comments
LGTM. I could think of some alternative suggested names for this subsystem,
but don't think it would be helpful at this juncture so I will refrain :-)
> [PATCH v68 06/31] pgstat: add pgstat_copy_relation_stats().
> [PATCH v68 07/31] pgstat: move transactional code into pgstat_xact.c.
LGTM.
> [PATCH v68 08/31] pgstat: introduce PgStat_Kind enum.
+#define PGSTAT_KIND_FIRST PGSTAT_KIND_DATABASE
+#define PGSTAT_KIND_LAST PGSTAT_KIND_WAL
+#define PGSTAT_NUM_KINDS (PGSTAT_KIND_LAST + 1)
It's a little confusing that PGSTAT_NUM_KINDS isn't really the number of kinds,
because there is no kind 0. For the two users of it... maybe just use
pgstat_kind_infos[] = {...}, and
global_valid[PGSTAT_KIND_LAST + 1]?
> [PATCH v68 10/31] pgstat: scaffolding for transactional stats creation / drop.
+ /*
+ * Dropping the statistics for objects that dropped transactionally itself
+ * needs to be transactional. ...
Hard to parse. How about: "Objects are dropped transactionally, so
related statistics need to be dropped transactionally too."
> [PATCH v68 13/31] pgstat: store statistics in shared memory.
+ * Single-writer stats use the changecount mechanism to achieve low-overhead
+ * writes - they're obviously performance critical than reads. Check the
+ * definition of struct PgBackendStatus for some explanation of the
+ * changecount mechanism.
Missing word "more" after obviously?
+ /*
+ * Whenever the for a dropped stats entry could not be freed (because
+ * backends still have references), this is incremented, causing backends
+ * to run pgstat_gc_entry_refs(), allowing that memory to be reclaimed.
+ */
+ pg_atomic_uint64 gc_count;
Whenever the ...?
Would it be better to call this variable gc_request_count?
+ * Initialize refcount to 1, marking it as valid / not tdroped. The entry
s/tdroped/dropped/
+ * further if a longer lived references is needed.
s/references/reference/
+ /*
+ * There are legitimate cases where the old stats entry might not
+ * yet have been dropped by the time it's reused. The easiest case
+ * are replication slot stats. But oid wraparound can lead to
+ * other cases as well. We just reset the stats to their plain
+ * state.
+ */
+ shheader = pgstat_reinit_entry(kind, shhashent);
This whole comment is repeated in pgstat_reinit_entry and its caller.
+ /*
+ * XXX: Might be worth adding some frobbing of the allocation before
+ * freeing, to make it easier to detect use-after-free style bugs.
+ */
+ dsa_free(pgStatLocal.dsa, pdsa);
FWIW dsa_free() clobbers memory in assert builds, just like pfree().
+static Size
+pgstat_dsa_init_size(void)
+{
+ Size sz;
+
+ /*
+ * The dshash header / initial buckets array needs to fit into "plain"
+ * shared memory, but it's beneficial to not need dsm segments
+ * immediately. A size of 256kB seems works well and is not
+ * disproportional compared to other constant sized shared memory
+ * allocations. NB: To avoid DSMs further, the user can configure
+ * min_dynamic_shared_memory.
+ */
+ sz = 256 * 1024;
It kinda bothers me that the memory reserved by
min_dynamic_shared_memory might eventually fill up with stats, and not
be available for temporary use by parallel queries (which can benefit
more from fast acquire/release on DSMs, and probably also huge pages,
or maybe not...), and that's hard to diagnose.
+ * (4) turn off the idle-in-transaction, idle-session and
+ * idle-state-update timeouts if active. We do this before step (5) so
s/idle-state-/idle-stats-/
+ /*
+ * Some of the pending stats may have not been flushed due to lock
+ * contention. If we have such pending stats here, let the caller know
+ * the retry interval.
+ */
+ if (partial_flush)
+ {
I think it's better for a comment that is outside the block to say "If
some of the pending...". Or the comment should be inside the blocks.
+static void
+pgstat_build_snapshot(void)
+{
...
+ dshash_seq_init(&hstat, pgStatLocal.shared_hash, false);
+ while ((p = dshash_seq_next(&hstat)) != NULL)
+ {
...
+ entry->data = MemoryContextAlloc(pgStatLocal.snapshot.context,
...
+ }
+ dshash_seq_term(&hstat);
Doesn't allocation failure leave the shared hash table locked?
> PATCH v68 16/31] pgstat: add pg_stat_exists_stat() for easier testing.
pg_stat_exists_stat() is a weird name, ... would it be better as
pg_stat_object_exists()?
> [PATCH v68 28/31] pgstat: update docs.
+ Determines the behaviour when cumulative statistics are accessed
AFAIK our manual is written in en_US, so s/behaviour/behavior/.
+ memory. When set to <literal>cache</literal>, the first access to
+ statistics for an object caches those statistics until the end of the
+ transaction / until <function>pg_stat_clear_snapshot()</function> is
s|/|unless|
+ <literal>none</literal> is most suitable for monitoring solutions. If
I'd change "solutions" to "tools" or maybe "systems".
+ When using the accumulated statistics views and functions to
monitor collected data, it is important
Did you intend to write "accumulated" instead of "cumulative" here?
+ You can invoke <function>pg_stat_clear_snapshot</function>() to discard the
+ current transaction's statistics snapshot / cache (if any). The next use
I'd change s|/ cache|or cached values|. I think "/" like this is an informal
thing.
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v68
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 13:16 ` Re: shared-memory based stats collector - v68 Thomas Munro <[email protected]>
@ 2022-04-04 17:54 ` Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2022-04-04 17:54 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Ibrar Ahmed <[email protected]>; Fujii Masao <[email protected]>; Georgios <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi,
On 2022-04-05 01:16:04 +1200, Thomas Munro wrote:
> On Mon, Apr 4, 2022 at 4:16 PM Andres Freund <[email protected]> wrote:
> > Please take a look!
>
> A few superficial comments:
>
> > [PATCH v68 01/31] pgstat: consistent function header formatting.
> > [PATCH v68 02/31] pgstat: remove some superflous comments from pgstat.h.
>
> +1
Planning to commit these after making another coffee and proof reading them
some more.
> > [PATCH v68 03/31] dshash: revise sequential scan support.
>
> Logic looks good. That is,
> lock-0-and-ensure_valid_bucket_pointers()-only-once makes sense. Just
> some comment trivia:
>
> + * dshash_seq_term needs to be called when a scan finished. The caller may
> + * delete returned elements midst of a scan by using dshash_delete_current()
> + * if exclusive = true.
>
> s/scan finished/scan is finished/
> s/midst of/during/ (or /in the middle of/, ...)
>
> > [PATCH v68 04/31] dsm: allow use in single user mode.
>
> LGTM.
> + Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
> (Not this patch's fault, but I wish we had a more explicit way to say "am
> single user".)
Agreed.
> > [PATCH v68 05/31] pgstat: stats collector references in comments
>
> LGTM. I could think of some alternative suggested names for this subsystem,
> but don't think it would be helpful at this juncture so I will refrain :-)
Heh. I did start a thread about it a while ago :)
> > [PATCH v68 08/31] pgstat: introduce PgStat_Kind enum.
>
> +#define PGSTAT_KIND_FIRST PGSTAT_KIND_DATABASE
> +#define PGSTAT_KIND_LAST PGSTAT_KIND_WAL
> +#define PGSTAT_NUM_KINDS (PGSTAT_KIND_LAST + 1)
>
> It's a little confusing that PGSTAT_NUM_KINDS isn't really the number of kinds,
> because there is no kind 0. For the two users of it... maybe just use
> pgstat_kind_infos[] = {...}, and
> global_valid[PGSTAT_KIND_LAST + 1]?
Maybe the whole justification for not defining an invalid kind is moot
now. There's not a single switch covering all kinds of stats left, and I hope
that we don't introduce one again...
> > [PATCH v68 10/31] pgstat: scaffolding for transactional stats creation / drop.
>
> + /*
> + * Dropping the statistics for objects that dropped transactionally itself
> + * needs to be transactional. ...
>
> Hard to parse. How about: "Objects are dropped transactionally, so
> related statistics need to be dropped transactionally too."
Not all objects are dropped transactionally. But I agree it reads awkwardly. I
now, incorporating feedback from Justin as well, rephrased it to:
/*
* Statistics for transactionally dropped objects need to be
* transactionally dropped as well. Collect the stats dropped in the
* current (sub-)transaction and only execute the stats drop when we know
* if the transaction commits/aborts. To handle replicas and crashes,
* stats drops are included in commit / abort records.
*/
A few too many "drop"s in there, but maybe that's unavoidable.
> + /*
> + * Whenever the for a dropped stats entry could not be freed (because
> + * backends still have references), this is incremented, causing backends
> + * to run pgstat_gc_entry_refs(), allowing that memory to be reclaimed.
> + */
> + pg_atomic_uint64 gc_count;
>
> Whenever the ...?
* Whenever statistics for dropped objects could not be freed - because
* backends still have references - the dropping backend calls
* pgstat_request_entry_refs_gc() incrementing this counter. Eventually
* that causes backends to run pgstat_gc_entry_refs(), allowing memory to
* be reclaimed.
> Would it be better to call this variable gc_request_count?
Agreed.
> + * Initialize refcount to 1, marking it as valid / not tdroped. The entry
>
> s/tdroped/dropped/
>
> + * further if a longer lived references is needed.
>
> s/references/reference/
Fixed.
> + /*
> + * There are legitimate cases where the old stats entry might not
> + * yet have been dropped by the time it's reused. The easiest case
> + * are replication slot stats. But oid wraparound can lead to
> + * other cases as well. We just reset the stats to their plain
> + * state.
> + */
> + shheader = pgstat_reinit_entry(kind, shhashent);
>
> This whole comment is repeated in pgstat_reinit_entry and its caller.
I guess I felt as indecisive about where to place it between the two locations
when I wrote it as I do now. Left it at the callsite for now.
> + /*
> + * XXX: Might be worth adding some frobbing of the allocation before
> + * freeing, to make it easier to detect use-after-free style bugs.
> + */
> + dsa_free(pgStatLocal.dsa, pdsa);
>
> FWIW dsa_free() clobbers memory in assert builds, just like pfree().
Oh. I could swear I saw that not being the case a while ago. But clearly it is
the case. Removed.
> +static Size
> +pgstat_dsa_init_size(void)
> +{
> + Size sz;
> +
> + /*
> + * The dshash header / initial buckets array needs to fit into "plain"
> + * shared memory, but it's beneficial to not need dsm segments
> + * immediately. A size of 256kB seems works well and is not
> + * disproportional compared to other constant sized shared memory
> + * allocations. NB: To avoid DSMs further, the user can configure
> + * min_dynamic_shared_memory.
> + */
> + sz = 256 * 1024;
>
> It kinda bothers me that the memory reserved by
> min_dynamic_shared_memory might eventually fill up with stats, and not
> be available for temporary use by parallel queries (which can benefit
> more from fast acquire/release on DSMs, and probably also huge pages,
> or maybe not...), and that's hard to diagnose.
It's not great, but I don't really see an alternative? The saving grace is
that it's hard to imagine "real" usages of min_dynamic_shared_memory being
used up by stats.
> + * (4) turn off the idle-in-transaction, idle-session and
> + * idle-state-update timeouts if active. We do this before step (5) so
>
> s/idle-state-/idle-stats-/
>
> + /*
> + * Some of the pending stats may have not been flushed due to lock
> + * contention. If we have such pending stats here, let the caller know
> + * the retry interval.
> + */
> + if (partial_flush)
> + {
>
> I think it's better for a comment that is outside the block to say "If
> some of the pending...". Or the comment should be inside the blocks.
The comment says "if" in the second sentence? But it's a bit awkward anyway,
rephrased to:
* If some of the pending stats could not be flushed due to lock
* contention, let the caller know when to retry.
> +static void
> +pgstat_build_snapshot(void)
> +{
> ...
> + dshash_seq_init(&hstat, pgStatLocal.shared_hash, false);
> + while ((p = dshash_seq_next(&hstat)) != NULL)
> + {
> ...
> + entry->data = MemoryContextAlloc(pgStatLocal.snapshot.context,
> ...
> + }
> + dshash_seq_term(&hstat);
>
> Doesn't allocation failure leave the shared hash table locked?
The shared table itself not - the error path does LWLockReleaseAll(). The
problem is the backend local dshash_table, specifically
find_[exclusively_]locked will stay set, and then cause assertion failures
when used next.
I think we need to fix that in dshash.c. We have code in released branches
that's vulnerable to this problem. E.g.
ensure_record_cache_typmod_slot_exists() in lookup_rowtype_tupdesc_internal().
See also
https://postgr.es/m/20220311012712.botrpsikaufzteyt%40alap3.anarazel.de
Afaics the only real choice is to remove find_[exclusively_]locked and rely on
LWLockHeldByMeInMode() instead.
> > PATCH v68 16/31] pgstat: add pg_stat_exists_stat() for easier testing.
>
> pg_stat_exists_stat() is a weird name, ... would it be better as
> pg_stat_object_exists()?
I was fighting with this one a bunch :). Earlier it was called
pg_stat_stats_exist() I think. "object" makes it sound a bit too much like
it's the database object?
Maybe pg_stat_have_stat()?
> > [PATCH v68 28/31] pgstat: update docs.
>
> + Determines the behaviour when cumulative statistics are accessed
>
> AFAIK our manual is written in en_US, so s/behaviour/behavior/.
Fixed like 10 instances of this in the patchset. Not sure why I just can't
make myself type behavior.
> + memory. When set to <literal>cache</literal>, the first access to
> + statistics for an object caches those statistics until the end of the
> + transaction / until <function>pg_stat_clear_snapshot()</function> is
>
> s|/|unless|
>
> + <literal>none</literal> is most suitable for monitoring solutions. If
>
> I'd change "solutions" to "tools" or maybe "systems".
Done.
> + When using the accumulated statistics views and functions to
> monitor collected data, it is important
>
> Did you intend to write "accumulated" instead of "cumulative" here?
Not sure. I think I got bored of the word at some point :P
> + You can invoke <function>pg_stat_clear_snapshot</function>() to discard the
> + current transaction's statistics snapshot / cache (if any). The next use
>
> I'd change s|/ cache|or cached values|. I think "/" like this is an informal
> thing.
I think we have a few other uses of it. But anyway, changed.
Thanks!
Andres
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v68
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
@ 2022-04-04 19:08 ` Andres Freund <[email protected]>
3 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2022-04-04 19:08 UTC (permalink / raw)
To: Kyotaro Horiguchi <[email protected]>; +Cc: [email protected]; [email protected]; [email protected]; [email protected]; [email protected]
Hi,
On 2022-04-03 21:15:16 -0700, Andres Freund wrote:
> - collect who reviewed earlier revisions
I found reviews by
- Tomas Vondra <[email protected]>
- Arthur Zakirov <[email protected]>
- Antonin Houska <[email protected]>
There's also reviews by Fujii and Alvaro, but afaics just for parts that were
separately committed.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v68
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
@ 2022-04-04 20:45 ` David G. Johnston <[email protected]>
2022-04-04 21:06 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
3 siblings, 1 reply; 87+ messages in thread
From: David G. Johnston @ 2022-04-04 20:45 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Ibrar Ahmed <[email protected]>; Fujii Masao <[email protected]>; [email protected]; PostgreSQL Hackers <[email protected]>
On Sun, Apr 3, 2022 at 9:16 PM Andres Freund <[email protected]> wrote:
>
> Please take a look!
>
>
I didn't take the time to fixup all the various odd typos in the general
code comments; none of them reduced comprehension appreciably. I may do so
when/if I do another pass.
I did skim over the entire patch set and, FWIW, found it to be quite
understandable. I don't have the experience to comment on the lower-level
details like locking and such but the medium picture stuff makes sense to
me both as a user and a developer. I did leave a couple of comments about
parts that at least piqued my interest (reset single stats) or seemed like
an undesirable restriction that was under addressed (before server shutdown
called exactly once).
I agree with Thomas's observation regarding PGSTAT_KIND_LAST. I also think
that leaving it starting at 1 makes sense - maybe just fix the name and
comment to better reflect its actual usage in core.
I concur also with changing usages of " / " to ", or"
My first encounter with pg_stat_exists_stat() didn't draw my attention as
being problematic so I'd say we just stick with it. As a SQL user reading:
WHERE exists (...) is somewhat natural; using "have" or back-to-back
stat_stat is less appealing.
I would suggest we do away with stats_fetch_consistency "snapshot" mode and
instead add a function that can be called that would accomplish the same
thing but in "cache" mode. Future iterations of that function could accept
patterns, allowing for something between "one" and "everything".
I'm also not an immediate fan of "fetch_consistency"; with the function
suggestion it is basically "cache" and "no-cache" so maybe:
stats_use_transaction_cache ? (haven't thought hard or long on this one...)
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 22d0a1e491..e889c11d9e 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -2123,7 +2123,7 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss
11:34 0:00 postgres: ser
</row>
<row>
<entry><literal>PgStatsData</literal></entry>
- <entry>Waiting fo shared memory stats data access</entry>
+ <entry>Waiting for shared memory stats data access</entry>
</row>
<row>
<entry><literal>SerializableXactHash</literal></entry>
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 2689d0962c..bc7bdf8064 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -4469,7 +4469,7 @@ PostgresMain(const char *dbname, const char *username)
/*
* (4) turn off the idle-in-transaction, idle-session and
- * idle-state-update timeouts if active. We do this before step (5) so
+ * idle-stats-update timeouts if active. We do this before step (5) so
* that any last-moment timeout is certain to be detected in step (5).
*
* At most one of these timeouts will be active, so there's no need to
diff --git a/src/backend/utils/activity/pgstat.c
b/src/backend/utils/activity/pgstat.c
index dbd55a065d..370638b33b 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -5,7 +5,7 @@
* Provides the infrastructure to collect and access cumulative statistics,
* e.g. per-table access statistics, of all backends in shared memory.
*
- * Most statistics updates are first first accumulated locally in each
process
+ * Most statistics updates are first accumulated locally in each process
* as pending entries, then later flushed to shared memory (just after
commit,
* or by idle-timeout).
*
@@ -371,7 +371,9 @@ pgstat_discard_stats(void)
/*
* pgstat_before_server_shutdown() needs to be called by exactly one
process
* during regular server shutdowns. Otherwise all stats will be lost.
- *
+ * XXX: What bad things happen if this is invoked by more than one process?
+ * I'd presume stats are not actually lost in that case. Can we just
'no-op'
+ * subsequent calls and say "at least once at shutdown, as late as
possible"
* We currently only write out stats for proc_exit(0). We might want to
change
* that at some point... But right now pgstat_discard_stats() would be
called
* during the start after a disorderly shutdown, anyway.
@@ -654,6 +656,14 @@ pgstat_reset_single_counter(PgStat_Kind kind, Oid
objoid)
Assert(!pgstat_kind_info_for(kind)->fixed_amount);
+ /*
+ * More of a conceptual observation here - the fact that something is
fixed does not imply
+ * that it is not fixed at a value greater than zero and thus could have
single subentries
+ * that could be addressed.
+ * I also am unsure, off the top of my head, whether both replication
slots and subscriptions,
+ * which are fixed, can be reset singly (today, and/or whether this patch
enables that capability)
+ */
+
/* Set the reset timestamp for the whole database */
pgstat_reset_database_timestamp(MyDatabaseId, ts);
David J.
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v68
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 20:45 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
@ 2022-04-04 21:06 ` Andres Freund <[email protected]>
2022-04-04 21:25 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: Andres Freund @ 2022-04-04 21:06 UTC (permalink / raw)
To: David G. Johnston <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Ibrar Ahmed <[email protected]>; Fujii Masao <[email protected]>; [email protected]; PostgreSQL Hackers <[email protected]>
Hi,
On 2022-04-04 13:45:40 -0700, David G. Johnston wrote:
> I didn't take the time to fixup all the various odd typos in the general
> code comments; none of them reduced comprehension appreciably. I may do so
> when/if I do another pass.
Cool.
> My first encounter with pg_stat_exists_stat() didn't draw my attention as
> being problematic so I'd say we just stick with it. As a SQL user reading:
> WHERE exists (...) is somewhat natural; using "have" or back-to-back
> stat_stat is less appealing.
There are a number of other *_exists functions, albeit not within
pg_stat_*. Like jsonb_exists. Perhaps just pg_stat_exists()?
> I would suggest we do away with stats_fetch_consistency "snapshot" mode and
> instead add a function that can be called that would accomplish the same
> thing but in "cache" mode. Future iterations of that function could accept
> patterns, allowing for something between "one" and "everything".
I don't want to do that. We had a lot of discussion around what consistency
model we want, and Tom was adamant that there needs to be a mode that behaves
like the current consistency model (which is what snapshot behaves like, with
very minor differences). A way to get back to the old behaviour seems good,
and the function idea doesn't provide that.
(merged the typos that I hadn't already fixed based on Justin / Thomas'
feedback)
> @@ -371,7 +371,9 @@ pgstat_discard_stats(void)
> /*
> * pgstat_before_server_shutdown() needs to be called by exactly one
> process
> * during regular server shutdowns. Otherwise all stats will be lost.
> - *
> + * XXX: What bad things happen if this is invoked by more than one process?
> + * I'd presume stats are not actually lost in that case. Can we just
> 'no-op'
> + * subsequent calls and say "at least once at shutdown, as late as
> possible"
What's the reason behind this question? There really shouldn't be a second
call (and there's only a single callsite). As is you'd get an assertion
failure about things already having been shutdown.
I don't think we want to relax that, because in all the realistic scenarios
that I can think of that'd open us up to loosing stats that were generated
after the first writeout of the stats data.
You mentioned this as a restriction above - I'm not seeing it as such? I'd
like to write out stats more often in the future (e.g. in the checkpointer),
but then it'd not be written out with this function...
> @@ -654,6 +656,14 @@ pgstat_reset_single_counter(PgStat_Kind kind, Oid
> objoid)
>
> Assert(!pgstat_kind_info_for(kind)->fixed_amount);
>
> + /*
> + * More of a conceptual observation here - the fact that something is
> fixed does not imply
> + * that it is not fixed at a value greater than zero and thus could have
> single subentries
> + * that could be addressed.
pgstat_reset_single_counter() is a pre-existing function (with a pre-existing
name, but adapted signature in the patch), it's currently only used for
functions and relation stats.
> + * I also am unsure, off the top of my head, whether both replication
> slots and subscriptions,
> + * which are fixed, can be reset singly (today, and/or whether this patch
> enables that capability)
> + */
FWIW, neither are implemented as fixed amount stats. There's afaics no limit
at all for the number of existing subscriptions (although some would either
need to be disabled or you'd get errors). While there is a limit on the number
of slots, that's a configurable limit. So replication slot stats are also
implemented as variable amount stats (that used to be different, wasn't nice).
There's one example of fixed amount stats that can be reset more granularly,
namely slru. That can be done via pg_stat_reset_slru().
Thanks,
Andres
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v68
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 20:45 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 21:06 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
@ 2022-04-04 21:25 ` David G. Johnston <[email protected]>
2022-04-04 21:54 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: David G. Johnston @ 2022-04-04 21:25 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Ibrar Ahmed <[email protected]>; Fujii Masao <[email protected]>; [email protected]; PostgreSQL Hackers <[email protected]>
On Mon, Apr 4, 2022 at 2:06 PM Andres Freund <[email protected]> wrote:
>
> > My first encounter with pg_stat_exists_stat() didn't draw my attention as
> > being problematic so I'd say we just stick with it. As a SQL user
> reading:
> > WHERE exists (...) is somewhat natural; using "have" or back-to-back
> > stat_stat is less appealing.
>
> There are a number of other *_exists functions, albeit not within
> pg_stat_*. Like jsonb_exists. Perhaps just pg_stat_exists()?
>
>
Works for me.
> A way to get back to the old behaviour seems good,
> and the function idea doesn't provide that.
>
Makes sense.
> (merged the typos that I hadn't already fixed based on Justin / Thomas'
> feedback)
>
>
> > @@ -371,7 +371,9 @@ pgstat_discard_stats(void)
> > /*
> > * pgstat_before_server_shutdown() needs to be called by exactly one
> > process
> > * during regular server shutdowns. Otherwise all stats will be lost.
> > - *
> > + * XXX: What bad things happen if this is invoked by more than one
> process?
> > + * I'd presume stats are not actually lost in that case. Can we just
> > 'no-op'
> > + * subsequent calls and say "at least once at shutdown, as late as
> > possible"
>
> What's the reason behind this question? There really shouldn't be a second
> call (and there's only a single callsite). As is you'd get an assertion
> failure about things already having been shutdown.
>
Mostly OCD I guess, "exactly one" has two failure modes - zero, and > 1;
and the "Otherwise" only covers the zero mode.
> I don't think we want to relax that, because in all the realistic scenarios
> that I can think of that'd open us up to loosing stats that were generated
> after the first writeout of the stats data.
> You mentioned this as a restriction above - I'm not seeing it as such? I'd
> like to write out stats more often in the future (e.g. in the
> checkpointer),
> but then it'd not be written out with this function...
>
>
Yeah, the idea only really works if you can implement "last one out, shut
off the lights". I think I was subconsciously wanting this to work that
way, but the existing process is good.
>
> > @@ -654,6 +656,14 @@ pgstat_reset_single_counter(PgStat_Kind kind, Oid
> > objoid)
> >
> > Assert(!pgstat_kind_info_for(kind)->fixed_amount);
> >
> > + /*
> > + * More of a conceptual observation here - the fact that something is
> > fixed does not imply
> > + * that it is not fixed at a value greater than zero and thus could have
> > single subentries
> > + * that could be addressed.
>
> pgstat_reset_single_counter() is a pre-existing function (with a
> pre-existing
> name, but adapted signature in the patch), it's currently only used for
> functions and relation stats.
>
>
> > + * I also am unsure, off the top of my head, whether both replication
> > slots and subscriptions,
> > + * which are fixed, can be reset singly (today, and/or whether this
> patch
> > enables that capability)
> > + */
>
> FWIW, neither are implemented as fixed amount stats.
That was a typo, I meant to write variable. My point was that of these 5
kinds that will pass the assertion test only 2 of them are actually handled
by the function today.
+ PGSTAT_KIND_DATABASE = 1, /* database-wide statistics */
+ PGSTAT_KIND_RELATION, /* per-table statistics */
+ PGSTAT_KIND_FUNCTION, /* per-function statistics */
+ PGSTAT_KIND_REPLSLOT, /* per-slot statistics */
+ PGSTAT_KIND_SUBSCRIPTION, /* per-subscription statistics */
> There's one example of fixed amount stats that can be reset more
> granularly,
> namely slru. That can be done via pg_stat_reset_slru().
>
>
Right, hence the conceptual disconnect. It doesn't affect the
implementation, everything is working just fine, but is something to ponder
for future maintainers getting up to speed here.
As the existing function only handles functions and relations why not just
perform a specific Kind check for them? Generalizing to assert on whether
or not the function works on fixed or variable Kinds seems beyond its
present state. Or could it be used, as-is, for databases, replication
slots, and subscriptions today, and we just haven't migrated those areas to
use the now generalized function? Even then, unless we do expand the
definition of the this publicly facing function is seems better to
precisely define what it requires as an input Kind by checking for RELATION
or FUNCTION specifically.
David J.
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v68
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 20:45 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 21:06 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 21:25 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
@ 2022-04-04 21:54 ` Andres Freund <[email protected]>
2022-04-04 22:24 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: Andres Freund @ 2022-04-04 21:54 UTC (permalink / raw)
To: David G. Johnston <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Ibrar Ahmed <[email protected]>; Fujii Masao <[email protected]>; [email protected]; PostgreSQL Hackers <[email protected]>
Hi,
On 2022-04-04 14:25:57 -0700, David G. Johnston wrote:
> > You mentioned this as a restriction above - I'm not seeing it as such? I'd
> > like to write out stats more often in the future (e.g. in the
> > checkpointer),
> > but then it'd not be written out with this function...
> >
> >
> Yeah, the idea only really works if you can implement "last one out, shut
> off the lights". I think I was subconsciously wanting this to work that
> way, but the existing process is good.
Preserving stats more than we do today (the patch doesn't really affect that)
will require a good chunk more work. My idea for it is that we'd write the
file out as part of a checkpoint / restartpoint, with a name including the
redo-lsn. Then when recovery starts, it can use the stats file associated with
that to start from. Then we'd loose at most 1 checkpoint's worth of stats
during a crash, not more.
There's a few non-trivial corner cases to solve, around stats objects getting
dropped concurrently with creating that serialized snapshot. Solvable, but not
trivial.
> > > + * I also am unsure, off the top of my head, whether both replication
> > > slots and subscriptions,
> > > + * which are fixed, can be reset singly (today, and/or whether this
> > patch
> > > enables that capability)
> > > + */
> >
> > FWIW, neither are implemented as fixed amount stats.
>
>
> That was a typo, I meant to write variable. My point was that of these 5
> kinds that will pass the assertion test only 2 of them are actually handled
> by the function today.
>
> + PGSTAT_KIND_DATABASE = 1, /* database-wide statistics */
> + PGSTAT_KIND_RELATION, /* per-table statistics */
> + PGSTAT_KIND_FUNCTION, /* per-function statistics */
> + PGSTAT_KIND_REPLSLOT, /* per-slot statistics */
> + PGSTAT_KIND_SUBSCRIPTION, /* per-subscription statistics */
> As the existing function only handles functions and relations why not just
> perform a specific Kind check for them? Generalizing to assert on whether
> or not the function works on fixed or variable Kinds seems beyond its
> present state. Or could it be used, as-is, for databases, replication
> slots, and subscriptions today, and we just haven't migrated those areas to
> use the now generalized function?
It couldn't quite be used for those, because it really only makes sense for
objects "within a database", because it wants to reset the timestamp of the
pg_stat_database row too (I don't like that behaviour as-is, but that's the
topic of another thread as you know...).
It will work for other per-database stats though, once we have them.
> Even then, unless we do expand the
> definition of the this publicly facing function is seems better to
> precisely define what it requires as an input Kind by checking for RELATION
> or FUNCTION specifically.
I don't see a benefit in adding a restriction on it that we'd just have to
lift again?
How about adding a
Assert(!pgstat_kind_info_for(kind)->accessed_across_databases)
and extending the function comment to say that it's used for per-database
stats and that it resets both the passed-in stats object as well as
pg_stat_database?
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v68
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 20:45 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 21:06 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 21:25 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 21:54 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
@ 2022-04-04 22:24 ` David G. Johnston <[email protected]>
2022-04-04 22:44 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: David G. Johnston @ 2022-04-04 22:24 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Ibrar Ahmed <[email protected]>; Fujii Masao <[email protected]>; [email protected]; PostgreSQL Hackers <[email protected]>
On Mon, Apr 4, 2022 at 2:54 PM Andres Freund <[email protected]> wrote:
>
> > As the existing function only handles functions and relations why not
> just
> > perform a specific Kind check for them? Generalizing to assert on
> whether
> > or not the function works on fixed or variable Kinds seems beyond its
> > present state. Or could it be used, as-is, for databases, replication
> > slots, and subscriptions today, and we just haven't migrated those areas
> to
> > use the now generalized function?
>
> It couldn't quite be used for those, because it really only makes sense for
> objects "within a database", because it wants to reset the timestamp of the
> pg_stat_database row too (I don't like that behaviour as-is, but that's the
> topic of another thread as you know...).
>
> It will work for other per-database stats though, once we have them.
>
>
> > Even then, unless we do expand the
> > definition of the this publicly facing function is seems better to
> > precisely define what it requires as an input Kind by checking for
> RELATION
> > or FUNCTION specifically.
>
> I don't see a benefit in adding a restriction on it that we'd just have to
> lift again?
>
> How about adding a
> Assert(!pgstat_kind_info_for(kind)->accessed_across_databases)
>
> and extending the function comment to say that it's used for per-database
> stats and that it resets both the passed-in stats object as well as
> pg_stat_database?
>
>
I could live with adding that, but...
Replacing the existing assert(!kind->fixed_amount) with
assert(!kind->accessed_across_databases) produces the same result as the
later presently implies the former.
Now I start to dislike the behavioral aspect of the attribute and would
rather just name it: kind->is_cluster_scoped (or something else that is
descriptive of the stat category itself, not how it is used)
Then reorganize the Kind documentation to note and emphasize these two
primary descriptors:
variable, which can be cluster or database scoped
fixed, which are cluster scoped by definition (if this is true...but given
this is an optimization category I'm thinking maybe it doesn't actually
matter...)
+ /* cluster-scoped object stats having a variable number of entries */
+ PGSTAT_KIND_REPLSLOT = 1, /* per-slot statistics */
+ PGSTAT_KIND_SUBSCRIPTION, /* per-subscription statistics */
+ PGSTAT_KIND_DATABASE, /* database-wide statistics */ (I moved this to 3rd
spot to be closer to the database-scoped options)
+
+ /* database-scoped object stats having a variable number of entries */
+ PGSTAT_KIND_RELATION, /* per-table statistics */
+ PGSTAT_KIND_FUNCTION, /* per-function statistics */
+
+ /* cluster-scoped stats having a fixed number of entries */ (maybe these
should go first, the variable following?)
+ PGSTAT_KIND_ARCHIVER,
+ PGSTAT_KIND_BGWRITER,
+ PGSTAT_KIND_CHECKPOINTER,
+ PGSTAT_KIND_SLRU,
+ PGSTAT_KIND_WAL,
David J.
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v68
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 20:45 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 21:06 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 21:25 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 21:54 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 22:24 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
@ 2022-04-04 22:44 ` Andres Freund <[email protected]>
2022-04-05 02:03 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: Andres Freund @ 2022-04-04 22:44 UTC (permalink / raw)
To: David G. Johnston <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Ibrar Ahmed <[email protected]>; Fujii Masao <[email protected]>; [email protected]; PostgreSQL Hackers <[email protected]>
Hi,
On 2022-04-04 15:24:24 -0700, David G. Johnston wrote:
> Replacing the existing assert(!kind->fixed_amount) with
> assert(!kind->accessed_across_databases) produces the same result as the
> later presently implies the former.
I wasn't proposing to replace, but to add...
> Now I start to dislike the behavioral aspect of the attribute and would
> rather just name it: kind->is_cluster_scoped (or something else that is
> descriptive of the stat category itself, not how it is used)
I'm not in love with the name either. But cluster is just a badly overloaded
word :(.
system_wide? Or invert it and say: database_scoped? I think I like the latter.
> Then reorganize the Kind documentation to note and emphasize these two
> primary descriptors:
> variable, which can be cluster or database scoped
> fixed, which are cluster scoped by definition
Hm. There's not actually that much difference between cluster/non-cluster wide
scope for most of the system. I'm not strongly against, but I'm also not
really seeing the benefit.
> (if this is true...but given this is an optimization category I'm thinking
> maybe it doesn't actually matter...)
It is true. Not sure what you mean with "optimization category"?
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v68
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 20:45 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 21:06 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 21:25 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 21:54 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 22:24 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 22:44 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
@ 2022-04-05 02:03 ` David G. Johnston <[email protected]>
2022-04-05 02:36 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: David G. Johnston @ 2022-04-05 02:03 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Ibrar Ahmed <[email protected]>; Fujii Masao <[email protected]>; [email protected]; PostgreSQL Hackers <[email protected]>
On Mon, Apr 4, 2022 at 3:44 PM Andres Freund <[email protected]> wrote:
> Hi,
>
> On 2022-04-04 15:24:24 -0700, David G. Johnston wrote:
> > Replacing the existing assert(!kind->fixed_amount) with
> > assert(!kind->accessed_across_databases) produces the same result as the
> > later presently implies the former.
>
> I wasn't proposing to replace, but to add...
>
Right, but it seems redundant to have both when one implies the other. But
I'm not hard set against it either, though my idea below make them both
obsolete.
>
> > Now I start to dislike the behavioral aspect of the attribute and would
> > rather just name it: kind->is_cluster_scoped (or something else that is
> > descriptive of the stat category itself, not how it is used)
>
> I'm not in love with the name either. But cluster is just a badly
> overloaded
> word :(.
>
> system_wide? Or invert it and say: database_scoped? I think I like the
> latter.
>
>
I like database_scoped as well...but see my idea below that makes this
obsolete.
>
> > Then reorganize the Kind documentation to note and emphasize these two
> > primary descriptors:
> > variable, which can be cluster or database scoped
> > fixed, which are cluster scoped by definition
>
> Hm. There's not actually that much difference between cluster/non-cluster
> wide
> scope for most of the system. I'm not strongly against, but I'm also not
> really seeing the benefit.
>
Not married to it myself, something to come back to when the dust settles.
> > (if this is true...but given this is an optimization category I'm
> thinking
> > maybe it doesn't actually matter...)
>
> It is true. Not sure what you mean with "optimization category"?
>
>
I mean that distinguishing between stats that are fixed and those that are
variable implies that fixed kinds have a better performance (speed, memory)
characteristic than variable kinds (at least in part due to the presence of
changecount). If fixed kinds did not have a performance benefit then
having the variable kind implementation simply handle fixed kinds as well
(using the common struct header and storage in a hash table) would make the
implementation simpler since all statistics would report through the same
API. In that world, variability is simply a possibility that not every
actual reporter has to use. That improved performance characteristic is
what I meant by "optimization category". I question whether we should be
publishing "fixed" and "variable" as concrete properties. I'm not
presently against the current choice to do so, but as you say above, I'm
also not really seeing the benefit.
(goes and looks at all the places that use the fixed_amount
field...sparking an idea)
Coming back to this:
"""
+ /* cluster-scoped object stats having a variable number of entries */
+ PGSTAT_KIND_REPLSLOT = 1, /* per-slot statistics */
+ PGSTAT_KIND_SUBSCRIPTION, /* per-subscription statistics */
+ PGSTAT_KIND_DATABASE, /* database-wide statistics */ (I moved this to 3rd
spot to be closer to the database-scoped options)
+
+ /* database-scoped object stats having a variable number of entries */
+ PGSTAT_KIND_RELATION, /* per-table statistics */
+ PGSTAT_KIND_FUNCTION, /* per-function statistics */
+
+ /* cluster-scoped stats having a fixed number of entries */ (maybe these
should go first, the variable following?)
+ PGSTAT_KIND_ARCHIVER,
+ PGSTAT_KIND_BGWRITER,
+ PGSTAT_KIND_CHECKPOINTER,
+ PGSTAT_KIND_SLRU,
+ PGSTAT_KIND_WAL,
"""
I see three "KIND_GROUP" categories here:
PGSTAT_KIND_CLUSTER (open to a different word here though...)
PGSTAT_KIND_DATABASE (we seem to agree on this above)
PGSTAT_KIND_GLOBAL (already used in the code)
This single enum can replace the two booleans that, in combination, would
define 4 unique groups (of which only three are interesting -
database+fixed doesn't seem interesting and so is not given a name/value
here).
While the succinctness of the booleans has appeal the need for half of the
booleans to end up being negated quickly tarnishes it. With the three
groups, every assertion is positive in nature indicating which of the three
groups are handled by the function. While that is probably a few more
characters it seems like an easier read and is less complicated as it has
fewer independent parts. At most you OR two kinds together which is
succinct enough I would think. There are no gaps relative to the existing
implementation that defines fixed_amount and accessed_across_databases -
every call site using either of them can be transformed mechanically.
David J.
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v68
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 20:45 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 21:06 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 21:25 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 21:54 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 22:24 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 22:44 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 02:03 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
@ 2022-04-05 02:36 ` Andres Freund <[email protected]>
2022-04-05 15:49 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: Andres Freund @ 2022-04-05 02:36 UTC (permalink / raw)
To: David G. Johnston <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Ibrar Ahmed <[email protected]>; Fujii Masao <[email protected]>; [email protected]; PostgreSQL Hackers <[email protected]>
Hi,
On 2022-04-04 19:03:13 -0700, David G. Johnston wrote:
> > > (if this is true...but given this is an optimization category I'm
> > thinking
> > > maybe it doesn't actually matter...)
> >
> > It is true. Not sure what you mean with "optimization category"?
> >
> >
> I mean that distinguishing between stats that are fixed and those that are
> variable implies that fixed kinds have a better performance (speed, memory)
> characteristic than variable kinds (at least in part due to the presence of
> changecount). If fixed kinds did not have a performance benefit then
> having the variable kind implementation simply handle fixed kinds as well
> (using the common struct header and storage in a hash table) would make the
> implementation simpler since all statistics would report through the same
> API.
Yes, fixed-numbered stats are faster.
> Coming back to this:
> """
> + /* cluster-scoped object stats having a variable number of entries */
> + PGSTAT_KIND_REPLSLOT = 1, /* per-slot statistics */
> + PGSTAT_KIND_SUBSCRIPTION, /* per-subscription statistics */
> + PGSTAT_KIND_DATABASE, /* database-wide statistics */ (I moved this to 3rd
> spot to be closer to the database-scoped options)
> +
> + /* database-scoped object stats having a variable number of entries */
> + PGSTAT_KIND_RELATION, /* per-table statistics */
> + PGSTAT_KIND_FUNCTION, /* per-function statistics */
> +
> + /* cluster-scoped stats having a fixed number of entries */ (maybe these
> should go first, the variable following?)
> + PGSTAT_KIND_ARCHIVER,
> + PGSTAT_KIND_BGWRITER,
> + PGSTAT_KIND_CHECKPOINTER,
> + PGSTAT_KIND_SLRU,
> + PGSTAT_KIND_WAL,
> """
>
> I see three "KIND_GROUP" categories here:
> PGSTAT_KIND_CLUSTER (open to a different word here though...)
> PGSTAT_KIND_DATABASE (we seem to agree on this above)
> PGSTAT_KIND_GLOBAL (already used in the code)
>
> This single enum can replace the two booleans that, in combination, would
> define 4 unique groups (of which only three are interesting -
> database+fixed doesn't seem interesting and so is not given a name/value
> here).
The more I think about it, the less I think a split like that makes sense. The
difference between PGSTAT_KIND_CLUSTER / PGSTAT_KIND_DATABASE is tiny. Nearly
all code just deals with both together.
I think all this is going to achieve is to making code more complicated. There
is a *single* non-assert use of accessed_across_databases and now a single
assertion involving it.
What would having PGSTAT_KIND_CLUSTER and PGSTAT_KIND_DATABASE achieve?
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v68
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 20:45 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 21:06 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 21:25 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 21:54 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 22:24 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 22:44 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 02:03 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-05 02:36 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
@ 2022-04-05 15:49 ` David G. Johnston <[email protected]>
2022-04-05 17:30 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: David G. Johnston @ 2022-04-05 15:49 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Ibrar Ahmed <[email protected]>; Fujii Masao <[email protected]>; [email protected]; PostgreSQL Hackers <[email protected]>
On Mon, Apr 4, 2022 at 7:36 PM Andres Freund <[email protected]> wrote:
>
> I think all this is going to achieve is to making code more complicated.
> There
> is a *single* non-assert use of accessed_across_databases and now a single
> assertion involving it.
>
> What would having PGSTAT_KIND_CLUSTER and PGSTAT_KIND_DATABASE achieve?
>
So, I decided to see what this would look like; the results are attached,
portions of it also inlined below.
I'll admit this does introduce a terminology problem - but IMO these words
are much more meaningful to the reader and code than the existing
booleans. I'm hopeful we can bikeshed something agreeable as I'm strongly
in favor of making this change.
The ability to create defines for subsets nicely resolves the problem that
CLUSTER and DATABASE (now OBJECT to avoid DATABASE conflict in PgStat_Kind)
are generally related together - they are now grouped under the DYNAMIC
label (variable, if you want) while all of the fixed entries get associated
with GLOBAL. Thus the majority of usages, since accessed_across_databases
is rare, end up being either DYNAMIC or GLOBAL. The presence of any other
category should give one pause. We could add an ALL define if we ever
decide to consolidate the API - but for now it's largely used to ensure
that stats of one type don't get processed by the other. The boolean fixed
does that well enough but this just seems much cleaner and more
understandable to me. Though having made up the terms and model myself,
that isn't too surprising.
The only existing usage of accessed_across_databases is in the negative
form, which translates to excluding objects, but only those from other
actual databases.
@@ -909,7 +904,7 @@ pgstat_build_snapshot(void)
*/
if (p->key.dboid != MyDatabaseId &&
p->key.dboid != InvalidOid &&
- !kind_info->accessed_across_databases)
+ kind_info->kind_group == PGSTAT_OBJECT)
continue;
The only other usage of something other than GLOBAL or DYNAMIC is the
restriction on the behavior of reset_single_counter, which also has to be
an object in the current database (the later condition being enforced by
the presence of a valid object oid I presume). The replacement for this
below is not behavior-preserving, the proposed behavior I believe we agree
is correct though.
@@ -652,7 +647,7 @@ pgstat_reset_single_counter(PgStat_Kind kind, Oid
objoid)
- Assert(!pgstat_kind_info_for(kind)->fixed_amount);
+ Assert(pgstat_kind_info_for(kind)->kind_group == PGSTAT_OBJECT);
Everything else is a straight conversion of fixed_amount to CLUSTER+OBJECT
@@ -728,7 +723,7 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, Oid
objoid)
- AssertArg(!kind_info->fixed_amount);
+ AssertArg(kind_info->kind_group == PGSTAT_DYNAMIC);
and !fixed_amount to GLOBAL
@@ -825,7 +820,7 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_exists_entry(PgStat_Kind kind, Oid dboid, Oid objoid)
{
- if (pgstat_kind_info_for(kind)->fixed_amount)
+ if (pgstat_kind_info_for(kind)->kind_group == PGSTAT_GLOBAL)
return true;
return pgstat_get_entry_ref(kind, dboid, objoid, false, NULL) != NULL;
David J.
Attachments:
[application/octet-stream] rework-using-enums.diff (8.6K, ../../CAKFQuwYohxsmJqrWzs1PgxXuNWJDmEkztusfOXjzmFD0AOKphQ@mail.gmail.com/3-rework-using-enums.diff)
download | inline diff:
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index dbd55a065d..7fbdbb09bd 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -193,9 +193,7 @@ static const PgStat_KindInfo pgstat_kind_infos[PGSTAT_NUM_KINDS] = {
[PGSTAT_KIND_DATABASE] = {
.name = "database",
- .fixed_amount = false,
- /* so pg_stat_database entries can be seen in all databases */
- .accessed_across_databases = true,
+ .kind_group = PGSTAT_CLUSTER,
.shared_size = sizeof(PgStatShared_Database),
.shared_data_off = offsetof(PgStatShared_Database, stats),
@@ -209,7 +207,7 @@ static const PgStat_KindInfo pgstat_kind_infos[PGSTAT_NUM_KINDS] = {
[PGSTAT_KIND_RELATION] = {
.name = "relation",
- .fixed_amount = false,
+ .kind_group = PGSTAT_OBJECT,
.shared_size = sizeof(PgStatShared_Relation),
.shared_data_off = offsetof(PgStatShared_Relation, stats),
@@ -223,7 +221,7 @@ static const PgStat_KindInfo pgstat_kind_infos[PGSTAT_NUM_KINDS] = {
[PGSTAT_KIND_FUNCTION] = {
.name = "function",
- .fixed_amount = false,
+ .kind_group = PGSTAT_OBJECT,
.shared_size = sizeof(PgStatShared_Function),
.shared_data_off = offsetof(PgStatShared_Function, stats),
@@ -236,9 +234,8 @@ static const PgStat_KindInfo pgstat_kind_infos[PGSTAT_NUM_KINDS] = {
[PGSTAT_KIND_REPLSLOT] = {
.name = "replslot",
- .fixed_amount = false,
+ .kind_group = PGSTAT_CLUSTER,
- .accessed_across_databases = true,
.named_on_disk = true,
.shared_size = sizeof(PgStatShared_ReplSlot),
@@ -254,9 +251,7 @@ static const PgStat_KindInfo pgstat_kind_infos[PGSTAT_NUM_KINDS] = {
[PGSTAT_KIND_SUBSCRIPTION] = {
.name = "subscription",
- .fixed_amount = false,
- /* so pg_stat_subscription_stats entries can be seen in all databases */
- .accessed_across_databases = true,
+ .kind_group = PGSTAT_CLUSTER,
.shared_size = sizeof(PgStatShared_Subscription),
.shared_data_off = offsetof(PgStatShared_Subscription, stats),
@@ -273,7 +268,7 @@ static const PgStat_KindInfo pgstat_kind_infos[PGSTAT_NUM_KINDS] = {
[PGSTAT_KIND_ARCHIVER] = {
.name = "archiver",
- .fixed_amount = true,
+ .kind_group = PGSTAT_GLOBAL,
.reset_all_cb = pgstat_archiver_reset_all_cb,
.snapshot_cb = pgstat_archiver_snapshot_cb,
@@ -282,7 +277,7 @@ static const PgStat_KindInfo pgstat_kind_infos[PGSTAT_NUM_KINDS] = {
[PGSTAT_KIND_BGWRITER] = {
.name = "bgwriter",
- .fixed_amount = true,
+ .kind_group = PGSTAT_GLOBAL,
.reset_all_cb = pgstat_bgwriter_reset_all_cb,
.snapshot_cb = pgstat_bgwriter_snapshot_cb,
@@ -291,7 +286,7 @@ static const PgStat_KindInfo pgstat_kind_infos[PGSTAT_NUM_KINDS] = {
[PGSTAT_KIND_CHECKPOINTER] = {
.name = "checkpointer",
- .fixed_amount = true,
+ .kind_group = PGSTAT_GLOBAL,
.reset_all_cb = pgstat_checkpointer_reset_all_cb,
.snapshot_cb = pgstat_checkpointer_snapshot_cb,
@@ -300,7 +295,7 @@ static const PgStat_KindInfo pgstat_kind_infos[PGSTAT_NUM_KINDS] = {
[PGSTAT_KIND_SLRU] = {
.name = "slru",
- .fixed_amount = true,
+ .kind_group = PGSTAT_GLOBAL,
.reset_all_cb = pgstat_slru_reset_all_cb,
.snapshot_cb = pgstat_slru_snapshot_cb,
@@ -309,7 +304,7 @@ static const PgStat_KindInfo pgstat_kind_infos[PGSTAT_NUM_KINDS] = {
[PGSTAT_KIND_WAL] = {
.name = "wal",
- .fixed_amount = true,
+ .kind_group = PGSTAT_GLOBAL,
.reset_all_cb = pgstat_wal_reset_all_cb,
.snapshot_cb = pgstat_wal_snapshot_cb,
@@ -652,7 +647,7 @@ pgstat_reset_single_counter(PgStat_Kind kind, Oid objoid)
{
TimestampTz ts = GetCurrentTimestamp();
- Assert(!pgstat_kind_info_for(kind)->fixed_amount);
+ Assert(pgstat_kind_info_for(kind)->kind_group == PGSTAT_OBJECT);
/* Set the reset timestamp for the whole database */
pgstat_reset_database_timestamp(MyDatabaseId, ts);
@@ -673,7 +668,7 @@ pgstat_reset_shared_counters(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_kind_info_for(kind);
TimestampTz now = GetCurrentTimestamp();
- Assert(kind_info->fixed_amount);
+ Assert(kind_info->kind_group == PGSTAT_GLOBAL);
kind_info->reset_all_cb(now);
}
@@ -728,7 +723,7 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, Oid objoid)
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
- AssertArg(!kind_info->fixed_amount);
+ AssertArg(kind_info->kind_group == PGSTAT_DYNAMIC);
pgstat_prep_snapshot();
@@ -825,7 +820,7 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_exists_entry(PgStat_Kind kind, Oid dboid, Oid objoid)
{
- if (pgstat_kind_info_for(kind)->fixed_amount)
+ if (pgstat_kind_info_for(kind)->kind_group == PGSTAT_GLOBAL)
return true;
return pgstat_get_entry_ref(kind, dboid, objoid, false, NULL) != NULL;
@@ -841,7 +836,7 @@ void
pgstat_snapshot_global(PgStat_Kind kind)
{
AssertArg(pgstat_kind_valid(kind));
- AssertArg(pgstat_kind_info_for(kind)->fixed_amount);
+ AssertArg(pgstat_kind_info_for(kind)->kind_group == PGSTAT_GLOBAL);
if (pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_SNAPSHOT)
pgstat_build_snapshot();
@@ -909,7 +904,7 @@ pgstat_build_snapshot(void)
*/
if (p->key.dboid != MyDatabaseId &&
p->key.dboid != InvalidOid &&
- !kind_info->accessed_across_databases)
+ kind_info->kind_group == PGSTAT_OBJECT)
continue;
if (p->dropped)
@@ -938,7 +933,7 @@ pgstat_build_snapshot(void)
{
const PgStat_KindInfo *kind_info = pgstat_kind_info_for(kind);
- if (!kind_info->fixed_amount)
+ if (kind_info->kind_group == PGSTAT_DYNAMIC)
{
Assert(kind_info->snapshot_cb == NULL);
continue;
@@ -955,7 +950,7 @@ pgstat_build_snapshot_global(PgStat_Kind kind)
{
const PgStat_KindInfo *kind_info = pgstat_kind_info_for(kind);
- Assert(kind_info->fixed_amount);
+ Assert(kind_info->kind_group == PGSTAT_GLOBAL);
Assert(kind_info->snapshot_cb != NULL);
if (pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_NONE)
@@ -1047,8 +1042,8 @@ pgstat_delete_pending_entry(PgStat_EntryRef *entry_ref)
void *pending_data = entry_ref->pending;
Assert(pending_data != NULL);
- /* !fixed_amount stats should be handled explicitly */
- Assert(!pgstat_kind_info_for(kind)->fixed_amount);
+ /* global stats should be handled explicitly : why?*/
+ Assert(pgstat_kind_info_for(kind)->kind_group == PGSTAT_DYNAMIC);
if (kind_info->delete_pending_cb)
kind_info->delete_pending_cb(entry_ref);
@@ -1091,7 +1086,7 @@ pgstat_flush_pending_entries(bool nowait)
bool did_flush;
dlist_node *next;
- Assert(!kind_info->fixed_amount);
+ Assert(kind_info->kind_group == PGSTAT_DYNAMIC);
Assert(kind_info->flush_pending_cb != NULL);
/* flush the stats, if possible */
@@ -1585,7 +1580,7 @@ pgstat_reset_all_stats(TimestampTz ts)
{
const PgStat_KindInfo *kind_info = pgstat_kind_info_for(kind);
- if (!kind_info->fixed_amount)
+ if (kind_info->kind_group == PGSTAT_DYNAMIC)
continue;
kind_info->reset_all_cb(ts);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index d11b628710..82003df524 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -926,7 +926,7 @@ pgstat_reset_entry(PgStat_Kind kind, Oid dboid, Oid objoid)
{
PgStat_EntryRef *entry_ref;
- Assert(!pgstat_kind_info_for(kind)->fixed_amount);
+ Assert(pgstat_kind_info_for(kind)->kind_group == PGSTAT_DYNAMIC);
entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 8234f9fdfb..d62ad01ddb 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -31,6 +31,15 @@
/* Default directory to store temporary statistics data in */
#define PG_STAT_TMP_DIR "pg_stat_tmp"
+typedef enum PgStat_KindGroup
+{
+ PGSTAT_GLOBAL = 1,
+ PGSTAT_CLUSTER,
+ PGSTAT_OBJECT
+} PgStat_KindGroup;
+
+#define PGSTAT_DYNAMIC (PGSTAT_CLUSTER | PGSTAT_OBJECT)
+
/* The types of statistics entries */
typedef enum PgStat_Kind
{
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index 7efa9d32a9..3fde2034b1 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -181,17 +181,7 @@ typedef struct PgStat_SubXactStatus
*/
typedef struct PgStat_KindInfo
{
- /*
- * Does a fixed number of stats object exist for this kind of stats or not
- * (e.g. tables).
- */
- bool fixed_amount:1;
-
- /*
- * Can stats of this kind be accessed from another database? Determines
- * whether a stats object gets included in stats snapshots.
- */
- bool accessed_across_databases:1;
+ PgStat_KindGroup kind_group;
/*
* For variable number stats: Identified on-disk using a name, rather than
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v68
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 20:45 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 21:06 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 21:25 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 21:54 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 22:24 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 22:44 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 02:03 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-05 02:36 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 15:49 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
@ 2022-04-05 17:30 ` Andres Freund <[email protected]>
2022-04-05 18:26 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: Andres Freund @ 2022-04-05 17:30 UTC (permalink / raw)
To: David G. Johnston <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Ibrar Ahmed <[email protected]>; Fujii Masao <[email protected]>; [email protected]; PostgreSQL Hackers <[email protected]>
Hi,
On 2022-04-05 08:49:36 -0700, David G. Johnston wrote:
> On Mon, Apr 4, 2022 at 7:36 PM Andres Freund <[email protected]> wrote:
>
> >
> > I think all this is going to achieve is to making code more complicated.
> > There
> > is a *single* non-assert use of accessed_across_databases and now a single
> > assertion involving it.
> >
> > What would having PGSTAT_KIND_CLUSTER and PGSTAT_KIND_DATABASE achieve?
> >
>
> So, I decided to see what this would look like; the results are attached,
> portions of it also inlined below.
> I'll admit this does introduce a terminology problem - but IMO these words
> are much more meaningful to the reader and code than the existing
> booleans. I'm hopeful we can bikeshed something agreeable as I'm strongly
> in favor of making this change.
Sorry, I just don't agree. I'm happy to try to make it look better, but this
isn't it.
Do you think it should be your way strongly enough that you'd not want to get
it in the current way?
> The ability to create defines for subsets nicely resolves the problem that
> CLUSTER and DATABASE (now OBJECT to avoid DATABASE conflict in PgStat_Kind)
> are generally related together - they are now grouped under the DYNAMIC
> label (variable, if you want) while all of the fixed entries get associated
> with GLOBAL. Thus the majority of usages, since accessed_across_databases
> is rare, end up being either DYNAMIC or GLOBAL.
FWIW, as-is DYNAMIC isn't correct:
> +typedef enum PgStat_KindGroup
> +{
> + PGSTAT_GLOBAL = 1,
> + PGSTAT_CLUSTER,
> + PGSTAT_OBJECT
> +} PgStat_KindGroup;
> +
> +#define PGSTAT_DYNAMIC (PGSTAT_CLUSTER | PGSTAT_OBJECT)
Oring PGSTAT_CLUSTER = 2 with PGSTAT_OBJECT = 3 yields 3 again. To do this
kind of thing the different values need to have power-of-two values, and then
the tests need to be done with &.
Nicely demonstrated by the fact that with the patch applied initdb doesn't
pass...
> @@ -909,7 +904,7 @@ pgstat_build_snapshot(void)
> */
> if (p->key.dboid != MyDatabaseId &&
> p->key.dboid != InvalidOid &&
> - !kind_info->accessed_across_databases)
> + kind_info->kind_group == PGSTAT_OBJECT)
> continue;
>
> if (p->dropped)
Imo this is far harder to interpret - !kind_info->accessed_across_databases
tells you why we're skipping in clear code. Your alternative doesn't.
> @@ -938,7 +933,7 @@ pgstat_build_snapshot(void)
> {
> const PgStat_KindInfo *kind_info = pgstat_kind_info_for(kind);
>
> - if (!kind_info->fixed_amount)
> + if (kind_info->kind_group == PGSTAT_DYNAMIC)
These all would have to be kind_info->kind_group & PGSTAT_DYNAMIC, or even
(kind_group & PGSTAT_DYNAMIC) != 0, depending on the case.
> @@ -1047,8 +1042,8 @@ pgstat_delete_pending_entry(PgStat_EntryRef *entry_ref)
> void *pending_data = entry_ref->pending;
>
> Assert(pending_data != NULL);
> - /* !fixed_amount stats should be handled explicitly */
> - Assert(!pgstat_kind_info_for(kind)->fixed_amount);
> + /* global stats should be handled explicitly : why?*/
> + Assert(pgstat_kind_info_for(kind)->kind_group == PGSTAT_DYNAMIC);
The pending data infrastructure doesn't provide a way of dealing with fixed
amount stats, and there's no PgStat_EntryRef for them (since they're not in
the hashtable).
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v68
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 20:45 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 21:06 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 21:25 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 21:54 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 22:24 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 22:44 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 02:03 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-05 02:36 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 15:49 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-05 17:30 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
@ 2022-04-05 18:26 ` David G. Johnston <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: David G. Johnston @ 2022-04-05 18:26 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Ibrar Ahmed <[email protected]>; Fujii Masao <[email protected]>; [email protected] <[email protected]>; PostgreSQL Hackers <[email protected]>
On Tuesday, April 5, 2022, Andres Freund <[email protected]> wrote:
> Hi,
>
> On 2022-04-05 08:49:36 -0700, David G. Johnston wrote:
> > On Mon, Apr 4, 2022 at 7:36 PM Andres Freund <[email protected]> wrote:
> >
> > >
> > > I think all this is going to achieve is to making code more
> complicated.
> > > There
> > > is a *single* non-assert use of accessed_across_databases and now a
> single
> > > assertion involving it.
> > >
> > > What would having PGSTAT_KIND_CLUSTER and PGSTAT_KIND_DATABASE achieve?
> > >
> >
> > So, I decided to see what this would look like; the results are attached,
> > portions of it also inlined below.
>
> > I'll admit this does introduce a terminology problem - but IMO these
> words
> > are much more meaningful to the reader and code than the existing
> > booleans. I'm hopeful we can bikeshed something agreeable as I'm
> strongly
> > in favor of making this change.
>
> Sorry, I just don't agree. I'm happy to try to make it look better, but
> this
> isn't it.
>
> Do you think it should be your way strongly enough that you'd not want to
> get
> it in the current way?
>
>
Not that strongly; I’m good with the code as-is. Its not pervasive enough
to be hard to understand (I may ponder some code comments though) and the
system it is modeling has some legacy aspects that are more the root
problem and I don’t want to touch those here for sure.
>
> Oring PGSTAT_CLUSTER = 2 with PGSTAT_OBJECT = 3 yields 3 again. To do this
> kind of thing the different values need to have power-of-two values, and
> then
> the tests need to be done with &.
Thanks.
>
> Nicely demonstrated by the fact that with the patch applied initdb doesn't
> pass...
Yeah, I compiled but tried to run the tests and learned I still need to
figure out my setup for make check; then I forgot to make install…
It served its purpose at least.
>
> > @@ -1047,8 +1042,8 @@ pgstat_delete_pending_entry(PgStat_EntryRef
> *entry_ref)
> > void *pending_data = entry_ref->pending;
> >
> > Assert(pending_data != NULL);
> > - /* !fixed_amount stats should be handled explicitly */
> > - Assert(!pgstat_kind_info_for(kind)->fixed_amount);
> > + /* global stats should be handled explicitly : why?*/
> > + Assert(pgstat_kind_info_for(kind)->kind_group == PGSTAT_DYNAMIC);
>
> The pending data infrastructure doesn't provide a way of dealing with fixed
> amount stats, and there's no PgStat_EntryRef for them (since they're not in
> the hashtable).
>
>
Thanks.
David J.
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v69
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
@ 2022-04-05 03:05 ` Andres Freund <[email protected]>
2022-04-05 20:51 ` Re: shared-memory based stats collector - v69 David G. Johnston <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
3 siblings, 2 replies; 87+ messages in thread
From: Andres Freund @ 2022-04-05 03:05 UTC (permalink / raw)
To: Kyotaro Horiguchi <[email protected]>; +Cc: [email protected]; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; David G. Johnston <[email protected]>; [email protected]
Hi,
Thanks for the reviews Justin, Thomas, David. I tried to incorporate the
feedback, with the exception of the ongoing discussion around
accessed_across_databases. I've also not renamed pg_stat_exists_stat() yet,
not clear who likes what :)
Changes in v69:
- merged feedback
- committed the first few commits, mostly pretty boring stuff
- added an architecture overview comment to the top of pgstat.c - not sure if
it makes sense to anybody but me (and perhaps Horiguchi-san)?
- merged "only reset pgstat data after crash recovery." into the main commit,
added tests verifying the behaviour of not resetting stats on a standby when
in SHUTDOWNED_IN_RECOVERY.
- drop variable-amount stats when loading on-disk file fails partway through,
I'd raised this earlier in [1]
- made most pgstat_report_stat() calls pass force = true. In worker.c, the
only possibly frequent caller, I instead added a pgstat_report_stat(true) to
the idle path.
- added a handful more tests, but mostly out of "test coverage vanity" ;)
- made the test output of 030_stats_cleanup_replica a bit more informative,
plus other minor cleanups
The one definite TODO I know of is
> - fix the bug around pgstat_report_stat() I wrote about at [3]
> [3] https://www.postgresql.org/message-id/[email protected]
I'd hoped Horiguchi-san would chime in on that discussion...
Regards,
Andres
[1] https://www.postgresql.org/message-id/20220329191727.mzzwbl7udhpq7pmf%40alap3.anarazel.de
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v69
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
@ 2022-04-05 20:51 ` David G. Johnston <[email protected]>
2022-04-05 21:23 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
1 sibling, 1 reply; 87+ messages in thread
From: David G. Johnston @ 2022-04-05 20:51 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; PostgreSQL Hackers <[email protected]>
On Mon, Apr 4, 2022 at 8:05 PM Andres Freund <[email protected]> wrote:
> - added an architecture overview comment to the top of pgstat.c - not sure
> if
> it makes sense to anybody but me (and perhaps Horiguchi-san)?
>
>
I took a look at this, diff attached. Some typos and minor style stuff,
plus trying to bring a bit more detail to the caching mechanism. I may
have gotten it wrong in adding more detail though.
+ * read-only, backend-local, transaction-scoped, hashtable
(pgStatEntryRefHash)
+ * in front of the shared hashtable, containing references
(PgStat_EntryRef)
+ * to shared hashtable entries. The shared hashtable thus only needs to be
+ * accessed when the PgStat_HashKey is not present in the backend-local
hashtable,
+ * or if stats_fetch_consistency = 'none'.
I'm under the impression, but didn't try to confirm, that the pending
updates don't use the caching mechanism, but rather add to the shared
queue, and so the cache is effectively read-only. It is also
transaction-scoped based upon the GUC and the nature of stats vis-a-vis
transactions.
Even before I added the read-only and transaction-scoped I got a bit hung
up on reading:
"The shared hashtable only needs to be accessed when no prior reference to
the shared hashtable exists."
Thinking in terms of key seems to make more sense than value in this
sentence - even if there is a one-to-one correspondence.
The code comment about having per-kind definitions in pgstat.c being
annoying is probably sufficient but it does seem like a valid comment to
leave in the architecture as well. Having them in both places seems OK.
I am wondering why there are no mentions to the header files in this
architecture, only the .c files.
David J.
Attachments:
[application/octet-stream] pgstat-architecture.diff (3.3K, ../../CAKFQuwa9Ve-aUkHQn0FqX89anXfNOT6poJQvabBDhKC6OCgTnQ@mail.gmail.com/3-pgstat-architecture.diff)
download | inline diff:
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index bfbfe53deb..504f952c0e 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -4,9 +4,9 @@
*
*
* PgStat_KindInfo describes the different types of statistics handled. Some
- * kinds of statistics are collected for fixed number of objects
- * (e.g. checkpointer statistics). Other kinds are statistics are collected
- * for variable-numbered objects (e.g. relations).
+ * kinds of statistics are collected for a fixed number of objects
+ * (e.g., checkpointer statistics). Other kinds of statistics are collected
+ * for a varying number of objects (e.g., relations).
*
* Fixed-numbered stats are stored in plain (non-dynamic) shared memory.
*
@@ -19,19 +19,21 @@
*
* All variable-numbered stats are addressed by PgStat_HashKey while running.
* It is not possible to have statistics for an object that cannot be
- * addressed that way at runtime. A wider identifier can be used when
+ * addressed that way at runtime. A alternate identifier can be used when
* serializing to disk (used for replication slot stats).
*
* Each stats entry in shared memory is protected by a dedicated lwlock.
*
* To avoid contention on the shared hashtable, each backend has a
- * backend-local hashtable (pgStatEntryRefHash) in front of the shared
- * hashtable, containing references (PgStat_EntryRef) to shared hashtable
- * entries. The shared hashtable only needs to be accessed when no prior
- * reference to the shared hashtable exists. Besides pointing to the the
- * shared hashtable entry (PgStatShared_HashEntry) PgStat_EntryRef also
- * contains a pointer to the the shared statistics data, as a process-local
- * address, to reduce access costs.
+ * read-only, backend-local, transaction-scoped, hashtable (pgStatEntryRefHash)
+ * in front of the shared hashtable, containing references (PgStat_EntryRef)
+ * to shared hashtable entries. The shared hashtable thus only needs to be
+ * accessed when the PgStat_HashKey is not present in the backend-local hashtable,
+ * or if stats_fetch_consistency = 'none'.
+ *
+ * Besides pointing to the shared hashtable entry (PgStatShared_HashEntry),
+ * PgStat_EntryRef also contains a pointer to the the shared statistics data,
+ * as a process-local address, to reduce access costs.
*
* The names for structs stored in shared memory are prefixed with
* PgStatShared instead of PgStat.
@@ -53,15 +55,16 @@
* entry in pgstat_kind_infos, see PgStat_KindInfo for details.
*
*
- * To keep things manageable stats handling is split across several
+ * To keep things manageable, stats handling is split across several
* files. Infrastructure pieces are in:
- * - pgstat.c - this file, to tie it all together
+ * - pgstat.c - this file, which ties everything together
* - pgstat_shmem.c - nearly everything dealing with shared memory, including
* the maintenance of hashtable entries
* - pgstat_xact.c - transactional integration, including the transactional
- * creation / dropping of stats entries
+ * creation and dropping of stats entries
*
- * Each statistics kind is handled in a dedicated file:
+ * Each statistics kind is handled in a dedicated file, though their structs
+ * are defined here for lack of better ideas.
* - pgstat_archiver.c
* - pgstat_bgwriter.c
* - pgstat_checkpointer.c
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v69
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-05 20:51 ` Re: shared-memory based stats collector - v69 David G. Johnston <[email protected]>
@ 2022-04-05 21:23 ` Andres Freund <[email protected]>
2022-04-05 21:43 ` Re: shared-memory based stats collector - v69 David G. Johnston <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: Andres Freund @ 2022-04-05 21:23 UTC (permalink / raw)
To: David G. Johnston <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi,
On 2022-04-05 13:51:12 -0700, David G. Johnston wrote:
> On Mon, Apr 4, 2022 at 8:05 PM Andres Freund <[email protected]> wrote:
>
> > - added an architecture overview comment to the top of pgstat.c - not sure
> > if
> > it makes sense to anybody but me (and perhaps Horiguchi-san)?
> >
> >
> I took a look at this, diff attached.
Thanks!
> Some typos and minor style stuff,
> plus trying to bring a bit more detail to the caching mechanism. I may
> have gotten it wrong in adding more detail though.
>
> + * read-only, backend-local, transaction-scoped, hashtable
> (pgStatEntryRefHash)
> + * in front of the shared hashtable, containing references
> (PgStat_EntryRef)
> + * to shared hashtable entries. The shared hashtable thus only needs to be
> + * accessed when the PgStat_HashKey is not present in the backend-local
> hashtable,
> + * or if stats_fetch_consistency = 'none'.
>
> I'm under the impression, but didn't try to confirm, that the pending
> updates don't use the caching mechanism
They do.
>, but rather add to the shared queue
Queue? Maybe you mean the hashtable?
>, and so the cache is effectively read-only. It is also transaction-scoped
>based upon the GUC and the nature of stats vis-a-vis transactions.
No, that's not right. I think you might be thinking of
pgStatLocal.snapshot.stats?
I guess I should add a paragraph about snapshots / fetch consistency.
> Even before I added the read-only and transaction-scoped I got a bit hung
> up on reading:
> "The shared hashtable only needs to be accessed when no prior reference to
> the shared hashtable exists."
> Thinking in terms of key seems to make more sense than value in this
> sentence - even if there is a one-to-one correspondence.
Maybe "prior reference to the shared hashtable exists for the key"?
> I am wondering why there are no mentions to the header files in this
> architecture, only the .c files.
Hm, I guess, but I'm not sure it'd add a lot? It's really just intended to
give a starting point (and it can't be worse than explanation of the current
system).
> diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
> index bfbfe53deb..504f952c0e 100644
> --- a/src/backend/utils/activity/pgstat.c
> +++ b/src/backend/utils/activity/pgstat.c
> @@ -4,9 +4,9 @@
> *
> *
> * PgStat_KindInfo describes the different types of statistics handled. Some
> - * kinds of statistics are collected for fixed number of objects
> - * (e.g. checkpointer statistics). Other kinds are statistics are collected
> - * for variable-numbered objects (e.g. relations).
> + * kinds of statistics are collected for a fixed number of objects
> + * (e.g., checkpointer statistics). Other kinds of statistics are collected
Was that comma after e.g. intentional?
Applied the rest.
> + * for a varying number of objects (e.g., relations).
> * Fixed-numbered stats are stored in plain (non-dynamic) shared memory.
> *
> @@ -19,19 +19,21 @@
> *
> * All variable-numbered stats are addressed by PgStat_HashKey while running.
> * It is not possible to have statistics for an object that cannot be
> - * addressed that way at runtime. A wider identifier can be used when
> + * addressed that way at runtime. A alternate identifier can be used when
> * serializing to disk (used for replication slot stats).
Not sure this improves things.
> * The names for structs stored in shared memory are prefixed with
> * PgStatShared instead of PgStat.
> @@ -53,15 +55,16 @@
> * entry in pgstat_kind_infos, see PgStat_KindInfo for details.
> *
> *
> - * To keep things manageable stats handling is split across several
> + * To keep things manageable, stats handling is split across several
Done.
> * files. Infrastructure pieces are in:
> - * - pgstat.c - this file, to tie it all together
> + * - pgstat.c - this file, which ties everything together
I liked that :)
> - * Each statistics kind is handled in a dedicated file:
> + * Each statistics kind is handled in a dedicated file, though their structs
> + * are defined here for lack of better ideas.
-0.5
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v69
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-05 20:51 ` Re: shared-memory based stats collector - v69 David G. Johnston <[email protected]>
2022-04-05 21:23 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
@ 2022-04-05 21:43 ` David G. Johnston <[email protected]>
2022-04-05 23:16 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: David G. Johnston @ 2022-04-05 21:43 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; PostgreSQL Hackers <[email protected]>
On Tue, Apr 5, 2022 at 2:23 PM Andres Freund <[email protected]> wrote:
>
> On 2022-04-05 13:51:12 -0700, David G. Johnston wrote:
>
> >, but rather add to the shared queue
>
> Queue? Maybe you mean the hashtable?
>
Queue implemented by a list...? Anyway, I think I mean this:
/*
* List of PgStat_EntryRefs with unflushed pending stats.
*
* Newly pending entries should only ever be added to the end of the list,
* otherwise pgstat_flush_pending_entries() might not see them immediately.
*/
static dlist_head pgStatPending = DLIST_STATIC_INIT(pgStatPending);
>
>
> >, and so the cache is effectively read-only. It is also
> transaction-scoped
> >based upon the GUC and the nature of stats vis-a-vis transactions.
>
> No, that's not right. I think you might be thinking of
> pgStatLocal.snapshot.stats?
>
>
Probably...
> I guess I should add a paragraph about snapshots / fetch consistency.
>
I apparently confused/combined the two concepts just now so that would help.
>
> > Even before I added the read-only and transaction-scoped I got a bit hung
> > up on reading:
> > "The shared hashtable only needs to be accessed when no prior reference
> to
> > the shared hashtable exists."
>
> > Thinking in terms of key seems to make more sense than value in this
> > sentence - even if there is a one-to-one correspondence.
>
> Maybe "prior reference to the shared hashtable exists for the key"?
>
I specifically dislike having two mentions of the "shared hashtable" in the
same sentence, so I tried to phrase the second half in terms of the local
hashtable.
> > I am wondering why there are no mentions to the header files in this
> > architecture, only the .c files.
>
> Hm, I guess, but I'm not sure it'd add a lot? It's really just intended to
> give a starting point (and it can't be worse than explanation of the
> current
> system).
>
No need to try to come up with something. More curious if there was a
general reason to avoid it before I looked to see if I felt anything in
them seemed worth including from my perspective.
>
>
> > diff --git a/src/backend/utils/activity/pgstat.c
> b/src/backend/utils/activity/pgstat.c
> > index bfbfe53deb..504f952c0e 100644
> > --- a/src/backend/utils/activity/pgstat.c
> > +++ b/src/backend/utils/activity/pgstat.c
> > @@ -4,9 +4,9 @@
> > *
> > *
> > * PgStat_KindInfo describes the different types of statistics handled.
> Some
> > - * kinds of statistics are collected for fixed number of objects
> > - * (e.g. checkpointer statistics). Other kinds are statistics are
> collected
> > - * for variable-numbered objects (e.g. relations).
> > + * kinds of statistics are collected for a fixed number of objects
> > + * (e.g., checkpointer statistics). Other kinds of statistics are
> collected
>
> Was that comma after e.g. intentional?
>
It is. That is the style I was taught, and that we seem to adhere to in
user-facing documentation. Source code is a mixed bag with no enforcement,
but while we are here...
> > + * for a varying number of objects (e.g., relations).
> > * Fixed-numbered stats are stored in plain (non-dynamic) shared memory.
>
status-quo works for me too, and matches up with the desired labelling we
are using here.
> > *
> > @@ -19,19 +19,21 @@
> > *
> > * All variable-numbered stats are addressed by PgStat_HashKey while
> running.
> > * It is not possible to have statistics for an object that cannot be
> > - * addressed that way at runtime. A wider identifier can be used when
> > + * addressed that way at runtime. A alternate identifier can be used
> when
> > * serializing to disk (used for replication slot stats).
>
> Not sure this improves things.
>
>
It just seems odd that width is being mentioned when the actual struct is a
combination of three subcomponents. I do feel I'd need to understand
exactly what replication slot stats are doing uniquely here, though, to
make any point beyond that.
>
> > - * Each statistics kind is handled in a dedicated file:
> > + * Each statistics kind is handled in a dedicated file, though their
> structs
> > + * are defined here for lack of better ideas.
>
> -0.5
>
>
Status-quo works for me. Food for thought for other reviewers though.
David J.
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v69
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-05 20:51 ` Re: shared-memory based stats collector - v69 David G. Johnston <[email protected]>
2022-04-05 21:23 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-05 21:43 ` Re: shared-memory based stats collector - v69 David G. Johnston <[email protected]>
@ 2022-04-05 23:16 ` Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v69 David G. Johnston <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: Andres Freund @ 2022-04-05 23:16 UTC (permalink / raw)
To: David G. Johnston <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; PostgreSQL Hackers <[email protected]>
On 2022-04-05 14:43:49 -0700, David G. Johnston wrote:
> On Tue, Apr 5, 2022 at 2:23 PM Andres Freund <[email protected]> wrote:
>
> >
> > On 2022-04-05 13:51:12 -0700, David G. Johnston wrote:
> >
> > >, but rather add to the shared queue
> >
> > Queue? Maybe you mean the hashtable?
> >
>
> Queue implemented by a list...? Anyway, I think I mean this:
> /*
> * List of PgStat_EntryRefs with unflushed pending stats.
> *
> * Newly pending entries should only ever be added to the end of the list,
> * otherwise pgstat_flush_pending_entries() might not see them immediately.
> */
> static dlist_head pgStatPending = DLIST_STATIC_INIT(pgStatPending);
That's not in shared memory, but backend local...
> > >, and so the cache is effectively read-only. It is also
> > transaction-scoped
> > >based upon the GUC and the nature of stats vis-a-vis transactions.
> >
> > No, that's not right. I think you might be thinking of
> > pgStatLocal.snapshot.stats?
> >
> >
> Probably...
>
>
> > I guess I should add a paragraph about snapshots / fetch consistency.
> >
>
> I apparently confused/combined the two concepts just now so that would help.
Will add.
> >
> > > Even before I added the read-only and transaction-scoped I got a bit hung
> > > up on reading:
> > > "The shared hashtable only needs to be accessed when no prior reference
> > to
> > > the shared hashtable exists."
> >
> > > Thinking in terms of key seems to make more sense than value in this
> > > sentence - even if there is a one-to-one correspondence.
> >
> > Maybe "prior reference to the shared hashtable exists for the key"?
> >
>
> I specifically dislike having two mentions of the "shared hashtable" in the
> same sentence, so I tried to phrase the second half in terms of the local
> hashtable.
You left two mentions of "shared hashtable" in the sentence prior though
:). I'll try to rephrase. But it's not the end if this isn't the most elegant
prose...
> > Was that comma after e.g. intentional?
> >
>
> It is. That is the style I was taught, and that we seem to adhere to in
> user-facing documentation. Source code is a mixed bag with no enforcement,
> but while we are here...
Looks a bit odd to me. But I guess I'll add it then...
> > > *
> > > @@ -19,19 +19,21 @@
> > > *
> > > * All variable-numbered stats are addressed by PgStat_HashKey while
> > running.
> > > * It is not possible to have statistics for an object that cannot be
> > > - * addressed that way at runtime. A wider identifier can be used when
> > > + * addressed that way at runtime. A alternate identifier can be used
> > when
> > > * serializing to disk (used for replication slot stats).
> >
> > Not sure this improves things.
> >
> >
> It just seems odd that width is being mentioned when the actual struct is a
> combination of three subcomponents. I do feel I'd need to understand
> exactly what replication slot stats are doing uniquely here, though, to
> make any point beyond that.
There's no real numeric identifier for replication slot stats. So I'm using
the "index" used in slot.c while running. But that can change during
start/stop.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v69
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-05 20:51 ` Re: shared-memory based stats collector - v69 David G. Johnston <[email protected]>
2022-04-05 21:23 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-05 21:43 ` Re: shared-memory based stats collector - v69 David G. Johnston <[email protected]>
2022-04-05 23:16 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
@ 2022-04-06 03:00 ` David G. Johnston <[email protected]>
2022-04-06 03:14 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: David G. Johnston @ 2022-04-06 03:00 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; PostgreSQL Hackers <[email protected]>
On Tue, Apr 5, 2022 at 4:16 PM Andres Freund <[email protected]> wrote:
> On 2022-04-05 14:43:49 -0700, David G. Johnston wrote:
> > On Tue, Apr 5, 2022 at 2:23 PM Andres Freund <[email protected]> wrote:
>
> >
> > > I guess I should add a paragraph about snapshots / fetch consistency.
> > >
> >
> > I apparently confused/combined the two concepts just now so that would
> help.
>
> Will add.
>
>
Thank you.
On a slightly different track, I took the time to write-up a "Purpose"
section for pgstat.c :
It may possibly be duplicating some things written elsewhere as I didn't go
looking for similar prior art yet, I just wanted to get thoughts down.
This is the kind of preliminary framing I've been constructing in my own
mind as I try to absorb this patch. I haven't formed an opinion whether
the actual user-facing documentation should cover some or all of this
instead of the preamble to pgstat.c (which could just point to the docs for
prerequisite reading).
David J.
* Purpose:
* The PgStat namespace defines an API that facilitates concurrent access
* to a shared memory region where cumulative statistical data is saved.
* At shutdown, one of the running system workers will initiate the writing
* of the data to file. Then, during startup (following a clean shutdown)
the
* Postmaster process will early on ensure that the file is loaded into
memory.
*
* Each cumulative statistic producing system must construct a PgStat_Kind
* datum in this file. The details are described elsewhere, but of
* particular importance is that each kind is classified as having either a
* fixed number of objects that it tracks, or a variable number.
*
* During normal operations, the different consumers of the API will have
their
* accessed managed by the API, the protocol used is determined based upon
whether
* the statistical kind is fixed-numbered or variable-numbered.
* Readers of variable-numbered statistics will have the option to locally
* cache the data, while writers may have their updates locally queued
* and applied in a batch. Thus favoring speed over freshness.
* The fixed-numbered statistics are faster to process and thus forgo
* these mechanisms in favor of a light-weight lock.
*
* Cumulative in this context means that processes must, for numeric data,
send
* a delta (or change) value via the API which will then be added to the
* stored value in memory. The system does not track individual changes,
only
* their net effect. Additionally, both due to unclean shutdown or user
request,
* statistics can be reset - meaning that their stored numeric values are
returned
* to zero, and any non-numeric data that may be tracked (say a timestamp)
is cleared.
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v69
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-05 20:51 ` Re: shared-memory based stats collector - v69 David G. Johnston <[email protected]>
2022-04-05 21:23 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-05 21:43 ` Re: shared-memory based stats collector - v69 David G. Johnston <[email protected]>
2022-04-05 23:16 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v69 David G. Johnston <[email protected]>
@ 2022-04-06 03:14 ` Andres Freund <[email protected]>
2022-04-06 03:30 ` Re: shared-memory based stats collector - v69 David G. Johnston <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: Andres Freund @ 2022-04-06 03:14 UTC (permalink / raw)
To: David G. Johnston <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi,
On 2022-04-05 20:00:50 -0700, David G. Johnston wrote:
> On Tue, Apr 5, 2022 at 4:16 PM Andres Freund <[email protected]> wrote:
> > On 2022-04-05 14:43:49 -0700, David G. Johnston wrote:
> > > On Tue, Apr 5, 2022 at 2:23 PM Andres Freund <[email protected]> wrote:
> > > > I guess I should add a paragraph about snapshots / fetch consistency.
> > > >
> > >
> > > I apparently confused/combined the two concepts just now so that would
> > help.
> >
> > Will add.
I at least tried...
> On a slightly different track, I took the time to write-up a "Purpose"
> section for pgstat.c :
>
> It may possibly be duplicating some things written elsewhere as I didn't go
> looking for similar prior art yet, I just wanted to get thoughts down.
There's very very little prior documentation in this area.
> This is the kind of preliminary framing I've been constructing in my own
> mind as I try to absorb this patch. I haven't formed an opinion whether
> the actual user-facing documentation should cover some or all of this
> instead of the preamble to pgstat.c (which could just point to the docs for
> prerequisite reading).
> * The PgStat namespace defines an API that facilitates concurrent access
> * to a shared memory region where cumulative statistical data is saved.
> * At shutdown, one of the running system workers will initiate the writing
> * of the data to file. Then, during startup (following a clean shutdown)
> the
> * Postmaster process will early on ensure that the file is loaded into
> memory.
I added something roughly along those lines in the version I just sent, based
on a suggestion by Melanie over IM:
* Statistics are loaded from the filesystem during startup (by the startup
* process), unless preceded by a crash, in which case all stats are
* discarded. They are written out by the checkpointer process just before
* shutting down, except when shutting down in immediate mode.
> * Each cumulative statistic producing system must construct a PgStat_Kind
> * datum in this file. The details are described elsewhere, but of
> * particular importance is that each kind is classified as having either a
> * fixed number of objects that it tracks, or a variable number.
> *
> * During normal operations, the different consumers of the API will have
> their
> * accessed managed by the API, the protocol used is determined based upon
> whether
> * the statistical kind is fixed-numbered or variable-numbered.
> * Readers of variable-numbered statistics will have the option to locally
> * cache the data, while writers may have their updates locally queued
> * and applied in a batch. Thus favoring speed over freshness.
> * The fixed-numbered statistics are faster to process and thus forgo
> * these mechanisms in favor of a light-weight lock.
This feels a bit jumbled. Of course something using an API will be managed by
the API. I don't know what protocol reallly means?
> Additionally, both due to unclean shutdown or user
> request,
> * statistics can be reset - meaning that their stored numeric values are
> returned
> * to zero, and any non-numeric data that may be tracked (say a timestamp)
> is cleared.
I think this is basically covered in the above?
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v69
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-05 20:51 ` Re: shared-memory based stats collector - v69 David G. Johnston <[email protected]>
2022-04-05 21:23 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-05 21:43 ` Re: shared-memory based stats collector - v69 David G. Johnston <[email protected]>
2022-04-05 23:16 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v69 David G. Johnston <[email protected]>
2022-04-06 03:14 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
@ 2022-04-06 03:30 ` David G. Johnston <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: David G. Johnston @ 2022-04-06 03:30 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; PostgreSQL Hackers <[email protected]>
On Tue, Apr 5, 2022 at 8:14 PM Andres Freund <[email protected]> wrote:
>
> On 2022-04-05 20:00:50 -0700, David G. Johnston wrote:
>
> * Statistics are loaded from the filesystem during startup (by the startup
> * process), unless preceded by a crash, in which case all stats are
> * discarded. They are written out by the checkpointer process just before
> * shutting down, except when shutting down in immediate mode.
>
>
Cool. I was on the fence about the level of detail here, but mostly
excluded mentioning the checkpointer 'cause I didn't want to research the
correct answer tonight.
>
> > * Each cumulative statistic producing system must construct a
> PgStat_Kind
> > * datum in this file. The details are described elsewhere, but of
> > * particular importance is that each kind is classified as having
> either a
> > * fixed number of objects that it tracks, or a variable number.
> > *
> > * During normal operations, the different consumers of the API will have
> > their
> > * accessed managed by the API, the protocol used is determined based
> upon
> > whether
> > * the statistical kind is fixed-numbered or variable-numbered.
> > * Readers of variable-numbered statistics will have the option to
> locally
> > * cache the data, while writers may have their updates locally queued
> > * and applied in a batch. Thus favoring speed over freshness.
> > * The fixed-numbered statistics are faster to process and thus forgo
> > * these mechanisms in favor of a light-weight lock.
>
> This feels a bit jumbled.
I had that inkling as well. First draft and I needed to stop at some
point. It didn't seem bad or wrong at least.
Of course something using an API will be managed by
> the API. I don't know what protocol reallly means?
>
>
Procedure, process, algorithm are synonyms. Procedure probably makes more
sense here since it is a procedural language we are using. I thought of
algorithm while writing this but it carried too much technical baggage for
me (compression, encryption, etc..) that this didn't seem to fit in with.
>
> > Additionally, both due to unclean shutdown or user
> > request,
> > * statistics can be reset - meaning that their stored numeric values are
> > returned
> > * to zero, and any non-numeric data that may be tracked (say a
> timestamp)
> > is cleared.
>
> I think this is basically covered in the above?
>
>
Yes and no. The first paragraph says they are forced to reset due to
system error. This paragraph basically says that resetting this kind of
statistic is an acceptable, and even expected, thing to do. And in fact
can also be done intentionally and not only due to system error. I am
pondering whether to mention this dynamic first and/or better blend it in -
but the minor repetition in the different contexts seems ok.
David J.
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
@ 2022-04-06 03:00 ` Andres Freund <[email protected]>
2022-04-06 03:06 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
2022-04-06 03:11 ` Re: shared-memory based stats collector - v70 Greg Stark <[email protected]>
2022-04-06 09:11 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
2022-04-06 09:24 ` Re: shared-memory based stats collector - v70 John Naylor <[email protected]>
2022-04-06 11:31 ` Re: shared-memory based stats collector - v70 Alvaro Herrera <[email protected]>
2022-04-06 19:14 ` Re: shared-memory based stats collector - v70 Lukas Fittl <[email protected]>
2022-04-06 22:12 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 07:28 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-13 23:34 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
1 sibling, 9 replies; 87+ messages in thread
From: Andres Freund @ 2022-04-06 03:00 UTC (permalink / raw)
To: Kyotaro Horiguchi <[email protected]>; +Cc: [email protected]; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; David G. Johnston <[email protected]>; [email protected]
Hi,
Here comes v70:
- extended / polished the architecture comment based on feedback from Melanie
and David
- other polishing as suggested by David
- addressed the open issue around pgstat_report_stat(), as described in
https://www.postgresql.org/message-id/20220405204019.6yj7ocmpw352c2u5%40alap3.anarazel.de
- while working on the above point, I noticed that hash_bytes() showed up
noticeably in profiles, so I replaced it with a fixed-width function
- found a few potential regression test instabilities by either *always*
flushing in pgstat_report_stat(), or only flushing when force = true.
- random minor improvements
- reordered commits some
I still haven't renamed pg_stat_exists_stat() yet - I'm leaning towards
pg_stat_have_stats() or pg_stat_exists() right now. But it's an SQL function
for testing, so it doesn't really matter.
I think this is basically ready, minus a a few comment adjustments here and
there. Unless somebody protests I'm planning to start pushing things tomorrow
morning.
It'll be a few hours to get to the main commit - but except for 0001 it
doesn't make sense to push without intending to push later changes too. I
might squash a few commits togther.
There's lots that can be done once all this is in place, both simplifying
pre-existing code and easy new features, but that's for a later release.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
@ 2022-04-06 03:06 ` David G. Johnston <[email protected]>
8 siblings, 0 replies; 87+ messages in thread
From: David G. Johnston @ 2022-04-06 03:06 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; PostgreSQL Hackers <[email protected]>
On Tue, Apr 5, 2022 at 8:00 PM Andres Freund <[email protected]> wrote:
>
> Here comes v70:
>
> I think this is basically ready, minus a a few comment adjustments here and
> there. Unless somebody protests I'm planning to start pushing things
> tomorrow
> morning.
>
>
Nothing I've come across, given my area of experience, gives me pause. I'm
mostly going to focus on docs and comments at this point - to try and help
the next person in my position (and end-users) have an easier go at
on-boarding. Toward that end, I did just add a "Purpose" section writeup
to the v69 thread.
David J.
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
@ 2022-04-06 03:11 ` Greg Stark <[email protected]>
2022-04-06 03:18 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
2022-04-06 03:22 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
8 siblings, 2 replies; 87+ messages in thread
From: Greg Stark @ 2022-04-06 03:11 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; David G. Johnston <[email protected]>; PostgreSQL Hackers <[email protected]>
I've never tried to review a 24-patch series before. It's kind of
intimidating.... Is there a good place to start to get a good idea of
the most important changes?
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 03:11 ` Re: shared-memory based stats collector - v70 Greg Stark <[email protected]>
@ 2022-04-06 03:18 ` David G. Johnston <[email protected]>
1 sibling, 0 replies; 87+ messages in thread
From: David G. Johnston @ 2022-04-06 03:18 UTC (permalink / raw)
To: Greg Stark <[email protected]>; +Cc: Andres Freund <[email protected]>; Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; PostgreSQL Hackers <[email protected]>
On Tue, Apr 5, 2022 at 8:11 PM Greg Stark <[email protected]> wrote:
> I've never tried to review a 24-patch series before. It's kind of
> intimidating.... Is there a good place to start to get a good idea of
> the most important changes?
>
It isn't as bad as the number makes it sound - I just used "git am" to
apply the patches to a branch and skimmed each commit separately. Most of
them are tests or other minor pieces. The remaining few cover different
aspects of the major commit and you can choose them based upon your
experience and time.
David J.
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 03:11 ` Re: shared-memory based stats collector - v70 Greg Stark <[email protected]>
@ 2022-04-06 03:22 ` Andres Freund <[email protected]>
1 sibling, 0 replies; 87+ messages in thread
From: Andres Freund @ 2022-04-06 03:22 UTC (permalink / raw)
To: Greg Stark <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; David G. Johnston <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi,
On 2022-04-05 23:11:07 -0400, Greg Stark wrote:
> I've never tried to review a 24-patch series before. It's kind of
> intimidating.... Is there a good place to start to get a good idea of
> the most important changes?
It was more at some point :). And believe me, I find this whole project
intimidating and exhausting. The stats collector is entangled in a lot of
places, and there was a lot of preparatory work to get this point.
Most of the commits aren't really interesting, I broke them out to make the
"main commit" a bit smaller, because it's exhausting to look at a *huge*
single commit. I wish I could have broken it down more, but I didn't find a
good way.
The interesting commit is
v70-0010-pgstat-store-statistics-in-shared-memory.patch
which actually replaces the stats collector by storing stats in shared
memory. It contains a, now hopefully decent, overview of how things work at
the top of pgstat.c.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
@ 2022-04-06 09:11 ` Kyotaro Horiguchi <[email protected]>
2022-04-06 16:04 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
8 siblings, 1 reply; 87+ messages in thread
From: Kyotaro Horiguchi @ 2022-04-06 09:11 UTC (permalink / raw)
To: [email protected]; +Cc: [email protected]; [email protected]; [email protected]; [email protected]; [email protected]
At Tue, 5 Apr 2022 20:00:08 -0700, Andres Freund <[email protected]> wrote in
> Hi,
>
> Here comes v70:
> - extended / polished the architecture comment based on feedback from Melanie
> and David
> - other polishing as suggested by David
> - addressed the open issue around pgstat_report_stat(), as described in
> https://www.postgresql.org/message-id/20220405204019.6yj7ocmpw352c2u5%40alap3.anarazel.de
> - while working on the above point, I noticed that hash_bytes() showed up
> noticeably in profiles, so I replaced it with a fixed-width function
> - found a few potential regression test instabilities by either *always*
> flushing in pgstat_report_stat(), or only flushing when force = true.
> - random minor improvements
> - reordered commits some
>
> I still haven't renamed pg_stat_exists_stat() yet - I'm leaning towards
> pg_stat_have_stats() or pg_stat_exists() right now. But it's an SQL function
> for testing, so it doesn't really matter.
>
> I think this is basically ready, minus a a few comment adjustments here and
> there. Unless somebody protests I'm planning to start pushing things tomorrow
> morning.
>
> It'll be a few hours to get to the main commit - but except for 0001 it
> doesn't make sense to push without intending to push later changes too. I
> might squash a few commits togther.
>
> There's lots that can be done once all this is in place, both simplifying
> pre-existing code and easy new features, but that's for a later release.
I'm not sure it's in time but..
(Sorry in advance for possible duplicate or pointless comments.)
0001: Looks fine.
0002:
All references to "stats collector" or alike looks like eliminated
after all of the 24 patches are applied. So this seems fine.
0003:
This is just moving around functions and variables. Looks fine.
0004:
I can see padding_pgstat_send and fun:pgstat_send in valgrind.supp
0005:
The function is changed later patch, and it looks fine.
0006:
I'm fine with the categorize for now.
+#define PGSTAT_KIND_LAST PGSTAT_KIND_WAL
+#define PGSTAT_NUM_KINDS (PGSTAT_KIND_LAST + 1)
The number of kinds is 10. And PGSTAT_NUM_KINDS is 11?
+ * Don't define an INVALID value so switch() statements can warn if some
+ * cases aren't covered. But define the first member to 1 so that
+ * uninitialized values can be detected more easily.
FWIW, I like this.
0007:
(mmm no comments)
0008:
+ xact_desc_stats(buf, "", parsed.nstats, parsed.stats);
+ xact_desc_stats(buf, "commit ", parsed.nstats, parsed.stats);
+ xact_desc_stats(buf, "abort ", parsed.nabortstats, parsed.abortstats);
I'm not sure I like this, but I don't object to this..
0009: (skipped)
0010:
(I didn't look this closer. The comments arised while looking other
patches.)
+pgstat_kind_from_str(char *kind_str)
I don't think I like "str" so much. Don't we spell it as
"pgstat_kind_from_name"?
0011:
Looks fine.
0012:
Looks like covering all related parts.
0013:
Just fine.
0014:
I bilieve it:p
0015:
Function attributes seems correct. Looks fine.
0016:
(skipped, but looks fine by a quick look.)
0017:
I don't find a problem with this.
0018: (skipped)
0019:
+my $og_stats = $datadir . '/' . "pg_stat" . '/' . "pgstat.stat";
It can be "$datadir/pg_stat/pgstat.stat" or $datadir . '/pgstat/pgstat.stat'.
Isn't it simpler?
0020-24:(I believe them:p)
regards.
--
Kyotaro Horiguchi
NTT Open Source Software Center
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 09:11 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
@ 2022-04-06 16:04 ` Andres Freund <[email protected]>
2022-04-07 01:36 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: Andres Freund @ 2022-04-06 16:04 UTC (permalink / raw)
To: Kyotaro Horiguchi <[email protected]>; +Cc: [email protected]; [email protected]; [email protected]; [email protected]; [email protected]
Hi,
On 2022-04-06 18:11:04 +0900, Kyotaro Horiguchi wrote:
> 0004:
>
> I can see padding_pgstat_send and fun:pgstat_send in valgrind.supp
Those shouldn't be affected by the patch, I think? But I did indeed forget to
remove those in 0010.
> 0006:
>
> I'm fine with the categorize for now.
>
> +#define PGSTAT_KIND_LAST PGSTAT_KIND_WAL
> +#define PGSTAT_NUM_KINDS (PGSTAT_KIND_LAST + 1)
>
> The number of kinds is 10. And PGSTAT_NUM_KINDS is 11?
Yea, it's not great. I think I'll introduce INVALID and rename
PGSTAT_KIND_FIRST to FIRST_VALID.
> + * Don't define an INVALID value so switch() statements can warn if some
> + * cases aren't covered. But define the first member to 1 so that
> + * uninitialized values can be detected more easily.
>
> FWIW, I like this.
I think there's no switches left now, so it's not actually providing too much.
> 0008:
>
> + xact_desc_stats(buf, "", parsed.nstats, parsed.stats);
> + xact_desc_stats(buf, "commit ", parsed.nstats, parsed.stats);
> + xact_desc_stats(buf, "abort ", parsed.nabortstats, parsed.abortstats);
>
> I'm not sure I like this, but I don't object to this..
The string prefixes? Or the entire patch?
> 0010:
> (I didn't look this closer. The comments arised while looking other
> patches.)
>
> +pgstat_kind_from_str(char *kind_str)
>
> I don't think I like "str" so much. Don't we spell it as
> "pgstat_kind_from_name"?
name makes me think of NameData. What do you dislike about str? We seem to use
str in plenty places?
> 0019:
>
> +my $og_stats = $datadir . '/' . "pg_stat" . '/' . "pgstat.stat";
>
> It can be "$datadir/pg_stat/pgstat.stat" or $datadir . '/pgstat/pgstat.stat'.
> Isn't it simpler?
Yes, will change.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 09:11 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
2022-04-06 16:04 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
@ 2022-04-07 01:36 ` Kyotaro Horiguchi <[email protected]>
2022-04-07 01:58 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: Kyotaro Horiguchi @ 2022-04-07 01:36 UTC (permalink / raw)
To: [email protected]; +Cc: [email protected]; [email protected]; [email protected]; [email protected]; [email protected]
At Wed, 6 Apr 2022 09:04:09 -0700, Andres Freund <[email protected]> wrote in
> > + * Don't define an INVALID value so switch() statements can warn if some
> > + * cases aren't covered. But define the first member to 1 so that
> > + * uninitialized values can be detected more easily.
> >
> > FWIW, I like this.
>
> I think there's no switches left now, so it's not actually providing too much.
(Ouch!)
> > 0008:
> >
> > + xact_desc_stats(buf, "", parsed.nstats, parsed.stats);
> > + xact_desc_stats(buf, "commit ", parsed.nstats, parsed.stats);
> > + xact_desc_stats(buf, "abort ", parsed.nabortstats, parsed.abortstats);
> >
> > I'm not sure I like this, but I don't object to this..
>
> The string prefixes? Or the entire patch?
The string prefixes, since they are a limited set of fixed
strings. That being said, I don't think it's better to use an enum
instead, too. So I don't object to pass the strings here.
> > 0010:
> > (I didn't look this closer. The comments arised while looking other
> > patches.)
> >
> > +pgstat_kind_from_str(char *kind_str)
> >
> > I don't think I like "str" so much. Don't we spell it as
> > "pgstat_kind_from_name"?
>
> name makes me think of NameData. What do you dislike about str? We seem to use
> str in plenty places?
For clarity, I don't dislike it so much. So, I'm fine with the
current name.
I found that you meant a type by the "str". I thought it as an
instance (I'm not sure I can express my feeling correctly here..) and
the following functions were in my mind.
char *get_namespace/rel/collation/func_name(Oid someoid)
char *pgstat_slru_name(int slru_idx)
Another instance of the same direction is
ForkNumber forkname_to_number(const char *forkName)
regards.
--
Kyotaro Horiguchi
NTT Open Source Software Center
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 09:11 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
2022-04-06 16:04 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 01:36 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
@ 2022-04-07 01:58 ` Andres Freund <[email protected]>
2022-04-07 08:00 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: Andres Freund @ 2022-04-07 01:58 UTC (permalink / raw)
To: Kyotaro Horiguchi <[email protected]>; +Cc: [email protected]; [email protected]; [email protected]; [email protected]; [email protected]
Hi,
On 2022-04-07 10:36:30 +0900, Kyotaro Horiguchi wrote:
> At Wed, 6 Apr 2022 09:04:09 -0700, Andres Freund <[email protected]> wrote in
> > > + * Don't define an INVALID value so switch() statements can warn if some
> > > + * cases aren't covered. But define the first member to 1 so that
> > > + * uninitialized values can be detected more easily.
> > >
> > > FWIW, I like this.
> >
> > I think there's no switches left now, so it's not actually providing too much.
>
> (Ouch!)
I think it's great that there's no switches left - means we're pretty close to
pgstat being runtime extensible...
> > > 0010:
> > > (I didn't look this closer. The comments arised while looking other
> > > patches.)
> > >
> > > +pgstat_kind_from_str(char *kind_str)
> > >
> > > I don't think I like "str" so much. Don't we spell it as
> > > "pgstat_kind_from_name"?
> >
> > name makes me think of NameData. What do you dislike about str? We seem to use
> > str in plenty places?
>
> For clarity, I don't dislike it so much. So, I'm fine with the
> current name.
>
> I found that you meant a type by the "str". I thought it as an
> instance (I'm not sure I can express my feeling correctly here..) and
> the following functions were in my mind.
>
> char *get_namespace/rel/collation/func_name(Oid someoid)
> char *pgstat_slru_name(int slru_idx)
>
> Another instance of the same direction is
>
> ForkNumber forkname_to_number(const char *forkName)
It's now pgstat_get_kind_from_str().
It was harder to see earlier (I certainly didn't really see it) - because
there were so many "violations" - but most of pgstat is
pgstat_<verb>_<subject>() or just <verb>_<subject>. I'd already moved most of
the patch series over to that (maybe in v68 or so). Now I also did that with
the internal functions.
There's a few functions breaking that pattern, partially because I added them
:(, but since they're not touched in these patches I've not renamed them. But
it's probably worth doing so tomorrow.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 09:11 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
2022-04-06 16:04 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 01:36 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
2022-04-07 01:58 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
@ 2022-04-07 08:00 ` Kyotaro Horiguchi <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Kyotaro Horiguchi @ 2022-04-07 08:00 UTC (permalink / raw)
To: [email protected]; +Cc: [email protected]; [email protected]; [email protected]; [email protected]; [email protected]
At Wed, 6 Apr 2022 18:58:52 -0700, Andres Freund <[email protected]> wrote in
> Hi,
>
> On 2022-04-07 10:36:30 +0900, Kyotaro Horiguchi wrote:
> > At Wed, 6 Apr 2022 09:04:09 -0700, Andres Freund <[email protected]> wrote in
> > > I think there's no switches left now, so it's not actually providing too much.
> >
> > (Ouch!)
>
> I think it's great that there's no switches left - means we're pretty close to
> pgstat being runtime extensible...
Yes. I agree.
> It's now pgstat_get_kind_from_str().
I'm fine with it.
> It was harder to see earlier (I certainly didn't really see it) - because
> there were so many "violations" - but most of pgstat is
> pgstat_<verb>_<subject>() or just <verb>_<subject>. I'd already moved most of
> the patch series over to that (maybe in v68 or so). Now I also did that with
> the internal functions.
>
> There's a few functions breaking that pattern, partially because I added them
> :(, but since they're not touched in these patches I've not renamed them. But
> it's probably worth doing so tomorrow.
Thab being said, it gets far cleaner. Thanks!
regards.
--
Kyotaro Horiguchi
NTT Open Source Software Center
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
@ 2022-04-06 09:24 ` John Naylor <[email protected]>
2022-04-06 16:20 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
8 siblings, 1 reply; 87+ messages in thread
From: John Naylor @ 2022-04-06 09:24 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>
On Wed, Apr 6, 2022 at 10:00 AM Andres Freund <[email protected]> wrote:
> - while working on the above point, I noticed that hash_bytes() showed up
> noticeably in profiles, so I replaced it with a fixed-width function
I'm curious about this -- could you direct me to which patch introduces this?
--
John Naylor
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 09:24 ` Re: shared-memory based stats collector - v70 John Naylor <[email protected]>
@ 2022-04-06 16:20 ` Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2022-04-06 16:20 UTC (permalink / raw)
To: John Naylor <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>
Hi,
On 2022-04-06 16:24:28 +0700, John Naylor wrote:
> On Wed, Apr 6, 2022 at 10:00 AM Andres Freund <[email protected]> wrote:
> > - while working on the above point, I noticed that hash_bytes() showed up
> > noticeably in profiles, so I replaced it with a fixed-width function
>
> I'm curious about this -- could you direct me to which patch introduces this?
Commit 0010, search for pgstat_hash_key_hash. For simplicity I'm including it
here inline:
/* helpers for dshash / simplehash hashtables */
static inline int
pgstat_hash_key_cmp(const void *a, const void *b, size_t size, void *arg)
{
AssertArg(size == sizeof(PgStat_HashKey) && arg == NULL);
return memcmp(a, b, sizeof(PgStat_HashKey));
}
static inline uint32
pgstat_hash_key_hash(const void *d, size_t size, void *arg)
{
const PgStat_HashKey *key = (PgStat_HashKey *)d;
uint32 hash;
AssertArg(size == sizeof(PgStat_HashKey) && arg == NULL);
hash = murmurhash32(key->kind);
hash = hash_combine(hash, murmurhash32(key->dboid));
hash = hash_combine(hash, murmurhash32(key->objoid));
return hash;
}
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
@ 2022-04-06 11:31 ` Alvaro Herrera <[email protected]>
2022-04-06 16:17 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
8 siblings, 1 reply; 87+ messages in thread
From: Alvaro Herrera @ 2022-04-06 11:31 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; [email protected]; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; David G. Johnston <[email protected]>; [email protected]
Just skimming a bit here ...
On 2022-Apr-05, Andres Freund wrote:
> From 0532b869033595202d5797b148f22c61e4eb4969 Mon Sep 17 00:00:00 2001
> From: Andres Freund <[email protected]>
> Date: Mon, 4 Apr 2022 16:53:16 -0700
> Subject: [PATCH v70 10/27] pgstat: store statistics in shared memory.
> + <entry><literal>PgStatsData</literal></entry>
> + <entry>Waiting fo shared memory stats data access</entry>
> + </row>
Typo "fo" -> "for"
> @@ -5302,7 +5317,9 @@ StartupXLOG(void)
> performedWalRecovery = true;
> }
> else
> + {
> performedWalRecovery = false;
> + }
Why? :-)
Why give pgstat_get_entry_ref the responsibility of initializing
created_entry to false? The vast majority of callers don't care about
that flag; it seems easier/cleaner to set it to false in
pgstat_init_function_usage (the only caller that cares that I could
find) before calling pgstat_prep_pending_entry.
(I suggest pgstat_prep_pending_entry should have a comment line stating
"*created_entry, if not NULL, is set true if the entry required to be
created.", same as pgstat_get_entry_ref.)
--
Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
"How amazing is that? I call it a night and come back to find that a bug has
been identified and patched while I sleep." (Robert Davidson)
http://archives.postgresql.org/pgsql-sql/2006-03/msg00378.php
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 11:31 ` Re: shared-memory based stats collector - v70 Alvaro Herrera <[email protected]>
@ 2022-04-06 16:17 ` Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2022-04-06 16:17 UTC (permalink / raw)
To: Alvaro Herrera <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; [email protected]; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; David G. Johnston <[email protected]>; [email protected]
Hi,
On 2022-04-06 13:31:31 +0200, Alvaro Herrera wrote:
> Just skimming a bit here ...
Thanks!
> On 2022-Apr-05, Andres Freund wrote:
>
> > From 0532b869033595202d5797b148f22c61e4eb4969 Mon Sep 17 00:00:00 2001
> > From: Andres Freund <[email protected]>
> > Date: Mon, 4 Apr 2022 16:53:16 -0700
> > Subject: [PATCH v70 10/27] pgstat: store statistics in shared memory.
>
> > + <entry><literal>PgStatsData</literal></entry>
> > + <entry>Waiting fo shared memory stats data access</entry>
> > + </row>
>
> Typo "fo" -> "for"
Oh, oops. I had fixed that in the wrong patch.
> > @@ -5302,7 +5317,9 @@ StartupXLOG(void)
> > performedWalRecovery = true;
> > }
> > else
> > + {
> > performedWalRecovery = false;
> > + }
>
> Why? :-)
Damage from merging two commits yesterday. I'd left open where exactly we'd
reset stats, with the "main commit" implementing the current behaviour more
closely, and then a followup commit implementing something a bit
better. Nobody seemed to argue for keeping the behaviour 1:1, so I merged
them. Without removing the parens again :)
> Why give pgstat_get_entry_ref the responsibility of initializing
> created_entry to false? The vast majority of callers don't care about
> that flag; it seems easier/cleaner to set it to false in
> pgstat_init_function_usage (the only caller that cares that I could
> find) before calling pgstat_prep_pending_entry.
It's annoying to have to initialize it, I agree. But I think it's bugprone for
the caller to know that it has to be pre-initialized to false.
> (I suggest pgstat_prep_pending_entry should have a comment line stating
> "*created_entry, if not NULL, is set true if the entry required to be
> created.", same as pgstat_get_entry_ref.)
Added something along those lines.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
@ 2022-04-06 19:14 ` Lukas Fittl <[email protected]>
2022-04-06 19:27 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
8 siblings, 1 reply; 87+ messages in thread
From: Lukas Fittl @ 2022-04-06 19:14 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; [email protected]; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; David G. Johnston <[email protected]>; [email protected]
On Tue, Apr 5, 2022 at 8:00 PM Andres Freund <[email protected]> wrote:
> Here comes v70:
>
Some small nitpicks on the docs:
> From 13090823fc4c7fb94512110fb4d1b3e86fb312db Mon Sep 17 00:00:00 2001
> From: Andres Freund <[email protected]>
> Date: Sat, 2 Apr 2022 19:38:01 -0700
> Subject: [PATCH v70 14/27] pgstat: update docs.
> ...
> diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
> - These parameters control server-wide statistics collection
features.
> - When statistics collection is enabled, the data that is produced
can be
> + These parameters control server-wide cumulative statistics system.
> + When enabled, the data that is collected can be
Missing "the" ("These parameters control the server-wide cumulative
statistics system").
> diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
> + any of the accumulated statistics, acessed values are cached until
the end
"acessed" => "accessed"
> + <varname>stats_fetch_consistency</varname> can be set
> + <literal>snapshot</literal>, at the price of increased memory usage
for
Missing "to" ("can be set to <literal>snapshot</literal>")
> + caching not-needed statistics data. Conversely, if it's known that
statistics
Double space between "data." and "Conversely" (not sure if that matters)
> + current transaction's statistics snapshot or cached values (if any).
The
Double space between "(if any)." and "The" (not sure if that matters)
> + next use of statistical information will cause a new snapshot to be
built
> + or accessed statistics to be cached.
I believe this should be an "and", not an "or". (next access builds both a
new snapshot and caches accessed statistics)
Thanks,
Lukas
--
Lukas Fittl
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 19:14 ` Re: shared-memory based stats collector - v70 Lukas Fittl <[email protected]>
@ 2022-04-06 19:27 ` Andres Freund <[email protected]>
2022-04-06 19:34 ` Re: shared-memory based stats collector - v70 Justin Pryzby <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: Andres Freund @ 2022-04-06 19:27 UTC (permalink / raw)
To: Lukas Fittl <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; [email protected]; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; David G. Johnston <[email protected]>; [email protected]
Hi,
On 2022-04-06 12:14:35 -0700, Lukas Fittl wrote:
> On Tue, Apr 5, 2022 at 8:00 PM Andres Freund <[email protected]> wrote:
>
> > Here comes v70:
> >
>
> Some small nitpicks on the docs:
Thanks!
> > From 13090823fc4c7fb94512110fb4d1b3e86fb312db Mon Sep 17 00:00:00 2001
> > From: Andres Freund <[email protected]>
> > Date: Sat, 2 Apr 2022 19:38:01 -0700
> > Subject: [PATCH v70 14/27] pgstat: update docs.
> > ...
> > diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
> > - These parameters control server-wide statistics collection
> features.
> > - When statistics collection is enabled, the data that is produced
> can be
> > + These parameters control server-wide cumulative statistics system.
> > + When enabled, the data that is collected can be
>
> Missing "the" ("These parameters control the server-wide cumulative
> statistics system").
> > diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
> > + any of the accumulated statistics, acessed values are cached until
> the end
>
> "acessed" => "accessed"
> > + <varname>stats_fetch_consistency</varname> can be set
> > + <literal>snapshot</literal>, at the price of increased memory usage
> for
>
> Missing "to" ("can be set to <literal>snapshot</literal>")
Fixed.
> > + caching not-needed statistics data. Conversely, if it's known that
> statistics
>
> Double space between "data." and "Conversely" (not sure if that matters)
> > + current transaction's statistics snapshot or cached values (if any).
> The
>
> Double space between "(if any)." and "The" (not sure if that matters)
That's done pretty widely in the docs and comments.
> > + next use of statistical information will cause a new snapshot to be
> built
> > + or accessed statistics to be cached.
>
> I believe this should be an "and", not an "or". (next access builds both a
> new snapshot and caches accessed statistics)
I *think* or is correct? The new snapshot is when stats_fetch_consistency =
snapshot, the cached is when stats_fetch_consistency = cache. Not sure how to
make that clearer without making it a lot longer. Suggestions?
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 19:14 ` Re: shared-memory based stats collector - v70 Lukas Fittl <[email protected]>
2022-04-06 19:27 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
@ 2022-04-06 19:34 ` Justin Pryzby <[email protected]>
2022-04-06 19:37 ` Re: shared-memory based stats collector - v70 Lukas Fittl <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: Justin Pryzby @ 2022-04-06 19:34 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Lukas Fittl <[email protected]>; Kyotaro Horiguchi <[email protected]>; [email protected]; Thomas Munro <[email protected]>; David G. Johnston <[email protected]>; [email protected]
On Wed, Apr 06, 2022 at 12:27:34PM -0700, Andres Freund wrote:
> > > + next use of statistical information will cause a new snapshot to be built
> > > + or accessed statistics to be cached.
> >
> > I believe this should be an "and", not an "or". (next access builds both a
> > new snapshot and caches accessed statistics)
>
> I *think* or is correct? The new snapshot is when stats_fetch_consistency =
> snapshot, the cached is when stats_fetch_consistency = cache. Not sure how to
> make that clearer without making it a lot longer. Suggestions?
I think it's correct. Maybe it's clearer to say:
+ next use of statistical information will (when in snapshot mode) cause a new snapshot to be built
+ or (when in cache mode) accessed statistics to be cached.
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 19:14 ` Re: shared-memory based stats collector - v70 Lukas Fittl <[email protected]>
2022-04-06 19:27 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 19:34 ` Re: shared-memory based stats collector - v70 Justin Pryzby <[email protected]>
@ 2022-04-06 19:37 ` Lukas Fittl <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Lukas Fittl @ 2022-04-06 19:37 UTC (permalink / raw)
To: Justin Pryzby <[email protected]>; +Cc: Andres Freund <[email protected]>; David G. Johnston <[email protected]>; Kyotaro Horiguchi <[email protected]>; Thomas Munro <[email protected]>; [email protected]; [email protected]
On Wed, Apr 6, 2022 at 12:34 PM Justin Pryzby <[email protected]> wrote:
> On Wed, Apr 06, 2022 at 12:27:34PM -0700, Andres Freund wrote:
> > > > + next use of statistical information will cause a new snapshot to
> be built
> > > > + or accessed statistics to be cached.
> > >
> > > I believe this should be an "and", not an "or". (next access builds
> both a
> > > new snapshot and caches accessed statistics)
> >
> > I *think* or is correct? The new snapshot is when
> stats_fetch_consistency =
> > snapshot, the cached is when stats_fetch_consistency = cache. Not sure
> how to
> > make that clearer without making it a lot longer. Suggestions?
>
> I think it's correct. Maybe it's clearer to say:
>
> + next use of statistical information will (when in snapshot mode) cause
> a new snapshot to be built
> + or (when in cache mode) accessed statistics to be cached.
>
Ah, yes, that does clarify what was meant.
+1 to Justin's edit, or something like it.
Thanks,
Lukas
--
Lukas Fittl
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
@ 2022-04-06 22:12 ` Andres Freund <[email protected]>
2022-04-06 22:32 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
8 siblings, 1 reply; 87+ messages in thread
From: Andres Freund @ 2022-04-06 22:12 UTC (permalink / raw)
To: Kyotaro Horiguchi <[email protected]>; +Cc: [email protected]; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; David G. Johnston <[email protected]>; [email protected]
Hi,
On 2022-04-05 20:00:08 -0700, Andres Freund wrote:
> It'll be a few hours to get to the main commit - but except for 0001 it
> doesn't make sense to push without intending to push later changes too. I
> might squash a few commits togther.
I just noticed an existing incoherency that I'm wondering about fixing as part
of 0007 "pgstat: prepare APIs used by pgstatfuncs for shared memory stats."
The SQL functions to reset function and relation stats
pg_stat_reset_single_table_counters() and
pg_stat_reset_single_function_counters() respectively both make use of
pgstat_reset_single_counter().
Note that the SQL function uses plural "counters" (which makes sense, it
resets all counters for that object), whereas the C function they call to
perform the the reset uses singular.
Similarly pg_stat_reset_slru(), pg_stat_reset_replication_slot(),
pg_stat_reset_subscription_stats() SQL function use
pgstat_reset_subscription_counter(), pgstat_reset_replslot_counter() and
pgstat_reset_subscription_counter() to reset either the stats for one or all
SLRUs/slots.
This is relevant for the commit mentioned above because it separates the
functions to reset the stats for one slru / slot / sub from the function to
reset all slrus / slots / subs. Going with the existing naming I'd just named
them pgstat_reset_*_counters(). But that doesn't really make sense.
If it were just existing code I'd just not touch this for now. But because the
patch introduces further functions, I'd rather not introducing more weird
function names.
I'd go for
pgstat_reset_slru_counter() -> pgstat_reset_slru()
pgstat_reset_subscription_counter() -> pgstat_reset_subscription()
pgstat_reset_subscription_counters() -> pgstat_reset_all_subscriptions()
pgstat_reset_replslot_counter() -> pgstat_reset_replslot()
pgstat_reset_replslot_counters() -> pgstat_reset_all_replslots()
We could leave out the _all_ and just use plural too, but I think it's a bit
nicer with _all_ in there.
Not quite sure what to do with pgstat_reset_single_counter(). I'd either go
for the minimal pgstat_reset_single_counters() or pgstat_reset_one()?
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 22:12 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
@ 2022-04-06 22:32 ` David G. Johnston <[email protected]>
2022-04-06 23:12 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: David G. Johnston @ 2022-04-06 22:32 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; [email protected] <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; [email protected] <[email protected]>
On Wednesday, April 6, 2022, Andres Freund <[email protected]> wrote:
>
>
> I'd go for
> pgstat_reset_slru_counter() -> pgstat_reset_slru()
> pgstat_reset_subscription_counter() -> pgstat_reset_subscription()
> pgstat_reset_subscription_counters() -> pgstat_reset_all_subscriptions()
> pgstat_reset_replslot_counter() -> pgstat_reset_replslot()
> pgstat_reset_replslot_counters() -> pgstat_reset_all_replslots()
I like having the SQL function paired with a matching implementation in
this scheme.
>
> We could leave out the _all_ and just use plural too, but I think it's a
> bit
> nicer with _all_ in there.
+1 to _all_
>
> Not quite sure what to do with pgstat_reset_single_counter(). I'd either go
> for the minimal pgstat_reset_single_counters() or pgstat_reset_one()?
>
Why not add both pgstat_resert_function() and pgstat_reset_table() (to keep
the pairing) and they can call the renamed pgstat_reset_function_or_table()
internally (since the function indeed handle both paths and we’ve yet to
come up with a label to use instead of “function and table stats”)?
These are private functions right?
David J.
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 22:12 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 22:32 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
@ 2022-04-06 23:12 ` Andres Freund <[email protected]>
2022-04-07 00:01 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: Andres Freund @ 2022-04-06 23:12 UTC (permalink / raw)
To: David G. Johnston <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; [email protected] <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; [email protected] <[email protected]>
Hi,
On 2022-04-06 15:32:39 -0700, David G. Johnston wrote:
> On Wednesday, April 6, 2022, Andres Freund <[email protected]> wrote:
>
> >
> >
> > I'd go for
> > pgstat_reset_slru_counter() -> pgstat_reset_slru()
> > pgstat_reset_subscription_counter() -> pgstat_reset_subscription()
> > pgstat_reset_subscription_counters() -> pgstat_reset_all_subscriptions()
> > pgstat_reset_replslot_counter() -> pgstat_reset_replslot()
> > pgstat_reset_replslot_counters() -> pgstat_reset_all_replslots()
>
>
> I like having the SQL function paired with a matching implementation in
> this scheme.
It would have gotten things closer than it was before imo. We can't just
rename the SQL functions, they're obviously exposed API.
I'd like to remove the NULL -> all behaviour, but that should be discussed
separately.
I've hacked up the above, but after doing so I think I found a cleaner
approach:
I've introduced:
pgstat_reset_of_kind(PgStat_Kind kind) which works for both fixed and variable
numbered stats. That allows to remove pgstat_reset_subscription_counters(),
pgstat_reset_replslot_counters(), pgstat_reset_shared_counters().
pgstat_reset(PgStat_Kind kind, Oid dboid, Oid objoid), which removes the need
for pgstat_reset_subscription_counter(),
pgstat_reset_single_counter(). pgstat_reset_replslot() is still needed, to do
the name -> index lookup.
That imo makes a lot more sense than requiring each variable-amount kind to
have wrapper functions.
> > Not quite sure what to do with pgstat_reset_single_counter(). I'd either go
> > for the minimal pgstat_reset_single_counters() or pgstat_reset_one()?
> >
>
> Why not add both pgstat_resert_function() and pgstat_reset_table() (to keep
> the pairing) and they can call the renamed pgstat_reset_function_or_table()
> internally (since the function indeed handle both paths and we’ve yet to
> come up with a label to use instead of “function and table stats”)?
>
> These are private functions right?
What does "private" mean for you? They're exposed via pgstat.h not
pgstat_internal.h. But not to SQL.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 22:12 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 22:32 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
2022-04-06 23:12 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
@ 2022-04-07 00:01 ` David G. Johnston <[email protected]>
2022-04-07 00:48 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: David G. Johnston @ 2022-04-07 00:01 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; [email protected] <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; [email protected] <[email protected]>
On Wed, Apr 6, 2022 at 4:12 PM Andres Freund <[email protected]> wrote:
>
> On 2022-04-06 15:32:39 -0700, David G. Johnston wrote:
> > On Wednesday, April 6, 2022, Andres Freund <[email protected]> wrote:
> >
> >
> > I like having the SQL function paired with a matching implementation in
> > this scheme.
>
> It would have gotten things closer than it was before imo. We can't just
> rename the SQL functions, they're obviously exposed API.
>
Right, I meant the naming scheme proposed was acceptable. Not that I
wanted to change the SQL functions too.
I've hacked up the above, but after doing so I think I found a cleaner
>
approach:
>
> I've introduced:
>
> pgstat_reset_of_kind(PgStat_Kind kind) which works for both fixed and
> variable
> numbered stats. That allows to remove pgstat_reset_subscription_counters(),
> pgstat_reset_replslot_counters(), pgstat_reset_shared_counters().
>
> pgstat_reset(PgStat_Kind kind, Oid dboid, Oid objoid), which removes the
> need
> for pgstat_reset_subscription_counter(),
> pgstat_reset_single_counter().
> pgstat_reset_replslot() is still needed, to do
> the name -> index lookup.
>
> That imo makes a lot more sense than requiring each variable-amount kind to
> have wrapper functions.
>
>
I can see benefits of both, or even possibly combining them. Absent being
able to point to some other part of the system and saying "it is done this
way there, let's do the same here" I think the details will inform the
decision. The fact there is just the one outlier here suggests that this
is indeed the better option.
> What does "private" mean for you? They're exposed via pgstat.h not
> pgstat_internal.h. But not to SQL.
>
>
I was thinking specifically of the freedom to rename and not break
extensions. Namely, are these truly implementation details or something
that, while unlikely to be used by extensions, still constitute an exposed
API? It was mainly a passing thought, I'm not looking for a crash-course
in how all that works right now.
David J.
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 22:12 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 22:32 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
2022-04-06 23:12 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 00:01 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
@ 2022-04-07 00:48 ` Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2022-04-07 00:48 UTC (permalink / raw)
To: David G. Johnston <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; [email protected] <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; [email protected] <[email protected]>
Hi,
On 2022-04-06 17:01:17 -0700, David G. Johnston wrote:
> On Wed, Apr 6, 2022 at 4:12 PM Andres Freund <[email protected]> wrote:
>
> The fact there is just the one outlier here suggests that this is indeed the
> better option.
FWIW, the outlier also uses pgstat_reset(), just with a small wrapper doing
the translation from slot name to slot index.
> > What does "private" mean for you? They're exposed via pgstat.h not
> > pgstat_internal.h. But not to SQL.
> I was thinking specifically of the freedom to rename and not break
> extensions. Namely, are these truly implementation details or something
> that, while unlikely to be used by extensions, still constitute an exposed
> API? It was mainly a passing thought, I'm not looking for a crash-course
> in how all that works right now.
I doubt there are extension using these functions - and they'd have been
broken the way things were in v70, because the signature already had changed.
Generally, between major releases, we don't worry too much about changing C
APIs. Of course we try to avoid unnecessarily breaking things, particularly
when it's going to cause widespread breakage.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
@ 2022-04-07 07:28 ` Andres Freund <[email protected]>
2022-04-07 08:02 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
2022-04-07 23:37 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
8 siblings, 2 replies; 87+ messages in thread
From: Andres Freund @ 2022-04-07 07:28 UTC (permalink / raw)
To: Kyotaro Horiguchi <[email protected]>; +Cc: [email protected]; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; David G. Johnston <[email protected]>; [email protected]
Hi,
On 2022-04-05 20:00:08 -0700, Andres Freund wrote:
> It'll be a few hours to get to the main commit - but except for 0001 it
> doesn't make sense to push without intending to push later changes too. I
> might squash a few commits togther.
I've gotten through the main commits (and then a fix for the apparently
inevitable bug that's immediately highlighted by the buildfarm), and the first
test. I'll call it a night now, and work on the other tests & docs tomorrow.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 07:28 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
@ 2022-04-07 08:02 ` Kyotaro Horiguchi <[email protected]>
1 sibling, 0 replies; 87+ messages in thread
From: Kyotaro Horiguchi @ 2022-04-07 08:02 UTC (permalink / raw)
To: [email protected]; +Cc: [email protected]; [email protected]; [email protected]; [email protected]; [email protected]
At Thu, 7 Apr 2022 00:28:45 -0700, Andres Freund <[email protected]> wrote in
> Hi,
>
> On 2022-04-05 20:00:08 -0700, Andres Freund wrote:
> > It'll be a few hours to get to the main commit - but except for 0001 it
> > doesn't make sense to push without intending to push later changes too. I
> > might squash a few commits togther.
>
> I've gotten through the main commits (and then a fix for the apparently
> inevitable bug that's immediately highlighted by the buildfarm), and the first
> test. I'll call it a night now, and work on the other tests & docs tomorrow.
Thank you very much for the great effort on this to make it get in!
regards.
--
Kyotaro Horiguchi
NTT Open Source Software Center
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 07:28 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
@ 2022-04-07 23:37 ` Andres Freund <[email protected]>
2022-04-08 02:10 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
2022-04-08 04:38 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
1 sibling, 2 replies; 87+ messages in thread
From: Andres Freund @ 2022-04-07 23:37 UTC (permalink / raw)
To: Kyotaro Horiguchi <[email protected]>; +Cc: [email protected]; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; David G. Johnston <[email protected]>; [email protected]
Hi,
On 2022-04-07 00:28:45 -0700, Andres Freund wrote:
> I've gotten through the main commits (and then a fix for the apparently
> inevitable bug that's immediately highlighted by the buildfarm), and the first
> test. I'll call it a night now, and work on the other tests & docs tomorrow.
I've gotten through the tests now. There's one known, not yet addressed, issue
with the stats isolation test, see [1].
Working on the docs. Found a few things worth raising:
1)
Existing text:
When the server shuts down cleanly, a permanent copy of the statistics
data is stored in the <filename>pg_stat</filename> subdirectory, so that
statistics can be retained across server restarts. When recovery is
performed at server start (e.g., after immediate shutdown, server crash,
and point-in-time recovery), all statistics counters are reset.
The existing docs patch hadn't updated yet. My current edit is
When the server shuts down cleanly, a permanent copy of the statistics
data is stored in the <filename>pg_stat</filename> subdirectory, so that
statistics can be retained across server restarts. When crash recovery is
performed at server start (e.g., after immediate shutdown, server crash,
and point-in-time recovery, but not when starting a standby that was shut
down normally), all statistics counters are reset.
but I'm not sure the parenthetical is easy enough to understand?
2)
The edit is not a problem, but it's hard to understand what the existing
paragraph actually means?
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index 3247e056663..8bfb584b752 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -2222,17 +2222,17 @@ HINT: You can then restart the server after making the necessary configuration
...
<para>
- The statistics collector is active during recovery. All scans, reads, blocks,
+ The cumulative statistics system is active during recovery. All scans, reads, blocks,
index usage, etc., will be recorded normally on the standby. Replayed
actions will not duplicate their effects on primary, so replaying an
insert will not increment the Inserts column of pg_stat_user_tables.
The stats file is deleted at the start of recovery, so stats from primary
and standby will differ; this is considered a feature, not a bug.
</para>
<para>
I'll just commit the necessary bit, but we really ought to rephrase this.
Greetings,
Andres Freund
[1] https://www.postgresql.org/message-id/20220407165709.jgdkrzqlkcwue6ko%40alap3.anarazel.de
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 07:28 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 23:37 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
@ 2022-04-08 02:10 ` Kyotaro Horiguchi <[email protected]>
2022-04-08 03:51 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
2022-04-08 03:59 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
1 sibling, 2 replies; 87+ messages in thread
From: Kyotaro Horiguchi @ 2022-04-08 02:10 UTC (permalink / raw)
To: [email protected]; +Cc: [email protected]; [email protected]; [email protected]; [email protected]; [email protected]
At Thu, 7 Apr 2022 16:37:51 -0700, Andres Freund <[email protected]> wrote in
> Hi,
>
> On 2022-04-07 00:28:45 -0700, Andres Freund wrote:
> > I've gotten through the main commits (and then a fix for the apparently
> > inevitable bug that's immediately highlighted by the buildfarm), and the first
> > test. I'll call it a night now, and work on the other tests & docs tomorrow.
>
> I've gotten through the tests now. There's one known, not yet addressed, issue
> with the stats isolation test, see [1].
>
>
> Working on the docs. Found a few things worth raising:
>
> 1)
> Existing text:
> When the server shuts down cleanly, a permanent copy of the statistics
> data is stored in the <filename>pg_stat</filename> subdirectory, so that
> statistics can be retained across server restarts. When recovery is
> performed at server start (e.g., after immediate shutdown, server crash,
> and point-in-time recovery), all statistics counters are reset.
>
> The existing docs patch hadn't updated yet. My current edit is
>
> When the server shuts down cleanly, a permanent copy of the statistics
> data is stored in the <filename>pg_stat</filename> subdirectory, so that
> statistics can be retained across server restarts. When crash recovery is
> performed at server start (e.g., after immediate shutdown, server crash,
> and point-in-time recovery, but not when starting a standby that was shut
> down normally), all statistics counters are reset.
>
> but I'm not sure the parenthetical is easy enough to understand?
I can read it. But I'm not sure that the difference is obvious for
average users between "starting a standby from a basebackup" and
"starting a standby after a normal shutdown"..
Other than that, it might be easier to read if the additional part
were moved out to the end of the paragraph, prefixing with "Note:
". For example,
...
statistics can be retained across server restarts. When crash recovery is
performed at server start (e.g., after immediate shutdown, server crash,
and point-in-time recovery), all statistics counters are reset. Note that
crash recovery is not performed when starting a standby that was shut
down normally then all counters are retained.
> 2)
> The edit is not a problem, but it's hard to understand what the existing
> paragraph actually means?
>
> diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
> index 3247e056663..8bfb584b752 100644
> --- a/doc/src/sgml/high-availability.sgml
> +++ b/doc/src/sgml/high-availability.sgml
> @@ -2222,17 +2222,17 @@ HINT: You can then restart the server after making the necessary configuration
> ...
> <para>
> - The statistics collector is active during recovery. All scans, reads, blocks,
> + The cumulative statistics system is active during recovery. All scans, reads, blocks,
> index usage, etc., will be recorded normally on the standby. Replayed
> actions will not duplicate their effects on primary, so replaying an
> insert will not increment the Inserts column of pg_stat_user_tables.
> The stats file is deleted at the start of recovery, so stats from primary
> and standby will differ; this is considered a feature, not a bug.
> </para>
>
> <para>
Agreed partially. It's too detailed. It might not need to mention WAL
replay.
> I'll just commit the necessary bit, but we really ought to rephrase this.
>
>
>
>
> Greetings,
>
> Andres Freund
>
> [1] https://www.postgresql.org/message-id/20220407165709.jgdkrzqlkcwue6ko%40alap3.anarazel.de
--
Kyotaro Horiguchi
NTT Open Source Software Center
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 07:28 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 23:37 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-08 02:10 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
@ 2022-04-08 03:51 ` David G. Johnston <[email protected]>
2022-04-08 04:16 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
1 sibling, 1 reply; 87+ messages in thread
From: David G. Johnston @ 2022-04-08 03:51 UTC (permalink / raw)
To: Kyotaro Horiguchi <[email protected]>; +Cc: Andres Freund <[email protected]>; Melanie Plageman <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; PostgreSQL Hackers <[email protected]>
On Thu, Apr 7, 2022 at 7:10 PM Kyotaro Horiguchi <[email protected]>
wrote:
> At Thu, 7 Apr 2022 16:37:51 -0700, Andres Freund <[email protected]>
> wrote in
> > Hi,
> >
> > On 2022-04-07 00:28:45 -0700, Andres Freund wrote:
> > > I've gotten through the main commits (and then a fix for the apparently
> > > inevitable bug that's immediately highlighted by the buildfarm), and
> the first
> > > test. I'll call it a night now, and work on the other tests & docs
> tomorrow.
> >
> > I've gotten through the tests now. There's one known, not yet addressed,
> issue
> > with the stats isolation test, see [1].
> >
> >
> > Working on the docs. Found a few things worth raising:
> >
> > 1)
> > Existing text:
> > When the server shuts down cleanly, a permanent copy of the statistics
> > data is stored in the <filename>pg_stat</filename> subdirectory, so
> that
> > statistics can be retained across server restarts. When recovery is
> > performed at server start (e.g., after immediate shutdown, server
> crash,
> > and point-in-time recovery), all statistics counters are reset.
> >
> > The existing docs patch hadn't updated yet. My current edit is
> >
> > When the server shuts down cleanly, a permanent copy of the statistics
> > data is stored in the <filename>pg_stat</filename> subdirectory, so
> that
> > statistics can be retained across server restarts. When crash
> recovery is
> > performed at server start (e.g., after immediate shutdown, server
> crash,
> > and point-in-time recovery, but not when starting a standby that was
> shut
> > down normally), all statistics counters are reset.
> >
> > but I'm not sure the parenthetical is easy enough to understand?
>
> I can read it. But I'm not sure that the difference is obvious for
> average users between "starting a standby from a basebackup" and
> "starting a standby after a normal shutdown"..
>
> Other than that, it might be easier to read if the additional part
> were moved out to the end of the paragraph, prefixing with "Note:
> ". For example,
>
> ...
> statistics can be retained across server restarts. When crash recovery is
> performed at server start (e.g., after immediate shutdown, server crash,
> and point-in-time recovery), all statistics counters are reset. Note that
> crash recovery is not performed when starting a standby that was shut
> down normally then all counters are retained.
>
>
Maybe:
When the server shuts down cleanly a permanent copy of the statistics data
is stored in the <filename>pg_stat</filename> subdirectory so that
statistics can be retained across server restarts. However, if crash
recovery is performed (i.e., after immediate shutdown, server crash, or
point-in-time recovery), all statistics counters are reset. For any
standby server, the initial startup to get the cluster initialized is a
point-in-time crash recovery startup. For all subsequent startups it
behaves like any other server. For a hot standby server, statistics are
retained during a failover promotion.
I'm pretty sure i.e., is correct since those three situations are not
examples but rather the complete set.
Is crash recovery ever performed other than at server start? If so I
choose to remove the redundancy.
I feel like some of this detail about standby servers is/should be covered
elsewhere and we are at least missing a cross-reference chance even if we
leave the material coverage as-is.
> 2)
> > The edit is not a problem, but it's hard to understand what the existing
> > paragraph actually means?
> >
> > diff --git a/doc/src/sgml/high-availability.sgml
> b/doc/src/sgml/high-availability.sgml
> > index 3247e056663..8bfb584b752 100644
> > --- a/doc/src/sgml/high-availability.sgml
> > +++ b/doc/src/sgml/high-availability.sgml
> > @@ -2222,17 +2222,17 @@ HINT: You can then restart the server after
> making the necessary configuration
> > ...
> > <para>
> > - The statistics collector is active during recovery. All scans,
> reads, blocks,
> > + The cumulative statistics system is active during recovery. All
> scans, reads, blocks,
> > index usage, etc., will be recorded normally on the standby.
> Replayed
> > actions will not duplicate their effects on primary, so replaying an
> > insert will not increment the Inserts column of pg_stat_user_tables.
> > The stats file is deleted at the start of recovery, so stats from
> primary
> > and standby will differ; this is considered a feature, not a bug.
> > </para>
> >
> > <para>
>
> Agreed partially. It's too detailed. It might not need to mention WAL
> replay.
>
>
The insert example seems like a poor one...IIUC cumulative statistics are
not WAL logged and while in recovery INSERT is prohibited, so how would
replaying the insert in the WAL result in a duplicated effect on
pg_stat_user_tables.inserts?
I also have no idea what, in the fragment, "Replayed actions will not
duplicate their effects on primary...", what "on primary" is supposed to
mean.
I would like to write the following but I don't believe it is sufficiently
true:
"The cumulative statistics system records only the locally generated
activity of the cluster, including while in recovery. Activity happening
only due to the replay of WAL is not considered local."
But to apply the WAL we have to fetch blocks from the local filesystem and
write them back out. That is local activity happening due to the replay of
WAL which sounds like it is and should be reported ("All...blocks...", and
the example given being logical DDL oriented).
I cannot think of a better paragraph at the moment, the minimal change is
good, and the detail it contains presently seems like the right amount, if
indeed my interpretation of it is correct (i.e., the standby records
physical stats, not logical ones). It still has wording issues around "on
primary" and maybe a better example choice than a
disallowed-in-recovery-anyway insert.
David J.
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 07:28 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 23:37 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-08 02:10 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
2022-04-08 03:51 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
@ 2022-04-08 04:16 ` Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2022-04-08 04:16 UTC (permalink / raw)
To: David G. Johnston <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi,
On 2022-04-07 20:51:10 -0700, David G. Johnston wrote:
> On Thu, Apr 7, 2022 at 7:10 PM Kyotaro Horiguchi <[email protected]>
> wrote:
> > I can read it. But I'm not sure that the difference is obvious for
> > average users between "starting a standby from a basebackup" and
> > "starting a standby after a normal shutdown"..
> >
> > Other than that, it might be easier to read if the additional part
> > were moved out to the end of the paragraph, prefixing with "Note:
> > ". For example,
> >
> > ...
> > statistics can be retained across server restarts. When crash recovery is
> > performed at server start (e.g., after immediate shutdown, server crash,
> > and point-in-time recovery), all statistics counters are reset. Note that
> > crash recovery is not performed when starting a standby that was shut
> > down normally then all counters are retained.
> >
> >
> Maybe:
> When the server shuts down cleanly a permanent copy of the statistics data
> is stored in the <filename>pg_stat</filename> subdirectory so that
> statistics can be retained across server restarts. However, if crash
> recovery is performed (i.e., after immediate shutdown, server crash, or
> point-in-time recovery), all statistics counters are reset. For any
> standby server, the initial startup to get the cluster initialized is a
> point-in-time crash recovery startup. For all subsequent startups it
> behaves like any other server. For a hot standby server, statistics are
> retained during a failover promotion.
>
> I'm pretty sure i.e., is correct since those three situations are not
> examples but rather the complete set.
I don't think the "initial startup ..." bit is quite correct. A standby can be
created for a shut down server, and IIRC there's some differences in how PITR
is handled too.
> Is crash recovery ever performed other than at server start? If so I
> choose to remove the redundancy.
No.
> I feel like some of this detail about standby servers is/should be covered
> elsewhere and we are at least missing a cross-reference chance even if we
> leave the material coverage as-is.
I didn't find anything good to reference...
> > 2)
> > > The edit is not a problem, but it's hard to understand what the existing
> > > paragraph actually means?
> > >
> > > diff --git a/doc/src/sgml/high-availability.sgml
> > b/doc/src/sgml/high-availability.sgml
> > > index 3247e056663..8bfb584b752 100644
> > > --- a/doc/src/sgml/high-availability.sgml
> > > +++ b/doc/src/sgml/high-availability.sgml
> > > @@ -2222,17 +2222,17 @@ HINT: You can then restart the server after
> > making the necessary configuration
> > > ...
> > > <para>
> > > - The statistics collector is active during recovery. All scans,
> > reads, blocks,
> > > + The cumulative statistics system is active during recovery. All
> > scans, reads, blocks,
> > > index usage, etc., will be recorded normally on the standby.
> > Replayed
> > > actions will not duplicate their effects on primary, so replaying an
> > > insert will not increment the Inserts column of pg_stat_user_tables.
> > > The stats file is deleted at the start of recovery, so stats from
> > primary
> > > and standby will differ; this is considered a feature, not a bug.
> > > </para>
> > >
> > > <para>
> >
> > Agreed partially. It's too detailed. It might not need to mention WAL
> > replay.
> >
> >
> The insert example seems like a poor one...IIUC cumulative statistics are
> not WAL logged and while in recovery INSERT is prohibited, so how would
> replaying the insert in the WAL result in a duplicated effect on
> pg_stat_user_tables.inserts?
I agree, the sentence doesn't make much sense.
It doesn't really matter that stats aren't WAL logged, one could infer them
from the WAL at a decent level of accuracy. However, we can't actually
associate those actions to relations, we just know the relfilenode... And the
startup process can't read the catalog to figure the mapping out.
> I also have no idea what, in the fragment, "Replayed actions will not
> duplicate their effects on primary...", what "on primary" is supposed to
> mean.
I think it's trying to say "will not duplicate the effect on
pg_stat_user_tables they had on the primary".
> I would like to write the following but I don't believe it is sufficiently
> true:
>
> "The cumulative statistics system records only the locally generated
> activity of the cluster, including while in recovery. Activity happening
> only due to the replay of WAL is not considered local."
>
> But to apply the WAL we have to fetch blocks from the local filesystem and
> write them back out. That is local activity happening due to the replay of
> WAL which sounds like it is and should be reported ("All...blocks...", and
> the example given being logical DDL oriented).
That's not true today - the startup processes reads / writes aren't reflected
in pg_statio, pg_stat_database or whatnot.
> I cannot think of a better paragraph at the moment, the minimal change is
> good, and the detail it contains presently seems like the right amount, if
> indeed my interpretation of it is correct (i.e., the standby records
> physical stats, not logical ones). It still has wording issues around "on
> primary" and maybe a better example choice than a
> disallowed-in-recovery-anyway insert.
What do you think about my suggested paragraphs in
https://postgr.es/m/20220408035921.xlmjrv7wdmk3xm7k%40alap3.anarazel.de ?
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 07:28 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 23:37 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-08 02:10 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
@ 2022-04-08 03:59 ` Andres Freund <[email protected]>
2022-04-08 04:37 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-08 04:39 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
2022-04-08 04:44 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
1 sibling, 3 replies; 87+ messages in thread
From: Andres Freund @ 2022-04-08 03:59 UTC (permalink / raw)
To: Kyotaro Horiguchi <[email protected]>; +Cc: [email protected]; [email protected]; [email protected]; [email protected]; [email protected]
Hi,
On 2022-04-08 11:10:14 +0900, Kyotaro Horiguchi wrote:
> At Thu, 7 Apr 2022 16:37:51 -0700, Andres Freund <[email protected]> wrote in
> > Hi,
> >
> > On 2022-04-07 00:28:45 -0700, Andres Freund wrote:
> > > I've gotten through the main commits (and then a fix for the apparently
> > > inevitable bug that's immediately highlighted by the buildfarm), and the first
> > > test. I'll call it a night now, and work on the other tests & docs tomorrow.
> >
> > I've gotten through the tests now. There's one known, not yet addressed, issue
> > with the stats isolation test, see [1].
> >
> >
> > Working on the docs. Found a few things worth raising:
> >
> > 1)
> > Existing text:
> > When the server shuts down cleanly, a permanent copy of the statistics
> > data is stored in the <filename>pg_stat</filename> subdirectory, so that
> > statistics can be retained across server restarts. When recovery is
> > performed at server start (e.g., after immediate shutdown, server crash,
> > and point-in-time recovery), all statistics counters are reset.
> >
> > The existing docs patch hadn't updated yet. My current edit is
> >
> > When the server shuts down cleanly, a permanent copy of the statistics
> > data is stored in the <filename>pg_stat</filename> subdirectory, so that
> > statistics can be retained across server restarts. When crash recovery is
> > performed at server start (e.g., after immediate shutdown, server crash,
> > and point-in-time recovery, but not when starting a standby that was shut
> > down normally), all statistics counters are reset.
> >
> > but I'm not sure the parenthetical is easy enough to understand?
>
> I can read it. But I'm not sure that the difference is obvious for
> average users between "starting a standby from a basebackup" and
> "starting a standby after a normal shutdown"..
Yea, that's what I was concerned about. How about:
<para>
Cumulative statistics are collected in shared memory. Every
<productname>PostgreSQL</productname> process collects statistics locally
then updates the shared data at appropriate intervals. When a server,
including a physical replica, shuts down cleanly, a permanent copy of the
statistics data is stored in the <filename>pg_stat</filename> subdirectory,
so that statistics can be retained across server restarts. In contrast,
when starting from an unclean shutdown (e.g., after an immediate shutdown,
a server crash, starting from a base backup, and point-in-time recovery),
all statistics counters are reset.
</para>
> Other than that, it might be easier to read if the additional part
> were moved out to the end of the paragraph, prefixing with "Note:
> ". For example,
>
> ...
> statistics can be retained across server restarts. When crash recovery is
> performed at server start (e.g., after immediate shutdown, server crash,
> and point-in-time recovery), all statistics counters are reset. Note that
> crash recovery is not performed when starting a standby that was shut
> down normally then all counters are retained.
I think I like my version above a bit better?
> > 2)
> > The edit is not a problem, but it's hard to understand what the existing
> > paragraph actually means?
> >
> > diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
> > index 3247e056663..8bfb584b752 100644
> > --- a/doc/src/sgml/high-availability.sgml
> > +++ b/doc/src/sgml/high-availability.sgml
> > @@ -2222,17 +2222,17 @@ HINT: You can then restart the server after making the necessary configuration
> > ...
> > <para>
> > - The statistics collector is active during recovery. All scans, reads, blocks,
> > + The cumulative statistics system is active during recovery. All scans, reads, blocks,
> > index usage, etc., will be recorded normally on the standby. Replayed
> > actions will not duplicate their effects on primary, so replaying an
> > insert will not increment the Inserts column of pg_stat_user_tables.
> > The stats file is deleted at the start of recovery, so stats from primary
> > and standby will differ; this is considered a feature, not a bug.
> > </para>
> >
> > <para>
>
> Agreed partially. It's too detailed. It might not need to mention WAL
> replay.
My concern is more that it seems halfway nonsensical. "Replayed actions will
not duplicate their effects on primary" - I can guess what that means, but not
more. There's no "Inserts" column of pg_stat_user_tables.
<para>
The cumulative statistics system is active during recovery. All scans,
reads, blocks, index usage, etc., will be recorded normally on the
standby. However, WAL replay will not increment relation and database
specific counters. I.e. replay will not increment pg_stat_all_tables
columns (like n_tup_ins), nor will reads or writes performed by the
startup process be tracked in the pg_statio views, nor will associated
pg_stat_database columns be incremented.
</para>
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 07:28 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 23:37 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-08 02:10 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
2022-04-08 03:59 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
@ 2022-04-08 04:37 ` Andres Freund <[email protected]>
2 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2022-04-08 04:37 UTC (permalink / raw)
To: Kyotaro Horiguchi <[email protected]>; +Cc: [email protected]; [email protected]; [email protected]; [email protected]; [email protected]
Hi,
On 2022-04-07 20:59:21 -0700, Andres Freund wrote:
>
> <para>
> Cumulative statistics are collected in shared memory. Every
> <productname>PostgreSQL</productname> process collects statistics locally
> then updates the shared data at appropriate intervals. When a server,
> including a physical replica, shuts down cleanly, a permanent copy of the
> statistics data is stored in the <filename>pg_stat</filename> subdirectory,
> so that statistics can be retained across server restarts. In contrast,
> when starting from an unclean shutdown (e.g., after an immediate shutdown,
> a server crash, starting from a base backup, and point-in-time recovery),
> all statistics counters are reset.
> </para>
> ...
> <para>
> The cumulative statistics system is active during recovery. All scans,
> reads, blocks, index usage, etc., will be recorded normally on the
> standby. However, WAL replay will not increment relation and database
> specific counters. I.e. replay will not increment pg_stat_all_tables
> columns (like n_tup_ins), nor will reads or writes performed by the
> startup process be tracked in the pg_statio views, nor will associated
> pg_stat_database columns be incremented.
> </para>
I went with these for now. My guess is that there's further improvements in
them, and in surrounding areas...
With that, I'll close this CF entry. It's been a while.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 07:28 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 23:37 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-08 02:10 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
2022-04-08 03:59 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
@ 2022-04-08 04:39 ` David G. Johnston <[email protected]>
2022-04-09 19:06 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2 siblings, 1 reply; 87+ messages in thread
From: David G. Johnston @ 2022-04-08 04:39 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; PostgreSQL Hackers <[email protected]>
On Thu, Apr 7, 2022 at 8:59 PM Andres Freund <[email protected]> wrote:
>
> <para>
> Cumulative statistics are collected in shared memory. Every
> <productname>PostgreSQL</productname> process collects statistics
> locally
> then updates the shared data at appropriate intervals. When a server,
> including a physical replica, shuts down cleanly, a permanent copy of
> the
> statistics data is stored in the <filename>pg_stat</filename>
> subdirectory,
> so that statistics can be retained across server restarts. In contrast,
> when starting from an unclean shutdown (e.g., after an immediate
> shutdown,
> a server crash, starting from a base backup, and point-in-time
> recovery),
> all statistics counters are reset.
> </para>
>
I like this. My comment regarding using "i.e.," here stands though.
>
> <para>
> The cumulative statistics system is active during recovery. All scans,
> reads, blocks, index usage, etc., will be recorded normally on the
> standby. However, WAL replay will not increment relation and database
> specific counters. I.e. replay will not increment pg_stat_all_tables
> columns (like n_tup_ins), nor will reads or writes performed by the
> startup process be tracked in the pg_statio views, nor will associated
> pg_stat_database columns be incremented.
> </para>
>
>
I like this too. The second part with three nors is a bit rough. Maybe:
... specific counters. In particular, replay will not increment
pg_stat_database or pg_stat_all_tables columns, and the startup process
will not report reads and writes for the pg_statio views.
It would helpful to give at least one specific example of what is being
recorded normally, especially since we give three of what is not.
David J.
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 07:28 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 23:37 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-08 02:10 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
2022-04-08 03:59 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-08 04:39 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
@ 2022-04-09 19:06 ` Andres Freund <[email protected]>
2022-04-09 19:38 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
0 siblings, 1 reply; 87+ messages in thread
From: Andres Freund @ 2022-04-09 19:06 UTC (permalink / raw)
To: David G. Johnston <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi,
On 2022-04-07 21:39:55 -0700, David G. Johnston wrote:
> On Thu, Apr 7, 2022 at 8:59 PM Andres Freund <[email protected]> wrote:
>
> >
> > <para>
> > Cumulative statistics are collected in shared memory. Every
> > <productname>PostgreSQL</productname> process collects statistics
> > locally
> > then updates the shared data at appropriate intervals. When a server,
> > including a physical replica, shuts down cleanly, a permanent copy of
> > the
> > statistics data is stored in the <filename>pg_stat</filename>
> > subdirectory,
> > so that statistics can be retained across server restarts. In contrast,
> > when starting from an unclean shutdown (e.g., after an immediate
> > shutdown,
> > a server crash, starting from a base backup, and point-in-time
> > recovery),
> > all statistics counters are reset.
> > </para>
> >
>
> I like this. My comment regarding using "i.e.," here stands though.
Argh. I'd used in e.g., but not i.e..
> >
> > <para>
> > The cumulative statistics system is active during recovery. All scans,
> > reads, blocks, index usage, etc., will be recorded normally on the
> > standby. However, WAL replay will not increment relation and database
> > specific counters. I.e. replay will not increment pg_stat_all_tables
> > columns (like n_tup_ins), nor will reads or writes performed by the
> > startup process be tracked in the pg_statio views, nor will associated
> > pg_stat_database columns be incremented.
> > </para>
> >
> >
> I like this too. The second part with three nors is a bit rough. Maybe:
Agreed. I tried to come up with a smoother formulation, but didn't (perhaps
because I was a tad tired).
> ... specific counters. In particular, replay will not increment
> pg_stat_database or pg_stat_all_tables columns, and the startup process
> will not report reads and writes for the pg_statio views.
>
> It would helpful to give at least one specific example of what is being
> recorded normally, especially since we give three of what is not.
The second sentence is a set of examples - or do you mean examples for what
actions by the startup process are counted?
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 07:28 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 23:37 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-08 02:10 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
2022-04-08 03:59 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-08 04:39 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
2022-04-09 19:06 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
@ 2022-04-09 19:38 ` David G. Johnston <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: David G. Johnston @ 2022-04-09 19:38 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; PostgreSQL Hackers <[email protected]>
On Sat, Apr 9, 2022 at 12:07 PM Andres Freund <[email protected]> wrote:
>
> > ... specific counters. In particular, replay will not increment
> > pg_stat_database or pg_stat_all_tables columns, and the startup process
> > will not report reads and writes for the pg_statio views.
> >
> > It would helpful to give at least one specific example of what is being
> > recorded normally, especially since we give three of what is not.
>
> The second sentence is a set of examples - or do you mean examples for what
> actions by the startup process are counted?
>
>
Specific views that these statistics will be updating; like
pg_stat_database being the example of a view that is not updating.
David J.
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 07:28 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 23:37 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-08 02:10 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
2022-04-08 03:59 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
@ 2022-04-08 04:44 ` Kyotaro Horiguchi <[email protected]>
2 siblings, 0 replies; 87+ messages in thread
From: Kyotaro Horiguchi @ 2022-04-08 04:44 UTC (permalink / raw)
To: [email protected]; +Cc: [email protected]; [email protected]; [email protected]; [email protected]; [email protected]
At Thu, 7 Apr 2022 20:59:21 -0700, Andres Freund <[email protected]> wrote in
> Hi,
>
> On 2022-04-08 11:10:14 +0900, Kyotaro Horiguchi wrote:
> > I can read it. But I'm not sure that the difference is obvious for
> > average users between "starting a standby from a basebackup" and
> > "starting a standby after a normal shutdown"..
>
> Yea, that's what I was concerned about. How about:
>
> <para>
> Cumulative statistics are collected in shared memory. Every
> <productname>PostgreSQL</productname> process collects statistics locally
> then updates the shared data at appropriate intervals. When a server,
> including a physical replica, shuts down cleanly, a permanent copy of the
> statistics data is stored in the <filename>pg_stat</filename> subdirectory,
> so that statistics can be retained across server restarts. In contrast,
> when starting from an unclean shutdown (e.g., after an immediate shutdown,
> a server crash, starting from a base backup, and point-in-time recovery),
> all statistics counters are reset.
> </para>
Looks perfect generally, and especially in regard to the concern.
> I think I like my version above a bit better?
Quite a bit. It didn't answer for the concern.
> > > 2)
> > > The edit is not a problem, but it's hard to understand what the existing
> > > paragraph actually means?
> > >
> > > diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
> > > index 3247e056663..8bfb584b752 100644
> > > --- a/doc/src/sgml/high-availability.sgml
> > > +++ b/doc/src/sgml/high-availability.sgml
> > > @@ -2222,17 +2222,17 @@ HINT: You can then restart the server after making the necessary configuration
> > > ...
> > > <para>
> > > - The statistics collector is active during recovery. All scans, reads, blocks,
> > > + The cumulative statistics system is active during recovery. All scans, reads, blocks,
> > > index usage, etc., will be recorded normally on the standby. Replayed
> > > actions will not duplicate their effects on primary, so replaying an
> > > insert will not increment the Inserts column of pg_stat_user_tables.
> > > The stats file is deleted at the start of recovery, so stats from primary
> > > and standby will differ; this is considered a feature, not a bug.
> > > </para>
> > >
> > > <para>
> >
> > Agreed partially. It's too detailed. It might not need to mention WAL
> > replay.
>
> My concern is more that it seems halfway nonsensical. "Replayed actions will
> not duplicate their effects on primary" - I can guess what that means, but not
> more. There's no "Inserts" column of pg_stat_user_tables.
>
>
> <para>
> The cumulative statistics system is active during recovery. All scans,
> reads, blocks, index usage, etc., will be recorded normally on the
> standby. However, WAL replay will not increment relation and database
> specific counters. I.e. replay will not increment pg_stat_all_tables
> columns (like n_tup_ins), nor will reads or writes performed by the
> startup process be tracked in the pg_statio views, nor will associated
> pg_stat_database columns be incremented.
> </para>
Looks clearer since it mention user-facing interfaces with concrete
example columns.
regards.
--
Kyotaro Horiguchi
NTT Open Source Software Center
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 07:28 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 23:37 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
@ 2022-04-08 04:38 ` Andres Freund <[email protected]>
1 sibling, 0 replies; 87+ messages in thread
From: Andres Freund @ 2022-04-08 04:38 UTC (permalink / raw)
To: Kyotaro Horiguchi <[email protected]>; +Cc: [email protected]; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; David G. Johnston <[email protected]>; [email protected]
On 2022-04-07 16:37:51 -0700, Andres Freund wrote:
> On 2022-04-07 00:28:45 -0700, Andres Freund wrote:
> > I've gotten through the main commits (and then a fix for the apparently
> > inevitable bug that's immediately highlighted by the buildfarm), and the first
> > test. I'll call it a night now, and work on the other tests & docs tomorrow.
>
> I've gotten through the tests now. There's one known, not yet addressed, issue
> with the stats isolation test, see [1].
That has since been fixed, in d6c0db14836cd843d589372d909c73aab68c7a24
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
@ 2022-04-13 23:34 ` David G. Johnston <[email protected]>
2022-04-13 23:56 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
8 siblings, 1 reply; 87+ messages in thread
From: David G. Johnston @ 2022-04-13 23:34 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; PostgreSQL Hackers <[email protected]>
On Tue, Apr 5, 2022 at 8:00 PM Andres Freund <[email protected]> wrote:
> Here comes v70:
>
>
One thing I just noticed while peeking at pg_stat_slru:
The stats_reset column for my newly initdb'd cluster is showing me
"2000-01-01 00:00:00" (v15). I was expecting null, though a non-null value
restriction does make sense. Neither choice is documented though.
Based upon my expectation I checked to see if v14 reported null, and thus
this was a behavior change. v14 reports the initdb timestamp (e.g.,
2022-04-13 23:26:48.349115+00)
Can we document the non-null aspect of this value (pg_stat_database is
happy being null, this seems to be a "fixed" type behavior) but have it
continue to report initdb as its initial value?
David J.
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-13 23:34 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
@ 2022-04-13 23:56 ` David G. Johnston <[email protected]>
2022-04-14 00:55 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-14 01:20 ` Re: shared-memory based stats collector - v70 Michael Paquier <[email protected]>
0 siblings, 2 replies; 87+ messages in thread
From: David G. Johnston @ 2022-04-13 23:56 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; PostgreSQL Hackers <[email protected]>
On Wed, Apr 13, 2022 at 4:34 PM David G. Johnston <
[email protected]> wrote:
> On Tue, Apr 5, 2022 at 8:00 PM Andres Freund <[email protected]> wrote:
>
>> Here comes v70:
>>
>>
> One thing I just noticed while peeking at pg_stat_slru:
>
> The stats_reset column for my newly initdb'd cluster is showing me
> "2000-01-01 00:00:00" (v15). I was expecting null, though a non-null value
> restriction does make sense. Neither choice is documented though.
>
> Based upon my expectation I checked to see if v14 reported null, and thus
> this was a behavior change. v14 reports the initdb timestamp (e.g.,
> 2022-04-13 23:26:48.349115+00)
>
> Can we document the non-null aspect of this value (pg_stat_database is
> happy being null, this seems to be a "fixed" type behavior) but have it
> continue to report initdb as its initial value?
>
>
Sorry, apparently this "2000-01-01" behavior only manifests after crash
recovery on v15 (didn't check v14); after a clean initdb on v15 I got the
same initdb timestamp.
Feels like we should still report the "end of crash recovery timestamp" for
these instead of 2000-01-01 (which I guess is derived from 0) if we are not
willing to produce null (and it seems other parts of the system using these
stats assumes non-null).
David J.
David J.
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-13 23:34 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
2022-04-13 23:56 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
@ 2022-04-14 00:55 ` Andres Freund <[email protected]>
2022-04-15 00:41 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
1 sibling, 1 reply; 87+ messages in thread
From: Andres Freund @ 2022-04-14 00:55 UTC (permalink / raw)
To: David G. Johnston <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi,
On 2022-04-13 16:56:45 -0700, David G. Johnston wrote:
> On Wed, Apr 13, 2022 at 4:34 PM David G. Johnston <
> [email protected]> wrote:
> Sorry, apparently this "2000-01-01" behavior only manifests after crash
> recovery on v15 (didn't check v14); after a clean initdb on v15 I got the
> same initdb timestamp.
> Feels like we should still report the "end of crash recovery timestamp" for
> these instead of 2000-01-01 (which I guess is derived from 0) if we are not
> willing to produce null (and it seems other parts of the system using these
> stats assumes non-null).
Yes, that's definitely not correct. I see the bug (need to call
pgstat_reset_after_failure(); in pgstat_discard_stats()). Stupid, but
easy to fix - too fried to write a test tonight, but will commit the fix
tomorrow.
Thanks for catching!
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-13 23:34 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
2022-04-13 23:56 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
2022-04-14 00:55 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
@ 2022-04-15 00:41 ` Andres Freund <[email protected]>
0 siblings, 0 replies; 87+ messages in thread
From: Andres Freund @ 2022-04-15 00:41 UTC (permalink / raw)
To: David G. Johnston <[email protected]>; +Cc: Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi,
On 2022-04-13 17:55:18 -0700, Andres Freund wrote:
> On 2022-04-13 16:56:45 -0700, David G. Johnston wrote:
> > On Wed, Apr 13, 2022 at 4:34 PM David G. Johnston <
> > [email protected]> wrote:
> > Sorry, apparently this "2000-01-01" behavior only manifests after crash
> > recovery on v15 (didn't check v14); after a clean initdb on v15 I got the
> > same initdb timestamp.
>
> > Feels like we should still report the "end of crash recovery timestamp" for
> > these instead of 2000-01-01 (which I guess is derived from 0) if we are not
> > willing to produce null (and it seems other parts of the system using these
> > stats assumes non-null).
>
> Yes, that's definitely not correct. I see the bug (need to call
> pgstat_reset_after_failure(); in pgstat_discard_stats()). Stupid, but
> easy to fix - too fried to write a test tonight, but will commit the fix
> tomorrow.
Pushed the fix (including a test that previously failed). Thanks again!
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 87+ messages in thread
* Re: shared-memory based stats collector - v70
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-13 23:34 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
2022-04-13 23:56 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
@ 2022-04-14 01:20 ` Michael Paquier <[email protected]>
1 sibling, 0 replies; 87+ messages in thread
From: Michael Paquier @ 2022-04-14 01:20 UTC (permalink / raw)
To: David G. Johnston <[email protected]>; +Cc: Andres Freund <[email protected]>; Kyotaro Horiguchi <[email protected]>; Melanie Plageman <[email protected]>; Justin Pryzby <[email protected]>; Thomas Munro <[email protected]>; PostgreSQL Hackers <[email protected]>
On Wed, Apr 13, 2022 at 04:56:45PM -0700, David G. Johnston wrote:
> Sorry, apparently this "2000-01-01" behavior only manifests after crash
> recovery on v15 (didn't check v14); after a clean initdb on v15 I got the
> same initdb timestamp.
>
> Feels like we should still report the "end of crash recovery timestamp" for
> these instead of 2000-01-01 (which I guess is derived from 0) if we are not
> willing to produce null (and it seems other parts of the system using these
> stats assumes non-null).
I can see this timestamp as well after crash recovery. This seems
rather misleading to me. I have added an open item.
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 87+ messages in thread
end of thread, other threads:[~2022-04-15 00:41 UTC | newest]
Thread overview: 87+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-07-15 22:35 [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids. Andres Freund <[email protected]>
2020-07-15 22:35 [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids. Andres Freund <[email protected]>
2020-07-15 22:35 [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids. Andres Freund <[email protected]>
2020-07-15 22:35 [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids. Andres Freund <[email protected]>
2020-07-15 22:35 [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids. Andres Freund <[email protected]>
2020-07-15 22:35 [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids. Andres Freund <[email protected]>
2020-07-15 22:35 [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids. Andres Freund <[email protected]>
2020-07-15 22:35 [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids. Andres Freund <[email protected]>
2020-07-15 22:35 [PATCH v12 4/7] snapshot scalability: Introduce dense array of in-progress xids. Andres Freund <[email protected]>
2020-07-15 22:35 [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids. Andres Freund <[email protected]>
2020-07-15 22:35 [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids. Andres Freund <[email protected]>
2020-07-15 22:35 [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids. Andres Freund <[email protected]>
2020-07-15 22:35 [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids. Andres Freund <[email protected]>
2020-07-15 22:35 [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids. Andres Freund <[email protected]>
2020-07-15 22:35 [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids. Andres Freund <[email protected]>
2020-07-15 22:35 [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids. Andres Freund <[email protected]>
2020-07-15 22:35 [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids. Andres Freund <[email protected]>
2020-07-15 22:35 [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids. Andres Freund <[email protected]>
2020-07-15 22:35 [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids. Andres Freund <[email protected]>
2020-07-15 22:35 [PATCH v11 3/6] snapshot scalability: Introduce dense array of in-progress xids. Andres Freund <[email protected]>
2020-07-15 22:35 [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids. Andres Freund <[email protected]>
2020-07-15 22:35 [PATCH v13 3/6] snapshot scalability: Introduce dense array of in-progress xids. Andres Freund <[email protected]>
2022-04-02 08:21 Re: shared-memory based stats collector - v66 Andres Freund <[email protected]>
2022-04-04 04:15 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 13:16 ` Re: shared-memory based stats collector - v68 Thomas Munro <[email protected]>
2022-04-04 17:54 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 19:08 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 20:45 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 21:06 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 21:25 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 21:54 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-04 22:24 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-04 22:44 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 02:03 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-05 02:36 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 15:49 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-05 17:30 ` Re: shared-memory based stats collector - v68 Andres Freund <[email protected]>
2022-04-05 18:26 ` Re: shared-memory based stats collector - v68 David G. Johnston <[email protected]>
2022-04-05 03:05 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-05 20:51 ` Re: shared-memory based stats collector - v69 David G. Johnston <[email protected]>
2022-04-05 21:23 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-05 21:43 ` Re: shared-memory based stats collector - v69 David G. Johnston <[email protected]>
2022-04-05 23:16 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v69 David G. Johnston <[email protected]>
2022-04-06 03:14 ` Re: shared-memory based stats collector - v69 Andres Freund <[email protected]>
2022-04-06 03:30 ` Re: shared-memory based stats collector - v69 David G. Johnston <[email protected]>
2022-04-06 03:00 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 03:06 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
2022-04-06 03:11 ` Re: shared-memory based stats collector - v70 Greg Stark <[email protected]>
2022-04-06 03:18 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
2022-04-06 03:22 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 09:11 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
2022-04-06 16:04 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 01:36 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
2022-04-07 01:58 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 08:00 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
2022-04-06 09:24 ` Re: shared-memory based stats collector - v70 John Naylor <[email protected]>
2022-04-06 16:20 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 11:31 ` Re: shared-memory based stats collector - v70 Alvaro Herrera <[email protected]>
2022-04-06 16:17 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 19:14 ` Re: shared-memory based stats collector - v70 Lukas Fittl <[email protected]>
2022-04-06 19:27 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 19:34 ` Re: shared-memory based stats collector - v70 Justin Pryzby <[email protected]>
2022-04-06 19:37 ` Re: shared-memory based stats collector - v70 Lukas Fittl <[email protected]>
2022-04-06 22:12 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-06 22:32 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
2022-04-06 23:12 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 00:01 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
2022-04-07 00:48 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 07:28 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-07 08:02 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
2022-04-07 23:37 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-08 02:10 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
2022-04-08 03:51 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
2022-04-08 04:16 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-08 03:59 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-08 04:37 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-08 04:39 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
2022-04-09 19:06 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-09 19:38 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
2022-04-08 04:44 ` Re: shared-memory based stats collector - v70 Kyotaro Horiguchi <[email protected]>
2022-04-08 04:38 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-13 23:34 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
2022-04-13 23:56 ` Re: shared-memory based stats collector - v70 David G. Johnston <[email protected]>
2022-04-14 00:55 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-15 00:41 ` Re: shared-memory based stats collector - v70 Andres Freund <[email protected]>
2022-04-14 01:20 ` Re: shared-memory based stats collector - v70 Michael Paquier <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox