 src/backend/commands/vacuum.c  | 133 +++++++++++++++++++++++++++++++++++++----
 src/backend/nodes/copyfuncs.c  |   3 +
 src/backend/nodes/equalfuncs.c |   3 +
 src/backend/parser/gram.y      |  10 +++-
 src/backend/tcop/utility.c     |  13 ++++
 src/include/commands/vacuum.h  |   1 +
 src/include/nodes/nodes.h      |   1 +
 src/include/nodes/parsenodes.h |  12 ++++
 8 files changed, 165 insertions(+), 11 deletions(-)

diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 287098e4d0..d1c59a78e9 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -52,6 +52,7 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/acl.h"
+#include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
@@ -271,10 +272,132 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
 	/* user-invoked vacuum never uses this parameter */
 	params.log_min_duration = -1;
 
+	/*
+	 * Create special memory context for cross-transaction storage.
+	 *
+	 * Since it is a child of PortalContext, it will go away eventually even
+	 * if we suffer an error; there's no need for special abort cleanup logic.
+	 */
+	vac_context = AllocSetContextCreate(PortalContext,
+										"Vacuum",
+										ALLOCSET_DEFAULT_SIZES);
+
 	/* Now go through the common routine */
 	vacuum(vacstmt->rels, &params, NULL, isTopLevel);
 }
 
