@@ -257,6 +257,17 @@ typedef enum GlobalVisHorizonKind
257257 VISHORIZON_TEMP
258258} GlobalVisHorizonKind ;
259259
260+ /*
261+ * Reason codes for KnownAssignedXidsCompress().
262+ */
263+ typedef enum KAXCompressReason
264+ {
265+ KAX_NO_SPACE , /* need to free up space at array end */
266+ KAX_PRUNE , /* we just pruned old entries */
267+ KAX_TRANSACTION_END , /* we just committed/removed some XIDs */
268+ KAX_STARTUP_PROCESS_IDLE /* startup process is about to sleep */
269+ } KAXCompressReason ;
270+
260271
261272static ProcArrayStruct * procArray ;
262273
@@ -336,7 +347,7 @@ static void DisplayXidCache(void);
336347#endif /* XIDCACHE_DEBUG */
337348
338349/* Primitives for KnownAssignedXids array handling for standby */
339- static void KnownAssignedXidsCompress (bool force );
350+ static void KnownAssignedXidsCompress (KAXCompressReason reason , bool haveLock );
340351static void KnownAssignedXidsAdd (TransactionId from_xid , TransactionId to_xid ,
341352 bool exclusive_lock );
342353static bool KnownAssignedXidsSearch (TransactionId xid , bool remove );
@@ -4509,6 +4520,17 @@ ExpireOldKnownAssignedTransactionIds(TransactionId xid)
45094520 LWLockRelease (ProcArrayLock );
45104521}
45114522
4523+ /*
4524+ * KnownAssignedTransactionIdsIdleMaintenance
4525+ * Opportunistically do maintenance work when the startup process
4526+ * is about to go idle.
4527+ */
4528+ void
4529+ KnownAssignedTransactionIdsIdleMaintenance (void )
4530+ {
4531+ KnownAssignedXidsCompress (KAX_STARTUP_PROCESS_IDLE , false);
4532+ }
4533+
45124534
45134535/*
45144536 * Private module functions to manipulate KnownAssignedXids
@@ -4591,50 +4613,101 @@ ExpireOldKnownAssignedTransactionIds(TransactionId xid)
45914613 * so there is an optimal point for any workload mix. We use a heuristic to
45924614 * decide when to compress the array, though trimming also helps reduce
45934615 * frequency of compressing. The heuristic requires us to track the number of
4594- * currently valid XIDs in the array.
4616+ * currently valid XIDs in the array (N). Except in special cases, we'll
4617+ * compress when S >= 2N. Bounding S at 2N in turn bounds the time for
4618+ * taking a snapshot to be O(N), which it would have to be anyway.
45954619 */
45964620
45974621
45984622/*
45994623 * Compress KnownAssignedXids by shifting valid data down to the start of the
46004624 * array, removing any gaps.
46014625 *
4602- * A compression step is forced if "force " is true , otherwise we do it
4603- * only if a heuristic indicates it's a good time to do it.
4626+ * A compression step is forced if "reason " is KAX_NO_SPACE , otherwise
4627+ * we do it only if a heuristic indicates it's a good time to do it.
46044628 *
4605- * Caller must hold ProcArrayLock in exclusive mode.
4629+ * Compression requires holding ProcArrayLock in exclusive mode.
4630+ * Caller must pass haveLock = true if it already holds the lock.
46064631 */
46074632static void
4608- KnownAssignedXidsCompress (bool force )
4633+ KnownAssignedXidsCompress (KAXCompressReason reason , bool haveLock )
46094634{
46104635 ProcArrayStruct * pArray = procArray ;
46114636 int head ,
4612- tail ;
4637+ tail ,
4638+ nelements ;
46134639 int compress_index ;
46144640 int i ;
46154641
4616- /* no spinlock required since we hold ProcArrayLock exclusively */
4642+ /* Counters for compression heuristics */
4643+ static unsigned int transactionEndsCounter ;
4644+ static TimestampTz lastCompressTs ;
4645+
4646+ /* Tuning constants */
4647+ #define KAX_COMPRESS_FREQUENCY 128 /* in transactions */
4648+ #define KAX_COMPRESS_IDLE_INTERVAL 1000 /* in ms */
4649+
4650+ /*
4651+ * Since only the startup process modifies the head/tail pointers, we
4652+ * don't need a lock to read them here.
4653+ */
46174654 head = pArray -> headKnownAssignedXids ;
46184655 tail = pArray -> tailKnownAssignedXids ;
4656+ nelements = head - tail ;
46194657
4620- if (!force )
4658+ /*
4659+ * If we can choose whether to compress, use a heuristic to avoid
4660+ * compressing too often or not often enough. "Compress" here simply
4661+ * means moving the values to the beginning of the array, so it is not as
4662+ * complex or costly as typical data compression algorithms.
4663+ */
4664+ if (nelements == pArray -> numKnownAssignedXids )
46214665 {
46224666 /*
4623- * If we can choose how much to compress, use a heuristic to avoid
4624- * compressing too often or not often enough.
4625- *
4626- * Heuristic is if we have a large enough current spread and less than
4627- * 50% of the elements are currently in use, then compress. This
4628- * should ensure we compress fairly infrequently. We could compress
4629- * less often though the virtual array would spread out more and
4630- * snapshots would become more expensive.
4667+ * When there are no gaps between head and tail, don't bother to
4668+ * compress, except in the KAX_NO_SPACE case where we must compress to
4669+ * create some space after the head.
4670+ */
4671+ if (reason != KAX_NO_SPACE )
4672+ return ;
4673+ }
4674+ else if (reason == KAX_TRANSACTION_END )
4675+ {
4676+ /*
4677+ * Consider compressing only once every so many commits. Frequency
4678+ * determined by benchmarks.
46314679 */
4632- int nelements = head - tail ;
4680+ if ((transactionEndsCounter ++ ) % KAX_COMPRESS_FREQUENCY != 0 )
4681+ return ;
46334682
4634- if (nelements < 4 * PROCARRAY_MAXPROCS ||
4635- nelements < 2 * pArray -> numKnownAssignedXids )
4683+ /*
4684+ * Furthermore, compress only if the used part of the array is less
4685+ * than 50% full (see comments above).
4686+ */
4687+ if (nelements < 2 * pArray -> numKnownAssignedXids )
46364688 return ;
46374689 }
4690+ else if (reason == KAX_STARTUP_PROCESS_IDLE )
4691+ {
4692+ /*
4693+ * We're about to go idle for lack of new WAL, so we might as well
4694+ * compress. But not too often, to avoid ProcArray lock contention
4695+ * with readers.
4696+ */
4697+ if (lastCompressTs != 0 )
4698+ {
4699+ TimestampTz compress_after ;
4700+
4701+ compress_after = TimestampTzPlusMilliseconds (lastCompressTs ,
4702+ KAX_COMPRESS_IDLE_INTERVAL );
4703+ if (GetCurrentTimestamp () < compress_after )
4704+ return ;
4705+ }
4706+ }
4707+
4708+ /* Need to compress, so get the lock if we don't have it. */
4709+ if (!haveLock )
4710+ LWLockAcquire (ProcArrayLock , LW_EXCLUSIVE );
46384711
46394712 /*
46404713 * We compress the array by reading the valid values from tail to head,
@@ -4650,9 +4723,16 @@ KnownAssignedXidsCompress(bool force)
46504723 compress_index ++ ;
46514724 }
46524725 }
4726+ Assert (compress_index == pArray -> numKnownAssignedXids );
46534727
46544728 pArray -> tailKnownAssignedXids = 0 ;
46554729 pArray -> headKnownAssignedXids = compress_index ;
4730+
4731+ if (!haveLock )
4732+ LWLockRelease (ProcArrayLock );
4733+
4734+ /* Update timestamp for maintenance. No need to hold lock for this. */
4735+ lastCompressTs = GetCurrentTimestamp ();
46564736}
46574737
46584738/*
@@ -4724,18 +4804,11 @@ KnownAssignedXidsAdd(TransactionId from_xid, TransactionId to_xid,
47244804 */
47254805 if (head + nxids > pArray -> maxKnownAssignedXids )
47264806 {
4727- /* must hold lock to compress */
4728- if (!exclusive_lock )
4729- LWLockAcquire (ProcArrayLock , LW_EXCLUSIVE );
4730-
4731- KnownAssignedXidsCompress (true);
4807+ KnownAssignedXidsCompress (KAX_NO_SPACE , exclusive_lock );
47324808
47334809 head = pArray -> headKnownAssignedXids ;
47344810 /* note: we no longer care about the tail pointer */
47354811
4736- if (!exclusive_lock )
4737- LWLockRelease (ProcArrayLock );
4738-
47394812 /*
47404813 * If it still won't fit then we're out of memory
47414814 */
@@ -4929,7 +5002,7 @@ KnownAssignedXidsRemoveTree(TransactionId xid, int nsubxids,
49295002 KnownAssignedXidsRemove (subxids [i ]);
49305003
49315004 /* Opportunistically compress the array */
4932- KnownAssignedXidsCompress (false );
5005+ KnownAssignedXidsCompress (KAX_TRANSACTION_END , true );
49335006}
49345007
49355008/*
@@ -5004,7 +5077,7 @@ KnownAssignedXidsRemovePreceding(TransactionId removeXid)
50045077 }
50055078
50065079 /* Opportunistically compress the array */
5007- KnownAssignedXidsCompress (false );
5080+ KnownAssignedXidsCompress (KAX_PRUNE , true );
50085081}
50095082
50105083/*
0 commit comments