From 6d227af53823866b56cbb7932a7d8e4f21f764d0 Mon Sep 17 00:00:00 2001 From: Masahiko Sawada Date: Thu, 18 Mar 2021 14:30:10 +0900 Subject: [PATCH v3 3/3] Skip index vacuuming when there is a risk of wraparound. If a table's relfrozenxid/relminmxid is too older than freeze max age threshold (e.g., autovacuum_freeze_max_age * 1.5 XIDsold), we skip index vacuuming to complete lazy vacuum quickly and advance relfrozenxid/relminmxid. --- src/backend/access/heap/vacuumlazy.c | 92 ++++++++++++++++++++++++---- src/backend/utils/misc/guc.c | 4 +- src/include/postmaster/autovacuum.h | 6 ++ 3 files changed, 89 insertions(+), 13 deletions(-) diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 0bed78bd17..435a2df763 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -388,8 +388,9 @@ static void lazy_scan_heap(Relation onerel, VacuumParams *params, static void two_pass_strategy(Relation onerel, LVRelStats *vacrelstats, Relation *Irel, IndexBulkDeleteResult **indstats, int nindexes, LVParallelState *lps, - VacOptIndexCleanupValue index_cleanup, - BlockNumber has_dead_items_pages, bool onecall); + VacuumParams *params, BlockNumber has_dead_items_pages, + bool onecall); +static bool check_index_cleanup_xid_limit(Relation onerel); static void lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats); static bool lazy_check_needs_freeze(Buffer buf, bool *hastup, LVRelStats *vacrelstats); @@ -1619,8 +1620,7 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, /* Remove the collected garbage tuples from table and indexes */ two_pass_strategy(onerel, vacrelstats, Irel, indstats, nindexes, - lps, params->index_cleanup, - has_dead_items_pages, false); + lps, params, has_dead_items_pages, false); /* * Vacuum the Free Space Map to make newly-freed space visible on @@ -1904,8 +1904,7 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, Assert(nindexes > 0 || dead_tuples->num_tuples == 0); if (dead_tuples->num_tuples > 0) two_pass_strategy(onerel, vacrelstats, Irel, indstats, nindexes, - lps, params->index_cleanup, - has_dead_items_pages, !calledtwopass); + lps, params, has_dead_items_pages, !calledtwopass); /* * Vacuum the remainder of the Free Space Map. We must do this whether or @@ -1992,7 +1991,7 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, static void two_pass_strategy(Relation onerel, LVRelStats *vacrelstats, Relation *Irel, IndexBulkDeleteResult **indstats, int nindexes, - LVParallelState *lps, VacOptIndexCleanupValue index_cleanup, + LVParallelState *lps, VacuumParams *params, BlockNumber has_dead_items_pages, bool onecall) { bool skipping; @@ -2018,17 +2017,30 @@ two_pass_strategy(Relation onerel, LVRelStats *vacrelstats, * HOT-pruning but are not marked dead yet. We do not process them because * it's a very rare condition, and the next vacuum will process them anyway. */ - if (index_cleanup == VACOPT_CLEANUP_DISABLED) + if (params->index_cleanup == VACOPT_CLEANUP_DISABLED) skipping = true; - else if (index_cleanup == VACOPT_CLEANUP_ENABLED) + else if (params->index_cleanup == VACOPT_CLEANUP_ENABLED) skipping = false; else if (!onecall) skipping = false; + + /* + * If a table is at risk of wraparound, we further check if the table's + * relfrozenxid/relminmxid is too older than freeze maximum age (e.g., more + * than autovacuum_freeze_max_age * 1.5 XIDs old). If so, we disable index + * vacuuming to quickly complete vacuum operation and advance relfrozenxid/relminmxid. + * Note that this can be applied to only autovacuum workers. + */ + else if (params->is_wraparound && check_index_cleanup_xid_limit(onerel)) + { + Assert(IsAutoVacuumWorkerProcess()); + skipping = true; + } else { BlockNumber rel_pages_threshold; - Assert(onecall && index_cleanup == VACOPT_CLEANUP_AUTO); + Assert(onecall && params->index_cleanup == VACOPT_CLEANUP_AUTO); rel_pages_threshold = (double) vacrelstats->rel_pages * SKIP_VACUUM_PAGES_RATIO; @@ -2062,7 +2074,7 @@ two_pass_strategy(Relation onerel, LVRelStats *vacrelstats, * one or more LP_DEAD items (could be from us or from another * VACUUM), not # blocks scanned. */ - if (index_cleanup == VACOPT_CLEANUP_AUTO) + if (params->index_cleanup == VACOPT_CLEANUP_AUTO) ereport(elevel, (errmsg("\"%s\": opted to not totally remove %d pruned items in %u pages", vacrelstats->relname, @@ -2084,6 +2096,64 @@ two_pass_strategy(Relation onerel, LVRelStats *vacrelstats, vacrelstats->dead_tuples->num_tuples = 0; } +/* + * Return true if the table's relfrozenxid/relminmxid is too older than + * freeze age. + */ +static bool +check_index_cleanup_xid_limit(Relation onerel) +{ + StdRdOptions *relopts = (StdRdOptions *) onerel->rd_options; + TransactionId xid_skip_limit; + MultiXactId multi_skip_limit; + int freeze_max_age; + int multixact_freeze_max_age; + int effective_multixact_freeze_max_age; + + /* + * Check if table's relfrozenxid is too older than autovacuum_freeze_max_age + * (more than autovacuum_freeze_max_age * 1.5 XIDs old). + */ + freeze_max_age = (relopts && relopts->autovacuum.freeze_max_age >= 0) + ? Min(relopts->autovacuum.freeze_max_age, autovacuum_freeze_max_age) + : autovacuum_freeze_max_age; + freeze_max_age = Min(freeze_max_age * 1.5, MAX_AUTOVACUUM_FREEZE_MAX_AGE); + + xid_skip_limit = ReadNextTransactionId() - freeze_max_age; + if (!TransactionIdIsNormal(xid_skip_limit)) + xid_skip_limit = FirstNormalTransactionId; + + if (TransactionIdIsNormal(onerel->rd_rel->relfrozenxid) && + TransactionIdPrecedes(onerel->rd_rel->relfrozenxid, + xid_skip_limit)) + return true; + + /* + * Similar to above, check multixact age. This is normally + * autovacuum_multixact_freeze_max_age, but may be less if we are short of + * multixact member space. + */ + effective_multixact_freeze_max_age = MultiXactMemberFreezeThreshold(); + + multixact_freeze_max_age = (relopts && relopts->autovacuum.multixact_freeze_max_age >= 0) + ? Min(relopts->autovacuum.multixact_freeze_max_age, + effective_multixact_freeze_max_age) + : effective_multixact_freeze_max_age; + multixact_freeze_max_age = Min(multixact_freeze_max_age * 1.5, + MAX_AUTOVACUUM_FREEZE_MAX_AGE); + + multi_skip_limit = ReadNextMultiXactId() - multixact_freeze_max_age; + if (multi_skip_limit < FirstMultiXactId) + multi_skip_limit = FirstMultiXactId; + + if (MultiXactIdIsValid(onerel->rd_rel->relminmxid) && + MultiXactIdPrecedes(onerel->rd_rel->relminmxid, + multi_skip_limit)) + return true; + + return false; +} + /* * lazy_vacuum_all_indexes() -- Main entry for index vacuuming * diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index b263e3493b..53aa444e13 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -3207,7 +3207,7 @@ static struct config_int ConfigureNamesInt[] = }, &autovacuum_freeze_max_age, /* see pg_resetwal if you change the upper-limit value */ - 200000000, 100000, 2000000000, + 200000000, 100000, MAX_AUTOVACUUM_FREEZE_MAX_AGE, NULL, NULL, NULL }, { @@ -3217,7 +3217,7 @@ static struct config_int ConfigureNamesInt[] = NULL }, &autovacuum_multixact_freeze_max_age, - 400000000, 10000, 2000000000, + 400000000, 10000, MAX_AUTOVACUUM_FREEZE_MAX_AGE, NULL, NULL, NULL }, { diff --git a/src/include/postmaster/autovacuum.h b/src/include/postmaster/autovacuum.h index aacdd0f575..e56e0d73ad 100644 --- a/src/include/postmaster/autovacuum.h +++ b/src/include/postmaster/autovacuum.h @@ -16,6 +16,12 @@ #include "storage/block.h" +/* + * Maximum value of autovacuum_freeze_max_age and + * autovacuum_multixact_freeze_max_age parameters. + */ +#define MAX_AUTOVACUUM_FREEZE_MAX_AGE 2000000000 + /* * Other processes can request specific work from autovacuum, identified by * AutoVacuumWorkItem elements. -- 2.24.3 (Apple Git-128)