From e556df208b9ec84056dee03796343f339d3c9a19 Mon Sep 17 00:00:00 2001
From: Peter Geoghegan <pg@bowt.ie>
Date: Tue, 2 Aug 2022 13:08:56 -0700
Subject: [PATCH v1] Derive VACUUM's FreezeLimit from next transaction.

---
 src/backend/commands/vacuum.c | 179 ++++++++++++++++------------------
 1 file changed, 86 insertions(+), 93 deletions(-)

diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 8df25f59d..bdcfb38fc 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -953,7 +953,7 @@ get_all_vacuum_rels(int options)
  * oldestXmin and oldestMxact are the most recent values that can ever be
  * passed to vac_update_relstats() as frozenxid and minmulti arguments by our
  * vacuumlazy.c caller later on.  These values should be passed when it turns
- * out that VACUUM will leave no unfrozen XIDs/XMIDs behind in the table.
+ * out that VACUUM will leave no unfrozen XIDs/MXIDs behind in the table.
  */
 bool
 vacuum_set_xid_limits(Relation rel,
@@ -969,11 +969,13 @@ vacuum_set_xid_limits(Relation rel,
 	int			freezemin;
 	int			mxid_freezemin;
 	int			effective_multixact_freeze_max_age;
-	TransactionId limit;
-	TransactionId safeLimit;
-	MultiXactId mxactLimit;
-	MultiXactId safeMxactLimit;
-	int			freezetable;
+	TransactionId nextXID,
+				  safeOldestXmin,
+				  aggressiveXIDCutoff;
+	MultiXactId nextMXID,
+				safeOldestMxact,
+				aggressiveMXIDCutoff;
+	int			aggressive_table_age;
 
 	/*
 	 * We can always ignore processes running lazy vacuum.  This is because we
@@ -985,6 +987,7 @@ vacuum_set_xid_limits(Relation rel,
 	 * any time, and that each vacuum is always an independent transaction.
 	 */
 	*oldestXmin = GetOldestNonRemovableTransactionId(rel);
+	*oldestMxact = GetOldestMultiXactId();
 
 	if (OldSnapshotThresholdActive())
 	{
@@ -999,6 +1002,11 @@ vacuum_set_xid_limits(Relation rel,
 			 * basis of the increased limits.  Not as crucial here as it is
 			 * for opportunistic pruning (which often happens at a much higher
 			 * frequency), but would still be a significant improvement.
+			 *
+			 * XXX We don't do push back oldestMxact here, which is not ideal
+			 * because VACUUM might have to allocate replacement MXIDs in order
+			 * to _avoid_ freezing somewhat old XIDs that are still after
+			 * limit_xmin.
 			 */
 			SetOldSnapshotThresholdTimestamp(limit_ts, limit_xmin);
 			*oldestXmin = limit_xmin;
@@ -1006,6 +1014,15 @@ vacuum_set_xid_limits(Relation rel,
 	}
 
 	Assert(TransactionIdIsNormal(*oldestXmin));
+	Assert(MultiXactIdIsValid(*oldestMxact));
+
+	/*
+	 * Now that oldestXmin and oldestMxact are established, determine next
+	 * transaction ID and next MXID.  Seems like a good idea to use the exact
+	 * same values for everything.
+	 */
+	nextXID = ReadNextTransactionId();
+	nextMXID = ReadNextMultiXactId();
 
 	/*
 	 * Determine the minimum freeze age to use: as specified by the caller, or
@@ -1019,32 +1036,28 @@ vacuum_set_xid_limits(Relation rel,
 	freezemin = Min(freezemin, autovacuum_freeze_max_age / 2);
 	Assert(freezemin >= 0);
 
-	/*
-	 * Compute the cutoff XID, being careful not to generate a "permanent" XID
-	 */
-	limit = *oldestXmin - freezemin;
-	if (!TransactionIdIsNormal(limit))
-		limit = FirstNormalTransactionId;
+	/* Compute freezeLimit, being careful to generate a normal XID */
+	*freezeLimit = nextXID - freezemin;
+	if (!TransactionIdIsNormal(*freezeLimit))
+		*freezeLimit = FirstNormalTransactionId;
+	/* freezeLimit must always be <= oldestXmin */
+	if (TransactionIdPrecedes(*oldestXmin, *freezeLimit))
+		*freezeLimit = *oldestXmin;
 
-	/*
-	 * 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))
+	/* Complain when oldestXmin held back by too much */
+	safeOldestXmin = nextXID - autovacuum_freeze_max_age;
+	if (!TransactionIdIsNormal(safeOldestXmin))
+		safeOldestXmin = FirstNormalTransactionId;
+	if (TransactionIdPrecedes(*oldestXmin, safeOldestXmin))
 	{
 		ereport(WARNING,
-				(errmsg("oldest xmin is far in the past"),
+				(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.")));
-		limit = *oldestXmin;
-	}
 
-	*freezeLimit = limit;
+		/* Force minimum freeze age of zero (redundant) */
+		*freezeLimit = *oldestXmin;
+	}
 
 	/*
 	 * Compute the multixact age for which freezing is urgent.  This is
@@ -1054,10 +1067,9 @@ vacuum_set_xid_limits(Relation rel,
 	effective_multixact_freeze_max_age = MultiXactMemberFreezeThreshold();
 
 	/*
-	 * Determine the minimum multixact freeze age to use: as specified by
-	 * caller, or vacuum_multixact_freeze_min_age, but in any case not more
-	 * than half effective_multixact_freeze_max_age, so that autovacuums to
-	 * prevent MultiXact wraparound won't occur too frequently.
+	 * Determine the minimum multixact freeze age to use, much like XID code:
+	 * as specified by caller, or vacuum_multixact_freeze_min_age, but in any
+	 * case not more than half effective_multixact_freeze_max_age
 	 */
 	mxid_freezemin = multixact_freeze_min_age;
 	if (mxid_freezemin < 0)
@@ -1066,86 +1078,67 @@ vacuum_set_xid_limits(Relation rel,
 						 effective_multixact_freeze_max_age / 2);
 	Assert(mxid_freezemin >= 0);
 
-	/* Remember for caller */
-	*oldestMxact = GetOldestMultiXactId();
+	/* Compute multiXactCutoff, being careful to generate a valid value */
+	*multiXactCutoff = nextMXID - mxid_freezemin;
+	if (*multiXactCutoff < FirstMultiXactId)
+		*multiXactCutoff = FirstMultiXactId;
+	/* multiXactCutoff must always be <= oldestMxact */
+	if (MultiXactIdPrecedes(*oldestMxact, *multiXactCutoff))
+		*multiXactCutoff = *oldestMxact;
 
-	/* 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))
+	/* Complain when oldestMxact held back by too much */
+	safeOldestMxact = nextMXID - effective_multixact_freeze_max_age;
+	if (safeOldestMxact < FirstMultiXactId)
+		safeOldestMxact = FirstMultiXactId;
+	if (MultiXactIdPrecedes(*oldestMxact, safeOldestMxact))
 	{
 		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;
-	}
+				(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.")));
 
-	*multiXactCutoff = mxactLimit;
+		/* Force minimum freeze age of zero (redundant) */
+		*multiXactCutoff = *oldestMxact;
+	}
 
 	/*
 	 * Done setting output parameters; just need to 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
+	 * Determine the age(relfrozenxid) cutoff: 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.
+	 * 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.
 	 */
-	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;
+	aggressive_table_age = freeze_table_age;
+	if (aggressive_table_age < 0)
+		aggressive_table_age = vacuum_freeze_table_age;
+	aggressive_table_age = Min(aggressive_table_age,
+							   autovacuum_freeze_max_age * 0.95);
+	Assert(aggressive_table_age >= 0);
+	aggressiveXIDCutoff = nextXID - aggressive_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.
-	 * 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;
+	/* Now check relminmxid (approach is analogous to relfrozenxid check) */
+	aggressive_table_age = multixact_freeze_table_age;
+	if (aggressive_table_age < 0)
+		aggressive_table_age = vacuum_multixact_freeze_table_age;
+	aggressive_table_age = Min(aggressive_table_age,
+							   effective_multixact_freeze_max_age * 0.95);
+	Assert(aggressive_table_age >= 0);
+	aggressiveMXIDCutoff = nextMXID - aggressive_table_age;
+	if (aggressiveMXIDCutoff < FirstMultiXactId)
+		aggressiveMXIDCutoff = FirstMultiXactId;
 	if (MultiXactIdPrecedesOrEquals(rel->rd_rel->relminmxid,
-									mxactLimit))
+									aggressiveMXIDCutoff))
 		return true;
 
+	/* Instruct caller to make its VACUUM non-aggressive */
 	return false;
 }
 
-- 
2.32.0

