From 1a71b8b4e06b160c59c323439df4a0d75d1b6baa Mon Sep 17 00:00:00 2001
From: Peter Geoghegan <pg@bowt.ie>
Date: Tue, 2 Aug 2022 13:08:56 -0700
Subject: [PATCH v2] Derive freeze cutoff from nextXID, not OldestXmin.

Author: Peter Geoghegan <pg@bowt.ie>
Reviewed-By: Nathan Bossart <nathandbossart@gmail.com>
Reviewed-By: Matthias van de Meent <boekewurm+postgres@gmail.com>
Discussion: https://postgr.es/m/CAH2-WzkOv5CEeyOO=c91XnT5WBR_0gii0Wn5UbZhJ=4TTykDYg@mail.gmail.com
---
 src/include/commands/vacuum.h        |   3 +-
 src/backend/access/heap/vacuumlazy.c |   2 +-
 src/backend/commands/vacuum.c        | 197 ++++++++++++---------------
 3 files changed, 91 insertions(+), 111 deletions(-)

diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index f38e1148f..5d816ba7f 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -287,8 +287,9 @@ extern void vac_update_relstats(Relation relation,
 								bool *minmulti_updated,
 								bool in_outer_xact);
 extern bool vacuum_set_xid_limits(Relation rel,
-								  int freeze_min_age, int freeze_table_age,
+								  int freeze_min_age,
 								  int multixact_freeze_min_age,
+								  int freeze_table_age,
 								  int multixact_freeze_table_age,
 								  TransactionId *oldestXmin,
 								  MultiXactId *oldestMxact,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index b802ed247..21eaf1d8c 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -360,8 +360,8 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	 */
 	aggressive = vacuum_set_xid_limits(rel,
 									   params->freeze_min_age,
-									   params->freeze_table_age,
 									   params->multixact_freeze_min_age,
+									   params->freeze_table_age,
 									   params->multixact_freeze_table_age,
 									   &OldestXmin, &OldestMxact,
 									   &FreezeLimit, &MultiXactCutoff);
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index b60378122..7ccde07de 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -956,24 +956,25 @@ get_all_vacuum_rels(int options)
 bool
 vacuum_set_xid_limits(Relation rel,
 					  int freeze_min_age,
-					  int freeze_table_age,
 					  int multixact_freeze_min_age,
+					  int freeze_table_age,
 					  int multixact_freeze_table_age,
 					  TransactionId *oldestXmin,
 					  MultiXactId *oldestMxact,
 					  TransactionId *freezeLimit,
 					  MultiXactId *multiXactCutoff)
 {
-	int			freezemin;
-	int			mxid_freezemin;
+	TransactionId nextXID,
+				safeOldestXmin,
+				aggressiveXIDCutoff;
+	MultiXactId nextMXID,
+				safeOldestMxact,
+				aggressiveMXIDCutoff;
 	int			effective_multixact_freeze_max_age;
-	TransactionId limit;
-	TransactionId safeLimit;
-	MultiXactId mxactLimit;
-	MultiXactId safeMxactLimit;
-	int			freezetable;
 
 	/*
+	 * Acquire oldestXmin.
+	 *
 	 * We can always ignore processes running lazy vacuum.  This is because we
 	 * use these values only for deciding which tuples we must keep in the
 	 * tables.  Since lazy vacuum doesn't write its XID anywhere (usually no
@@ -1005,44 +1006,32 @@ vacuum_set_xid_limits(Relation rel,
 
 	Assert(TransactionIdIsNormal(*oldestXmin));
 
+	/* Acquire oldestMxact */
+	*oldestMxact = GetOldestMultiXactId();
+	Assert(MultiXactIdIsValid(*oldestMxact));
+
+	/* Acquire next XID/next MXID values used to apply age-based settings */
+	nextXID = ReadNextTransactionId();
+	nextMXID = ReadNextMultiXactId();
+
 	/*
 	 * Determine the minimum freeze age to use: as specified by the caller, or
 	 * vacuum_freeze_min_age, but in any case not more than half
 	 * autovacuum_freeze_max_age, so that autovacuums to prevent XID
 	 * wraparound won't occur too frequently.
 	 */
-	freezemin = freeze_min_age;
-	if (freezemin < 0)
-		freezemin = vacuum_freeze_min_age;
-	freezemin = Min(freezemin, autovacuum_freeze_max_age / 2);
-	Assert(freezemin >= 0);
+	if (freeze_min_age < 0)
+		freeze_min_age = vacuum_freeze_min_age;
+	freeze_min_age = Min(freeze_min_age, autovacuum_freeze_max_age / 2);
+	Assert(freeze_min_age >= 0);
 
-	/*
-	 * Compute the cutoff XID, being careful not to generate a "permanent" XID
-	 */
-	limit = *oldestXmin - freezemin;
-	if (!TransactionIdIsNormal(limit))
-		limit = FirstNormalTransactionId;
-
-	/*
-	 * If oldestXmin is very far back (in practice, more than
-	 * autovacuum_freeze_max_age / 2 XIDs old), complain and force a minimum
-	 * freeze age of zero.
-	 */
-	safeLimit = ReadNextTransactionId() - autovacuum_freeze_max_age;
-	if (!TransactionIdIsNormal(safeLimit))
-		safeLimit = FirstNormalTransactionId;
-
-	if (TransactionIdPrecedes(limit, safeLimit))
-	{
-		ereport(WARNING,
-				(errmsg("oldest xmin is far in the past"),
-				 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.")));
-		limit = *oldestXmin;
-	}
-
-	*freezeLimit = limit;
+	/* Compute freezeLimit, being careful to generate a normal XID */
+	*freezeLimit = nextXID - freeze_min_age;
+	if (!TransactionIdIsNormal(*freezeLimit))
+		*freezeLimit = FirstNormalTransactionId;
+	/* freezeLimit must always be <= oldestXmin */
+	if (TransactionIdPrecedes(*oldestXmin, *freezeLimit))
+		*freezeLimit = *oldestXmin;
 
 	/*
 	 * Compute the multixact age for which freezing is urgent.  This is
@@ -1057,93 +1046,83 @@ vacuum_set_xid_limits(Relation rel,
 	 * than half effective_multixact_freeze_max_age, so that autovacuums to
 	 * prevent MultiXact wraparound won't occur too frequently.
 	 */
-	mxid_freezemin = multixact_freeze_min_age;
-	if (mxid_freezemin < 0)
-		mxid_freezemin = vacuum_multixact_freeze_min_age;
-	mxid_freezemin = Min(mxid_freezemin,
-						 effective_multixact_freeze_max_age / 2);
-	Assert(mxid_freezemin >= 0);
+	if (multixact_freeze_min_age < 0)
+		multixact_freeze_min_age = vacuum_multixact_freeze_min_age;
+	multixact_freeze_min_age = Min(multixact_freeze_min_age,
+								   effective_multixact_freeze_max_age / 2);
+	Assert(multixact_freeze_min_age >= 0);
 
-	/* Remember for caller */
-	*oldestMxact = GetOldestMultiXactId();
-
-	/* compute the cutoff multi, being careful to generate a valid value */
-	mxactLimit = *oldestMxact - mxid_freezemin;
-	if (mxactLimit < FirstMultiXactId)
-		mxactLimit = FirstMultiXactId;
-
-	safeMxactLimit =
-		ReadNextMultiXactId() - effective_multixact_freeze_max_age;
-	if (safeMxactLimit < FirstMultiXactId)
-		safeMxactLimit = FirstMultiXactId;
-
-	if (MultiXactIdPrecedes(mxactLimit, safeMxactLimit))
-	{
-		ereport(WARNING,
-				(errmsg("oldest multixact is far in the past"),
-				 errhint("Close open transactions with multixacts soon to avoid wraparound problems.")));
-		/* Use the safe limit, unless an older mxact is still running */
-		if (MultiXactIdPrecedes(*oldestMxact, safeMxactLimit))
-			mxactLimit = *oldestMxact;
-		else
-			mxactLimit = safeMxactLimit;
-	}
-
-	*multiXactCutoff = mxactLimit;
+	/* Compute multiXactCutoff, being careful to generate a valid value */
+	*multiXactCutoff = nextMXID - multixact_freeze_min_age;
+	if (*multiXactCutoff < FirstMultiXactId)
+		*multiXactCutoff = FirstMultiXactId;
+	/* multiXactCutoff must always be <= oldestMxact */
+	if (MultiXactIdPrecedes(*oldestMxact, *multiXactCutoff))
+		*multiXactCutoff = *oldestMxact;
 
 	/*
-	 * Done setting output parameters; just need to figure out if caller needs
-	 * to do an aggressive VACUUM or not.
+	 * Done setting 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))
+		safeOldestXmin = FirstNormalTransactionId;
+	safeOldestMxact = nextMXID - effective_multixact_freeze_max_age;
+	if (safeOldestMxact < FirstMultiXactId)
+		safeOldestMxact = FirstMultiXactId;
+	if (TransactionIdPrecedes(*oldestXmin, safeOldestXmin))
+		ereport(WARNING,
+				(errmsg("cutoff for removing and freezing tuples is far in the past"),
+				 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.")));
+	if (MultiXactIdPrecedes(*oldestMxact, safeOldestMxact))
+		ereport(WARNING,
+				(errmsg("cutoff for freezing multixacts is far in the past"),
+				 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.")));
+
+	/*
+	 * Finally, figure out if caller needs to do an aggressive VACUUM or not.
 	 *
 	 * Determine the table freeze age to use: as specified by the caller, or
-	 * vacuum_freeze_table_age, 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 tuples
-	 * before anti-wraparound autovacuum is launched.
+	 * 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.
 	 */
-	freezetable = freeze_table_age;
-	if (freezetable < 0)
-		freezetable = vacuum_freeze_table_age;
-	freezetable = Min(freezetable, autovacuum_freeze_max_age * 0.95);
-	Assert(freezetable >= 0);
-
-	/*
-	 * Compute XID limit causing an aggressive vacuum, being careful not to
-	 * generate a "permanent" XID
-	 */
-	limit = ReadNextTransactionId() - freezetable;
-	if (!TransactionIdIsNormal(limit))
-		limit = FirstNormalTransactionId;
+	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);
+	Assert(freeze_table_age >= 0);
+	aggressiveXIDCutoff = nextXID - freeze_table_age;
+	if (!TransactionIdIsNormal(aggressiveXIDCutoff))
+		aggressiveXIDCutoff = FirstNormalTransactionId;
 	if (TransactionIdPrecedesOrEquals(rel->rd_rel->relfrozenxid,
-									  limit))
+									  aggressiveXIDCutoff))
 		return true;
 
 	/*
 	 * Similar to the above, determine the table freeze age to use for
-	 * multixacts: as specified by the caller, or
-	 * vacuum_multixact_freeze_table_age, but in any case not more than
-	 * autovacuum_multixact_freeze_table_age * 0.95, so that if you have e.g.
+	 * 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.
 	 */
-	freezetable = multixact_freeze_table_age;
-	if (freezetable < 0)
-		freezetable = vacuum_multixact_freeze_table_age;
-	freezetable = Min(freezetable,
-					  effective_multixact_freeze_max_age * 0.95);
-	Assert(freezetable >= 0);
-
-	/*
-	 * Compute MultiXact limit causing an aggressive vacuum, being careful to
-	 * generate a valid MultiXact value
-	 */
-	mxactLimit = ReadNextMultiXactId() - freezetable;
-	if (mxactLimit < FirstMultiXactId)
-		mxactLimit = FirstMultiXactId;
+	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);
+	Assert(multixact_freeze_table_age >= 0);
+	aggressiveMXIDCutoff = nextMXID - multixact_freeze_table_age;
+	if (aggressiveMXIDCutoff < FirstMultiXactId)
+		aggressiveMXIDCutoff = FirstMultiXactId;
 	if (MultiXactIdPrecedesOrEquals(rel->rd_rel->relminmxid,
-									mxactLimit))
+									aggressiveMXIDCutoff))
 		return true;
 
+	/* Non-aggressive VACUUM */
 	return false;
 }
 
-- 
2.34.1

