From f7d265d6ec8783786d642aea6cb0964552bcce9a Mon Sep 17 00:00:00 2001 From: Peter Geoghegan Date: Mon, 5 Sep 2022 17:46:34 -0700 Subject: [PATCH v3 4/5] Unify aggressive VACUUM with antiwraparound VACUUM. The concept of aggressive/scan_all VACUUM dates back to the introduction of the visibility map in Postgres 8.4. Before then, every lazy VACUUM was "equally aggressive": each operation froze whatever tuples before the age-wise cutoff needed to be frozen. And each table's relfrozenxid was updated at the end. In short, the previous behavior was much less efficient, but did at least have one thing going for it: it was much easier to understand. Even when the visibility map first went in, there was some awareness of the problem of successive VACUUMs against the same table that had unpredictable performance characteristics. The vacuum_freeze_table_age GUC was added to 8.4 (around the same time as the visibility map itself) by commit 65878185, to ameliorate problems in this area. The GUC made the behavior of successive VACUUMs somewhat more continuous by forcing "early aggressive VACUUMs" of tables whose relfrozenxid had already attained an age that exceeded vacuum_freeze_table_age when VACUUM began. An aggressive VACUUM could therefore sometimes take place before an aggressive antiwraparound autovacuum was triggered. Antiwraparound autovacuums were just another way that autovacuum.c could trigger an autovacuum worker before now (for the most part). Although antiwraparound "implies aggressive", aggressive has never "implied antiwraparound". This is a consequence of having two table-age GUCs that both influence VACUUM's behavior around relfrozenxid advancement. While table age certainly does matter, it's far from the only thing that matters. And while we should sometimes "behave aggressively", it's more useful to structure everything as being on a continuum between laziness and eagerness/aggressiveness. Rather than relying on vacuum_freeze_table_age to "escalate to an aggressive VACUUM early", the skipping strategy infrastructure now gives some consideration to table age (in addition to the target relation's physical characteristics, in particular the number of extra all-visible blocks). The costs and the benefits are weighed against each other. The closer we get to needing an antiwraparound VACUUM, the less concerned we are about the added cost of advancing relfrozenxid in the ongoing VACUUM. We now _start_ to give _some_ weight to table age at a relatively early stage, when the table's age(relfrozenxid) first crosses the half-way point (autovacuum_freeze_max_age/2, or the multixact equivalent). Once we're very close to the point of antiwraparound for a given table, any VACUUM against that table will automatically choose the eager skipping strategy. The concept of aggressive VACUUM is now merged with the concept of antiwraparound VACUUM. Note that this means that a manually issued VACUUM command can now sometimes be classified as an antiwraparound VACUUM (and get reported as such in VERBOSE output). The default value of vacuum_freeze_table_age is now -1, which is interpreted as "the current value of the autovacuum_freeze_max_age GUC". The "table age" GUCs/reloptions can be used as compatibility options, but are otherwise superseded by VACUUM's freezing and skipping strategies. --- src/include/commands/vacuum.h | 5 +- src/include/storage/proc.h | 4 +- src/backend/access/heap/vacuumlazy.c | 171 ++++--- src/backend/commands/cluster.c | 3 +- src/backend/commands/vacuum.c | 96 ++-- src/backend/postmaster/autovacuum.c | 4 +- src/backend/storage/lmgr/proc.c | 2 +- src/backend/utils/activity/pgstat_relation.c | 6 +- src/backend/utils/misc/guc.c | 8 +- src/backend/utils/misc/postgresql.conf.sample | 4 +- doc/src/sgml/config.sgml | 80 ++-- doc/src/sgml/logicaldecoding.sgml | 2 +- doc/src/sgml/maintenance.sgml | 451 ++++++------------ doc/src/sgml/ref/create_table.sgml | 2 +- doc/src/sgml/ref/prepare_transaction.sgml | 2 +- doc/src/sgml/ref/vacuum.sgml | 23 +- doc/src/sgml/ref/vacuumdb.sgml | 5 +- .../expected/vacuum-no-cleanup-lock.out | 24 +- .../specs/vacuum-no-cleanup-lock.spec | 27 +- src/test/regress/expected/reloptions.out | 4 +- src/test/regress/sql/reloptions.sql | 4 +- 21 files changed, 424 insertions(+), 503 deletions(-) diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h index 52379f819..5bb33a2bb 100644 --- a/src/include/commands/vacuum.h +++ b/src/include/commands/vacuum.h @@ -223,7 +223,7 @@ typedef struct VacuumParams int freeze_strategy_threshold; /* threshold to use eager * freezing, in total heap blocks, * -1 to use default */ - bool is_wraparound; /* force a for-wraparound vacuum */ + bool is_antiwrap_autovac; /* antiwraparound autovacuum */ int log_min_duration; /* minimum execution threshold in ms at * which autovacuum is logged, -1 to use * default */ @@ -298,7 +298,8 @@ extern bool vacuum_set_xid_limits(Relation rel, TransactionId *oldestXmin, MultiXactId *oldestMxact, TransactionId *freezeLimit, - MultiXactId *multiXactCutoff); + MultiXactId *multiXactCutoff, + double *antiwrapfrac); extern bool vacuum_xid_failsafe_check(TransactionId relfrozenxid, MultiXactId relminmxid); extern void vac_update_datfrozenxid(void); diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 2579e619e..785ef610d 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -57,7 +57,7 @@ struct XidCache * CONCURRENTLY or REINDEX * CONCURRENTLY on non-expressional, * non-partial index */ -#define PROC_VACUUM_FOR_WRAPAROUND 0x08 /* set by autovac only */ +#define PROC_AUTOVACUUM_FOR_WRAPAROUND 0x08 /* affects cancellation */ #define PROC_IN_LOGICAL_DECODING 0x10 /* currently doing logical * decoding outside xact */ #define PROC_AFFECTS_ALL_HORIZONS 0x20 /* this proc's xmin must be @@ -66,7 +66,7 @@ struct XidCache /* flags reset at EOXact */ #define PROC_VACUUM_STATE_MASK \ - (PROC_IN_VACUUM | PROC_IN_SAFE_IC | PROC_VACUUM_FOR_WRAPAROUND) + (PROC_IN_VACUUM | PROC_IN_SAFE_IC | PROC_AUTOVACUUM_FOR_WRAPAROUND) /* * Xmin-related flags. Make sure any flags that affect how the process' Xmin diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 8d750ac7f..950347c50 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -109,10 +109,11 @@ ((BlockNumber) (((uint64) 8 * 1024 * 1024 * 1024) / BLCKSZ)) /* - * Threshold that controls whether non-aggressive VACUUMs will skip any - * all-visible pages when using the lazy freezing strategy + * Thresholds that control whether VACUUM will skip any all-visible pages when + * using the lazy freezing strategy */ #define SKIPALLVIS_THRESHOLD_PAGES 0.05 /* i.e. 5% of rel_pages */ +#define SKIPALLVIS_MIDPOINT_THRESHOLD_PAGES 0.15 /* * Size of the prefetch window for lazy vacuum backwards truncation scan. @@ -144,9 +145,9 @@ typedef struct LVRelState Relation *indrels; int nindexes; - /* Aggressive VACUUM? (must set relfrozenxid >= FreezeLimit) */ - bool aggressive; - /* Skip (don't scan) all-visible pages? (must be !aggressive) */ + /* Antiwraparound VACUUM? (must set relfrozenxid >= FreezeLimit) */ + bool antiwraparound; + /* Skip (don't scan) all-visible pages? (must be !antiwraparound) */ bool skipallvis; /* Skip (don't scan) all-frozen pages? */ bool skipallfrozen; @@ -258,7 +259,8 @@ static void lazy_scan_heap(LVRelState *vacrel); static BlockNumber lazy_scan_strategy(LVRelState *vacrel, BlockNumber eager_threshold, BlockNumber all_visible, - BlockNumber all_frozen); + BlockNumber all_frozen, + double antiwrapfrac); static BlockNumber lazy_scan_skip(LVRelState *vacrel, BlockNumber next_block, bool *all_visible); static bool lazy_scan_new_or_empty(LVRelState *vacrel, Buffer buf, @@ -322,7 +324,7 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, LVRelState *vacrel; bool verbose, instrument, - aggressive, + antiwraparound, skipallfrozen, frozenxid_updated, minmulti_updated; @@ -330,6 +332,7 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, FreezeLimit; MultiXactId OldestMxact, MultiXactCutoff; + double antiwrapfrac; BlockNumber orig_rel_pages, eager_threshold, all_visible, @@ -369,32 +372,42 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, * Get OldestXmin cutoff, which is used to determine which deleted tuples * are considered DEAD, not just RECENTLY_DEAD. Also get related cutoffs * used to determine which XIDs/MultiXactIds will be frozen. If this is - * an aggressive VACUUM then lazy_scan_heap cannot leave behind unfrozen - * XIDs < FreezeLimit (all MXIDs < MultiXactCutoff also need to go away). + * an antiwraparound VACUUM then lazy_scan_heap cannot leave behind + * unfrozen XIDs < FreezeLimit (all MXIDs < MultiXactCutoff also need to + * go away). * * Also determine our cutoff for applying the eager/all-visible freezing * strategy. If rel_pages is larger than this cutoff we use the strategy, - * even during non-aggressive VACUUMs. + * even during regular VACUUMs. */ - aggressive = vacuum_set_xid_limits(rel, - params->freeze_min_age, - params->multixact_freeze_min_age, - params->freeze_table_age, - params->multixact_freeze_table_age, - &OldestXmin, &OldestMxact, - &FreezeLimit, &MultiXactCutoff); + antiwraparound = vacuum_set_xid_limits(rel, + params->freeze_min_age, + params->multixact_freeze_min_age, + params->freeze_table_age, + params->multixact_freeze_table_age, + &OldestXmin, &OldestMxact, + &FreezeLimit, &MultiXactCutoff, + &antiwrapfrac); eager_threshold = params->freeze_strategy_threshold < 0 ? vacuum_freeze_strategy_threshold : params->freeze_strategy_threshold; + /* + * An autovacuum to prevent wraparound should already be recognized as + * antiwraparound based on generic criteria. Even still, make sure that + * autovacuum.c always gets what it asked for. + */ + if (params->is_antiwrap_autovac) + antiwraparound = true; + skipallfrozen = true; if (params->options & VACOPT_DISABLE_PAGE_SKIPPING) { /* - * Force aggressive mode, and disable skipping blocks using the + * Force antiwraparound mode, and disable skipping blocks using the * visibility map (even those set all-frozen) */ - aggressive = true; + antiwraparound = true; skipallfrozen = false; } @@ -445,9 +458,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, Assert(params->index_cleanup != VACOPTVALUE_UNSPECIFIED); Assert(params->truncate != VACOPTVALUE_UNSPECIFIED && params->truncate != VACOPTVALUE_AUTO); - vacrel->aggressive = aggressive; + vacrel->antiwraparound = antiwraparound; /* Set skipallvis/skipallfrozen provisionally (before lazy_scan_strategy) */ - vacrel->skipallvis = (!aggressive && skipallfrozen); + vacrel->skipallvis = (!antiwraparound && skipallfrozen); vacrel->skipallfrozen = skipallfrozen; vacrel->failsafe_active = false; vacrel->consider_bypass_optimization = true; @@ -541,7 +554,8 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, vacrel->vmsnap = visibilitymap_snap(rel, orig_rel_pages, &all_visible, &all_frozen); scanned_pages = lazy_scan_strategy(vacrel, eager_threshold, - all_visible, all_frozen); + all_visible, all_frozen, + antiwrapfrac); if (verbose) { char *msgfmt; @@ -549,8 +563,8 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, Assert(!IsAutoVacuumWorkerProcess()); - if (aggressive) - msgfmt = _("aggressively vacuuming \"%s.%s.%s\""); + if (antiwraparound) + msgfmt = _("vacuuming \"%s.%s.%s\" to prevent wraparound"); else msgfmt = _("vacuuming \"%s.%s.%s\""); @@ -617,25 +631,25 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, /* * Prepare to update rel's pg_class entry. * - * Aggressive VACUUMs must always be able to advance relfrozenxid to a + * Antiwraparound VACUUMs must always be able to advance relfrozenxid to a * value >= FreezeLimit, and relminmxid to a value >= MultiXactCutoff. - * Non-aggressive VACUUMs may advance them by any amount, or not at all. + * Regular VACUUMs may advance them by any amount, or not at all. */ Assert(vacrel->NewRelfrozenXid == OldestXmin || - TransactionIdPrecedesOrEquals(aggressive ? FreezeLimit : + TransactionIdPrecedesOrEquals(antiwraparound ? FreezeLimit : vacrel->relfrozenxid, vacrel->NewRelfrozenXid)); Assert(vacrel->NewRelminMxid == OldestMxact || - MultiXactIdPrecedesOrEquals(aggressive ? MultiXactCutoff : + MultiXactIdPrecedesOrEquals(antiwraparound ? MultiXactCutoff : vacrel->relminmxid, vacrel->NewRelminMxid)); if (vacrel->skipallvis) { /* - * Must keep original relfrozenxid in a non-aggressive VACUUM whose + * Must keep original relfrozenxid in a regular VACUUM whose * lazy_scan_strategy call determined it would skip all-visible pages */ - Assert(!aggressive); + Assert(!antiwraparound); vacrel->NewRelfrozenXid = InvalidTransactionId; vacrel->NewRelminMxid = InvalidMultiXactId; } @@ -709,29 +723,23 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, if (verbose) { /* - * Aggressiveness already reported earlier, in dedicated + * Antiwraparound-ness already reported earlier, in dedicated * VACUUM VERBOSE ereport */ - Assert(!params->is_wraparound); + Assert(!params->is_antiwrap_autovac); msgfmt = _("finished vacuuming \"%s.%s.%s\": index scans: %d\n"); } - else if (params->is_wraparound) - { - /* - * While it's possible for a VACUUM to be both is_wraparound - * and !aggressive, that's just a corner-case -- is_wraparound - * implies aggressive. Produce distinct output for the corner - * case all the same, just in case. - */ - if (aggressive) - msgfmt = _("automatic aggressive vacuum to prevent wraparound of table \"%s.%s.%s\": index scans: %d\n"); - else - msgfmt = _("automatic vacuum to prevent wraparound of table \"%s.%s.%s\": index scans: %d\n"); - } else { - if (aggressive) - msgfmt = _("automatic aggressive vacuum of table \"%s.%s.%s\": index scans: %d\n"); + /* + * Note that we don't differentiate between an antiwraparound + * autovacuum that was launched by autovacuum.c as + * antiwraparound and one that only became antiwraparound + * because freeze_table_age is set. + */ + Assert(IsAutoVacuumWorkerProcess()); + if (antiwraparound) + msgfmt = _("automatic vacuum to prevent wraparound of table \"%s.%s.%s\": index scans: %d\n"); else msgfmt = _("automatic vacuum of table \"%s.%s.%s\": index scans: %d\n"); } @@ -1063,7 +1071,7 @@ lazy_scan_heap(LVRelState *vacrel) * lazy_scan_noprune could not do all required processing. Wait * for a cleanup lock, and call lazy_scan_prune in the usual way. */ - Assert(vacrel->aggressive); + Assert(vacrel->antiwraparound); LockBuffer(buf, BUFFER_LOCK_UNLOCK); LockBufferForCleanup(buf); } @@ -1325,18 +1333,22 @@ lazy_scan_heap(LVRelState *vacrel) * Antiwraparound VACUUMs of append-only tables should generally be avoided. * * Also determines if the ongoing VACUUM operation should skip all-visible - * pages for non-aggressive VACUUMs, where advancing relfrozenxid is optional. - * When VACUUM freezes eagerly it always also scans pages eagerly, since it's + * pages for regular VACUUMs, where advancing relfrozenxid is optional. When + * VACUUM freezes eagerly it always also scans pages eagerly, since it's * important that relfrozenxid advance in affected tables, which are larger. * When VACUUM freezes lazily it might make sense to scan pages lazily (skip * all-visible pages) or eagerly (be capable of relfrozenxid advancement), * depending on the extra cost - we might need to scan only a few extra pages. + * Decision is based in part on caller's antiwrapfrac argument, which is a + * value from 0.0 - 1.0 that represents how close the table age is to needing + * an antiwraparound VACUUM. * * Returns final scanned_pages for the VACUUM operation. */ static BlockNumber lazy_scan_strategy(LVRelState *vacrel, BlockNumber eager_threshold, - BlockNumber all_visible, BlockNumber all_frozen) + BlockNumber all_visible, BlockNumber all_frozen, + double antiwrapfrac) { BlockNumber rel_pages = vacrel->rel_pages, scanned_pages_skipallvis, @@ -1379,21 +1391,21 @@ lazy_scan_strategy(LVRelState *vacrel, BlockNumber eager_threshold, if (!vacrel->skipallfrozen) { /* DISABLE_PAGE_SKIPPING makes all skipping unsafe */ - Assert(vacrel->aggressive && !vacrel->skipallvis); + Assert(vacrel->antiwraparound && !vacrel->skipallvis); vacrel->allvis_freeze_strategy = true; return rel_pages; } - else if (vacrel->aggressive) + else if (vacrel->antiwraparound) { - /* Always freeze all-visible pages during aggressive VACUUMs */ + /* Always freeze all-visible pages during antiwraparound VACUUMs */ Assert(!vacrel->skipallvis); vacrel->allvis_freeze_strategy = true; } else if (rel_pages >= eager_threshold) { /* - * Non-aggressive VACUUM of table whose rel_pages now exceeds - * GUC-based threshold for eager freezing. + * Regular VACUUM of table whose rel_pages now exceeds GUC-based + * threshold for eager freezing. * * We always scan all-visible pages when the threshold is crossed, so * that relfrozenxid can be advanced. There will typically be few or @@ -1408,7 +1420,7 @@ lazy_scan_strategy(LVRelState *vacrel, BlockNumber eager_threshold, BlockNumber nextra, nextra_threshold; - /* Non-aggressive VACUUM of small table -- use lazy freeze strategy */ + /* Regular VACUUM of small table -- use lazy freeze strategy */ vacrel->allvis_freeze_strategy = false; /* @@ -1424,13 +1436,32 @@ lazy_scan_strategy(LVRelState *vacrel, BlockNumber eager_threshold, * that way, so be lazy (just skip) unless the added cost is very low. * We opt for a skipallfrozen-only VACUUM when the number of extra * pages (extra scanned pages that are all-visible but not all-frozen) - * is less than 5% of rel_pages (or 32 pages when rel_pages is small). + * is less than 5% of rel_pages (or 32 pages when rel_pages is small) + * if relfrozenxid has yet to attain an age that uses 50% of the XID + * space available before an antiwraparound VACUUM becomes necessary. + * A more aggressive threshold of 15% is used when relfrozenxid is + * older than that. */ nextra = scanned_pages_skipallfrozen - scanned_pages_skipallvis; - nextra_threshold = (double) rel_pages * SKIPALLVIS_THRESHOLD_PAGES; + + if (antiwrapfrac < 0.5) + nextra_threshold = (double) rel_pages * + SKIPALLVIS_THRESHOLD_PAGES; + else + nextra_threshold = (double) rel_pages * + SKIPALLVIS_MIDPOINT_THRESHOLD_PAGES; + nextra_threshold = Max(32, nextra_threshold); - vacrel->skipallvis = nextra >= nextra_threshold; + /* + * We always prefer eagerly advancing relfrozenxid when it already + * attained an age that consumes >= 90% of the available XID space + * before the crossover point for antiwraparound VACUUM. + */ + if (antiwrapfrac < 0.9) + vacrel->skipallvis = nextra >= nextra_threshold; + else + vacrel->skipallvis = false; } /* Return the appropriate variant of scanned_pages */ @@ -2083,11 +2114,11 @@ retry: * operation left LP_DEAD items behind. We'll at least collect any such items * in the dead_items array for removal from indexes. * - * For aggressive VACUUM callers, we may return false to indicate that a full - * cleanup lock is required for processing by lazy_scan_prune. This is only - * necessary when the aggressive VACUUM needs to freeze some tuple XIDs from - * one or more tuples on the page. We always return true for non-aggressive - * callers. + * For antiwraparound VACUUM callers, we may return false to indicate that a + * full cleanup lock is required for processing by lazy_scan_prune. This is + * only necessary when the antiwraparound VACUUM needs to freeze some tuple + * XIDs from one or more tuples on the page. We always return true for + * regular VACUUM callers. * * See lazy_scan_prune for an explanation of hastup return flag. * recordfreespace flag instructs caller on whether or not it should do @@ -2160,13 +2191,13 @@ lazy_scan_noprune(LVRelState *vacrel, &NewRelfrozenXid, &NewRelminMxid)) { /* Tuple with XID < FreezeLimit (or MXID < MultiXactCutoff) */ - if (vacrel->aggressive) + if (vacrel->antiwraparound) { /* - * Aggressive VACUUMs must always be able to advance rel's + * Antiwraparound VACUUMs must always be able to advance rel's * relfrozenxid to a value >= FreezeLimit (and be able to * advance rel's relminmxid to a value >= MultiXactCutoff). - * The ongoing aggressive VACUUM won't be able to do that + * The ongoing antiwraparound VACUUM won't be able to do that * unless it can freeze an XID (or MXID) from this tuple now. * * The only safe option is to have caller perform processing @@ -2178,8 +2209,8 @@ lazy_scan_noprune(LVRelState *vacrel, } /* - * Non-aggressive VACUUMs are under no obligation to advance - * relfrozenxid (even by one XID). We can be much laxer here. + * Regular VACUUMs are under no obligation to advance relfrozenxid + * (even by one XID). We can be much laxer here. * * Currently we always just accept an older final relfrozenxid * and/or relminmxid value. We never make caller wait or work a diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index dc35b0291..bfcf157ab 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -825,6 +825,7 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose, FreezeXid; MultiXactId OldestMxact, MultiXactCutoff; + double antiwrapfrac; bool use_sort; double num_tuples = 0, tups_vacuumed = 0, @@ -913,7 +914,7 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose, * not to be aggressive about this. */ vacuum_set_xid_limits(OldHeap, 0, 0, 0, 0, &OldestXmin, &OldestMxact, - &FreezeXid, &MultiXactCutoff); + &FreezeXid, &MultiXactCutoff, &antiwrapfrac); /* * FreezeXid will become the table's new relfrozenxid, and that mustn't go diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index b837e0331..81686ccce 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -267,8 +267,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel) /* Determine freezing strategy later on using GUC or reloption */ params.freeze_strategy_threshold = -1; - /* user-invoked vacuum is never "for wraparound" */ - params.is_wraparound = false; + /* user-invoked vacuum isn't an autovacuum */ + params.is_antiwrap_autovac = false; /* user-invoked vacuum uses VACOPT_VERBOSE instead of log_min_duration */ params.log_min_duration = -1; @@ -943,14 +943,20 @@ get_all_vacuum_rels(int options) * - oldestMxact is the Mxid below which MultiXacts are definitely not * seen as visible by any running transaction. * - freezeLimit is the Xid below which all Xids are definitely replaced by - * FrozenTransactionId during aggressive vacuums. + * FrozenTransactionId during antiwraparound vacuums. * - multiXactCutoff is the value below which all MultiXactIds are definitely - * removed from Xmax during aggressive vacuums. + * removed from Xmax during antiwraparound vacuums. * * Return value indicates if vacuumlazy.c caller should make its VACUUM - * operation aggressive. An aggressive VACUUM must advance relfrozenxid up to - * FreezeLimit (at a minimum), and relminmxid up to multiXactCutoff (at a - * minimum). + * operation antiwraparound. An antiwraparound VACUUM is required to advance + * relfrozenxid up to FreezeLimit (at a minimum), and relminmxid up to + * multiXactCutoff (at a minimum). Otherwise VACUUM advances relfrozenxid on + * a best-effort basis. + * + * Sets *antiwrapfrac to give caller a sense of how close we came to requiring + * an antiwraparound VACUUM in terms of XID/MXID space consumed. This is set + * to a value between 0.0 and 1.0, where 1.0 represents the point that an + * antiwraparound VACUUM will be (or has already) been forced. * * oldestXmin and oldestMxact are the most recent values that can ever be * passed to vac_update_relstats() as frozenxid and minmulti arguments by our @@ -966,15 +972,20 @@ vacuum_set_xid_limits(Relation rel, TransactionId *oldestXmin, MultiXactId *oldestMxact, TransactionId *freezeLimit, - MultiXactId *multiXactCutoff) + MultiXactId *multiXactCutoff, + double *antiwrapfrac) { TransactionId nextXID, safeOldestXmin, - aggressiveXIDCutoff; + antiwrapXIDCutoff; MultiXactId nextMXID, safeOldestMxact, - aggressiveMXIDCutoff; - int effective_multixact_freeze_max_age; + antiwrapMXIDCutoff; + double XIDFrac, + MXIDFrac; + int effective_multixact_freeze_max_age, + relfrozenxid_age, + relminmxid_age; /* * Acquire oldestXmin. @@ -1065,8 +1076,8 @@ vacuum_set_xid_limits(Relation rel, *multiXactCutoff = *oldestMxact; /* - * Done setting output parameters; check if oldestXmin or oldestMxact are - * held back to an unsafe degree in passing + * Done setting cutoff output parameters; check if oldestXmin or + * oldestMxact are held back to an unsafe degree in passing */ safeOldestXmin = nextXID - autovacuum_freeze_max_age; if (!TransactionIdIsNormal(safeOldestXmin)) @@ -1085,48 +1096,59 @@ vacuum_set_xid_limits(Relation rel, errhint("Close open transactions soon to avoid wraparound problems.\n" "You might also need to commit or roll back old prepared transactions, or drop stale replication slots."))); + *antiwrapfrac = 1.0; /* Initialize */ + /* - * Finally, figure out if caller needs to do an aggressive VACUUM or not. + * Finally, figure out if caller needs to do an antiwraparound VACUUM now. * * Determine the table freeze age to use: as specified by the caller, or - * the value of the vacuum_freeze_table_age GUC, but in any case not more - * than autovacuum_freeze_max_age * 0.95, so that if you have e.g nightly - * VACUUM schedule, the nightly VACUUM gets a chance to freeze XIDs before - * anti-wraparound autovacuum is launched. + * the value of the vacuum_freeze_table_age GUC. The GUC's default value + * of -1 is interpreted as "just use autovacuum_freeze_max_age value". + * Also clamp using autovacuum_freeze_max_age. */ if (freeze_table_age < 0) freeze_table_age = vacuum_freeze_table_age; - freeze_table_age = Min(freeze_table_age, autovacuum_freeze_max_age * 0.95); + if (freeze_table_age < 0 || freeze_table_age > autovacuum_freeze_max_age) + freeze_table_age = autovacuum_freeze_max_age; Assert(freeze_table_age >= 0); - aggressiveXIDCutoff = nextXID - freeze_table_age; - if (!TransactionIdIsNormal(aggressiveXIDCutoff)) - aggressiveXIDCutoff = FirstNormalTransactionId; + antiwrapXIDCutoff = nextXID - freeze_table_age; + if (!TransactionIdIsNormal(antiwrapXIDCutoff)) + antiwrapXIDCutoff = FirstNormalTransactionId; if (TransactionIdPrecedesOrEquals(rel->rd_rel->relfrozenxid, - aggressiveXIDCutoff)) + antiwrapXIDCutoff)) return true; /* * Similar to the above, determine the table freeze age to use for * multixacts: as specified by the caller, or the value of the - * vacuum_multixact_freeze_table_age GUC, but in any case not more than - * effective_multixact_freeze_max_age * 0.95, so that if you have e.g. - * nightly VACUUM schedule, the nightly VACUUM gets a chance to freeze - * multixacts before anti-wraparound autovacuum is launched. + * vacuum_multixact_freeze_table_age GUC. The GUC's default value of -1 + * is interpreted as "just use effective_multixact_freeze_max_age value". + * Also clamp using effective_multixact_freeze_max_age. */ if (multixact_freeze_table_age < 0) multixact_freeze_table_age = vacuum_multixact_freeze_table_age; - multixact_freeze_table_age = - Min(multixact_freeze_table_age, - effective_multixact_freeze_max_age * 0.95); + if (multixact_freeze_table_age < 0 || + multixact_freeze_table_age > effective_multixact_freeze_max_age) + multixact_freeze_table_age = effective_multixact_freeze_max_age; Assert(multixact_freeze_table_age >= 0); - aggressiveMXIDCutoff = nextMXID - multixact_freeze_table_age; - if (aggressiveMXIDCutoff < FirstMultiXactId) - aggressiveMXIDCutoff = FirstMultiXactId; + antiwrapMXIDCutoff = nextMXID - multixact_freeze_table_age; + if (antiwrapMXIDCutoff < FirstMultiXactId) + antiwrapMXIDCutoff = FirstMultiXactId; if (MultiXactIdPrecedesOrEquals(rel->rd_rel->relminmxid, - aggressiveMXIDCutoff)) + antiwrapMXIDCutoff)) return true; - /* Non-aggressive VACUUM */ + /* + * Regular VACUUM for vacuumlazy.c caller. Need to work out how close we + * came to needing an antiwraparound VACUUM. + */ + relfrozenxid_age = Max(nextXID - rel->rd_rel->relfrozenxid, 1); + relminmxid_age = Max(nextMXID - rel->rd_rel->relminmxid, 1); + XIDFrac = (double) relfrozenxid_age / (double) freeze_table_age; + MXIDFrac = (double) relminmxid_age / (double) multixact_freeze_table_age; + + *antiwrapfrac = Max(XIDFrac, MXIDFrac); + return false; } @@ -1869,8 +1891,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params) */ LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); MyProc->statusFlags |= PROC_IN_VACUUM; - if (params->is_wraparound) - MyProc->statusFlags |= PROC_VACUUM_FOR_WRAPAROUND; + if (params->is_antiwrap_autovac) + MyProc->statusFlags |= PROC_AUTOVACUUM_FOR_WRAPAROUND; ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags; LWLockRelease(ProcArrayLock); } diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 18a8e8b80..112c84b01 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2881,7 +2881,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, tab->at_params.multixact_freeze_min_age = multixact_freeze_min_age; tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age; tab->at_params.freeze_strategy_threshold = freeze_strategy_threshold; - tab->at_params.is_wraparound = wraparound; + tab->at_params.is_antiwrap_autovac = wraparound; tab->at_params.log_min_duration = log_min_duration; tab->at_vacuum_cost_limit = vac_cost_limit; tab->at_vacuum_cost_delay = vac_cost_delay; @@ -3193,7 +3193,7 @@ autovac_report_activity(autovac_table *tab) snprintf(activity + len, MAX_AUTOVAC_ACTIV_LEN - len, " %s.%s%s", tab->at_nspname, tab->at_relname, - tab->at_params.is_wraparound ? " (to prevent wraparound)" : ""); + tab->at_params.is_antiwrap_autovac ? " (to prevent wraparound)" : ""); /* Set statement_timestamp() to current time for pg_stat_activity */ SetCurrentStatementStartTimestamp(); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 37aaab133..158eab321 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -1384,7 +1384,7 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable) * wraparound. */ if ((statusFlags & PROC_IS_AUTOVACUUM) && - !(statusFlags & PROC_VACUUM_FOR_WRAPAROUND)) + !(statusFlags & PROC_AUTOVACUUM_FOR_WRAPAROUND)) { int pid = autovac->pid; diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index a846d9ffb..3b7618b6d 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -234,9 +234,9 @@ pgstat_report_vacuum(Oid tableoid, bool shared, tabentry->n_dead_tuples = deadtuples; /* - * It is quite possible that a non-aggressive VACUUM ended up skipping - * various pages, however, we'll zero the insert counter here regardless. - * It's currently used only to track when we need to perform an "insert" + * It is quite possible that a regular VACUUM ended up skipping various + * pages, however, we'll zero the insert counter here regardless. It's + * currently used only to track when we need to perform an "insert" * autovacuum, which are mainly intended to freeze newly inserted tuples. * Zeroing this may just mean we'll not try to vacuum the table again * until enough tuples have been inserted to trigger another insert diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index bb018ae62..6b337a6af 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -2706,10 +2706,10 @@ static struct config_int ConfigureNamesInt[] = { {"vacuum_freeze_table_age", PGC_USERSET, CLIENT_CONN_STATEMENT, gettext_noop("Age at which VACUUM should scan whole table to freeze tuples."), - NULL + gettext_noop("-1 to use autovacuum_freeze_max_age value.") }, &vacuum_freeze_table_age, - 150000000, 0, 2000000000, + -1, -1, 2000000000, NULL, NULL, NULL }, @@ -2726,10 +2726,10 @@ static struct config_int ConfigureNamesInt[] = { {"vacuum_multixact_freeze_table_age", PGC_USERSET, CLIENT_CONN_STATEMENT, gettext_noop("Multixact age at which VACUUM should scan whole table to freeze tuples."), - NULL + gettext_noop("-1 to use autovacuum_multixact_freeze_max_age value.") }, &vacuum_multixact_freeze_table_age, - 150000000, 0, 2000000000, + -1, -1, 2000000000, NULL, NULL, NULL }, diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index e701e464e..81502d0ca 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -693,11 +693,11 @@ #lock_timeout = 0 # in milliseconds, 0 is disabled #idle_in_transaction_session_timeout = 0 # in milliseconds, 0 is disabled #idle_session_timeout = 0 # in milliseconds, 0 is disabled -#vacuum_freeze_table_age = 150000000 +#vacuum_freeze_table_age = -1 #vacuum_freeze_strategy_threshold = 4GB #vacuum_freeze_min_age = 50000000 #vacuum_failsafe_age = 1600000000 -#vacuum_multixact_freeze_table_age = 150000000 +#vacuum_multixact_freeze_table_age = -1 #vacuum_multixact_freeze_min_age = 5000000 #vacuum_multixact_failsafe_age = 1600000000 #bytea_output = 'hex' # hex, escape diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 091be17c3..01f246464 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -8217,7 +8217,7 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; Note that even when this parameter is disabled, the system will launch autovacuum processes if necessary to prevent transaction ID wraparound. See for more information. + linkend="vacuum-xid-space"/> for more information. @@ -8406,7 +8406,7 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; This parameter can only be set at server start, but the setting can be reduced for individual tables by changing table storage parameters. - For more information see . + For more information see . @@ -9130,20 +9130,31 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; - VACUUM performs an aggressive scan if the table's - pg_class.relfrozenxid field has reached - the age specified by this setting. An aggressive scan differs from - a regular VACUUM in that it visits every page that might - contain unfrozen XIDs or MXIDs, not just those that might contain dead - tuples. The default is 150 million transactions. Although users can - set this value anywhere from zero to two billion, VACUUM - will silently limit the effective value to 95% of - , so that a - periodic manual VACUUM has a chance to run before an - anti-wraparound autovacuum is launched for the table. For more - information see - . + VACUUM performs antiwraparound vacuuming if the + table's pg_class.relfrozenxid + field has reached the age specified by this setting. + Antiwraparound vacuuming differs from regular vacuuming in + that it will reliably advance + relfrozenxid to a recent value, + even when VACUUM wouldn't usually deemed it + necessary. The default is -1. If -1 is specified, the value + of is used. + Although users can set this value anywhere from zero to two + billion, VACUUM will silently limit the + effective value to . For more + information see . + + + The meaning of this parameter, and its default value, changed + in PostgreSQL 16. Freezing and + advancing + pg_class.relfrozenxid + now take place more proactively, in every + VACUUM operation. + + @@ -9179,9 +9190,9 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; users can set this value anywhere from zero to one billion, VACUUM will silently limit the effective value to half the value of , so - that there is not an unreasonably short time between forced + that there is not an unreasonably short time between forced antiwraparound autovacuums. For more information see . + linkend="vacuum-xid-space"/>. @@ -9227,19 +9238,28 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; - VACUUM performs an aggressive scan if the table's - pg_class.relminmxid field has reached - the age specified by this setting. An aggressive scan differs from - a regular VACUUM in that it visits every page that might - contain unfrozen XIDs or MXIDs, not just those that might contain dead - tuples. The default is 150 million multixacts. - Although users can set this value anywhere from zero to two billion, - VACUUM will silently limit the effective value to 95% of - , so that a - periodic manual VACUUM has a chance to run before an - anti-wraparound is launched for the table. - For more information see . + VACUUM performs antiwraparound vacuuming if + the table's pg_class.relminmxid + field has reached the multixact age specified by this setting. + Antiwraparound vacuuming differs from regular vacuuming in + that it will reliably advance + relminmxid to a recent value, even + when VACUUM wouldn't usually deemed it + necessary. The default is -1. If -1 is specified, the value + of + is used. For more information see . + + + The meaning of this parameter, and its default value, changed + in PostgreSQL 16. Freezing and + advancing + pg_class.relminmxid + now take place more proactively, in every + VACUUM operation. + + @@ -9258,7 +9278,7 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; Although users can set this value anywhere from zero to one billion, VACUUM will silently limit the effective value to half the value of , - so that there is not an unreasonably short time between forced + so that there is not an unreasonably short time between forced antiwraparound autovacuums. For more information see . diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml index 38ee69dcc..380da3c1e 100644 --- a/doc/src/sgml/logicaldecoding.sgml +++ b/doc/src/sgml/logicaldecoding.sgml @@ -324,7 +324,7 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU because neither required WAL nor required rows from the system catalogs can be removed by VACUUM as long as they are required by a replication slot. In extreme cases this could cause the database to shut down to prevent - transaction ID wraparound (see ). + transaction ID wraparound (see ). So if a slot is no longer required it should be dropped. diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml index 554b3a75d..80fd3d548 100644 --- a/doc/src/sgml/maintenance.sgml +++ b/doc/src/sgml/maintenance.sgml @@ -400,18 +400,8 @@ - - Preventing Transaction ID Wraparound Failures - - - transaction ID - wraparound - - - - wraparound - of transaction IDs - + + Managing the 32-bit Transaction ID address space PostgreSQL's @@ -419,165 +409,23 @@ depend on being able to compare transaction ID (XID) numbers: a row version with an insertion XID greater than the current transaction's XID is in the future and should not be visible - to the current transaction. But since transaction IDs have limited size - (32 bits) a cluster that runs for a long time (more - than 4 billion transactions) would suffer transaction ID - wraparound: the XID counter wraps around to zero, and all of a sudden - transactions that were in the past appear to be in the future — which - means their output become invisible. In short, catastrophic data loss. - (Actually the data is still there, but that's cold comfort if you cannot - get at it.) To avoid this, it is necessary to vacuum every table - in every database at least once every two billion transactions. + to the current transaction. But since the on-disk representation + of transaction IDs is only 32-bits, the system is incapable of + representing distances between any two XIDs + that exceed about 2 billion transaction IDs. - The reason that periodic vacuuming solves the problem is that - VACUUM will mark rows as frozen, indicating that - they were inserted by a transaction that committed sufficiently far in - the past that the effects of the inserting transaction are certain to be - visible to all current and future transactions. - Normal XIDs are - compared using modulo-232 arithmetic. This means - that for every normal XID, there are two billion XIDs that are - older and two billion that are newer; another - way to say it is that the normal XID space is circular with no - endpoint. Therefore, once a row version has been created with a particular - normal XID, the row version will appear to be in the past for - the next two billion transactions, no matter which normal XID we are - talking about. If the row version still exists after more than two billion - transactions, it will suddenly appear to be in the future. To - prevent this, PostgreSQL reserves a special XID, - FrozenTransactionId, which does not follow the normal XID - comparison rules and is always considered older - than every normal XID. - Frozen row versions are treated as if the inserting XID were - FrozenTransactionId, so that they will appear to be - in the past to all normal transactions regardless of wraparound - issues, and so such row versions will be valid until deleted, no matter - how long that is. - - - - - In PostgreSQL versions before 9.4, freezing was - implemented by actually replacing a row's insertion XID - with FrozenTransactionId, which was visible in the - row's xmin system column. Newer versions just set a flag - bit, preserving the row's original xmin for possible - forensic use. However, rows with xmin equal - to FrozenTransactionId (2) may still be found - in databases pg_upgrade'd from pre-9.4 versions. - - - Also, system catalogs may contain rows with xmin equal - to BootstrapTransactionId (1), indicating that they were - inserted during the first phase of initdb. - Like FrozenTransactionId, this special XID is treated as - older than every normal XID. - - - - - - controls how old an XID value has to be before rows bearing that XID will be - frozen. Increasing this setting may avoid unnecessary work if the - rows that would otherwise be frozen will soon be modified again, - but decreasing this setting increases - the number of transactions that can elapse before the table must be - vacuumed again. - - - - VACUUM uses the visibility map - to determine which pages of a table must be scanned. Normally, it - will skip pages that don't have any dead row versions even if those pages - might still have row versions with old XID values. Therefore, normal - VACUUMs won't always freeze every old row version in the table. - When that happens, VACUUM will eventually need to perform an - aggressive vacuum, which will freeze all eligible unfrozen - XID and MXID values, including those from all-visible but not all-frozen pages. - In practice most tables require periodic aggressive vacuuming. - - controls when VACUUM does that: all-visible but not all-frozen - pages are scanned if the number of transactions that have passed since the - last such scan is greater than vacuum_freeze_table_age minus - vacuum_freeze_min_age. Setting - vacuum_freeze_table_age to 0 forces VACUUM to - always use its aggressive strategy. - - - - The maximum time that a table can go unvacuumed is two billion - transactions minus the vacuum_freeze_min_age value at - the time of the last aggressive vacuum. If it were to go - unvacuumed for longer than - that, data loss could result. To ensure that this does not happen, - autovacuum is invoked on any table that might contain unfrozen rows with - XIDs older than the age specified by the configuration parameter . (This will happen even if - autovacuum is disabled.) - - - - This implies that if a table is not otherwise vacuumed, - autovacuum will be invoked on it approximately once every - autovacuum_freeze_max_age minus - vacuum_freeze_min_age transactions. - For tables that are regularly vacuumed for space reclamation purposes, - this is of little importance. However, for static tables - (including tables that receive inserts, but no updates or deletes), - there is no need to vacuum for space reclamation, so it can - be useful to try to maximize the interval between forced autovacuums - on very large static tables. Obviously one can do this either by - increasing autovacuum_freeze_max_age or decreasing - vacuum_freeze_min_age. - - - - The effective maximum for vacuum_freeze_table_age is 0.95 * - autovacuum_freeze_max_age; a setting higher than that will be - capped to the maximum. A value higher than - autovacuum_freeze_max_age wouldn't make sense because an - anti-wraparound autovacuum would be triggered at that point anyway, and - the 0.95 multiplier leaves some breathing room to run a manual - VACUUM before that happens. As a rule of thumb, - vacuum_freeze_table_age should be set to a value somewhat - below autovacuum_freeze_max_age, leaving enough gap so that - a regularly scheduled VACUUM or an autovacuum triggered by - normal delete and update activity is run in that window. Setting it too - close could lead to anti-wraparound autovacuums, even though the table - was recently vacuumed to reclaim space, whereas lower values lead to more - frequent aggressive vacuuming. - - - - The sole disadvantage of increasing autovacuum_freeze_max_age - (and vacuum_freeze_table_age along with it) is that - the pg_xact and pg_commit_ts - subdirectories of the database cluster will take more space, because it - must store the commit status and (if track_commit_timestamp is - enabled) timestamp of all transactions back to - the autovacuum_freeze_max_age horizon. The commit status uses - two bits per transaction, so if - autovacuum_freeze_max_age is set to its maximum allowed value - of two billion, pg_xact can be expected to grow to about half - a gigabyte and pg_commit_ts to about 20GB. If this - is trivial compared to your total database size, - setting autovacuum_freeze_max_age to its maximum allowed value - is recommended. Otherwise, set it depending on what you are willing to - allow for pg_xact and pg_commit_ts storage. - (The default, 200 million transactions, translates to about 50MB - of pg_xact storage and about 2GB of pg_commit_ts - storage.) - - - - One disadvantage of decreasing vacuum_freeze_min_age is that - it might cause VACUUM to do useless work: freezing a row - version is a waste of time if the row is modified - soon thereafter (causing it to acquire a new XID). So the setting should - be large enough that rows are not frozen until they are unlikely to change - any more. + One of the purposes of periodic vacuuming is to manage the + Transaction Id address space. VACUUM will mark + rows as frozen, indicating that they were + inserted by a transaction that committed sufficiently far in the + past that the effects of the inserting transaction are certain to + be visible to all current and future transactions. There is, in + effect, an infinite distance between a frozen transaction ID and + any unfrozen transaction ID. This allows the on-disk + representation of transaction IDs to recycle the 32-bit address + space efficiently. @@ -587,15 +435,15 @@ pg_database. In particular, the relfrozenxid column of a table's pg_class row contains the oldest remaining unfrozen - XID at the end of the most recent VACUUM that successfully - advanced relfrozenxid. All rows inserted by - transactions older than this cutoff XID are guaranteed to have been frozen. - Similarly, the datfrozenxid column of a database's - pg_database row is a lower bound on the unfrozen XIDs - appearing in that database — it is just the minimum of the - per-table relfrozenxid values within the database. - A convenient way to - examine this information is to execute queries such as: + XID at the end of the most recent VACUUM. All + rows inserted by transactions older than this cutoff XID are + guaranteed to have been frozen. Similarly, the + datfrozenxid column of a database's + pg_database row is a lower bound on the + unfrozen XIDs appearing in that database — it is just the + minimum of the per-table relfrozenxid + values within the database. A convenient way to examine this + information is to execute queries such as: SELECT c.oid::regclass as table_name, @@ -611,89 +459,13 @@ SELECT datname, age(datfrozenxid) FROM pg_database; cutoff XID to the current transaction's XID. - - - When the VACUUM command's VERBOSE - parameter is specified, VACUUM prints various - statistics about the table. This includes information about how - relfrozenxid and - relminmxid advanced. The same details appear - in the server log when autovacuum logging (controlled by ) reports on a - VACUUM operation executed by autovacuum. - - - - - VACUUM normally only scans pages that have been modified - since the last vacuum, but relfrozenxid can only be - advanced when every page of the table - that might contain unfrozen XIDs is scanned. This happens when - relfrozenxid is more than - vacuum_freeze_table_age transactions old, when - VACUUM's FREEZE option is used, or when all - pages that are not already all-frozen happen to - require vacuuming to remove dead row versions. When VACUUM - scans every page in the table that is not already all-frozen, it should - set age(relfrozenxid) to a value just a little more than the - vacuum_freeze_min_age setting - that was used (more by the number of transactions started since the - VACUUM started). VACUUM - will set relfrozenxid to the oldest XID - that remains in the table, so it's possible that the final value - will be much more recent than strictly required. - If no relfrozenxid-advancing - VACUUM is issued on the table until - autovacuum_freeze_max_age is reached, an autovacuum will soon - be forced for the table. - - - - If for some reason autovacuum fails to clear old XIDs from a table, the - system will begin to emit warning messages like this when the database's - oldest XIDs reach forty million transactions from the wraparound point: - - -WARNING: database "mydb" must be vacuumed within 39985967 transactions -HINT: To avoid a database shutdown, execute a database-wide VACUUM in that database. - - - (A manual VACUUM should fix the problem, as suggested by the - hint; but note that the VACUUM must be performed by a - superuser, else it will fail to process system catalogs and thus not - be able to advance the database's datfrozenxid.) - If these warnings are - ignored, the system will shut down and refuse to start any new - transactions once there are fewer than three million transactions left - until wraparound: - - -ERROR: database is not accepting commands to avoid wraparound data loss in database "mydb" -HINT: Stop the postmaster and vacuum that database in single-user mode. - - - The three-million-transaction safety margin exists to let the - administrator recover without data loss, by manually executing the - required VACUUM commands. However, since the system will not - execute commands once it has gone into the safety shutdown mode, - the only way to do this is to stop the server and start the server in single-user - mode to execute VACUUM. The shutdown mode is not enforced - in single-user mode. See the reference - page for details about using single-user mode. - - - Multixacts and Wraparound + Managing the 32-bit MultiXactId address space MultiXactId - - wraparound - of multixact IDs - - Multixact IDs are used to support row locking by multiple transactions. Since there is only limited space in a tuple @@ -704,49 +476,137 @@ HINT: Stop the postmaster and vacuum that database in single-user mode. particular multixact ID is stored separately in the pg_multixact subdirectory, and only the multixact ID appears in the xmax field in the tuple header. - Like transaction IDs, multixact IDs are implemented as a - 32-bit counter and corresponding storage, all of which requires - careful aging management, storage cleanup, and wraparound handling. - There is a separate storage area which holds the list of members in - each multixact, which also uses a 32-bit counter and which must also - be managed. + Like transaction IDs, multixact IDs are implemented as a 32-bit + counter and corresponding storage. + + + A separate relminmxid field can be + advanced any time relfrozenxid is + advanced. VACUUM manages the MultiXactId + address space by implementing rules that are analogous to the + approach taken with Transaction IDs. Many of the XID-based + settings that influence VACUUM's behavior have + direct MultiXactId analogs. A convenient way to examine + information about the MultiXactId address space is to execute + queries such as: + + +SELECT c.oid::regclass as table_name, + mxid_age(c.relminmxid) +FROM pg_class c +WHERE c.relkind IN ('r', 'm'); + +SELECT datname, mxid_age(datminmxid) FROM pg_database; + + + + + Controlling freezing + + As a general rule, the more tuples that VACUUM + freezes, the more recently VACUUM can set the + table's relfrozenxid and + relminmxid fields to afterwards. + and control how old + an XID or MultiXactId value has to be before the row will be + frozen (absent any other factor that triggers freezing). + This is only enforced in smaller tables that use the lazy freezing + strategy (controlled by + ). + Increasing these settings may avoid unnecessary work, but that + isn't generally recommended. + + + + + When the VACUUM command's VERBOSE + parameter is specified, VACUUM prints various + statistics about the table. This includes information about how + relfrozenxid and + relminmxid advanced. The same details appear + in the server log when autovacuum logging (controlled by ) reports on a + VACUUM operation executed by autovacuum. + + + + + + Anti-wraparound VACUUM + + + wraparound + of transaction IDs + + + + wraparound + of multixact IDs + + + + relfrozenxid and + relminmxid can only be advanced when + VACUUM actually runs. Even then, + VACUUM must scan every page of the table that + might contain unfrozen XIDs. VACUUM usually + advances relfrozenxid on a best-effort + basis, weighing costs against benefits. This approach spreads + out the burden of freezing over time, across multiple + VACUUM operations. However, if no + relfrozenxid-advancing + VACUUM is issued on the table before + autovacuum_freeze_max_age is reached, an + anti-wraparound autovacuum will soon be forced for the table. + This will reliably set relfrozenxid + and relminmxid to a relatively recent + values. - Whenever VACUUM scans any part of a table, it will replace - any multixact ID it encounters which is older than - - by a different value, which can be the zero value, a single - transaction ID, or a newer multixact ID. For each table, - pg_class.relminmxid stores the oldest - possible multixact ID still appearing in any tuple of that table. - If this value is older than - , an aggressive - vacuum is forced. As discussed in the previous section, an aggressive - vacuum means that only those pages which are known to be all-frozen will - be skipped. mxid_age() can be used on - pg_class.relminmxid to find its age. + An anti-wraparound autovacuum will also be triggered for any + table whose multixact-age is greater than . However, + if the storage occupied by multixacts members exceeds 2GB, + anti-wraparound vacuum might occur more often than this. - Aggressive VACUUMs, regardless of what causes - them, are guaranteed to be able to advance - the table's relminmxid. - Eventually, as all tables in all databases are scanned and their - oldest multixact values are advanced, on-disk storage for older - multixacts can be removed. - + If for some reason autovacuum fails to clear old XIDs from a table, the + system will begin to emit warning messages like this when the database's + oldest XIDs reach forty million transactions from the wraparound point: - - As a safety device, an aggressive vacuum scan will - occur for any table whose multixact-age is greater than . Also, if the - storage occupied by multixacts members exceeds 2GB, aggressive vacuum - scans will occur more often for all tables, starting with those that - have the oldest multixact-age. Both of these kinds of aggressive - scans will occur even if autovacuum is nominally disabled. + + WARNING: database "mydb" must be vacuumed within 39985967 transactions + HINT: To avoid a database shutdown, execute a database-wide VACUUM in that database. + + + (A manual VACUUM should fix the problem, as suggested by the + hint; but note that the VACUUM must be performed by a + superuser, else it will fail to process system catalogs and thus not + be able to advance the database's datfrozenxid.) + If these warnings are + ignored, the system will shut down and refuse to start any new + transactions once there are fewer than three million transactions left + until wraparound: + + + ERROR: database is not accepting commands to avoid wraparound data loss in database "mydb" + HINT: Stop the postmaster and vacuum that database in single-user mode. + + + The three-million-transaction safety margin exists to let the + administrator recover without data loss, by manually executing the + required VACUUM commands. However, since the system will not + execute commands once it has gone into the safety shutdown mode, + the only way to do this is to stop the server and start the server in single-user + mode to execute VACUUM. The shutdown mode is not enforced + in single-user mode. See the reference + page for details about using single-user mode. + @@ -832,22 +692,13 @@ vacuum insert threshold = vacuum base insert threshold + vacuum insert scale fac and vacuum insert scale factor is . Such vacuums may allow portions of the table to be marked as - all visible and also allow tuples to be frozen, which - can reduce the work required in subsequent vacuums. - For tables which receive INSERT operations but no or - almost no UPDATE/DELETE operations, - it may be beneficial to lower the table's - as this may allow - tuples to be frozen by earlier vacuums. The number of obsolete tuples and + all visible and also allow tuples to be frozen. + The number of obsolete tuples and the number of inserted tuples are obtained from the cumulative statistics system; it is a semi-accurate count updated by each UPDATE, DELETE and INSERT operation. (It is only semi-accurate because some information might be lost under heavy - load.) If the relfrozenxid value of the table - is more than vacuum_freeze_table_age transactions old, - an aggressive vacuum is performed to freeze old tuples and advance - relfrozenxid; otherwise, only pages that have been modified - since the last vacuum are scanned. + load.) diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index 7e684d187..74a61abe2 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -1501,7 +1501,7 @@ WITH ( MODULUS numeric_literal, REM and/or ANALYZE operations on this table following the rules discussed in . If false, this table will not be autovacuumed, except to prevent - transaction ID wraparound. See for + transaction ID wraparound. See for more about wraparound prevention. Note that the autovacuum daemon does not run at all (except to prevent transaction ID wraparound) if the diff --git a/doc/src/sgml/ref/prepare_transaction.sgml b/doc/src/sgml/ref/prepare_transaction.sgml index f4f6118ac..1817ed1e3 100644 --- a/doc/src/sgml/ref/prepare_transaction.sgml +++ b/doc/src/sgml/ref/prepare_transaction.sgml @@ -128,7 +128,7 @@ PREPARE TRANSACTION transaction_id This will interfere with the ability of VACUUM to reclaim storage, and in extreme cases could cause the database to shut down to prevent transaction ID wraparound (see ). Keep in mind also that the transaction + linkend="vacuum-xid-space"/>). Keep in mind also that the transaction continues to hold whatever locks it held. The intended usage of the feature is that a prepared transaction will normally be committed or rolled back as soon as an external transaction manager has verified that diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml index c582021d2..42360f165 100644 --- a/doc/src/sgml/ref/vacuum.sgml +++ b/doc/src/sgml/ref/vacuum.sgml @@ -119,12 +119,12 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ and - parameters - set to zero. Aggressive freezing is always performed when the + Selects eager freezing of tuples, and forces + antiwraparound mode. Specifying FREEZE is + equivalent to performing VACUUM with the + and parameters + set to zero. Eager freezing is always performed when the table is rewritten, so this option is redundant when FULL is specified. @@ -154,13 +154,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ visibility map. Pages where - all tuples are known to be frozen can always be skipped, and those - where all tuples are known to be visible to all transactions may be - skipped except when performing an aggressive vacuum. Furthermore, - except when performing an aggressive vacuum, some pages may be skipped - in order to avoid waiting for other sessions to finish using them. + Normally, VACUUM will skip pages based on the + visibility map. This option disables all page-skipping behavior, and is intended to be used only when the contents of the visibility map are suspect, which should happen only if there is a hardware or software @@ -215,7 +210,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ ). However, the + (see ). However, the wraparound failsafe mechanism controlled by will generally trigger automatically to avoid transaction ID wraparound failure, and diff --git a/doc/src/sgml/ref/vacuumdb.sgml b/doc/src/sgml/ref/vacuumdb.sgml index 841aced3b..fdc81a237 100644 --- a/doc/src/sgml/ref/vacuumdb.sgml +++ b/doc/src/sgml/ref/vacuumdb.sgml @@ -180,7 +180,8 @@ PostgreSQL documentation - Aggressively freeze tuples. + Eagerly freeze tuples and force antiwraparound + mode. @@ -259,7 +260,7 @@ PostgreSQL documentation transaction ID age of at least xid_age. This setting is useful for prioritizing tables to process to prevent transaction - ID wraparound (see ). + ID wraparound (see ). For the purposes of this option, the transaction ID age of a relation diff --git a/src/test/isolation/expected/vacuum-no-cleanup-lock.out b/src/test/isolation/expected/vacuum-no-cleanup-lock.out index f7bc93e8f..6a266033a 100644 --- a/src/test/isolation/expected/vacuum-no-cleanup-lock.out +++ b/src/test/isolation/expected/vacuum-no-cleanup-lock.out @@ -1,6 +1,6 @@ Parsed test spec with 4 sessions -starting permutation: vacuumer_pg_class_stats dml_insert vacuumer_nonaggressive_vacuum vacuumer_pg_class_stats +starting permutation: vacuumer_pg_class_stats dml_insert vacuumer_regular_vacuum vacuumer_pg_class_stats step vacuumer_pg_class_stats: SELECT relpages, reltuples FROM pg_class WHERE oid = 'smalltbl'::regclass; @@ -12,7 +12,7 @@ relpages|reltuples step dml_insert: INSERT INTO smalltbl SELECT max(id) + 1 FROM smalltbl; -step vacuumer_nonaggressive_vacuum: +step vacuumer_regular_vacuum: VACUUM smalltbl; step vacuumer_pg_class_stats: @@ -24,7 +24,7 @@ relpages|reltuples (1 row) -starting permutation: vacuumer_pg_class_stats dml_insert pinholder_cursor vacuumer_nonaggressive_vacuum vacuumer_pg_class_stats pinholder_commit +starting permutation: vacuumer_pg_class_stats dml_insert pinholder_cursor vacuumer_regular_vacuum vacuumer_pg_class_stats pinholder_commit step vacuumer_pg_class_stats: SELECT relpages, reltuples FROM pg_class WHERE oid = 'smalltbl'::regclass; @@ -46,7 +46,7 @@ dummy 1 (1 row) -step vacuumer_nonaggressive_vacuum: +step vacuumer_regular_vacuum: VACUUM smalltbl; step vacuumer_pg_class_stats: @@ -61,7 +61,7 @@ step pinholder_commit: COMMIT; -starting permutation: vacuumer_pg_class_stats pinholder_cursor dml_insert dml_delete dml_insert vacuumer_nonaggressive_vacuum vacuumer_pg_class_stats pinholder_commit +starting permutation: vacuumer_pg_class_stats pinholder_cursor dml_insert dml_delete dml_insert vacuumer_regular_vacuum vacuumer_pg_class_stats pinholder_commit step vacuumer_pg_class_stats: SELECT relpages, reltuples FROM pg_class WHERE oid = 'smalltbl'::regclass; @@ -89,7 +89,7 @@ step dml_delete: step dml_insert: INSERT INTO smalltbl SELECT max(id) + 1 FROM smalltbl; -step vacuumer_nonaggressive_vacuum: +step vacuumer_regular_vacuum: VACUUM smalltbl; step vacuumer_pg_class_stats: @@ -104,7 +104,7 @@ step pinholder_commit: COMMIT; -starting permutation: vacuumer_pg_class_stats dml_insert dml_delete pinholder_cursor dml_insert vacuumer_nonaggressive_vacuum vacuumer_pg_class_stats pinholder_commit +starting permutation: vacuumer_pg_class_stats dml_insert dml_delete pinholder_cursor dml_insert vacuumer_regular_vacuum vacuumer_pg_class_stats pinholder_commit step vacuumer_pg_class_stats: SELECT relpages, reltuples FROM pg_class WHERE oid = 'smalltbl'::regclass; @@ -132,7 +132,7 @@ dummy step dml_insert: INSERT INTO smalltbl SELECT max(id) + 1 FROM smalltbl; -step vacuumer_nonaggressive_vacuum: +step vacuumer_regular_vacuum: VACUUM smalltbl; step vacuumer_pg_class_stats: @@ -147,7 +147,7 @@ step pinholder_commit: COMMIT; -starting permutation: dml_begin dml_other_begin dml_key_share dml_other_key_share vacuumer_nonaggressive_vacuum pinholder_cursor dml_other_update dml_commit dml_other_commit vacuumer_nonaggressive_vacuum pinholder_commit vacuumer_nonaggressive_vacuum +starting permutation: dml_begin dml_other_begin dml_key_share dml_other_key_share vacuumer_regular_vacuum pinholder_cursor dml_other_update dml_commit dml_other_commit vacuumer_regular_vacuum pinholder_commit vacuumer_regular_vacuum step dml_begin: BEGIN; step dml_other_begin: BEGIN; step dml_key_share: SELECT id FROM smalltbl WHERE id = 3 FOR KEY SHARE; @@ -162,7 +162,7 @@ id 3 (1 row) -step vacuumer_nonaggressive_vacuum: +step vacuumer_regular_vacuum: VACUUM smalltbl; step pinholder_cursor: @@ -178,12 +178,12 @@ dummy step dml_other_update: UPDATE smalltbl SET t = 'u' WHERE id = 3; step dml_commit: COMMIT; step dml_other_commit: COMMIT; -step vacuumer_nonaggressive_vacuum: +step vacuumer_regular_vacuum: VACUUM smalltbl; step pinholder_commit: COMMIT; -step vacuumer_nonaggressive_vacuum: +step vacuumer_regular_vacuum: VACUUM smalltbl; diff --git a/src/test/isolation/specs/vacuum-no-cleanup-lock.spec b/src/test/isolation/specs/vacuum-no-cleanup-lock.spec index 05fd280f6..28fb52433 100644 --- a/src/test/isolation/specs/vacuum-no-cleanup-lock.spec +++ b/src/test/isolation/specs/vacuum-no-cleanup-lock.spec @@ -55,15 +55,15 @@ step dml_other_key_share { SELECT id FROM smalltbl WHERE id = 3 FOR KEY SHARE; step dml_other_update { UPDATE smalltbl SET t = 'u' WHERE id = 3; } step dml_other_commit { COMMIT; } -# This session runs non-aggressive VACUUM, but with maximally aggressive -# cutoffs for tuple freezing (e.g., FreezeLimit == OldestXmin): +# This session runs regular VACUUM with maximally aggressive cutoffs for tuple +# freezing (e.g., FreezeLimit == OldestXmin): session vacuumer setup { SET vacuum_freeze_min_age = 0; SET vacuum_multixact_freeze_min_age = 0; } -step vacuumer_nonaggressive_vacuum +step vacuumer_regular_vacuum { VACUUM smalltbl; } @@ -75,15 +75,14 @@ step vacuumer_pg_class_stats # Test VACUUM's reltuples counting mechanism. # # Final pg_class.reltuples should never be affected by VACUUM's inability to -# get a cleanup lock on any page, except to the extent that any cleanup lock -# contention changes the number of tuples that remain ("missed dead" tuples -# are counted in reltuples, much like "recently dead" tuples). +# get a cleanup lock on any page ("missed dead" tuples are counted in +# reltuples, much like "recently dead" tuples). # Easy case: permutation vacuumer_pg_class_stats # Start with 20 tuples dml_insert - vacuumer_nonaggressive_vacuum + vacuumer_regular_vacuum vacuumer_pg_class_stats # End with 21 tuples # Harder case -- count 21 tuples at the end (like last time), but with cleanup @@ -92,7 +91,7 @@ permutation vacuumer_pg_class_stats # Start with 20 tuples dml_insert pinholder_cursor - vacuumer_nonaggressive_vacuum + vacuumer_regular_vacuum vacuumer_pg_class_stats # End with 21 tuples pinholder_commit # order doesn't matter @@ -103,7 +102,7 @@ permutation dml_insert dml_delete dml_insert - vacuumer_nonaggressive_vacuum + vacuumer_regular_vacuum # reltuples is 21 here again -- "recently dead" tuple won't be included in # count here: vacuumer_pg_class_stats @@ -116,7 +115,7 @@ permutation dml_delete pinholder_cursor dml_insert - vacuumer_nonaggressive_vacuum + vacuumer_regular_vacuum # reltuples is 21 here again -- "missed dead" tuple ("recently dead" when # concurrent activity held back VACUUM's OldestXmin) won't be included in # count here: @@ -128,7 +127,7 @@ permutation # This provides test coverage for code paths that are only hit when we need to # freeze, but inability to acquire a cleanup lock on a heap page makes # freezing some XIDs/MXIDs < FreezeLimit/MultiXactCutoff impossible (without -# waiting for a cleanup lock, which non-aggressive VACUUM is unwilling to do). +# waiting for a cleanup lock, which only antiwraparound VACUUM is willing to do). permutation dml_begin dml_other_begin @@ -136,15 +135,15 @@ permutation dml_other_key_share # Will get cleanup lock, can't advance relminmxid yet: # (though will usually advance relfrozenxid by ~2 XIDs) - vacuumer_nonaggressive_vacuum + vacuumer_regular_vacuum pinholder_cursor dml_other_update dml_commit dml_other_commit # Can't cleanup lock, so still can't advance relminmxid here: # (relfrozenxid held back by XIDs in MultiXact too) - vacuumer_nonaggressive_vacuum + vacuumer_regular_vacuum pinholder_commit # Pin was dropped, so will advance relminmxid, at long last: # (ditto for relfrozenxid advancement) - vacuumer_nonaggressive_vacuum + vacuumer_regular_vacuum diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index b6aef6f65..a02348900 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -102,7 +102,7 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; INSERT INTO reloptions_test VALUES (1, NULL), (NULL, NULL); ERROR: null value in column "i" of relation "reloptions_test" violates not-null constraint DETAIL: Failing row contains (null, null). --- Do an aggressive vacuum to prevent page-skipping. +-- Do an antiwraparound vacuum to prevent page-skipping. VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) reloptions_test; SELECT pg_relation_size('reloptions_test') > 0; ?column? @@ -128,7 +128,7 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; INSERT INTO reloptions_test VALUES (1, NULL), (NULL, NULL); ERROR: null value in column "i" of relation "reloptions_test" violates not-null constraint DETAIL: Failing row contains (null, null). --- Do an aggressive vacuum to prevent page-skipping. +-- Do an antiwraparound vacuum to prevent page-skipping. VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) reloptions_test; SELECT pg_relation_size('reloptions_test') = 0; ?column? diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 4252b0202..6c727695e 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -61,7 +61,7 @@ CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) autovacuum_enabled=false); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; INSERT INTO reloptions_test VALUES (1, NULL), (NULL, NULL); --- Do an aggressive vacuum to prevent page-skipping. +-- Do an antiwraparound vacuum to prevent page-skipping. VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) reloptions_test; SELECT pg_relation_size('reloptions_test') > 0; @@ -72,7 +72,7 @@ SELECT reloptions FROM pg_class WHERE oid = ALTER TABLE reloptions_test RESET (vacuum_truncate); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; INSERT INTO reloptions_test VALUES (1, NULL), (NULL, NULL); --- Do an aggressive vacuum to prevent page-skipping. +-- Do an antiwraparound vacuum to prevent page-skipping. VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) reloptions_test; SELECT pg_relation_size('reloptions_test') = 0; -- 2.34.1