From 9dfd73e558078cc1a37ea39bfa8292f2cc0ee03e Mon Sep 17 00:00:00 2001 From: Michail Nikolaev Date: Sat, 6 Aug 2022 23:02:37 +0300 Subject: [PATCH v7] Currently, KnownAssignedXidsGetAndSetXmin requires an iterative loop through KnownAssignedXids array, including xids marked as invalid. Performance impact is especially noticeable in the presence of long (few seconds) transactions on primary, high value (few thousands) of max_connections and high read workload on standby. Most of the cpu spent on looping throw KnownAssignedXids while almost all xid are invalid anyway. KnownAssignedXidsCompress removes invalid xids from time to time, but performance is still affected. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To increase performance we lazily maintain an index in the KnownAssignedXidsNext array to skip known to be invalid xids. KnownAssignedXidsNext does not always point to “next” valid xid, it is just some gap safe to skip (known to be filled by only invalid xids). --- src/backend/storage/ipc/procarray.c | 56 ++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c index 0555b02a8d..31d5e4a09f 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -271,6 +271,7 @@ static TransactionId cachedXidIsNotInProgress = InvalidTransactionId; */ static TransactionId *KnownAssignedXids; static bool *KnownAssignedXidsValid; +static int32 *KnownAssignedXidsNext; static TransactionId latestObservedXid = InvalidTransactionId; /* @@ -450,6 +451,12 @@ CreateSharedProcArray(void) ShmemInitStruct("KnownAssignedXidsValid", mul_size(sizeof(bool), TOTAL_MAX_CACHED_SUBXIDS), &found); + KnownAssignedXidsNext = (int32 *) + ShmemInitStruct("KnownAssignedXidsNext", + mul_size(sizeof(int32), TOTAL_MAX_CACHED_SUBXIDS), + &found); + for (int i = 0; i < TOTAL_MAX_CACHED_SUBXIDS; i++) + KnownAssignedXidsNext[i] = i + 1; } } @@ -4539,7 +4546,15 @@ ExpireOldKnownAssignedTransactionIds(TransactionId xid) * XID entry itself. This preserves the property that the XID entries are * sorted, so we can do binary searches easily. Periodically we compress * out the unused entries; that's much cheaper than having to compress the - * array immediately on every deletion. + * array immediately on every deletion. Also, we lazily maintain indexes + * in KnownAssignedXidsNext[] array to skip known to be invalid xids. + * It helps to skip the gaps; it could significantly increase performance in + * the case of long transactions on the primary. KnownAssignedXidsNext + * is updated during taking the snapshot. The KnownAssignedXidsNext + * contains not an index to the next valid xid but a hint which helps to + * faster get to the next valid xid. KnownAssignedXidsNext[] values read + * and updated without additional locking because four-bytes read-writes are + * assumed to be atomic. * * The actually valid items in KnownAssignedXids[] and KnownAssignedXidsValid[] * are those with indexes tail <= i < head; items outside this subscript range @@ -4577,7 +4592,7 @@ ExpireOldKnownAssignedTransactionIds(TransactionId xid) * must happen) * * Compressing the array is O(S) and requires exclusive lock * * Removing an XID is O(logS) and requires exclusive lock - * * Taking a snapshot is O(S) and requires shared lock + * * Taking a snapshot is O(S), amortized O(N) next call; requires shared lock * * Checking for an XID is O(logS) and requires shared lock * * In comparison, using a hash table for KnownAssignedXids would mean that @@ -4637,12 +4652,13 @@ KnownAssignedXidsCompress(bool force) * re-aligning data to 0th element. */ compress_index = 0; - for (i = tail; i < head; i++) + for (i = tail; i < head; i = KnownAssignedXidsNext[i]) { if (KnownAssignedXidsValid[i]) { KnownAssignedXids[compress_index] = KnownAssignedXids[i]; KnownAssignedXidsValid[compress_index] = true; + KnownAssignedXidsNext[compress_index] = compress_index + 1; compress_index++; } } @@ -4745,6 +4761,7 @@ KnownAssignedXidsAdd(TransactionId from_xid, TransactionId to_xid, { KnownAssignedXids[head] = next_xid; KnownAssignedXidsValid[head] = true; + KnownAssignedXidsNext[head] = head + 1; TransactionIdAdvance(next_xid); head++; } @@ -4960,7 +4977,7 @@ KnownAssignedXidsRemovePreceding(TransactionId removeXid) tail = pArray->tailKnownAssignedXids; head = pArray->headKnownAssignedXids; - for (i = tail; i < head; i++) + for (i = tail; i < head; i = KnownAssignedXidsNext[i]) { if (KnownAssignedXidsValid[i]) { @@ -4983,7 +5000,7 @@ KnownAssignedXidsRemovePreceding(TransactionId removeXid) /* * Advance the tail pointer if we've marked the tail item invalid. */ - for (i = tail; i < head; i++) + for (i = tail; i < head; i = KnownAssignedXidsNext[i]) { if (KnownAssignedXidsValid[i]) break; @@ -5033,7 +5050,9 @@ KnownAssignedXidsGetAndSetXmin(TransactionId *xarray, TransactionId *xmin, int count = 0; int head, tail; - int i; + int i, + prev, + prevNext; /* * Fetch head just once, since it may change while we loop. We can stop @@ -5047,9 +5066,11 @@ KnownAssignedXidsGetAndSetXmin(TransactionId *xarray, TransactionId *xmin, SpinLockAcquire(&procArray->known_assigned_xids_lck); tail = procArray->tailKnownAssignedXids; head = procArray->headKnownAssignedXids; + prev = tail; + prevNext = KnownAssignedXidsNext[prev]; SpinLockRelease(&procArray->known_assigned_xids_lck); - for (i = tail; i < head; i++) + for (i = tail; i < head; i = KnownAssignedXidsNext[i]) { /* Skip any gaps in the array */ if (KnownAssignedXidsValid[i]) @@ -5074,6 +5095,23 @@ KnownAssignedXidsGetAndSetXmin(TransactionId *xarray, TransactionId *xmin, /* Add knownXid into output array */ xarray[count++] = knownXid; + + if (prev != i) + { + /* + * Do not touch the cache if value is unchanged. This way we + * can avoid additional cache miss. + */ + if (i != prevNext) + KnownAssignedXidsNext[prev] = i; + /* + * Remember this xid as previous valid. Also, manually store + * prevNext from current fetched value to avoid additional + * atomic read. + */ + prev = i; + prevNext = KnownAssignedXidsNext[i]; + } } } @@ -5099,7 +5137,7 @@ KnownAssignedXidsGetOldestXmin(void) head = procArray->headKnownAssignedXids; SpinLockRelease(&procArray->known_assigned_xids_lck); - for (i = tail; i < head; i++) + for (i = tail; i < head; i = KnownAssignedXidsNext[i]) { /* Skip any gaps in the array */ if (KnownAssignedXidsValid[i]) @@ -5134,7 +5172,7 @@ KnownAssignedXidsDisplay(int trace_level) initStringInfo(&buf); - for (i = tail; i < head; i++) + for (i = tail; i < head; i += KnownAssignedXidsNext[i]) { if (KnownAssignedXidsValid[i]) { -- 2.25.1