@@ -256,6 +256,17 @@ typedef enum GlobalVisHorizonKind
256
256
VISHORIZON_TEMP
257
257
} GlobalVisHorizonKind ;
258
258
259
+ /*
260
+ * Reason codes for KnownAssignedXidsCompress().
261
+ */
262
+ typedef enum KAXCompressReason
263
+ {
264
+ KAX_NO_SPACE , /* need to free up space at array end */
265
+ KAX_PRUNE , /* we just pruned old entries */
266
+ KAX_TRANSACTION_END , /* we just committed/removed some XIDs */
267
+ KAX_STARTUP_PROCESS_IDLE /* startup process is about to sleep */
268
+ } KAXCompressReason ;
269
+
259
270
260
271
static ProcArrayStruct * procArray ;
261
272
@@ -335,7 +346,7 @@ static void DisplayXidCache(void);
335
346
#endif /* XIDCACHE_DEBUG */
336
347
337
348
/* Primitives for KnownAssignedXids array handling for standby */
338
- static void KnownAssignedXidsCompress (bool force );
349
+ static void KnownAssignedXidsCompress (KAXCompressReason reason , bool haveLock );
339
350
static void KnownAssignedXidsAdd (TransactionId from_xid , TransactionId to_xid ,
340
351
bool exclusive_lock );
341
352
static bool KnownAssignedXidsSearch (TransactionId xid , bool remove );
@@ -4508,6 +4519,17 @@ ExpireOldKnownAssignedTransactionIds(TransactionId xid)
4508
4519
LWLockRelease (ProcArrayLock );
4509
4520
}
4510
4521
4522
+ /*
4523
+ * KnownAssignedTransactionIdsIdleMaintenance
4524
+ * Opportunistically do maintenance work when the startup process
4525
+ * is about to go idle.
4526
+ */
4527
+ void
4528
+ KnownAssignedTransactionIdsIdleMaintenance (void )
4529
+ {
4530
+ KnownAssignedXidsCompress (KAX_STARTUP_PROCESS_IDLE , false);
4531
+ }
4532
+
4511
4533
4512
4534
/*
4513
4535
* Private module functions to manipulate KnownAssignedXids
@@ -4590,50 +4612,101 @@ ExpireOldKnownAssignedTransactionIds(TransactionId xid)
4590
4612
* so there is an optimal point for any workload mix. We use a heuristic to
4591
4613
* decide when to compress the array, though trimming also helps reduce
4592
4614
* frequency of compressing. The heuristic requires us to track the number of
4593
- * currently valid XIDs in the array.
4615
+ * currently valid XIDs in the array (N). Except in special cases, we'll
4616
+ * compress when S >= 2N. Bounding S at 2N in turn bounds the time for
4617
+ * taking a snapshot to be O(N), which it would have to be anyway.
4594
4618
*/
4595
4619
4596
4620
4597
4621
/*
4598
4622
* Compress KnownAssignedXids by shifting valid data down to the start of the
4599
4623
* array, removing any gaps.
4600
4624
*
4601
- * A compression step is forced if "force " is true , otherwise we do it
4602
- * only if a heuristic indicates it's a good time to do it.
4625
+ * A compression step is forced if "reason " is KAX_NO_SPACE , otherwise
4626
+ * we do it only if a heuristic indicates it's a good time to do it.
4603
4627
*
4604
- * Caller must hold ProcArrayLock in exclusive mode.
4628
+ * Compression requires holding ProcArrayLock in exclusive mode.
4629
+ * Caller must pass haveLock = true if it already holds the lock.
4605
4630
*/
4606
4631
static void
4607
- KnownAssignedXidsCompress (bool force )
4632
+ KnownAssignedXidsCompress (KAXCompressReason reason , bool haveLock )
4608
4633
{
4609
4634
ProcArrayStruct * pArray = procArray ;
4610
4635
int head ,
4611
- tail ;
4636
+ tail ,
4637
+ nelements ;
4612
4638
int compress_index ;
4613
4639
int i ;
4614
4640
4615
- /* no spinlock required since we hold ProcArrayLock exclusively */
4641
+ /* Counters for compression heuristics */
4642
+ static unsigned int transactionEndsCounter ;
4643
+ static TimestampTz lastCompressTs ;
4644
+
4645
+ /* Tuning constants */
4646
+ #define KAX_COMPRESS_FREQUENCY 128 /* in transactions */
4647
+ #define KAX_COMPRESS_IDLE_INTERVAL 1000 /* in ms */
4648
+
4649
+ /*
4650
+ * Since only the startup process modifies the head/tail pointers, we
4651
+ * don't need a lock to read them here.
4652
+ */
4616
4653
head = pArray -> headKnownAssignedXids ;
4617
4654
tail = pArray -> tailKnownAssignedXids ;
4655
+ nelements = head - tail ;
4618
4656
4619
- if (!force )
4657
+ /*
4658
+ * If we can choose whether to compress, use a heuristic to avoid
4659
+ * compressing too often or not often enough. "Compress" here simply
4660
+ * means moving the values to the beginning of the array, so it is not as
4661
+ * complex or costly as typical data compression algorithms.
4662
+ */
4663
+ if (nelements == pArray -> numKnownAssignedXids )
4620
4664
{
4621
4665
/*
4622
- * If we can choose how much to compress, use a heuristic to avoid
4623
- * compressing too often or not often enough.
4624
- *
4625
- * Heuristic is if we have a large enough current spread and less than
4626
- * 50% of the elements are currently in use, then compress. This
4627
- * should ensure we compress fairly infrequently. We could compress
4628
- * less often though the virtual array would spread out more and
4629
- * snapshots would become more expensive.
4666
+ * When there are no gaps between head and tail, don't bother to
4667
+ * compress, except in the KAX_NO_SPACE case where we must compress to
4668
+ * create some space after the head.
4669
+ */
4670
+ if (reason != KAX_NO_SPACE )
4671
+ return ;
4672
+ }
4673
+ else if (reason == KAX_TRANSACTION_END )
4674
+ {
4675
+ /*
4676
+ * Consider compressing only once every so many commits. Frequency
4677
+ * determined by benchmarks.
4630
4678
*/
4631
- int nelements = head - tail ;
4679
+ if ((transactionEndsCounter ++ ) % KAX_COMPRESS_FREQUENCY != 0 )
4680
+ return ;
4632
4681
4633
- if (nelements < 4 * PROCARRAY_MAXPROCS ||
4634
- nelements < 2 * pArray -> numKnownAssignedXids )
4682
+ /*
4683
+ * Furthermore, compress only if the used part of the array is less
4684
+ * than 50% full (see comments above).
4685
+ */
4686
+ if (nelements < 2 * pArray -> numKnownAssignedXids )
4635
4687
return ;
4636
4688
}
4689
+ else if (reason == KAX_STARTUP_PROCESS_IDLE )
4690
+ {
4691
+ /*
4692
+ * We're about to go idle for lack of new WAL, so we might as well
4693
+ * compress. But not too often, to avoid ProcArray lock contention
4694
+ * with readers.
4695
+ */
4696
+ if (lastCompressTs != 0 )
4697
+ {
4698
+ TimestampTz compress_after ;
4699
+
4700
+ compress_after = TimestampTzPlusMilliseconds (lastCompressTs ,
4701
+ KAX_COMPRESS_IDLE_INTERVAL );
4702
+ if (GetCurrentTimestamp () < compress_after )
4703
+ return ;
4704
+ }
4705
+ }
4706
+
4707
+ /* Need to compress, so get the lock if we don't have it. */
4708
+ if (!haveLock )
4709
+ LWLockAcquire (ProcArrayLock , LW_EXCLUSIVE );
4637
4710
4638
4711
/*
4639
4712
* We compress the array by reading the valid values from tail to head,
@@ -4649,9 +4722,16 @@ KnownAssignedXidsCompress(bool force)
4649
4722
compress_index ++ ;
4650
4723
}
4651
4724
}
4725
+ Assert (compress_index == pArray -> numKnownAssignedXids );
4652
4726
4653
4727
pArray -> tailKnownAssignedXids = 0 ;
4654
4728
pArray -> headKnownAssignedXids = compress_index ;
4729
+
4730
+ if (!haveLock )
4731
+ LWLockRelease (ProcArrayLock );
4732
+
4733
+ /* Update timestamp for maintenance. No need to hold lock for this. */
4734
+ lastCompressTs = GetCurrentTimestamp ();
4655
4735
}
4656
4736
4657
4737
/*
@@ -4723,18 +4803,11 @@ KnownAssignedXidsAdd(TransactionId from_xid, TransactionId to_xid,
4723
4803
*/
4724
4804
if (head + nxids > pArray -> maxKnownAssignedXids )
4725
4805
{
4726
- /* must hold lock to compress */
4727
- if (!exclusive_lock )
4728
- LWLockAcquire (ProcArrayLock , LW_EXCLUSIVE );
4729
-
4730
- KnownAssignedXidsCompress (true);
4806
+ KnownAssignedXidsCompress (KAX_NO_SPACE , exclusive_lock );
4731
4807
4732
4808
head = pArray -> headKnownAssignedXids ;
4733
4809
/* note: we no longer care about the tail pointer */
4734
4810
4735
- if (!exclusive_lock )
4736
- LWLockRelease (ProcArrayLock );
4737
-
4738
4811
/*
4739
4812
* If it still won't fit then we're out of memory
4740
4813
*/
@@ -4928,7 +5001,7 @@ KnownAssignedXidsRemoveTree(TransactionId xid, int nsubxids,
4928
5001
KnownAssignedXidsRemove (subxids [i ]);
4929
5002
4930
5003
/* Opportunistically compress the array */
4931
- KnownAssignedXidsCompress (false );
5004
+ KnownAssignedXidsCompress (KAX_TRANSACTION_END , true );
4932
5005
}
4933
5006
4934
5007
/*
@@ -5003,7 +5076,7 @@ KnownAssignedXidsRemovePreceding(TransactionId removeXid)
5003
5076
}
5004
5077
5005
5078
/* Opportunistically compress the array */
5006
- KnownAssignedXidsCompress (false );
5079
+ KnownAssignedXidsCompress (KAX_PRUNE , true );
5007
5080
}
5008
5081
5009
5082
/*
0 commit comments