From 29c93a56d0db15050c33c0a130b3716632b8ae44 Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Tue, 7 Apr 2020 03:33:16 -0700 Subject: [PATCH v7 09/11] snapshot scalability: Move subxact info from PGXACT to ProcGlobal. Similar to the previous changes this increases the chance that data frequently needed by GetSnapshotData() stays in l2 cache. In many workloads subtransactions are very rare, and this makes the check for that cheaper. --- src/include/storage/proc.h | 19 ++++- src/backend/access/transam/clog.c | 7 +- src/backend/access/transam/twophase.c | 11 +-- src/backend/access/transam/varsup.c | 9 ++- src/backend/storage/ipc/procarray.c | 110 ++++++++++++++++---------- src/backend/storage/lmgr/proc.c | 3 + 6 files changed, 99 insertions(+), 60 deletions(-) diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 37208ddf342..3cd48382260 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -35,6 +35,14 @@ */ #define PGPROC_MAX_CACHED_SUBXIDS 64 /* XXX guessed-at value */ +typedef struct XidCacheStatus +{ + /* number of cached subxids, never more than PGPROC_MAX_CACHED_SUBXIDS */ + uint8 count; + /* has PGPROC->subxids overflowed */ + bool overflowed; +} XidCacheStatus; + struct XidCache { TransactionId xids[PGPROC_MAX_CACHED_SUBXIDS]; @@ -169,6 +177,7 @@ struct PGPROC */ SHM_QUEUE myProcLocks[NUM_LOCK_PARTITIONS]; + XidCacheStatus subxidStatusCopy; /* copy of ProcGlobal->subxidStates[i] */ struct XidCache subxids; /* cache for subtransaction XIDs */ uint8 vacuumFlagsCopy; /* this backend's vacuum flags, a copy of its @@ -235,9 +244,6 @@ extern PGDLLIMPORT struct PGXACT *MyPgXact; */ typedef struct PGXACT { - bool overflowed; - - uint8 nxids; } PGXACT; /* @@ -279,6 +285,13 @@ typedef struct PROC_HDR */ TransactionId *xids; + /* + * Subtransaction caching status for each proc's PGPROC.subxids. + * + * Each PGPROC has a copy of its value in PGPROC.subxidStatusCopy. + */ + XidCacheStatus *subxidStates; + /* * Vacuum flags. See PROC_* above. */ diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c index 8e9c211b02a..ee90ec5af29 100644 --- a/src/backend/access/transam/clog.c +++ b/src/backend/access/transam/clog.c @@ -295,7 +295,7 @@ TransactionIdSetPageStatus(TransactionId xid, int nsubxids, */ if (all_xact_same_page && xid == MyProc->xidCopy && nsubxids <= THRESHOLD_SUBTRANS_CLOG_OPT && - nsubxids == MyPgXact->nxids && + nsubxids == MyProc->subxidStatusCopy.count && memcmp(subxids, MyProc->subxids.xids, nsubxids * sizeof(TransactionId)) == 0) { @@ -510,16 +510,15 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status, while (nextidx != INVALID_PGPROCNO) { PGPROC *proc = &ProcGlobal->allProcs[nextidx]; - PGXACT *pgxact = &ProcGlobal->allPgXact[nextidx]; /* * Transactions with more than THRESHOLD_SUBTRANS_CLOG_OPT sub-XIDs * should not use group XID status update mechanism. */ - Assert(pgxact->nxids <= THRESHOLD_SUBTRANS_CLOG_OPT); + Assert(proc->subxidStatusCopy.count <= THRESHOLD_SUBTRANS_CLOG_OPT); TransactionIdSetPageStatusInternal(proc->clogGroupMemberXid, - pgxact->nxids, + proc->subxidStatusCopy.count, proc->subxids.xids, proc->clogGroupMemberXidStatus, proc->clogGroupMemberLsn, diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c index 06b61605649..f802544d378 100644 --- a/src/backend/access/transam/twophase.c +++ b/src/backend/access/transam/twophase.c @@ -447,14 +447,12 @@ MarkAsPreparingGuts(GlobalTransaction gxact, TransactionId xid, const char *gid, TimestampTz prepared_at, Oid owner, Oid databaseid) { PGPROC *proc; - PGXACT *pgxact; int i; Assert(LWLockHeldByMeInMode(TwoPhaseStateLock, LW_EXCLUSIVE)); Assert(gxact != NULL); proc = &ProcGlobal->allProcs[gxact->pgprocno]; - pgxact = &ProcGlobal->allPgXact[gxact->pgprocno]; /* Initialize the PGPROC entry */ MemSet(proc, 0, sizeof(PGPROC)); @@ -480,8 +478,8 @@ MarkAsPreparingGuts(GlobalTransaction gxact, TransactionId xid, const char *gid, for (i = 0; i < NUM_LOCK_PARTITIONS; i++) SHMQueueInit(&(proc->myProcLocks[i])); /* subxid data must be filled later by GXactLoadSubxactData */ - pgxact->overflowed = false; - pgxact->nxids = 0; + proc->subxidStatusCopy.count = 0; + proc->subxidStatusCopy.overflowed = 0; gxact->prepared_at = prepared_at; gxact->xid = xid; @@ -510,19 +508,18 @@ GXactLoadSubxactData(GlobalTransaction gxact, int nsubxacts, TransactionId *children) { PGPROC *proc = &ProcGlobal->allProcs[gxact->pgprocno]; - PGXACT *pgxact = &ProcGlobal->allPgXact[gxact->pgprocno]; /* We need no extra lock since the GXACT isn't valid yet */ if (nsubxacts > PGPROC_MAX_CACHED_SUBXIDS) { - pgxact->overflowed = true; + proc->subxidStatusCopy.overflowed = true; nsubxacts = PGPROC_MAX_CACHED_SUBXIDS; } if (nsubxacts > 0) { memcpy(proc->subxids.xids, children, nsubxacts * sizeof(TransactionId)); - pgxact->nxids = nsubxacts; + proc->subxidStatusCopy.count = PGPROC_MAX_CACHED_SUBXIDS; } } diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c index f703c229450..091a183c0e9 100644 --- a/src/backend/access/transam/varsup.c +++ b/src/backend/access/transam/varsup.c @@ -225,19 +225,22 @@ GetNewTransactionId(bool isSubXact) /* LWLockRelease acts as barrier */ ProcGlobal->xids[MyProc->pgxactoff] = xid; MyProc->xidCopy = xid; + + Assert(ProcGlobal->subxidStates[MyProc->pgxactoff].count == 0); } else { - int nxids = MyPgXact->nxids; + XidCacheStatus *substat = &ProcGlobal->subxidStates[MyProc->pgxactoff]; + int nxids = MyProc->subxidStatusCopy.count; if (nxids < PGPROC_MAX_CACHED_SUBXIDS) { MyProc->subxids.xids[nxids] = xid; pg_write_barrier(); - MyPgXact->nxids = nxids + 1; + MyProc->subxidStatusCopy.count = substat->count = nxids + 1; } else - MyPgXact->overflowed = true; + MyProc->subxidStatusCopy.overflowed = substat->overflowed = true; } LWLockRelease(XidGenLock); diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c index 384b5a8efb5..6de0590dbec 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -86,7 +86,7 @@ typedef struct ProcArrayStruct /* * Highest subxid that has been removed from KnownAssignedXids array to * prevent overflow; or InvalidTransactionId if none. We track this for - * similar reasons to tracking overflowing cached subxids in PGXACT + * similar reasons to tracking overflowing cached subxids in PGPROC * entries. Must hold exclusive ProcArrayLock to change this, and shared * lock to read it. */ @@ -441,11 +441,14 @@ ProcArrayAdd(PGPROC *proc) (arrayP->numProcs - index) * sizeof(*arrayP->pgprocnos)); memmove(&ProcGlobal->xids[index + 1], &ProcGlobal->xids[index], (arrayP->numProcs - index) * sizeof(*ProcGlobal->xids)); + memmove(&ProcGlobal->subxidStates[index + 1], &ProcGlobal->subxidStates[index], + (arrayP->numProcs - index) * sizeof(*ProcGlobal->subxidStates)); memmove(&ProcGlobal->vacuumFlags[index + 1], &ProcGlobal->vacuumFlags[index], (arrayP->numProcs - index) * sizeof(*ProcGlobal->vacuumFlags)); arrayP->pgprocnos[index] = proc->pgprocno; ProcGlobal->xids[index] = proc->xidCopy; + ProcGlobal->subxidStates[index] = proc->subxidStatusCopy; ProcGlobal->vacuumFlags[index] = proc->vacuumFlagsCopy; arrayP->numProcs++; @@ -499,6 +502,8 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid) MaintainLatestCompletedXid(latestXid); ProcGlobal->xids[proc->pgxactoff] = 0; + ProcGlobal->subxidStates[proc->pgxactoff].overflowed = false; + ProcGlobal->subxidStates[proc->pgxactoff].count = 0; } else { @@ -507,6 +512,8 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid) } Assert(TransactionIdIsValid(ProcGlobal->xids[proc->pgxactoff] == 0)); + Assert(TransactionIdIsValid(ProcGlobal->subxidStates[proc->pgxactoff].count == 0)); + Assert(TransactionIdIsValid(ProcGlobal->subxidStates[proc->pgxactoff].overflowed == false)); ProcGlobal->vacuumFlags[proc->pgxactoff] = 0; for (index = 0; index < arrayP->numProcs; index++) @@ -518,6 +525,8 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid) (arrayP->numProcs - index - 1) * sizeof(*arrayP->pgprocnos)); memmove(&ProcGlobal->xids[index], &ProcGlobal->xids[index + 1], (arrayP->numProcs - index - 1) * sizeof(*ProcGlobal->xids)); + memmove(&ProcGlobal->subxidStates[index], &ProcGlobal->subxidStates[index + 1], + (arrayP->numProcs - index - 1) * sizeof(*ProcGlobal->subxidStates)); memmove(&ProcGlobal->vacuumFlags[index], &ProcGlobal->vacuumFlags[index + 1], (arrayP->numProcs - index - 1) * sizeof(*ProcGlobal->vacuumFlags)); @@ -596,15 +605,13 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid) * estimate of global xmin, but that's OK. */ Assert(!TransactionIdIsValid(proc->xidCopy)); + Assert(proc->subxidStatusCopy.count == 0); proc->lxid = InvalidLocalTransactionId; proc->xmin = InvalidTransactionId; proc->delayChkpt = false; /* be sure this is cleared in abort */ proc->recoveryConflictPending = false; - Assert(pgxact->nxids == 0); - Assert(pgxact->overflowed == false); - /* must be cleared with xid/xmin: */ /* avoid unnecessarily dirtying shared cachelines */ if (proc->vacuumFlagsCopy & PROC_VACUUM_STATE_MASK) @@ -648,8 +655,15 @@ ProcArrayEndTransactionInternal(PGPROC *proc, PGXACT *pgxact, } /* Clear the subtransaction-XID cache too while holding the lock */ - pgxact->nxids = 0; - pgxact->overflowed = false; + Assert(ProcGlobal->subxidStates[pgxactoff].count == proc->subxidStatusCopy.count && + ProcGlobal->subxidStates[pgxactoff].overflowed == proc->subxidStatusCopy.overflowed); + if (proc->subxidStatusCopy.count > 0 || proc->subxidStatusCopy.overflowed) + { + ProcGlobal->subxidStates[pgxactoff].count = 0; + ProcGlobal->subxidStates[pgxactoff].overflowed = false; + proc->subxidStatusCopy.count = 0; + proc->subxidStatusCopy.overflowed = false; + } /* Also advance global latestCompletedXid while holding the lock */ MaintainLatestCompletedXid(latestXid); @@ -785,7 +799,6 @@ ProcArrayGroupClearXid(PGPROC *proc, TransactionId latestXid) void ProcArrayClearTransaction(PGPROC *proc) { - PGXACT *pgxact = &allPgXact[proc->pgprocno]; size_t pgxactoff; /* @@ -810,8 +823,15 @@ ProcArrayClearTransaction(PGPROC *proc) Assert(!proc->delayChkpt); /* Clear the subtransaction-XID cache too */ - pgxact->nxids = 0; - pgxact->overflowed = false; + Assert(ProcGlobal->subxidStates[pgxactoff].count == proc->subxidStatusCopy.count && + ProcGlobal->subxidStates[pgxactoff].overflowed == proc->subxidStatusCopy.overflowed); + if (proc->subxidStatusCopy.count > 0 || proc->subxidStatusCopy.overflowed) + { + ProcGlobal->subxidStates[pgxactoff].count = 0; + ProcGlobal->subxidStates[pgxactoff].overflowed = false; + proc->subxidStatusCopy.count = 0; + proc->subxidStatusCopy.overflowed = false; + } LWLockRelease(ProcArrayLock); } @@ -1208,6 +1228,7 @@ TransactionIdIsInProgress(TransactionId xid) { static TransactionId *xids = NULL; static TransactionId *other_xids; + XidCacheStatus *other_nsubxids; int nxids = 0; ProcArrayStruct *arrayP = procArray; TransactionId topxid; @@ -1269,6 +1290,7 @@ TransactionIdIsInProgress(TransactionId xid) } other_xids = ProcGlobal->xids; + other_nsubxids = ProcGlobal->subxidStates; LWLockAcquire(ProcArrayLock, LW_SHARED); @@ -1290,7 +1312,6 @@ TransactionIdIsInProgress(TransactionId xid) for (size_t pgxactoff = 0; pgxactoff < numProcs; pgxactoff++) { int pgprocno; - PGXACT *pgxact; PGPROC *proc; TransactionId pxid; int pxids; @@ -1325,12 +1346,16 @@ TransactionIdIsInProgress(TransactionId xid) /* * Step 2: check the cached child-Xids arrays */ - pgprocno = arrayP->pgprocnos[pgxactoff]; - pgxact = &allPgXact[pgprocno]; - pxids = pgxact->nxids; + pxids = other_nsubxids[pgxactoff].count; + + if (pxids == 0 && !other_nsubxids[pgxactoff].overflowed) + continue; + 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 */ @@ -1351,7 +1376,7 @@ TransactionIdIsInProgress(TransactionId xid) * we hold ProcArrayLock. So we can't miss an Xid that we need to * worry about.) */ - if (pgxact->overflowed) + if (other_nsubxids[pgxactoff].overflowed) xids[nxids++] = pxid; } @@ -1923,6 +1948,7 @@ GetSnapshotData(Snapshot snapshot) { size_t numProcs = arrayP->numProcs; TransactionId *xip = snapshot->xip; + XidCacheStatus *allnsubxids = ProcGlobal->subxidStates; int workspace_count = 0; uint8 *allVacuumFlags = ProcGlobal->vacuumFlags; @@ -1971,9 +1997,7 @@ GetSnapshotData(Snapshot snapshot) { int pgxactoff = snapshot_workspace_off[i]; TransactionId xid = snapshot_workspace_xid[i]; - int pgprocno = arrayP->pgprocnos[pgxactoff]; - PGXACT *pgxact = &allPgXact[pgprocno]; - uint8 vacuumFlags = allVacuumFlags[pgxactoff]; + uint8 vacuumFlags = allVacuumFlags[pgxactoff]; int nsubxids; Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff); @@ -2009,8 +2033,8 @@ GetSnapshotData(Snapshot snapshot) if (suboverflowed) continue; - suboverflowed = pgxact->overflowed; - nsubxids = pgxact->nxids; + suboverflowed = allnsubxids[pgxactoff].overflowed; + nsubxids = allnsubxids[pgxactoff].count; if (suboverflowed || nsubxids == 0) continue; @@ -2406,8 +2430,6 @@ GetRunningTransactionData(void) */ for (index = 0; index < arrayP->numProcs; index++) { - int pgprocno = arrayP->pgprocnos[index]; - PGXACT *pgxact = &allPgXact[pgprocno]; TransactionId xid; /* Fetch xid just once - see GetNewTransactionId */ @@ -2428,7 +2450,7 @@ GetRunningTransactionData(void) if (TransactionIdPrecedes(xid, oldestRunningXid)) oldestRunningXid = xid; - if (pgxact->overflowed) + if (ProcGlobal->subxidStates[index].overflowed) suboverflowed = true; /* @@ -2448,27 +2470,28 @@ GetRunningTransactionData(void) */ if (!suboverflowed) { + XidCacheStatus *other_nsubxids = ProcGlobal->subxidStates; + for (index = 0; index < arrayP->numProcs; index++) { int pgprocno = arrayP->pgprocnos[index]; PGPROC *proc = &allProcs[pgprocno]; - PGXACT *pgxact = &allPgXact[pgprocno]; - int nxids; + int nsubxids; /* * Save subtransaction XIDs. Other backends can't add or remove * entries while we're holding XidGenLock. */ - nxids = pgxact->nxids; - if (nxids > 0) + nsubxids = other_nsubxids[index].count; + if (nsubxids > 0) { /* barrier not really required, as XidGenLock is held, but ... */ pg_read_barrier(); /* pairs with GetNewTransactionId */ memcpy(&xids[count], (void *) proc->subxids.xids, - nxids * sizeof(TransactionId)); - count += nxids; - subcount += nxids; + nsubxids * sizeof(TransactionId)); + count += nsubxids; + subcount += nsubxids; /* * Top-level XID of a transaction is always less than any of @@ -3535,14 +3558,6 @@ ProcArrayGetReplicationSlotXmin(TransactionId *xmin, LWLockRelease(ProcArrayLock); } - -#define XidCacheRemove(i) \ - do { \ - MyProc->subxids.xids[i] = MyProc->subxids.xids[MyPgXact->nxids - 1]; \ - pg_write_barrier(); \ - MyPgXact->nxids--; \ - } while (0) - /* * XidCacheRemoveRunningXids * @@ -3558,6 +3573,7 @@ XidCacheRemoveRunningXids(TransactionId xid, { int i, j; + XidCacheStatus *mysubxidstat; Assert(TransactionIdIsValid(xid)); @@ -3575,6 +3591,8 @@ XidCacheRemoveRunningXids(TransactionId xid, */ LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); + mysubxidstat = &ProcGlobal->subxidStates[MyProc->pgxactoff]; + /* * Under normal circumstances xid and xids[] will be in increasing order, * as will be the entries in subxids. Scan backwards to avoid O(N^2) @@ -3584,11 +3602,14 @@ XidCacheRemoveRunningXids(TransactionId xid, { TransactionId anxid = xids[i]; - for (j = MyPgXact->nxids - 1; j >= 0; j--) + for (j = MyProc->subxidStatusCopy.count - 1; j >= 0; j--) { if (TransactionIdEquals(MyProc->subxids.xids[j], anxid)) { - XidCacheRemove(j); + MyProc->subxids.xids[j] = MyProc->subxids.xids[MyProc->subxidStatusCopy.count - 1]; + pg_write_barrier(); + mysubxidstat->count--; + MyProc->subxidStatusCopy.count--; break; } } @@ -3600,20 +3621,23 @@ XidCacheRemoveRunningXids(TransactionId xid, * error during AbortSubTransaction. So instead of Assert, emit a * debug warning. */ - if (j < 0 && !MyPgXact->overflowed) + if (j < 0 && !MyProc->subxidStatusCopy.overflowed) elog(WARNING, "did not find subXID %u in MyProc", anxid); } - for (j = MyPgXact->nxids - 1; j >= 0; j--) + for (j = MyProc->subxidStatusCopy.count - 1; j >= 0; j--) { if (TransactionIdEquals(MyProc->subxids.xids[j], xid)) { - XidCacheRemove(j); + MyProc->subxids.xids[j] = MyProc->subxids.xids[MyProc->subxidStatusCopy.count - 1]; + pg_write_barrier(); + mysubxidstat->count--; + MyProc->subxidStatusCopy.count--; break; } } /* Ordinarily we should have found it, unless the cache has overflowed */ - if (j < 0 && !MyPgXact->overflowed) + if (j < 0 && !MyProc->subxidStatusCopy.overflowed) elog(WARNING, "did not find subXID %u in MyProc", xid); /* Also advance global latestCompletedXid while holding the lock */ diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 00f26d93c1a..c619250318b 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -113,6 +113,7 @@ ProcGlobalShmemSize(void) 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))); + size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->subxidStates))); size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->vacuumFlags))); return size; @@ -231,6 +232,8 @@ InitProcGlobal(void) // XXX: Pad to cacheline (or even page?)! ProcGlobal->xids = (TransactionId *) ShmemAlloc(TotalProcs * sizeof(*ProcGlobal->xids)); MemSet(ProcGlobal->xids, 0, TotalProcs * sizeof(*ProcGlobal->xids)); + ProcGlobal->subxidStates = (XidCacheStatus *) ShmemAlloc(TotalProcs * sizeof(*ProcGlobal->subxidStates)); + MemSet(ProcGlobal->subxidStates, 0, TotalProcs * sizeof(*ProcGlobal->subxidStates)); ProcGlobal->vacuumFlags = (uint8 *) ShmemAlloc(TotalProcs * sizeof(*ProcGlobal->vacuumFlags)); MemSet(ProcGlobal->vacuumFlags, 0, TotalProcs * sizeof(*ProcGlobal->vacuumFlags)); -- 2.25.0.114.g5b0ca878e0