From 42395400b13ace519d0058122407604f1c23ad07 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 3 May 2019 15:15:20 -0400
Subject: [PATCH] vacuum: Don't try to truncate if the XID warn limit has been
 reached.

---
 src/backend/access/heap/vacuumlazy.c | 58 ++++++++++++++++++----------
 src/backend/access/transam/varsup.c  | 13 +++++++
 src/backend/commands/cluster.c       |  2 +-
 src/backend/commands/vacuum.c        | 18 +++++++--
 src/include/access/transam.h         |  2 +
 src/include/commands/vacuum.h        |  3 +-
 6 files changed, 70 insertions(+), 26 deletions(-)

diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 2aa729817c..4b963c0ed8 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -136,6 +136,7 @@ typedef struct LVRelStats
 	int			num_index_scans;
 	TransactionId latestRemovedXid;
 	bool		lock_waiter_detected;
+	bool		warn_limit_reached;
 } LVRelStats;
 
 
@@ -207,6 +208,7 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	double		new_live_tuples;
 	TransactionId new_frozen_xid;
 	MultiXactId new_min_multi;
+	bool		warn_limit_reached;
 
 	Assert(params != NULL);
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
@@ -238,7 +240,8 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 						  params->multixact_freeze_min_age,
 						  params->multixact_freeze_table_age,
 						  &OldestXmin, &FreezeLimit, &xidFullScanLimit,
-						  &MultiXactCutoff, &mxactFullScanLimit);
+						  &MultiXactCutoff, &mxactFullScanLimit,
+						  &warn_limit_reached);
 
 	/*
 	 * We request an aggressive scan if the table's frozen Xid is now older
@@ -277,6 +280,7 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	vacrelstats->num_index_scans = 0;
 	vacrelstats->pages_removed = 0;
 	vacrelstats->lock_waiter_detected = false;
+	vacrelstats->warn_limit_reached = warn_limit_reached;
 
 	/* Open all indexes of the relation */
 	vac_open_indexes(onerel, RowExclusiveLock, &nindexes, &Irel);
@@ -1065,9 +1069,9 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats,
 					 * cheaper to get rid of it in the next pruning pass than
 					 * to treat it like an indexed tuple. Finally, if index
 					 * cleanup is disabled, the second heap pass will not
-					 * execute, and the tuple will not get removed, so we
-					 * must treat it like any other dead tuple that we choose
-					 * to keep.
+					 * execute, and the tuple will not get removed, so we must
+					 * treat it like any other dead tuple that we choose to
+					 * keep.
 					 *
 					 * If this were to happen for a tuple that actually needed
 					 * to be deleted, we'd be in trouble, because it'd
@@ -1085,6 +1089,7 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats,
 					all_visible = false;
 					break;
 				case HEAPTUPLE_LIVE:
+
 					/*
 					 * Count it as live.  Not only is this natural, but it's
 					 * also what acquire_sample_rows() does.
@@ -1249,13 +1254,14 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats,
 			else
 			{
 				/*
-				 * Here, we have indexes but index cleanup is disabled. Instead of
-				 * vacuuming the dead tuples on the heap, we just forget them.
+				 * Here, we have indexes but index cleanup is disabled.
+				 * Instead of vacuuming the dead tuples on the heap, we just
+				 * forget them.
 				 *
 				 * Note that vacrelstats->dead_tuples could have tuples which
 				 * became dead after 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.
+				 * We do not process them because it's a very rare condition,
+				 * and the next vacuum will process them anyway.
 				 */
 				Assert(params->index_cleanup == VACOPT_TERNARY_DISABLED);
 			}
