From 6d227af53823866b56cbb7932a7d8e4f21f764d0 Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
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)

