@@ -101,6 +101,17 @@ static ProcArrayStruct *procArray;
101
101
static PGPROC * allProcs ;
102
102
static PGXACT * allPgXact ;
103
103
104
+ /*
105
+ * Reason codes for KnownAssignedXidsCompress().
106
+ */
107
+ typedef enum KAXCompressReason
108
+ {
109
+ KAX_NO_SPACE , /* need to free up space at array end */
110
+ KAX_PRUNE , /* we just pruned old entries */
111
+ KAX_TRANSACTION_END , /* we just committed/removed some XIDs */
112
+ KAX_STARTUP_PROCESS_IDLE /* startup process is about to sleep */
113
+ } KAXCompressReason ;
114
+
104
115
/*
105
116
* Cache to reduce overhead of repeated calls to TransactionIdIsInProgress()
106
117
*/
@@ -163,7 +174,7 @@ static bool HaveVirtualXIDsDelayingChkptGuts(VirtualTransactionId *vxids,
163
174
int nvxids , int type );
164
175
165
176
/* Primitives for KnownAssignedXids array handling for standby */
166
- static void KnownAssignedXidsCompress (bool force );
177
+ static void KnownAssignedXidsCompress (KAXCompressReason reason , bool haveLock );
167
178
static void KnownAssignedXidsAdd (TransactionId from_xid , TransactionId to_xid ,
168
179
bool exclusive_lock );
169
180
static bool KnownAssignedXidsSearch (TransactionId xid , bool remove );
@@ -3415,6 +3426,17 @@ ExpireOldKnownAssignedTransactionIds(TransactionId xid)
3415
3426
LWLockRelease (ProcArrayLock );
3416
3427
}
3417
3428
3429
+ /*
3430
+ * KnownAssignedTransactionIdsIdleMaintenance
3431
+ * Opportunistically do maintenance work when the startup process
3432
+ * is about to go idle.
3433
+ */
3434
+ void
3435
+ KnownAssignedTransactionIdsIdleMaintenance (void )
3436
+ {
3437
+ KnownAssignedXidsCompress (KAX_STARTUP_PROCESS_IDLE , false);
3438
+ }
3439
+
3418
3440
3419
3441
/*
3420
3442
* Private module functions to manipulate KnownAssignedXids
@@ -3497,50 +3519,101 @@ ExpireOldKnownAssignedTransactionIds(TransactionId xid)
3497
3519
* so there is an optimal point for any workload mix. We use a heuristic to
3498
3520
* decide when to compress the array, though trimming also helps reduce
3499
3521
* frequency of compressing. The heuristic requires us to track the number of
3500
- * currently valid XIDs in the array.
3522
+ * currently valid XIDs in the array (N). Except in special cases, we'll
3523
+ * compress when S >= 2N. Bounding S at 2N in turn bounds the time for
3524
+ * taking a snapshot to be O(N), which it would have to be anyway.
3501
3525
*/
3502
3526
3503
3527
3504
3528
/*
3505
3529
* Compress KnownAssignedXids by shifting valid data down to the start of the
3506
3530
* array, removing any gaps.
3507
3531
*
3508
- * A compression step is forced if "force " is true , otherwise we do it
3509
- * only if a heuristic indicates it's a good time to do it.
3532
+ * A compression step is forced if "reason " is KAX_NO_SPACE , otherwise
3533
+ * we do it only if a heuristic indicates it's a good time to do it.
3510
3534
*
3511
- * Caller must hold ProcArrayLock in exclusive mode.
3535
+ * Compression requires holding ProcArrayLock in exclusive mode.
3536
+ * Caller must pass haveLock = true if it already holds the lock.
3512
3537
*/
3513
3538
static void
3514
- KnownAssignedXidsCompress (bool force )
3539
+ KnownAssignedXidsCompress (KAXCompressReason reason , bool haveLock )
3515
3540
{
3516
3541
ProcArrayStruct * pArray = procArray ;
3517
3542
int head ,
3518
- tail ;
3543
+ tail ,
3544
+ nelements ;
3519
3545
int compress_index ;
3520
3546
int i ;
3521
3547
3522
- /* no spinlock required since we hold ProcArrayLock exclusively */
3548
+ /* Counters for compression heuristics */
3549
+ static unsigned int transactionEndsCounter ;
3550
+ static TimestampTz lastCompressTs ;
3551
+
3552
+ /* Tuning constants */
3553
+ #define KAX_COMPRESS_FREQUENCY 128 /* in transactions */
3554
+ #define KAX_COMPRESS_IDLE_INTERVAL 1000 /* in ms */
3555
+
3556
+ /*
3557
+ * Since only the startup process modifies the head/tail pointers, we
3558
+ * don't need a lock to read them here.
3559
+ */
3523
3560
head = pArray -> headKnownAssignedXids ;
3524
3561
tail = pArray -> tailKnownAssignedXids ;
3562
+ nelements = head - tail ;
3525
3563
3526
- if (!force )
3564
+ /*
3565
+ * If we can choose whether to compress, use a heuristic to avoid
3566
+ * compressing too often or not often enough. "Compress" here simply
3567
+ * means moving the values to the beginning of the array, so it is not as
3568
+ * complex or costly as typical data compression algorithms.
3569
+ */
3570
+ if (nelements == pArray -> numKnownAssignedXids )
3527
3571
{
3528
3572
/*
3529
- * If we can choose how much to compress, use a heuristic to avoid
3530
- * compressing too often or not often enough.
3531
- *
3532
- * Heuristic is if we have a large enough current spread and less than
3533
- * 50% of the elements are currently in use, then compress. This
3534
- * should ensure we compress fairly infrequently. We could compress
3535
- * less often though the virtual array would spread out more and
3536
- * snapshots would become more expensive.
3573
+ * When there are no gaps between head and tail, don't bother to
3574
+ * compress, except in the KAX_NO_SPACE case where we must compress to
3575
+ * create some space after the head.
3537
3576
*/
3538
- int nelements = head - tail ;
3577
+ if (reason != KAX_NO_SPACE )
3578
+ return ;
3579
+ }
3580
+ else if (reason == KAX_TRANSACTION_END )
3581
+ {
3582
+ /*
3583
+ * Consider compressing only once every so many commits. Frequency
3584
+ * determined by benchmarks.
3585
+ */
3586
+ if ((transactionEndsCounter ++ ) % KAX_COMPRESS_FREQUENCY != 0 )
3587
+ return ;
3539
3588
3540
- if (nelements < 4 * PROCARRAY_MAXPROCS ||
3541
- nelements < 2 * pArray -> numKnownAssignedXids )
3589
+ /*
3590
+ * Furthermore, compress only if the used part of the array is less
3591
+ * than 50% full (see comments above).
3592
+ */
3593
+ if (nelements < 2 * pArray -> numKnownAssignedXids )
3542
3594
return ;
3543
3595
}
3596
+ else if (reason == KAX_STARTUP_PROCESS_IDLE )
3597
+ {
3598
+ /*
3599
+ * We're about to go idle for lack of new WAL, so we might as well
3600
+ * compress. But not too often, to avoid ProcArray lock contention
3601
+ * with readers.
3602
+ */
3603
+ if (lastCompressTs != 0 )
3604
+ {
3605
+ TimestampTz compress_after ;
3606
+
3607
+ compress_after = TimestampTzPlusMilliseconds (lastCompressTs ,
3608
+ KAX_COMPRESS_IDLE_INTERVAL );
3609
+ if (GetCurrentTimestamp () < compress_after )
3610
+ return ;
3611
+ }
3612
+ }
3613
+
3614
+ /* Need to compress, so get the lock if we don't have it. */
3615
+ if (!haveLock )
3616
+ LWLockAcquire (ProcArrayLock , LW_EXCLUSIVE );
3544
3617
3545
3618
/*
3546
3619
* We compress the array by reading the valid values from tail to head,
@@ -3556,9 +3629,16 @@ KnownAssignedXidsCompress(bool force)
3556
3629
compress_index ++ ;
3557
3630
}
3558
3631
}
3632
+ Assert (compress_index == pArray -> numKnownAssignedXids );
3559
3633
3560
3634
pArray -> tailKnownAssignedXids = 0 ;
3561
3635
pArray -> headKnownAssignedXids = compress_index ;
3636
+
3637
+ if (!haveLock )
3638
+ LWLockRelease (ProcArrayLock );
3639
+
3640
+ /* Update timestamp for maintenance. No need to hold lock for this. */
3641
+ lastCompressTs = GetCurrentTimestamp ();
3562
3642
}
3563
3643
3564
3644
/*
@@ -3630,18 +3710,11 @@ KnownAssignedXidsAdd(TransactionId from_xid, TransactionId to_xid,
3630
3710
*/
3631
3711
if (head + nxids > pArray -> maxKnownAssignedXids )
3632
3712
{
3633
- /* must hold lock to compress */
3634
- if (!exclusive_lock )
3635
- LWLockAcquire (ProcArrayLock , LW_EXCLUSIVE );
3636
-
3637
- KnownAssignedXidsCompress (true);
3713
+ KnownAssignedXidsCompress (KAX_NO_SPACE , exclusive_lock );
3638
3714
3639
3715
head = pArray -> headKnownAssignedXids ;
3640
3716
/* note: we no longer care about the tail pointer */
3641
3717
3642
- if (!exclusive_lock )
3643
- LWLockRelease (ProcArrayLock );
3644
-
3645
3718
/*
3646
3719
* If it still won't fit then we're out of memory
3647
3720
*/
@@ -3835,7 +3908,7 @@ KnownAssignedXidsRemoveTree(TransactionId xid, int nsubxids,
3835
3908
KnownAssignedXidsRemove (subxids [i ]);
3836
3909
3837
3910
/* Opportunistically compress the array */
3838
- KnownAssignedXidsCompress (false );
3911
+ KnownAssignedXidsCompress (KAX_TRANSACTION_END , true );
3839
3912
}
3840
3913
3841
3914
/*
@@ -3910,7 +3983,7 @@ KnownAssignedXidsRemovePreceding(TransactionId removeXid)
3910
3983
}
3911
3984
3912
3985
/* Opportunistically compress the array */
3913
- KnownAssignedXidsCompress (false );
3986
+ KnownAssignedXidsCompress (KAX_PRUNE , true );
3914
3987
}
3915
3988
3916
3989
/*
0 commit comments