@@ -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
@@ -336,7 +347,7 @@ static void DisplayXidCache(void);
336
347
#endif /* XIDCACHE_DEBUG */
337
348
338
349
/* Primitives for KnownAssignedXids array handling for standby */
339
- static void KnownAssignedXidsCompress (bool force );
350
+ static void KnownAssignedXidsCompress (KAXCompressReason reason , bool haveLock );
340
351
static void KnownAssignedXidsAdd (TransactionId from_xid , TransactionId to_xid ,
341
352
bool exclusive_lock );
342
353
static bool KnownAssignedXidsSearch (TransactionId xid , bool remove );
@@ -4509,6 +4520,17 @@ ExpireOldKnownAssignedTransactionIds(TransactionId xid)
4509
4520
LWLockRelease (ProcArrayLock );
4510
4521
}
4511
4522
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
+
4512
4534
4513
4535
/*
4514
4536
* Private module functions to manipulate KnownAssignedXids
@@ -4591,50 +4613,101 @@ ExpireOldKnownAssignedTransactionIds(TransactionId xid)
4591
4613
* so there is an optimal point for any workload mix. We use a heuristic to
4592
4614
* decide when to compress the array, though trimming also helps reduce
4593
4615
* 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.
4595
4619
*/
4596
4620
4597
4621
4598
4622
/*
4599
4623
* Compress KnownAssignedXids by shifting valid data down to the start of the
4600
4624
* array, removing any gaps.
4601
4625
*
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.
4604
4628
*
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.
4606
4631
*/
4607
4632
static void
4608
- KnownAssignedXidsCompress (bool force )
4633
+ KnownAssignedXidsCompress (KAXCompressReason reason , bool haveLock )
4609
4634
{
4610
4635
ProcArrayStruct * pArray = procArray ;
4611
4636
int head ,
4612
- tail ;
4637
+ tail ,
4638
+ nelements ;
4613
4639
int compress_index ;
4614
4640
int i ;
4615
4641
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
+ */
4617
4654
head = pArray -> headKnownAssignedXids ;
4618
4655
tail = pArray -> tailKnownAssignedXids ;
4656
+ nelements = head - tail ;
4619
4657
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 )
4621
4665
{
4622
4666
/*
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.
4631
4679
*/
4632
- int nelements = head - tail ;
4680
+ if ((transactionEndsCounter ++ ) % KAX_COMPRESS_FREQUENCY != 0 )
4681
+ return ;
4633
4682
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 )
4636
4688
return ;
4637
4689
}
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 );
4638
4711
4639
4712
/*
4640
4713
* We compress the array by reading the valid values from tail to head,
@@ -4650,9 +4723,16 @@ KnownAssignedXidsCompress(bool force)
4650
4723
compress_index ++ ;
4651
4724
}
4652
4725
}
4726
+ Assert (compress_index == pArray -> numKnownAssignedXids );
4653
4727
4654
4728
pArray -> tailKnownAssignedXids = 0 ;
4655
4729
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 ();
4656
4736
}
4657
4737
4658
4738
/*
@@ -4724,18 +4804,11 @@ KnownAssignedXidsAdd(TransactionId from_xid, TransactionId to_xid,
4724
4804
*/
4725
4805
if (head + nxids > pArray -> maxKnownAssignedXids )
4726
4806
{
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 );
4732
4808
4733
4809
head = pArray -> headKnownAssignedXids ;
4734
4810
/* note: we no longer care about the tail pointer */
4735
4811
4736
- if (!exclusive_lock )
4737
- LWLockRelease (ProcArrayLock );
4738
-
4739
4812
/*
4740
4813
* If it still won't fit then we're out of memory
4741
4814
*/
@@ -4929,7 +5002,7 @@ KnownAssignedXidsRemoveTree(TransactionId xid, int nsubxids,
4929
5002
KnownAssignedXidsRemove (subxids [i ]);
4930
5003
4931
5004
/* Opportunistically compress the array */
4932
- KnownAssignedXidsCompress (false );
5005
+ KnownAssignedXidsCompress (KAX_TRANSACTION_END , true );
4933
5006
}
4934
5007
4935
5008
/*
@@ -5004,7 +5077,7 @@ KnownAssignedXidsRemovePreceding(TransactionId removeXid)
5004
5077
}
5005
5078
5006
5079
/* Opportunistically compress the array */
5007
- KnownAssignedXidsCompress (false );
5080
+ KnownAssignedXidsCompress (KAX_PRUNE , true );
5008
5081
}
5009
5082
5010
5083
/*
0 commit comments