+/*
+ * Like ExecVacuum, but specialized for recovering quickly from anti-wraparound
+ * shutdown.
+ */
+void
+ExecVacuumMinimal(VacuumMinimalStmt *fmstmt, bool isTopLevel)
+{
+	VacuumParams params;
+	List	   *vacrels = NIL;
+	Relation	pgclass;
+	TableScanDesc scan;
+	HeapTuple	tuple;
+	int32 table_xid_age;
+	int32 table_mxid_age;
+	int32 save_VacuumCostDelay;
+
+	/* use defaults */
+	// WIP: It might be worth trying to do less work here
+	params.freeze_min_age = -1;
+	params.multixact_freeze_min_age = -1;
+
+	/* it's unlikely any table we choose will not be eligible for aggressive vacuum, but make sure */
+	params.freeze_table_age = 0;
+	params.multixact_freeze_table_age = 0;
+
+	/* skip unnecessary work, as in failsafe mode */
+	params.index_cleanup = VACOPTVALUE_DISABLED;
+	params.truncate = VACOPTVALUE_DISABLED;
+
+	/* user-invoked vacuum is never "for wraparound" */
+	params.is_wraparound = false;
+
+	/* user-invoked vacuum never uses this parameter */
+	params.log_min_duration = -1;
+
+	/* we only expect this to run in single-user mode anyway */
+	params.nworkers = -1;
+
+	/* we don't need the toast relation since we select them separately */
+	params.options = VACOPT_VACUUM;
+
+	vac_context = AllocSetContextCreate(PortalContext,
+										"Vacuum",
+										ALLOCSET_DEFAULT_SIZES);
+
+	/* select relations closest to the wraparound limit */
+
+	pgclass = table_open(RelationRelationId, AccessShareLock);
+
+	scan = table_beginscan_catalog(pgclass, 0, NULL);
+
+	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	{
+		Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
+		MemoryContext oldcontext;
+		Oid			relid = classForm->oid;
+
+		/* check permissions of relation */
+		if (!vacuum_is_relation_owner(relid, classForm, params.options))
+		{
+			Assert(IsUnderPostmaster);
+			continue;
+		}
+
+		/*
+		 * Only consider relations able to hold unfrozen XIDs (anything else
+		 * should have InvalidTransactionId in relfrozenxid anyway).
+		 */
+		if (classForm->relkind != RELKIND_RELATION &&
+			classForm->relkind != RELKIND_MATVIEW &&
+			classForm->relkind != RELKIND_TOASTVALUE)
+		{
+			Assert(!TransactionIdIsValid(classForm->relfrozenxid));
+			Assert(!MultiXactIdIsValid(classForm->relminmxid));
+			continue;
+		}
+
+		table_xid_age = DirectFunctionCall1(xid_age, classForm->relfrozenxid);
+		table_mxid_age = DirectFunctionCall1(mxid_age, classForm->relminmxid);
+
+		// FIXME: also check reloption
+		// WIP: 95% is a starting point for discussion
+		if ((table_xid_age < autovacuum_freeze_max_age * 0.95) ||
+			(table_mxid_age < autovacuum_multixact_freeze_max_age * 0.95))
+			continue;
+
+		/*
+		 * Build VacuumRelation(s) specifying the table OIDs to be processed.
+		 * We omit a RangeVar since it wouldn't be appropriate to complain
+		 * about failure to open one of these relations later.
+		 */
+		oldcontext = MemoryContextSwitchTo(vac_context);
+		vacrels = lappend(vacrels, makeVacuumRelation(NULL,
+													  relid,
+													  NIL));
+		MemoryContextSwitchTo(oldcontext);
+	}
+
+	table_endscan(scan);
+	table_close(pgclass, AccessShareLock);
+
+	/* turn off cost delay */
+	save_VacuumCostDelay = VacuumCostDelay;
+	VacuumCostDelay = 0;
+
+	/* Now go through the common routine */
+	vacuum(vacrels, &params, NULL, isTopLevel);
+
+	/* restore cost delay, just in case we're not in single-user mode */
+	VacuumCostDelay = save_VacuumCostDelay;
+}
+
 /*
  * Internal entry point for VACUUM and ANALYZE commands.
  *
@@ -358,16 +481,6 @@ vacuum(List *relations, VacuumParams *params,
 	if ((params->options & VACOPT_VACUUM) && !IsAutoVacuumWorkerProcess())
 		pgstat_vacuum_stat();
 
-	/*
-	 * Create special memory context for cross-transaction storage.
-	 *
-	 * Since it is a child of PortalContext, it will go away eventually even
-	 * if we suffer an error; there's no need for special abort cleanup logic.
-	 */
-	vac_context = AllocSetContextCreate(PortalContext,
-										"Vacuum",
-										ALLOCSET_DEFAULT_SIZES);
-
 	/*
 	 * If caller didn't give us a buffer strategy object, make one in the
 	 * cross-transaction memory context.
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 456d563f34..1b2a5fc640 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -5578,6 +5578,9 @@ copyObjectImpl(const void *from)
 		case T_VacuumStmt:
 			retval = _copyVacuumStmt(from);
 			break;
+		case T_VacuumMinimalStmt:
+			retval = (void *) makeNode(VacuumMinimalStmt);
+			break;
 		case T_VacuumRelation:
 			retval = _copyVacuumRelation(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 53beef1488..9ba9601058 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -3580,6 +3580,9 @@ equal(const void *a, const void *b)
 		case T_VacuumStmt:
 			retval = _equalVacuumStmt(a, b);
 			break;
+		case T_VacuumMinimalStmt:
+			retval = true;
+			break;
 		case T_VacuumRelation:
 			retval = _equalVacuumRelation(a, b);
 			break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 879018377b..3ec299cd19 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -312,7 +312,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		RemoveFuncStmt RemoveOperStmt RenameStmt ReturnStmt RevokeStmt RevokeRoleStmt
 		RuleActionStmt RuleActionStmtOrEmpty RuleStmt
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
-		UnlistenStmt UpdateStmt VacuumStmt
+		UnlistenStmt UpdateStmt VacuumStmt VacuumMinimalStmt
 		VariableResetStmt VariableSetStmt VariableShowStmt
 		ViewStmt CheckPointStmt CreateConversionStmt
 		DeallocateStmt PrepareStmt ExecuteStmt
@@ -1043,6 +1043,7 @@ stmt:
 			| UnlistenStmt
 			| UpdateStmt
 			| VacuumStmt
+			| VacuumMinimalStmt
 			| VariableResetStmt
 			| VariableSetStmt
 			| VariableShowStmt
@@ -10866,6 +10867,13 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose opt_analyze opt_vacuum_relati
 				}
 		;
 
+VacuumMinimalStmt: VACUUM LIMIT
+				{
+					VacuumMinimalStmt *n = makeNode(VacuumMinimalStmt);
+					$$ = (Node *) n;
+				}
+		;
+
 AnalyzeStmt: analyze_keyword opt_verbose opt_vacuum_relation_list
 				{
 					VacuumStmt *n = makeNode(VacuumStmt);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 83e4e37c78..5907b29110 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -285,6 +285,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_ClusterStmt:
 		case T_ReindexStmt:
 		case T_VacuumStmt:
+		case T_VacuumMinimalStmt:
 			{
 				/*
 				 * These commands write WAL, so they're not strictly
@@ -859,6 +860,10 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 			ExecVacuum(pstate, (VacuumStmt *) parsetree, isTopLevel);
 			break;
 
+		case T_VacuumMinimalStmt:
+			ExecVacuumMinimal((VacuumMinimalStmt *) parsetree, isTopLevel);
+			break;
+
 		case T_ExplainStmt:
 			ExplainQuery(pstate, (ExplainStmt *) parsetree, params, dest);
 			break;
@@ -2843,6 +2848,10 @@ CreateCommandTag(Node *parsetree)
 				tag = CMDTAG_ANALYZE;
 			break;
 
+		case T_VacuumMinimalStmt:
+			tag = CMDTAG_VACUUM;
+			break;
+
 		case T_ExplainStmt:
 			tag = CMDTAG_EXPLAIN;
 			break;
@@ -3483,6 +3492,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_ALL;
 			break;
 
+		case T_VacuumMinimalStmt:
+			lev = LOGSTMT_ALL;
+			break;
+
 		case T_ExplainStmt:
 			{
 				ExplainStmt *stmt = (ExplainStmt *) parsetree;
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index f8a7b3664a..637afad45c 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -267,6 +267,7 @@ extern int	VacuumCostBalanceLocal;
 
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
+extern void ExecVacuumMinimal(VacuumMinimalStmt *fmstmt, bool isTopLevel);
 extern void vacuum(List *relations, VacuumParams *params,
 				   BufferAccessStrategy bstrategy, bool isTopLevel);
 extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 28cf5aefca..75074f6de0 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -351,6 +351,7 @@ typedef enum NodeTag
 	T_CreatedbStmt,
 	T_DropdbStmt,
 	T_VacuumStmt,
+	T_VacuumMinimalStmt,
 	T_ExplainStmt,
 	T_CreateTableAsStmt,
 	T_CreateSeqStmt,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 413e7c85a1..e6cad1baa4 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3361,6 +3361,18 @@ typedef struct VacuumStmt
 	bool		is_vacuumcmd;	/* true for VACUUM, false for ANALYZE */
 } VacuumStmt;
 
+/* ----------------------
+ *		VacuumMinimal Statement
+ *
+ * Although this statement executes a type of VACUUM, it takes no options
+ * or relations.
+ * ----------------------
+ */
+typedef struct VacuumMinimalStmt
+{
+	NodeTag		type;
+} VacuumMinimalStmt;
+
 /*
  * Info about a single target table of VACUUM/ANALYZE.
  *
