@@ -257,6 +257,17 @@ typedef enum GlobalVisHorizonKind
257
257
VISHORIZON_TEMP
258
258
} GlobalVisHorizonKind ;
259
259
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
+
260
271
261
272
static ProcArrayStruct * procArray ;
262
273
@@ -341,7 +352,7 @@ static bool HaveVirtualXIDsDelayingChkptGuts(VirtualTransactionId *vxids,
341
352
int nvxids , int type );
342
353
343
354
/* Primitives for KnownAssignedXids array handling for standby */
344
- static void KnownAssignedXidsCompress (bool force );
355
+ static void KnownAssignedXidsCompress (KAXCompressReason reason , bool haveLock );
345
356
static void KnownAssignedXidsAdd (TransactionId from_xid , TransactionId to_xid ,
346
357
bool exclusive_lock );
347
358
static bool KnownAssignedXidsSearch (TransactionId xid , bool remove );
@@ -4556,6 +4567,17 @@ ExpireOldKnownAssignedTransactionIds(TransactionId xid)
4556
4567
LWLockRelease (ProcArrayLock );
4557
4568
}
4558
4569
4570
+ /*
4571
+ * KnownAssignedTransactionIdsIdleMaintenance
4572
+ * Opportunistically do maintenance work when the startup process
4573
+ * is about to go idle.
4574
+ */
4575
+ void
4576
+ KnownAssignedTransactionIdsIdleMaintenance (void )
4577
+ {
4578
+ KnownAssignedXidsCompress (KAX_STARTUP_PROCESS_IDLE , false);
4579
+ }
4580
+
4559
4581
4560
4582
/*
4561
4583
* Private module functions to manipulate KnownAssignedXids
@@ -4638,50 +4660,101 @@ ExpireOldKnownAssignedTransactionIds(TransactionId xid)
4638
4660
* so there is an optimal point for any workload mix. We use a heuristic to
4639
4661
* decide when to compress the array, though trimming also helps reduce
4640
4662
* frequency of compressing. The heuristic requires us to track the number of
4641
- * currently valid XIDs in the array.
4663
+ * currently valid XIDs in the array (N). Except in special cases, we'll
4664
+ * compress when S >= 2N. Bounding S at 2N in turn bounds the time for
4665
+ * taking a snapshot to be O(N), which it would have to be anyway.
4642
4666
*/
4643
4667
4644
4668
4645
4669
/*
4646
4670
* Compress KnownAssignedXids by shifting valid data down to the start of the
4647
4671
* array, removing any gaps.
4648
4672
*
4649
- * A compression step is forced if "force " is true , otherwise we do it
4650
- * only if a heuristic indicates it's a good time to do it.
4673
+ * A compression step is forced if "reason " is KAX_NO_SPACE , otherwise
4674
+ * we do it only if a heuristic indicates it's a good time to do it.
4651
4675
*
4652
- * Caller must hold ProcArrayLock in exclusive mode.
4676
+ * Compression requires holding ProcArrayLock in exclusive mode.
4677
+ * Caller must pass haveLock = true if it already holds the lock.
4653
4678
*/
4654
4679
static void
4655
- KnownAssignedXidsCompress (bool force )
4680
+ KnownAssignedXidsCompress (KAXCompressReason reason , bool haveLock )
4656
4681
{
4657
4682
ProcArrayStruct * pArray = procArray ;
4658
4683
int head ,
4659
- tail ;
4684
+ tail ,
4685
+ nelements ;
4660
4686
int compress_index ;
4661
4687
int i ;
4662
4688
4663
- /* no spinlock required since we hold ProcArrayLock exclusively */
4689
+ /* Counters for compression heuristics */
4690
+ static unsigned int transactionEndsCounter ;
4691
+ static TimestampTz lastCompressTs ;
4692
+
4693
+ /* Tuning constants */
4694
+ #define KAX_COMPRESS_FREQUENCY 128 /* in transactions */
4695
+ #define KAX_COMPRESS_IDLE_INTERVAL 1000 /* in ms */
4696
+
4697
+ /*
4698
+ * Since only the startup process modifies the head/tail pointers, we
4699
+ * don't need a lock to read them here.
4700
+ */
4664
4701
head = pArray -> headKnownAssignedXids ;
4665
4702
tail = pArray -> tailKnownAssignedXids ;
4703
+ nelements = head - tail ;
4666
4704
4667
- if (!force )
4705
+ /*
4706
+ * If we can choose whether to compress, use a heuristic to avoid
4707
+ * compressing too often or not often enough. "Compress" here simply
4708
+ * means moving the values to the beginning of the array, so it is not as
4709
+ * complex or costly as typical data compression algorithms.
4710
+ */
4711
+ if (nelements == pArray -> numKnownAssignedXids )
4668
4712
{
4669
4713
/*
4670
- * If we can choose how much to compress, use a heuristic to avoid
4671
- * compressing too often or not often enough.
4672
- *
4673
- * Heuristic is if we have a large enough current spread and less than
4674
- * 50% of the elements are currently in use, then compress. This
4675
- * should ensure we compress fairly infrequently. We could compress
4676
- * less often though the virtual array would spread out more and
4677
- * snapshots would become more expensive.
4714
+ * When there are no gaps between head and tail, don't bother to
4715
+ * compress, except in the KAX_NO_SPACE case where we must compress to
4716
+ * create some space after the head.
4717
+ */
4718
+ if (reason != KAX_NO_SPACE )
4719
+ return ;
4720
+ }
4721
+ else if (reason == KAX_TRANSACTION_END )
4722
+ {
4723
+ /*
4724
+ * Consider compressing only once every so many commits. Frequency
4725
+ * determined by benchmarks.
4678
4726
*/
4679
- int nelements = head - tail ;
4727
+ if ((transactionEndsCounter ++ ) % KAX_COMPRESS_FREQUENCY != 0 )
4728
+ return ;
4680
4729
4681
- if (nelements < 4 * PROCARRAY_MAXPROCS ||
4682
- nelements < 2 * pArray -> numKnownAssignedXids )
4730
+ /*
4731
+ * Furthermore, compress only if the used part of the array is less
4732
+ * than 50% full (see comments above).
4733
+ */
4734
+ if (nelements < 2 * pArray -> numKnownAssignedXids )
4683
4735
return ;
4684
4736
}
4737
+ else if (reason == KAX_STARTUP_PROCESS_IDLE )
4738
+ {
4739
+ /*
4740
+ * We're about to go idle for lack of new WAL, so we might as well
4741
+ * compress. But not too often, to avoid ProcArray lock contention
4742
+ * with readers.
4743
+ */
4744
+ if (lastCompressTs != 0 )
4745
+ {
4746
+ TimestampTz compress_after ;
4747
+
4748
+ compress_after = TimestampTzPlusMilliseconds (lastCompressTs ,
4749
+ KAX_COMPRESS_IDLE_INTERVAL );
4750
+ if (GetCurrentTimestamp () < compress_after )
4751
+ return ;
4752
+ }
4753
+ }
4754
+
4755
+ /* Need to compress, so get the lock if we don't have it. */
4756
+ if (!haveLock )
4757
+ LWLockAcquire (ProcArrayLock , LW_EXCLUSIVE );
4685
4758
4686
4759
/*
4687
4760
* We compress the array by reading the valid values from tail to head,
@@ -4697,9 +4770,16 @@ KnownAssignedXidsCompress(bool force)
4697
4770
compress_index ++ ;
4698
4771
}
4699
4772
}
4773
+ Assert (compress_index == pArray -> numKnownAssignedXids );
4700
4774
4701
4775
pArray -> tailKnownAssignedXids = 0 ;
4702
4776
pArray -> headKnownAssignedXids = compress_index ;
4777
+
4778
+ if (!haveLock )
4779
+ LWLockRelease (ProcArrayLock );
4780
+
4781
+ /* Update timestamp for maintenance. No need to hold lock for this. */
4782
+ lastCompressTs = GetCurrentTimestamp ();
4703
4783
}
4704
4784
4705
4785
/*
@@ -4771,18 +4851,11 @@ KnownAssignedXidsAdd(TransactionId from_xid, TransactionId to_xid,
4771
4851
*/
4772
4852
if (head + nxids > pArray -> maxKnownAssignedXids )
4773
4853
{
4774
- /* must hold lock to compress */
4775
- if (!exclusive_lock )
4776
- LWLockAcquire (ProcArrayLock , LW_EXCLUSIVE );
4777
-
4778
- KnownAssignedXidsCompress (true);
4854
+ KnownAssignedXidsCompress (KAX_NO_SPACE , exclusive_lock );
4779
4855
4780
4856
head = pArray -> headKnownAssignedXids ;
4781
4857
/* note: we no longer care about the tail pointer */
4782
4858
4783
- if (!exclusive_lock )
4784
- LWLockRelease (ProcArrayLock );
4785
-
4786
4859
/*
4787
4860
* If it still won't fit then we're out of memory
4788
4861
*/
@@ -4976,7 +5049,7 @@ KnownAssignedXidsRemoveTree(TransactionId xid, int nsubxids,
4976
5049
KnownAssignedXidsRemove (subxids [i ]);
4977
5050
4978
5051
/* Opportunistically compress the array */
4979
- KnownAssignedXidsCompress (false );
5052
+ KnownAssignedXidsCompress (KAX_TRANSACTION_END , true );
4980
5053
}
4981
5054
4982
5055
/*
@@ -5051,7 +5124,7 @@ KnownAssignedXidsRemovePreceding(TransactionId removeXid)
5051
5124
}
5052
5125
5053
5126
/* Opportunistically compress the array */
5054
- KnownAssignedXidsCompress (false );
5127
+ KnownAssignedXidsCompress (KAX_PRUNE , true );
5055
5128
}
5056
5129
5057
5130
/*
0 commit comments