003_minimize_calls_to_SubTransSetParent.v7.patch
application/octet-stream
Filename: 003_minimize_calls_to_SubTransSetParent.v7.patch
Type: application/octet-stream
Part: 0
Patch
Format: unified
Series: patch v7
| File | + | − |
|---|---|---|
| src/backend/access/transam/clog.c | 12 | 1 |
| src/backend/access/transam/xact.c | 45 | 2 |
| src/backend/storage/ipc/procarray.c | 44 | 0 |
| src/backend/storage/lmgr/lmgr.c | 29 | 2 |
| src/include/access/transam.h | 3 | 0 |
| src/include/storage/procarray.h | 2 | 0 |
diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index 3d9088a704..d690774f33 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -107,7 +107,18 @@ static bool TransactionGroupUpdateXidStatus(TransactionId xid,
static void TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
TransactionId *subxids, XidStatus status,
XLogRecPtr lsn, int pageno);
+/*
+ * Run locally by a backend to establish whether or not it needs to call
+ * SubTransSetParent for subxid.
+ */
+bool
+TransactionIdsAreOnSameXactPage(TransactionId topxid, TransactionId subxid)
+{
+ int toppageno = TransactionIdToPage(topxid);
+ int subpageno = TransactionIdToPage(subxid);
+ return (toppageno == subpageno);
+}
/*
* TransactionIdSetTreeStatus
@@ -133,7 +144,7 @@ static void TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
* only once, and the status will be set to committed directly. Otherwise
* we must
* 1. set sub-committed all subxids that are not on the same page as the
- * main xid
+ * main xid (see TransactionIdsAreOnSameXactPage())
* 2. atomically set committed the main xid and the subxids on the same page
* 3. go over the first bunch again and set them committed
* Note that as far as concurrent checkers are concerned, main transaction
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 50f092d7eb..5577e5b8df 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -693,8 +693,51 @@ AssignTransactionId(TransactionState s)
XactTopFullTransactionId = s->fullTransactionId;
if (isSubXact)
- SubTransSetParent(XidFromFullTransactionId(s->fullTransactionId),
- XidFromFullTransactionId(s->parent->fullTransactionId));
+ {
+ TransactionId subxid = XidFromFullTransactionId(s->fullTransactionId);
+
+ /*
+ * Subtrans entries are only required in specific circumstances:
+ *
+ * 1. When there's no room in PG_PROC, as mentioned above.
+ * During XactLockTableWait() we sometimes need to know the topxid.
+ * If there is room in PG_PROC we can get a subxid's topxid direct
+ * from the procarray if the topxid is still running, using
+ * GetTopmostTransactionIdFromProcArray(). So we only ever need to
+ * call SubTransGetTopMostTransaction() if that xact overflowed;
+ * since that is our current transaction, we know whether or not to
+ * log the xid for future use.
+ * This occurs only when large number of subxids are requested by
+ * app user.
+ *
+ * 2. When IsolationIsSerializable() we sometimes need to access topxid
+ * This occurs only when SERIALIZABLE is requested by app user.
+ *
+ * 3. When TransactionIdSetStatus will use a status of SUB_COMMITTED,
+ * which then requires us to consult subtrans to find parent, which
+ * is needed to avoid race condition. In this case we ask Clog/Xact
+ * module if TransactionIdsAreOnSameXactPage(). Since we start a new
+ * clog page every 32000 xids, this is usually <<1% of subxids.
+ */
+ if (MyProc->subxidStatus.overflowed ||
+ IsolationIsSerializable() ||
+ !TransactionIdsAreOnSameXactPage(GetTopTransactionId(), subxid))
+ {
+ /*
+ * Insert entries into subtrans for this xid, noting that the entry
+ * points directly to the topxid, not the immediate parent. This is
+ * done for two reasons, (1) so it is faster in a long chain of subxids
+ * (2) so that we don't need to set subxids for unregistered parents.
+ * This has the downside that anyone waiting for a lock on aborted
+ * subtransactions would not be released immediately; that may or
+ * may not be an acceptable compromise. If not acceptable, this
+ * simple call needs to be replaced with a loop to register the
+ * parent for the current subxid stack, so we can walk back up it to
+ * the topxid.
+ */
+ SubTransSetParent(subxid, GetTopTransactionId());
+ }
+ }
/*
* If it's a top-level transaction, the predicate locking system needs to
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a9ad40e935..f7c1061a6e 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -261,6 +261,13 @@ static ProcArrayStruct *procArray;
static PGPROC *allProcs;
+/*
+ * Remember the last call to TransactionIdIsInProgress() to avoid need to call
+ * SubTransGetTopMostTransaction() when the subxid is present in the procarray.
+ */
+static TransactionId LastCallXidIsInProgressSubXid = InvalidTransactionId;
+static TransactionId LastCallXidIsInProgressParentXid = InvalidTransactionId;
+
/*
* Cache to reduce overhead of repeated calls to TransactionIdIsInProgress()
*/
@@ -1440,6 +1447,8 @@ TransactionIdIsInProgress(TransactionId xid)
other_xids = ProcGlobal->xids;
other_subxidstates = ProcGlobal->subxidStates;
+ LastCallXidIsInProgressSubXid = LastCallXidIsInProgressParentXid = InvalidTransactionId;
+
LWLockAcquire(ProcArrayLock, LW_SHARED);
/*
@@ -1508,6 +1517,15 @@ TransactionIdIsInProgress(TransactionId xid)
{
LWLockRelease(ProcArrayLock);
xc_by_child_xid_inc();
+
+ /*
+ * Remember the parent xid, for use during XactLockTableWait().
+ * We do this because it is cheaper than looking up pg_subtrans,
+ * and also allows us to reduce calls to subtrans.
+ */
+ LastCallXidIsInProgressSubXid = xid;
+ LastCallXidIsInProgressParentXid = pxid;
+
return true;
}
}
@@ -1591,7 +1609,11 @@ TransactionIdIsInProgress(TransactionId xid)
for (int i = 0; i < nxids; i++)
{
if (TransactionIdEquals(xids[i], topxid))
+ {
+ LastCallXidIsInProgressSubXid = xid;
+ LastCallXidIsInProgressParentXid = topxid;
return true;
+ }
}
}
@@ -1599,6 +1621,28 @@ TransactionIdIsInProgress(TransactionId xid)
return false;
}
+/*
+ * Allow the topmost xid to be accessed from the last call to
+ * TransactionIdIsInProgress(). Specifically designed for use in
+ * XactLockTableWait().
+ */
+bool
+GetTopmostTransactionIdFromProcArray(TransactionId xid, TransactionId *pxid)
+{
+ bool found = false;
+
+ Assert(TransactionIdIsNormal(xid));
+
+ if (LastCallXidIsInProgressSubXid == xid)
+ {
+ Assert(TransactionIdIsNormal(*pxid));
+ *pxid = LastCallXidIsInProgressParentXid;
+ found = true;
+ }
+
+ return found;
+}
+
/*
* TransactionIdIsActive -- is xid the top-level XID of an active backend?
*
diff --git a/src/backend/storage/lmgr/lmgr.c b/src/backend/storage/lmgr/lmgr.c
index 1543da6162..b13a8d20fd 100644
--- a/src/backend/storage/lmgr/lmgr.c
+++ b/src/backend/storage/lmgr/lmgr.c
@@ -694,6 +694,8 @@ XactLockTableWait(TransactionId xid, Relation rel, ItemPointer ctid,
for (;;)
{
+ TransactionId pxid = InvalidTransactionId;
+
Assert(TransactionIdIsValid(xid));
Assert(!TransactionIdEquals(xid, GetTopTransactionIdIfAny()));
@@ -703,6 +705,13 @@ XactLockTableWait(TransactionId xid, Relation rel, ItemPointer ctid,
LockRelease(&tag, ShareLock, false);
+ /*
+ * If a transaction has no lock, it might be a top-level transaction,
+ * in which case the procarray will show it as not in progress.
+ *
+ * If a transaction is a subtransaction, then it could have committed
+ * or aborted, yet the top-level transaction may still be in progress.
+ */
if (!TransactionIdIsInProgress(xid))
break;
@@ -724,7 +733,17 @@ XactLockTableWait(TransactionId xid, Relation rel, ItemPointer ctid,
if (!first)
pg_usleep(1000L);
first = false;
- xid = SubTransGetTopmostTransaction(xid);
+
+ /*
+ * In most cases, we can get the parent xid from our prior call to
+ * TransactionIdIsInProgress(), except in hot standby. If not, we have
+ * to ask subtrans for the parent.
+ */
+ if (GetTopmostTransactionIdFromProcArray(xid, &pxid) &&
+ TransactionIdIsValid(pxid))
+ xid = pxid;
+ else
+ xid = SubTransGetTopmostTransaction(xid);
}
if (oper != XLTW_None)
@@ -745,6 +764,8 @@ ConditionalXactLockTableWait(TransactionId xid)
for (;;)
{
+ TransactionId pxid = InvalidTransactionId;
+
Assert(TransactionIdIsValid(xid));
Assert(!TransactionIdEquals(xid, GetTopTransactionIdIfAny()));
@@ -762,7 +783,13 @@ ConditionalXactLockTableWait(TransactionId xid)
if (!first)
pg_usleep(1000L);
first = false;
- xid = SubTransGetTopmostTransaction(xid);
+
+ /* See XactLockTableWait about this case */
+ if (GetTopmostTransactionIdFromProcArray(xid, &pxid) &&
+ TransactionIdIsValid(pxid))
+ xid = pxid;
+ else
+ xid = SubTransGetTopmostTransaction(xid);
}
return true;
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index 775471d2a7..f404db552f 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -301,6 +301,9 @@ extern void AssertTransactionIdInAllowableRange(TransactionId xid);
#define AssertTransactionIdInAllowableRange(xid) ((void)true)
#endif
+/* in transam/clog.c */
+extern bool TransactionIdsAreOnSameXactPage(TransactionId topxid, TransactionId subxid);
+
/*
* Some frontend programs include this header. For compilers that emit static
* inline functions even when they're unused, that leads to unsatisfied
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index 1b2cfac5ad..d7ad1da6a8 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -52,6 +52,8 @@ extern bool ProcArrayInstallRestoredXmin(TransactionId xmin, PGPROC *proc);
extern RunningTransactions GetRunningTransactionData(void);
extern bool TransactionIdIsInProgress(TransactionId xid);
+extern bool GetTopmostTransactionIdFromProcArray(TransactionId xid, TransactionId *pxid);
+
extern bool TransactionIdIsActive(TransactionId xid);
extern TransactionId GetOldestNonRemovableTransactionId(Relation rel);
extern TransactionId GetOldestTransactionIdConsideredRunning(void);