@@ -1829,18 +1835,6 @@ lazy_cleanup_index(Relation indrel,
 /*
  * should_attempt_truncation - should we attempt to truncate the heap?
  *
- * Don't even think about it unless we have a shot at releasing a goodly
- * number of pages.  Otherwise, the time taken isn't worth it.
- *
- * Also don't attempt it if we are doing early pruning/vacuuming, because a
- * scan which cannot find a truncated heap page cannot determine that the
- * snapshot is too old to read that page.  We might be able to get away with
- * truncating all except one of the pages, setting its LSN to (at least) the
- * maximum of the truncated range if we also treated an index leaf tuple
- * pointing to a missing heap page as something to trigger the "snapshot too
- * old" error, but that seems fragile and seems like it deserves its own patch
- * if we consider it.
- *
  * This is split out so that we can test whether truncation is going to be
  * called for before we actually do it.  If you change the logic here, be
  * careful to depend only on fields that lazy_scan_heap updates on-the-fly.
@@ -1850,10 +1844,34 @@ should_attempt_truncation(Relation rel, LVRelStats *vacrelstats)
 {
 	BlockNumber possibly_freeable;
 
+	/* Don't truncate if truncation is disabled for this relation. */
 	if (rel->rd_options != NULL &&
 		((StdRdOptions *) rel->rd_options)->vacuum_truncate == false)
 		return false;
 
+	/*
+	 * Also don't truncate if we are very close to transaction ID wraparound.
+	 * Truncating the relation will require an AccessExclusiveLock, which will
+	 * force an XID assignment.  If there are not very many XIDs left before
+	 * the system starts refusing new write transactions, it is better to skip
+	 * truncation.
+	 */
+	if (vacrelstats->warn_limit_reached)
+		return false;
+
+	/*
+	 * Finally, don't try to truncate unless we have a shot at releasing a
+	 * goodly number of pages.  Otherwise, the time taken isn't worth it.
+	 *
+	 * Also don't attempt it if we are doing early pruning/vacuuming, because
+	 * a scan which cannot find a truncated heap page cannot determine that
+	 * the snapshot is too old to read that page.  We might be able to get
+	 * away with truncating all except one of the pages, setting its LSN to
+	 * (at least) the maximum of the truncated range if we also treated an
+	 * index leaf tuple pointing to a missing heap page as something to
+	 * trigger the "snapshot too old" error, but that seems fragile and seems
+	 * like it deserves its own patch if we consider it.
+	 */
 	possibly_freeable = vacrelstats->rel_pages - vacrelstats->nonempty_pages;
 	if (possibly_freeable > 0 &&
 		(possibly_freeable >= REL_TRUNCATE_MINIMUM ||
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 788b961ef0..e19bbb68f1 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -254,6 +254,19 @@ ReadNextFullTransactionId(void)
 	return fullXid;
 }
 
+/*
+ * Read the next XID that will be allocated without actually allocating it,
+ * and also the xidWarnLimit.
+ */
+void
+ReadNextXIDAndWarnLimit(TransactionId *nextXid, TransactionId *xidWarnLimit)
+{
+	LWLockAcquire(XidGenLock, LW_SHARED);
+	*nextXid = XidFromFullTransactionId(ShmemVariableCache->nextFullXid);
+	*xidWarnLimit = ShmemVariableCache->xidWarnLimit;
+	LWLockRelease(XidGenLock);
+}
+
 /*
  * Advance nextFullXid to the value after a given xid.  The epoch is inferred.
  * This must only be called during recovery or from two-phase start-up code.
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 3ee7056047..3e166a07c7 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -864,7 +864,7 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	 */
 	vacuum_set_xid_limits(OldHeap, 0, 0, 0, 0,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
-						  NULL);
+						  NULL, NULL);
 
 	/*
 	 * 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 94fb6f2606..a9ddfdc5fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -859,9 +859,11 @@ get_all_vacuum_rels(int options)
  *	 Xmax.
  * - mxactFullScanLimit is a value against which a table's relminmxid value is
  *	 compared to produce a full-table vacuum, as with xidFullScanLimit.
+ * - warn_limit_reached is set to true if the cluster has reached the point
+ *   where we emit warnings about imminent wraparound, and false otherwise.
  *
- * xidFullScanLimit and mxactFullScanLimit can be passed as NULL if caller is
- * not interested.
+ * xidFullScanLimit, mxactFullScanLimit, and warn_limit_reached can be passed
+ * as NULL if caller is not interested.
  */
 void
 vacuum_set_xid_limits(Relation rel,
@@ -873,11 +875,14 @@ vacuum_set_xid_limits(Relation rel,
 					  TransactionId *freezeLimit,
 					  TransactionId *xidFullScanLimit,
 					  MultiXactId *multiXactCutoff,
-					  MultiXactId *mxactFullScanLimit)
+					  MultiXactId *mxactFullScanLimit,
+					  bool *warn_limit_reached)
 {
 	int			freezemin;
 	int			mxid_freezemin;
 	int			effective_multixact_freeze_max_age;
+	TransactionId nextXid;
+	TransactionId xidWarnLimit;
 	TransactionId limit;
 	TransactionId safeLimit;
 	MultiXactId mxactLimit;
@@ -921,7 +926,8 @@ vacuum_set_xid_limits(Relation rel,
 	 * autovacuum_freeze_max_age / 2 XIDs old), complain and force a minimum
 	 * freeze age of zero.
 	 */
-	safeLimit = ReadNewTransactionId() - autovacuum_freeze_max_age;
+	ReadNextXIDAndWarnLimit(&nextXid, &xidWarnLimit);
+	safeLimit = nextXid - autovacuum_freeze_max_age;
 	if (!TransactionIdIsNormal(safeLimit))
 		safeLimit = FirstNormalTransactionId;
 
@@ -934,6 +940,10 @@ vacuum_set_xid_limits(Relation rel,
 		limit = *oldestXmin;
 	}
 
+	if (warn_limit_reached)
+		*warn_limit_reached =
+			TransactionIdFollowsOrEquals(nextXid, xidWarnLimit);
+
 	*freezeLimit = limit;
 
 	/*
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index 7966a9e90b..1c789f55d7 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -225,6 +225,8 @@ extern XLogRecPtr TransactionIdGetCommitLSN(TransactionId xid);
 extern FullTransactionId GetNewTransactionId(bool isSubXact);
 extern void AdvanceNextFullTransactionIdPastXid(TransactionId xid);
 extern FullTransactionId ReadNextFullTransactionId(void);
+extern void ReadNextXIDAndWarnLimit(TransactionId *nextXid,
+						TransactionId *xidWarnLimit);
 extern void SetTransactionIdLimit(TransactionId oldest_datfrozenxid,
 					  Oid oldest_datoid);
 extern void AdvanceOldestClogXid(TransactionId oldest_datfrozenxid);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 9cc6e0d023..cf6da0f785 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -219,7 +219,8 @@ extern void vacuum_set_xid_limits(Relation rel,
 					  TransactionId *freezeLimit,
 					  TransactionId *xidFullScanLimit,
 					  MultiXactId *multiXactCutoff,
-					  MultiXactId *mxactFullScanLimit);
+					  MultiXactId *mxactFullScanLimit,
+					  bool *warn_limit_reached);
 extern void vac_update_datfrozenxid(void);
 extern void vacuum_delay_point(void);
 extern bool vacuum_is_relation_owner(Oid relid, Form_pg_class reltuple,
-- 
2.17.2 (Apple Git-113)

