103
103
#define VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL 50 /* ms */
104
104
#define VACUUM_TRUNCATE_LOCK_TIMEOUT 5000 /* ms */
105
105
106
+ /*
107
+ * When a table is small (i.e. smaller than this), save cycles by avoiding
108
+ * repeated failsafe checks
109
+ */
110
+ #define FAILSAFE_MIN_PAGES \
111
+ ((BlockNumber) (((uint64) 4 * 1024 * 1024 * 1024) / BLCKSZ))
112
+
106
113
/*
107
114
* When a table has no indexes, vacuum the FSM after every 8GB, approximately
108
115
* (it won't be exact because we only vacuum FSM after processing a heap page
@@ -299,6 +306,8 @@ typedef struct LVRelState
299
306
/* Do index vacuuming/cleanup? */
300
307
bool do_index_vacuuming ;
301
308
bool do_index_cleanup ;
309
+ /* Wraparound failsafe in effect? (implies !do_index_vacuuming) */
310
+ bool do_failsafe ;
302
311
303
312
/* Buffer access strategy and parallel state */
304
313
BufferAccessStrategy bstrategy ;
@@ -393,12 +402,13 @@ static void lazy_scan_prune(LVRelState *vacrel, Buffer buf,
393
402
GlobalVisState * vistest ,
394
403
LVPagePruneState * prunestate );
395
404
static void lazy_vacuum (LVRelState * vacrel );
396
- static void lazy_vacuum_all_indexes (LVRelState * vacrel );
405
+ static bool lazy_vacuum_all_indexes (LVRelState * vacrel );
397
406
static void lazy_vacuum_heap_rel (LVRelState * vacrel );
398
407
static int lazy_vacuum_heap_page (LVRelState * vacrel , BlockNumber blkno ,
399
408
Buffer buffer , int tupindex , Buffer * vmbuffer );
400
409
static bool lazy_check_needs_freeze (Buffer buf , bool * hastup ,
401
410
LVRelState * vacrel );
411
+ static bool lazy_check_wraparound_failsafe (LVRelState * vacrel );
402
412
static void do_parallel_lazy_vacuum_all_indexes (LVRelState * vacrel );
403
413
static void do_parallel_lazy_cleanup_all_indexes (LVRelState * vacrel );
404
414
static void do_parallel_vacuum_or_cleanup (LVRelState * vacrel , int nworkers );
@@ -544,6 +554,7 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
544
554
& vacrel -> indrels );
545
555
vacrel -> do_index_vacuuming = true;
546
556
vacrel -> do_index_cleanup = true;
557
+ vacrel -> do_failsafe = false;
547
558
if (params -> index_cleanup == VACOPT_TERNARY_DISABLED )
548
559
{
549
560
vacrel -> do_index_vacuuming = false;
@@ -888,6 +899,12 @@ lazy_scan_heap(LVRelState *vacrel, VacuumParams *params, bool aggressive)
888
899
vacrel -> indstats = (IndexBulkDeleteResult * * )
889
900
palloc0 (vacrel -> nindexes * sizeof (IndexBulkDeleteResult * ));
890
901
902
+ /*
903
+ * Before beginning scan, check if it's already necessary to apply
904
+ * failsafe
905
+ */
906
+ lazy_check_wraparound_failsafe (vacrel );
907
+
891
908
/*
892
909
* Allocate the space for dead tuples. Note that this handles parallel
893
910
* VACUUM initialization as part of allocating shared memory space used
@@ -1311,12 +1328,17 @@ lazy_scan_heap(LVRelState *vacrel, VacuumParams *params, bool aggressive)
1311
1328
* Periodically perform FSM vacuuming to make newly-freed
1312
1329
* space visible on upper FSM pages. Note we have not yet
1313
1330
* performed FSM processing for blkno.
1331
+ *
1332
+ * Call lazy_check_wraparound_failsafe() here, too, since we
1333
+ * also don't want to do that too frequently, or too
1334
+ * infrequently.
1314
1335
*/
1315
1336
if (blkno - next_fsm_block_to_vacuum >= VACUUM_FSM_EVERY_PAGES )
1316
1337
{
1317
1338
FreeSpaceMapVacuumRange (vacrel -> rel , next_fsm_block_to_vacuum ,
1318
1339
blkno );
1319
1340
next_fsm_block_to_vacuum = blkno ;
1341
+ lazy_check_wraparound_failsafe (vacrel );
1320
1342
}
1321
1343
1322
1344
/*
@@ -1450,6 +1472,13 @@ lazy_scan_heap(LVRelState *vacrel, VacuumParams *params, bool aggressive)
1450
1472
* make available in cases where it's possible to truncate the
1451
1473
* page's line pointer array.
1452
1474
*
1475
+ * Note: It's not in fact 100% certain that we really will call
1476
+ * lazy_vacuum_heap_rel() -- lazy_vacuum() might yet opt to skip
1477
+ * index vacuuming (and so must skip heap vacuuming). This is
1478
+ * deemed okay because it only happens in emergencies. (Besides,
1479
+ * we start recording free space in the FSM once index vacuuming
1480
+ * has been abandoned.)
1481
+ *
1453
1482
* Note: The one-pass (no indexes) case is only supposed to make
1454
1483
* it this far when there were no LP_DEAD items during pruning.
1455
1484
*/
@@ -1499,7 +1528,7 @@ lazy_scan_heap(LVRelState *vacrel, VacuumParams *params, bool aggressive)
1499
1528
1500
1529
/*
1501
1530
* Vacuum the remainder of the Free Space Map. We must do this whether or
1502
- * not there were indexes.
1531
+ * not there were indexes, and whether or not we bypassed index vacuuming .
1503
1532
*/
1504
1533
if (blkno > next_fsm_block_to_vacuum )
1505
1534
FreeSpaceMapVacuumRange (vacrel -> rel , next_fsm_block_to_vacuum , blkno );
@@ -1953,6 +1982,11 @@ lazy_scan_prune(LVRelState *vacrel,
1953
1982
1954
1983
/*
1955
1984
* Remove the collected garbage tuples from the table and its indexes.
1985
+ *
1986
+ * In rare emergencies, the ongoing VACUUM operation can be made to skip both
1987
+ * index vacuuming and index cleanup at the point we're called. This avoids
1988
+ * having the whole system refuse to allocate further XIDs/MultiXactIds due to
1989
+ * wraparound.
1956
1990
*/
1957
1991
static void
1958
1992
lazy_vacuum (LVRelState * vacrel )
@@ -1969,11 +2003,30 @@ lazy_vacuum(LVRelState *vacrel)
1969
2003
return ;
1970
2004
}
1971
2005
1972
- /* Okay, we're going to do index vacuuming */
1973
- lazy_vacuum_all_indexes (vacrel );
1974
-
1975
- /* Remove tuples from heap */
1976
- lazy_vacuum_heap_rel (vacrel );
2006
+ if (lazy_vacuum_all_indexes (vacrel ))
2007
+ {
2008
+ /*
2009
+ * We successfully completed a round of index vacuuming. Do related
2010
+ * heap vacuuming now.
2011
+ */
2012
+ lazy_vacuum_heap_rel (vacrel );
2013
+ }
2014
+ else
2015
+ {
2016
+ /*
2017
+ * Failsafe case.
2018
+ *
2019
+ * we attempted index vacuuming, but didn't finish a full round/full
2020
+ * index scan. This happens when relfrozenxid or relminmxid is too
2021
+ * far in the past.
2022
+ *
2023
+ * From this point on the VACUUM operation will do no further index
2024
+ * vacuuming or heap vacuuming. It will do any remaining pruning that
2025
+ * may be required, plus other heap-related and relation-level
2026
+ * maintenance tasks. But that's it.
2027
+ */
2028
+ Assert (vacrel -> do_failsafe );
2029
+ }
1977
2030
1978
2031
/*
1979
2032
* Forget the now-vacuumed tuples -- just press on
@@ -1983,17 +2036,31 @@ lazy_vacuum(LVRelState *vacrel)
1983
2036
1984
2037
/*
1985
2038
* lazy_vacuum_all_indexes() -- Main entry for index vacuuming
2039
+ *
2040
+ * Returns true in the common case when all indexes were successfully
2041
+ * vacuumed. Returns false in rare cases where we determined that the ongoing
2042
+ * VACUUM operation is at risk of taking too long to finish, leading to
2043
+ * wraparound failure.
1986
2044
*/
1987
- static void
2045
+ static bool
1988
2046
lazy_vacuum_all_indexes (LVRelState * vacrel )
1989
2047
{
2048
+ bool allindexes = true;
2049
+
1990
2050
Assert (!IsParallelWorker ());
1991
2051
Assert (vacrel -> nindexes > 0 );
1992
2052
Assert (vacrel -> do_index_vacuuming );
1993
2053
Assert (vacrel -> do_index_cleanup );
1994
2054
Assert (TransactionIdIsNormal (vacrel -> relfrozenxid ));
1995
2055
Assert (MultiXactIdIsValid (vacrel -> relminmxid ));
1996
2056
2057
+ /* Precheck for XID wraparound emergencies */
2058
+ if (lazy_check_wraparound_failsafe (vacrel ))
2059
+ {
2060
+ /* Wraparound emergency -- don't even start an index scan */
2061
+ return false;
2062
+ }
2063
+
1997
2064
/* Report that we are now vacuuming indexes */
1998
2065
pgstat_progress_update_param (PROGRESS_VACUUM_PHASE ,
1999
2066
PROGRESS_VACUUM_PHASE_VACUUM_INDEX );
@@ -2008,26 +2075,50 @@ lazy_vacuum_all_indexes(LVRelState *vacrel)
2008
2075
vacrel -> indstats [idx ] =
2009
2076
lazy_vacuum_one_index (indrel , istat , vacrel -> old_live_tuples ,
2010
2077
vacrel );
2078
+
2079
+ if (lazy_check_wraparound_failsafe (vacrel ))
2080
+ {
2081
+ /* Wraparound emergency -- end current index scan */
2082
+ allindexes = false;
2083
+ break ;
2084
+ }
2011
2085
}
2012
2086
}
2013
2087
else
2014
2088
{
2015
2089
/* Outsource everything to parallel variant */
2016
2090
do_parallel_lazy_vacuum_all_indexes (vacrel );
2091
+
2092
+ /*
2093
+ * Do a postcheck to consider applying wraparound failsafe now. Note
2094
+ * that parallel VACUUM only gets the precheck and this postcheck.
2095
+ */
2096
+ if (lazy_check_wraparound_failsafe (vacrel ))
2097
+ allindexes = false;
2017
2098
}
2018
2099
2019
2100
/*
2020
2101
* We delete all LP_DEAD items from the first heap pass in all indexes on
2021
- * each call here. This makes the next call to lazy_vacuum_heap_rel()
2022
- * safe.
2102
+ * each call here (except calls where we choose to do the failsafe). This
2103
+ * makes the next call to lazy_vacuum_heap_rel() safe (except in the event
2104
+ * of the failsafe triggering, which prevents the next call from taking
2105
+ * place).
2023
2106
*/
2024
2107
Assert (vacrel -> num_index_scans > 0 ||
2025
2108
vacrel -> dead_tuples -> num_tuples == vacrel -> lpdead_items );
2109
+ Assert (allindexes || vacrel -> do_failsafe );
2026
2110
2027
- /* Increase and report the number of index scans */
2111
+ /*
2112
+ * Increase and report the number of index scans.
2113
+ *
2114
+ * We deliberately include the case where we started a round of bulk
2115
+ * deletes that we weren't able to finish due to the failsafe triggering.
2116
+ */
2028
2117
vacrel -> num_index_scans ++ ;
2029
2118
pgstat_progress_update_param (PROGRESS_VACUUM_NUM_INDEX_VACUUMS ,
2030
2119
vacrel -> num_index_scans );
2120
+
2121
+ return allindexes ;
2031
2122
}
2032
2123
2033
2124
/*
@@ -2320,6 +2411,67 @@ lazy_check_needs_freeze(Buffer buf, bool *hastup, LVRelState *vacrel)
2320
2411
return (offnum <= maxoff );
2321
2412
}
2322
2413
2414
+ /*
2415
+ * Trigger the failsafe to avoid wraparound failure when vacrel table has a
2416
+ * relfrozenxid and/or relminmxid that is dangerously far in the past.
2417
+ *
2418
+ * Triggering the failsafe makes the ongoing VACUUM bypass any further index
2419
+ * vacuuming and heap vacuuming. It also stops the ongoing VACUUM from
2420
+ * applying any cost-based delay that may be in effect.
2421
+ *
2422
+ * Returns true when failsafe has been triggered.
2423
+ *
2424
+ * Caller is expected to call here before and after vacuuming each index in
2425
+ * the case of two-pass VACUUM, or every VACUUM_FSM_EVERY_PAGES blocks in the
2426
+ * case of no-indexes/one-pass VACUUM.
2427
+ *
2428
+ * There is also a precheck before the first pass over the heap begins, which
2429
+ * is helpful when the failsafe initially triggers during a non-aggressive
2430
+ * VACUUM -- the automatic aggressive vacuum to prevent wraparound that
2431
+ * follows can independently trigger the failsafe right away.
2432
+ */
2433
+ static bool
2434
+ lazy_check_wraparound_failsafe (LVRelState * vacrel )
2435
+ {
2436
+ /* Avoid calling vacuum_xid_failsafe_check() very frequently */
2437
+ if (vacrel -> num_index_scans == 0 &&
2438
+ vacrel -> rel_pages <= FAILSAFE_MIN_PAGES )
2439
+ return false;
2440
+
2441
+ /* Don't warn more than once per VACUUM */
2442
+ if (vacrel -> do_failsafe )
2443
+ return true;
2444
+
2445
+ if (unlikely (vacuum_xid_failsafe_check (vacrel -> relfrozenxid ,
2446
+ vacrel -> relminmxid )))
2447
+ {
2448
+ Assert (vacrel -> do_index_vacuuming );
2449
+ Assert (vacrel -> do_index_cleanup );
2450
+
2451
+ vacrel -> do_index_vacuuming = false;
2452
+ vacrel -> do_index_cleanup = false;
2453
+ vacrel -> do_failsafe = true;
2454
+
2455
+ ereport (WARNING ,
2456
+ (errmsg ("abandoned index vacuuming of table \"%s.%s.%s\" as a failsafe after %d index scans" ,
2457
+ get_database_name (MyDatabaseId ),
2458
+ vacrel -> relnamespace ,
2459
+ vacrel -> relname ,
2460
+ vacrel -> num_index_scans ),
2461
+ errdetail ("table's relfrozenxid or relminmxid is too far in the past" ),
2462
+ errhint ("Consider increasing configuration parameter \"maintenance_work_mem\" or \"autovacuum_work_mem\".\n"
2463
+ "You might also need to consider other ways for VACUUM to keep up with the allocation of transaction IDs." )));
2464
+
2465
+ /* Stop applying cost limits from this point on */
2466
+ VacuumCostActive = false;
2467
+ VacuumCostBalance = 0 ;
2468
+
2469
+ return true;
2470
+ }
2471
+
2472
+ return false;
2473
+ }
2474
+
2323
2475
/*
2324
2476
* Perform lazy_vacuum_all_indexes() steps in parallel
2325
2477
*/
@@ -3173,7 +3325,7 @@ lazy_space_alloc(LVRelState *vacrel, int nworkers, BlockNumber nblocks)
3173
3325
* be used for an index, so we invoke parallelism only if there are at
3174
3326
* least two indexes on a table.
3175
3327
*/
3176
- if (nworkers >= 0 && vacrel -> nindexes > 1 )
3328
+ if (nworkers >= 0 && vacrel -> nindexes > 1 && vacrel -> do_index_vacuuming )
3177
3329
{
3178
3330
/*
3179
3331
* Since parallel workers cannot access data in temporary tables, we
0 commit comments