Add SKIP LOCKED to VACUUM and ANALYZE

Started by Bossart, Nathanover 7 years ago24 messages
#1Bossart, Nathan
bossartn@amazon.com
12 attachment(s)

Hello,

Previous thread: /messages/by-id/4BC0F3CD-F4B5-4F23-AADB-80607F9E4B4E@amazon.com

This is a new thread for tracking the work to add SKIP LOCKED to
VACUUM and ANALYZE. I've attached a rebased set of patches.

Nathan

Attachments:

v8-0001-Add-skip_locked-argument-to-find_inheritance_chil.patchapplication/octet-stream; name=v8-0001-Add-skip_locked-argument-to-find_inheritance_chil.patchDownload
From 684826d0a25f9c643ce6b4fba49d5d14effade73 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Thu, 5 Apr 2018 16:45:49 +0000
Subject: [PATCH v8 01/12] Add skip_locked argument to
 find_inheritance_children().

---
 src/backend/catalog/pg_inherits.c   | 26 +++++++++++++++++++++++---
 src/backend/commands/lockcmds.c     |  2 +-
 src/backend/commands/tablecmds.c    | 16 ++++++++--------
 src/backend/commands/trigger.c      |  2 +-
 src/backend/utils/cache/partcache.c |  2 +-
 src/include/catalog/pg_inherits.h   |  3 ++-
 6 files changed, 36 insertions(+), 15 deletions(-)

diff --git a/src/backend/catalog/pg_inherits.c b/src/backend/catalog/pg_inherits.c
index 85baca54cc..1b936123c5 100644
--- a/src/backend/catalog/pg_inherits.c
+++ b/src/backend/catalog/pg_inherits.c
@@ -51,9 +51,14 @@ typedef struct SeenRelsEntry
  * given rel; caller should already have locked it).  If lockmode is NoLock
  * then no locks are acquired, but caller must beware of race conditions
  * against possible DROPs of child relations.
+ *
+ * If skip_locked is true, any child relations that cannot be locked immediately
+ * without waiting are not added to the returned OID list, and skipped will be
+ * set to true if it is provided.  Otherwise, skipped will be set to false if it
+ * is provided.
  */
 List *
-find_inheritance_children(Oid parentrelId, LOCKMODE lockmode)
+find_inheritance_children(Oid parentrelId, LOCKMODE lockmode, bool skip_locked, bool *skipped)
 {
 	List	   *list = NIL;
 	Relation	relation;
@@ -65,13 +70,19 @@ find_inheritance_children(Oid parentrelId, LOCKMODE lockmode)
 	int			maxoids,
 				numoids,
 				i;
+	bool		did_skip = false;
 
 	/*
 	 * Can skip the scan if pg_class shows the relation has never had a
 	 * subclass.
 	 */
 	if (!has_subclass(parentrelId))
+	{
+		if (skipped != NULL)
+			*skipped = did_skip;
+
 		return NIL;
+	}
 
 	/*
 	 * Scan pg_inherits and build a working array of subclass OIDs.
@@ -124,7 +135,13 @@ find_inheritance_children(Oid parentrelId, LOCKMODE lockmode)
 		if (lockmode != NoLock)
 		{
 			/* Get the lock to synchronize against concurrent drop */
-			LockRelationOid(inhrelid, lockmode);
+			if (!skip_locked)
+				LockRelationOid(inhrelid, lockmode);
+			else if (!ConditionalLockRelationOid(inhrelid, lockmode))
+			{
+				did_skip = true;
+				continue;
+			}
 
 			/*
 			 * Now that we have the lock, double-check to see if the relation
@@ -145,6 +162,9 @@ find_inheritance_children(Oid parentrelId, LOCKMODE lockmode)
 
 	pfree(oidarr);
 
+	if (skipped != NULL)
+		*skipped = did_skip;
+
 	return list;
 }
 
@@ -199,7 +219,7 @@ find_all_inheritors(Oid parentrelId, LOCKMODE lockmode, List **numparents)
 		ListCell   *lc;
 
 		/* Get the direct children of this rel */
-		currentchildren = find_inheritance_children(currentrel, lockmode);
+		currentchildren = find_inheritance_children(currentrel, lockmode, false, NULL);
 
 		/*
 		 * Add to the queue only those children not already seen. This avoids
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 71278b38cf..ef4d4a8c9d 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -119,7 +119,7 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid)
 	List	   *children;
 	ListCell   *lc;
 
-	children = find_inheritance_children(reloid, NoLock);
+	children = find_inheritance_children(reloid, NoLock, false, NULL);
 
 	foreach(lc, children)
 	{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8b848f91a7..4f0640e9ba 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2781,7 +2781,7 @@ renameatt_internal(Oid myrelid,
 		 * expected_parents will only be 0 if we are not already recursing.
 		 */
 		if (expected_parents == 0 &&
-			find_inheritance_children(myrelid, NoLock) != NIL)
+			find_inheritance_children(myrelid, NoLock, false, NULL) != NIL)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("inherited column \"%s\" must be renamed in child tables too",
@@ -2980,7 +2980,7 @@ rename_constraint_internal(Oid myrelid,
 		else
 		{
 			if (expected_parents == 0 &&
-				find_inheritance_children(myrelid, NoLock) != NIL)
+				find_inheritance_children(myrelid, NoLock, false, NULL) != NIL)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("inherited constraint \"%s\" must be renamed in child tables too",
@@ -5441,7 +5441,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	 */
 	if (colDef->identity &&
 		recurse &&
-		find_inheritance_children(myrelid, NoLock) != NIL)
+		find_inheritance_children(myrelid, NoLock, false, NULL) != NIL)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("cannot recursively add identity column to table that has child tables")));
@@ -5684,7 +5684,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	 * routines, we have to do this one level of recursion at a time; we can't
 	 * use find_all_inheritors to do it in one pass.
 	 */
-	children = find_inheritance_children(RelationGetRelid(rel), lockmode);
+	children = find_inheritance_children(RelationGetRelid(rel), lockmode, false, NULL);
 
 	/*
 	 * If we are told not to recurse, there had better not be any child
@@ -6780,7 +6780,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	 * routines, we have to do this one level of recursion at a time; we can't
 	 * use find_all_inheritors to do it in one pass.
 	 */
-	children = find_inheritance_children(RelationGetRelid(rel), lockmode);
+	children = find_inheritance_children(RelationGetRelid(rel), lockmode, false, NULL);
 
 	if (children)
 	{
@@ -7231,7 +7231,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	 * routines, we have to do this one level of recursion at a time; we can't
 	 * use find_all_inheritors to do it in one pass.
 	 */
-	children = find_inheritance_children(RelationGetRelid(rel), lockmode);
+	children = find_inheritance_children(RelationGetRelid(rel), lockmode, false, NULL);
 
 	/*
 	 * Check if ONLY was specified with ALTER TABLE.  If so, allow the
@@ -8919,7 +8919,7 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 	 * use find_all_inheritors to do it in one pass.
 	 */
 	if (!is_no_inherit_constraint)
-		children = find_inheritance_children(RelationGetRelid(rel), lockmode);
+		children = find_inheritance_children(RelationGetRelid(rel), lockmode, false, NULL);
 	else
 		children = NIL;
 
@@ -9261,7 +9261,7 @@ ATPrepAlterColumnType(List **wqueue,
 		}
 	}
 	else if (!recursing &&
-			 find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
+			 find_inheritance_children(RelationGetRelid(rel), NoLock, false, NULL) != NIL)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("type of inherited column \"%s\" must be changed in child tables too",
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 57519fe8d6..24de0c58c0 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -1109,7 +1109,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 			ListCell   *l;
 			List	   *idxs = NIL;
 
-			idxs = find_inheritance_children(indexOid, ShareRowExclusiveLock);
+			idxs = find_inheritance_children(indexOid, ShareRowExclusiveLock, false, NULL);
 			foreach(l, idxs)
 				childTbls = lappend_oid(childTbls,
 										IndexGetRelation(lfirst_oid(l),
diff --git a/src/backend/utils/cache/partcache.c b/src/backend/utils/cache/partcache.c
index 115a9fe78f..c160e755e2 100644
--- a/src/backend/utils/cache/partcache.c
+++ b/src/backend/utils/cache/partcache.c
@@ -285,7 +285,7 @@ RelationBuildPartitionDesc(Relation rel)
 	PartitionRangeBound **rbounds = NULL;
 
 	/* Get partition oids from pg_inherits */
-	inhoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+	inhoids = find_inheritance_children(RelationGetRelid(rel), NoLock, false, NULL);
 
 	/* Collect bound spec nodes in a list */
 	i = 0;
diff --git a/src/include/catalog/pg_inherits.h b/src/include/catalog/pg_inherits.h
index 2a98e02c6a..a89b3f44a5 100644
--- a/src/include/catalog/pg_inherits.h
+++ b/src/include/catalog/pg_inherits.h
@@ -44,7 +44,8 @@ CATALOG(pg_inherits,2611,InheritsRelationId) BKI_WITHOUT_OIDS
 typedef FormData_pg_inherits *Form_pg_inherits;
 
 
-extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode);
+extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode,
+						  bool skip_locked, bool *skipped);
 extern List *find_all_inheritors(Oid parentrelId, LOCKMODE lockmode,
 					List **parents);
 extern bool has_subclass(Oid relationId);
-- 
2.16.2

v8-0002-Add-skip_locked-argument-to-find_all_inheritors.patchapplication/octet-stream; name=v8-0002-Add-skip_locked-argument-to-find_all_inheritors.patchDownload
From 2c29d3cfa15cc231430fd6ddffe52bfc082d3a35 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Thu, 5 Apr 2018 16:52:56 +0000
Subject: [PATCH v8 02/12] Add skip_locked argument to find_all_inheritors().

---
 contrib/sepgsql/dml.c                  |  2 +-
 src/backend/catalog/pg_inherits.c      | 30 +++++++++++++++++++++++++++---
 src/backend/commands/analyze.c         |  2 +-
 src/backend/commands/publicationcmds.c |  2 +-
 src/backend/commands/tablecmds.c       | 16 ++++++++--------
 src/backend/commands/trigger.c         |  2 +-
 src/backend/commands/vacuum.c          |  2 +-
 src/backend/executor/execPartition.c   |  2 +-
 src/backend/optimizer/prep/prepunion.c |  2 +-
 src/backend/partitioning/partbounds.c  |  2 +-
 src/backend/tcop/utility.c             |  3 ++-
 src/include/catalog/pg_inherits.h      |  2 +-
 12 files changed, 46 insertions(+), 21 deletions(-)

diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 9bdbd7b60f..bb7713380a 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -330,7 +330,7 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		if (!rte->inh)
 			tableIds = list_make1_oid(rte->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(rte->relid, NoLock, NULL, false);
 
 		foreach(li, tableIds)
 		{
diff --git a/src/backend/catalog/pg_inherits.c b/src/backend/catalog/pg_inherits.c
index 1b936123c5..a64037a958 100644
--- a/src/backend/catalog/pg_inherits.c
+++ b/src/backend/catalog/pg_inherits.c
@@ -180,10 +180,12 @@ find_inheritance_children(Oid parentrelId, LOCKMODE lockmode, bool skip_locked,
  * The specified lock type is acquired on all child relations (but not on the
  * given rel; caller should already have locked it).  If lockmode is NoLock
  * then no locks are acquired, but caller must beware of race conditions
- * against possible DROPs of child relations.
+ * against possible DROPs of child relations.  If skip_locked is true and a
+ * child relation can not be locked immediately without waiting, both the
+ * returned OID list and *numparents will be set to NIL.
  */
 List *
-find_all_inheritors(Oid parentrelId, LOCKMODE lockmode, List **numparents)
+find_all_inheritors(Oid parentrelId, LOCKMODE lockmode, List **numparents, bool skip_locked)
 {
 	/* hash table for O(1) rel_oid -> rel_numparents cell lookup */
 	HTAB	   *seen_rels;
@@ -191,6 +193,7 @@ find_all_inheritors(Oid parentrelId, LOCKMODE lockmode, List **numparents)
 	List	   *rels_list,
 			   *rel_numparents;
 	ListCell   *l;
+	bool		skipped = false;
 
 	memset(&ctl, 0, sizeof(ctl));
 	ctl.keysize = sizeof(Oid);
@@ -219,7 +222,7 @@ find_all_inheritors(Oid parentrelId, LOCKMODE lockmode, List **numparents)
 		ListCell   *lc;
 
 		/* Get the direct children of this rel */
-		currentchildren = find_inheritance_children(currentrel, lockmode, false, NULL);
+		currentchildren = find_inheritance_children(currentrel, lockmode, skip_locked, &skipped);
 
 		/*
 		 * Add to the queue only those children not already seen. This avoids
@@ -248,6 +251,27 @@ find_all_inheritors(Oid parentrelId, LOCKMODE lockmode, List **numparents)
 				hash_entry->numparents_cell = rel_numparents->tail;
 			}
 		}
+
+		if (skipped)
+			break;
+	}
+
+	/* if a child relation was skipped, set the return lists to NIL */
+	if (skipped)
+	{
+		/* release all locks */
+		foreach(l, rels_list)
+		{
+			Oid			currentrel = lfirst_oid(l);
+
+			if (currentrel != parentrelId)
+				UnlockRelationOid(currentrel, lockmode);
+		}
+
+		list_free(rel_numparents);
+		list_free(rels_list);
+		rel_numparents = NIL;
+		rels_list = NIL;
 	}
 
 	if (numparents)
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 25194e871c..651cf1f78e 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1335,7 +1335,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 	 * the children.
 	 */
 	tableOIDs =
-		find_all_inheritors(RelationGetRelid(onerel), AccessShareLock, NULL);
+		find_all_inheritors(RelationGetRelid(onerel), AccessShareLock, NULL, false);
 
 	/*
 	 * Check that there's at least one descendant, else fail.  This could
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 6f7762a906..a5e01ea61e 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -530,7 +530,7 @@ OpenTableList(List *tables)
 			List	   *children;
 
 			children = find_all_inheritors(myrelid, ShareUpdateExclusiveLock,
-										   NULL);
+										   NULL, false);
 
 			foreach(child, children)
 			{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 4f0640e9ba..a46aa8806e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1356,7 +1356,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 			ListCell   *child;
 			List	   *children;
 
-			children = find_all_inheritors(myrelid, AccessExclusiveLock, NULL);
+			children = find_all_inheritors(myrelid, AccessExclusiveLock, NULL, false);
 
 			foreach(child, children)
 			{
@@ -2754,7 +2754,7 @@ renameatt_internal(Oid myrelid,
 		 * outside the inheritance hierarchy being processed.
 		 */
 		child_oids = find_all_inheritors(myrelid, AccessExclusiveLock,
-										 &child_numparents);
+										 &child_numparents, false);
 
 		/*
 		 * find_all_inheritors does the recursive search of the inheritance
@@ -2964,7 +2964,7 @@ rename_constraint_internal(Oid myrelid,
 					   *li;
 
 			child_oids = find_all_inheritors(myrelid, AccessExclusiveLock,
-											 &child_numparents);
+											 &child_numparents, false);
 
 			forboth(lo, child_oids, li, child_numparents)
 			{
@@ -5030,7 +5030,7 @@ ATSimpleRecursion(List **wqueue, Relation rel,
 		ListCell   *child;
 		List	   *children;
 
-		children = find_all_inheritors(relid, lockmode, NULL);
+		children = find_all_inheritors(relid, lockmode, NULL, false);
 
 		/*
 		 * find_all_inheritors does the recursive search of the inheritance
@@ -8006,7 +8006,7 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 			 */
 			if (!recursing && !con->connoinherit)
 				children = find_all_inheritors(RelationGetRelid(rel),
-											   lockmode, NULL);
+											   lockmode, NULL, false);
 
 			/*
 			 * For CHECK constraints, we must ensure that we only mark the
@@ -9210,7 +9210,7 @@ ATPrepAlterColumnType(List **wqueue,
 		ListCell   *child;
 		List	   *children;
 
-		children = find_all_inheritors(relid, lockmode, NULL);
+		children = find_all_inheritors(relid, lockmode, NULL, false);
 
 		/*
 		 * find_all_inheritors does the recursive search of the inheritance
@@ -11443,7 +11443,7 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 	 * We use weakest lock we can on child's children, namely AccessShareLock.
 	 */
 	children = find_all_inheritors(RelationGetRelid(child_rel),
-								   AccessShareLock, NULL);
+								   AccessShareLock, NULL, false);
 
 	if (list_member_oid(children, RelationGetRelid(parent_rel)))
 		ereport(ERROR,
@@ -14126,7 +14126,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 * weaker lock now and the stronger one only when needed.
 	 */
 	attachrel_children = find_all_inheritors(RelationGetRelid(attachrel),
-											 AccessExclusiveLock, NULL);
+											 AccessExclusiveLock, NULL, false);
 	if (list_member_oid(attachrel_children, RelationGetRelid(rel)))
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_TABLE),
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 24de0c58c0..34c4cc0ef8 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -364,7 +364,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
 	if (partition_recurse)
 		list_free(find_all_inheritors(RelationGetRelid(rel),
-									  ShareRowExclusiveLock, NULL));
+									  ShareRowExclusiveLock, NULL, false));
 
 	/* Compute tgtype */
 	TRIGGER_CLEAR_TYPE(tgtype);
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d90cb9a902..74049741a7 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -479,7 +479,7 @@ expand_vacuum_rel(VacuumRelation *vrel)
 		 */
 		if (include_parts)
 		{
-			List	   *part_oids = find_all_inheritors(relid, NoLock, NULL);
+			List	   *part_oids = find_all_inheritors(relid, NoLock, NULL, false);
 			ListCell   *part_lc;
 
 			foreach(part_lc, part_oids)
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 7a4665cc4e..bfe2d1e1d6 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -89,7 +89,7 @@ ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
 	 * Get the information about the partition tree after locking all the
 	 * partitions.
 	 */
-	(void) find_all_inheritors(RelationGetRelid(rel), RowExclusiveLock, NULL);
+	(void) find_all_inheritors(RelationGetRelid(rel), RowExclusiveLock, NULL, false);
 	proute = (PartitionTupleRouting *) palloc0(sizeof(PartitionTupleRouting));
 	proute->partition_dispatch_info =
 		RelationGetPartitionDispatchInfo(rel, &proute->num_dispatch,
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 0ab4014be6..3019d4a335 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -1556,7 +1556,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 		lockmode = AccessShareLock;
 
 	/* Scan for all members of inheritance set, acquire needed locks */
-	inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+	inhOIDs = find_all_inheritors(parentOID, lockmode, NULL, false);
 
 	/*
 	 * Check that there's at least one descendant, else treat as no-child
diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index b19c76acc8..dcfe452a94 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -629,7 +629,7 @@ check_default_partition_contents(Relation parent, Relation default_rel,
 	 */
 	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
-										AccessExclusiveLock, NULL);
+										AccessExclusiveLock, NULL, false);
 	else
 		all_parts = list_make1_oid(RelationGetRelid(default_rel));
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index bdfb66fa74..82397eb9f5 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1325,7 +1325,8 @@ ProcessUtilitySlow(ParseState *pstate,
 						ListCell   *lc;
 						List	   *inheritors = NIL;
 
-						inheritors = find_all_inheritors(relid, lockmode, NULL);
+						inheritors = find_all_inheritors(relid, lockmode,
+															NULL, false);
 						foreach(lc, inheritors)
 						{
 							char	relkind = get_rel_relkind(lfirst_oid(lc));
diff --git a/src/include/catalog/pg_inherits.h b/src/include/catalog/pg_inherits.h
index a89b3f44a5..ccbf756ce5 100644
--- a/src/include/catalog/pg_inherits.h
+++ b/src/include/catalog/pg_inherits.h
@@ -47,7 +47,7 @@ typedef FormData_pg_inherits *Form_pg_inherits;
 extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode,
 						  bool skip_locked, bool *skipped);
 extern List *find_all_inheritors(Oid parentrelId, LOCKMODE lockmode,
-					List **parents);
+					List **parents, bool skip_locked);
 extern bool has_subclass(Oid relationId);
 extern bool has_superclass(Oid relationId);
 extern bool typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId);
-- 
2.16.2

v8-0003-Add-skip_locked-argument-to-vac_open_indexes.patchapplication/octet-stream; name=v8-0003-Add-skip_locked-argument-to-vac_open_indexes.patchDownload
From 856f84884d9d727623d70dc9fc7c98da3ae8fac0 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Thu, 5 Apr 2018 16:58:27 +0000
Subject: [PATCH v8 03/12] Add skip_locked argument to vac_open_indexes().

---
 src/backend/commands/analyze.c    |  2 +-
 src/backend/commands/vacuum.c     | 27 +++++++++++++++++++++++++--
 src/backend/commands/vacuumlazy.c |  2 +-
 src/include/commands/vacuum.h     |  3 ++-
 4 files changed, 29 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 651cf1f78e..ac6d279cbc 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -468,7 +468,7 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
 	 * all.
 	 */
 	if (!inh)
-		vac_open_indexes(onerel, AccessShareLock, &nindexes, &Irel);
+		vac_open_indexes(onerel, AccessShareLock, &nindexes, &Irel, false, NULL);
 	else
 	{
 		Irel = NULL;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 74049741a7..b4ff573818 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1603,14 +1603,19 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
  * vacuum even if the index isn't indisvalid; this is important because in a
  * unique index, uniqueness checks will be performed anyway and had better not
  * hit dangling index pointers.
+ *
+ * If skip_locked is true and an index cannot be locked immediately without
+ * waiting, *Irel will be set to NULL and nindexes will be set to 0.  If skipped
+ * is provided, it will be set to true if an index was skipped (false otherwise).
  */
 void
 vac_open_indexes(Relation relation, LOCKMODE lockmode,
-				 int *nindexes, Relation **Irel)
+				 int *nindexes, Relation **Irel, bool skip_locked, bool *skipped)
 {
 	List	   *indexoidlist;
 	ListCell   *indexoidscan;
 	int			i;
+	bool		did_skip = false;
 
 	Assert(lockmode != NoLock);
 
@@ -1631,13 +1636,31 @@ vac_open_indexes(Relation relation, LOCKMODE lockmode,
 		Oid			indexoid = lfirst_oid(indexoidscan);
 		Relation	indrel;
 
-		indrel = index_open(indexoid, lockmode);
+		if (skip_locked && !ConditionalLockRelationOid(indexoid, lockmode))
+		{
+			did_skip = true;
+			break;
+		}
+
+		indrel = index_open(indexoid, skip_locked ? NoLock : lockmode);
 		if (IndexIsReady(indrel->rd_index))
 			(*Irel)[i++] = indrel;
 		else
 			index_close(indrel, lockmode);
 	}
 
+	if (did_skip)
+	{
+		vac_close_indexes(i, *Irel, lockmode);
+
+		/* clear all return values */
+		*Irel = NULL;
+		i = 0;
+	}
+
+	if (skipped != NULL)
+		*skipped = did_skip;
+
 	*nindexes = i;
 
 	list_free(indexoidlist);
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 5649a70800..47cc0536dd 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -258,7 +258,7 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
 	vacrelstats->lock_waiter_detected = false;
 
 	/* Open all indexes of the relation */
-	vac_open_indexes(onerel, RowExclusiveLock, &nindexes, &Irel);
+	vac_open_indexes(onerel, RowExclusiveLock, &nindexes, &Irel, false, NULL);
 	vacrelstats->hasindex = (nindexes > 0);
 
 	/* Do the vacuuming */
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 85d472f0a5..69b1e384ef 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -160,7 +160,8 @@ extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(int options, List *relations, VacuumParams *params,
 	   BufferAccessStrategy bstrategy, bool isTopLevel);
 extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
-				 int *nindexes, Relation **Irel);
+				 int *nindexes, Relation **Irel,
+				 bool skip_locked, bool *skipped);
 extern void vac_close_indexes(int nindexes, Relation *Irel, LOCKMODE lockmode);
 extern double vac_estimate_reltuples(Relation relation,
 					   BlockNumber total_pages,
-- 
2.16.2

v8-0004-Rename-VACOPT_NOWAIT-to-VACOPT_SKIP_LOCKED.patchapplication/octet-stream; name=v8-0004-Rename-VACOPT_NOWAIT-to-VACOPT_SKIP_LOCKED.patchDownload
From 5c49d2dfbe76222cda5298e6291f725df4ddf66b Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Wed, 4 Apr 2018 16:54:53 +0000
Subject: [PATCH v8 04/12] Rename VACOPT_NOWAIT to VACOPT_SKIP_LOCKED.

---
 src/backend/commands/analyze.c      | 2 +-
 src/backend/commands/vacuum.c       | 2 +-
 src/backend/postmaster/autovacuum.c | 2 +-
 src/include/nodes/parsenodes.h      | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index ac6d279cbc..f27b5bd535 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -143,7 +143,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 * matter if we ever try to accumulate stats on dead tuples.) If the rel
 	 * has been dropped since we last saw it, we don't need to process it.
 	 */
-	if (!(options & VACOPT_NOWAIT))
+	if (!(options & VACOPT_SKIP_LOCKED))
 		onerel = try_relation_open(relid, ShareUpdateExclusiveLock);
 	else if (ConditionalLockRelationOid(relid, ShareUpdateExclusiveLock))
 		onerel = try_relation_open(relid, NoLock);
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index b4ff573818..8902eba337 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1371,7 +1371,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 * If we've been asked not to wait for the relation lock, acquire it first
 	 * in non-blocking mode, before calling try_relation_open().
 	 */
-	if (!(options & VACOPT_NOWAIT))
+	if (!(options & VACOPT_SKIP_LOCKED))
 		onerel = try_relation_open(relid, lmode);
 	else if (ConditionalLockRelationOid(relid, lmode))
 		onerel = try_relation_open(relid, NoLock);
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 02e6d8131e..78e4c85f5d 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2904,7 +2904,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
 		tab->at_vacoptions = VACOPT_SKIPTOAST |
 			(dovacuum ? VACOPT_VACUUM : 0) |
 			(doanalyze ? VACOPT_ANALYZE : 0) |
-			(!wraparound ? VACOPT_NOWAIT : 0);
+			(!wraparound ? VACOPT_SKIP_LOCKED : 0);
 		tab->at_params.freeze_min_age = freeze_min_age;
 		tab->at_params.freeze_table_age = freeze_table_age;
 		tab->at_params.multixact_freeze_min_age = multixact_freeze_min_age;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6390f7e8c1..b907f1efef 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3135,7 +3135,7 @@ typedef enum VacuumOption
 	VACOPT_VERBOSE = 1 << 2,	/* print progress info */
 	VACOPT_FREEZE = 1 << 3,		/* FREEZE option */
 	VACOPT_FULL = 1 << 4,		/* FULL (non-concurrent) vacuum */
-	VACOPT_NOWAIT = 1 << 5,		/* don't wait to get lock (autovacuum only) */
+	VACOPT_SKIP_LOCKED = 1 << 5,	/* don't wait to get lock (autovacuum only) */
 	VACOPT_SKIPTOAST = 1 << 6,	/* don't process the TOAST table, if any */
 	VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7	/* don't skip any pages */
 } VacuumOption;
-- 
2.16.2

v8-0005-Add-assertion-that-we-are-not-an-autovacuum-worke.patchapplication/octet-stream; name=v8-0005-Add-assertion-that-we-are-not-an-autovacuum-worke.patchDownload
From 10dee9e8ecbf8351c362e0d480172cec48f1025b Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Wed, 4 Apr 2018 16:55:33 +0000
Subject: [PATCH v8 05/12] Add assertion that we are not an autovacuum worker
 in expand_vacuum_rel().

---
 src/backend/commands/vacuum.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 8902eba337..d539e33b8b 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -443,6 +443,12 @@ expand_vacuum_rel(VacuumRelation *vrel)
 		Form_pg_class classForm;
 		bool		include_parts;
 
+		/*
+		 * Since autovacuum workers supply OIDs when calling vacuum(), no
+		 * autovacuum worker should reach this code.
+		 */
+		Assert(!IsAutoVacuumWorkerProcess());
+
 		/*
 		 * We transiently take AccessShareLock to protect the syscache lookup
 		 * below, as well as find_all_inheritors's expectation that the caller
-- 
2.16.2

v8-0006-Create-a-helper-function-for-determining-the-log-.patchapplication/octet-stream; name=v8-0006-Create-a-helper-function-for-determining-the-log-.patchDownload
From 6c85189eda913596d4fa1c32b92eefcbd7532b4d Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Thu, 5 Apr 2018 17:20:42 +0000
Subject: [PATCH v8 06/12] Create a helper function for determining the log
 level for skipped relations.

---
 src/backend/commands/analyze.c | 45 +++++++++++-----------------------
 src/backend/commands/vacuum.c  | 55 +++++++++++++++++++++++++-----------------
 src/include/commands/vacuum.h  |  1 +
 3 files changed, 48 insertions(+), 53 deletions(-)

diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f27b5bd535..3db80c7d51 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -159,38 +159,21 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 */
 	if (!onerel)
 	{
-		/*
-		 * If the RangeVar is not defined, we do not have enough information
-		 * to provide a meaningful log statement.  Chances are that
-		 * analyze_rel's caller has intentionally not provided this
-		 * information so that this logging is skipped, anyway.
-		 */
-		if (relation == NULL)
-			return;
-
-		/*
-		 * Determine the log level.  For autovacuum logs, we emit a LOG if
-		 * log_autovacuum_min_duration is not disabled.  For manual ANALYZE,
-		 * we emit a WARNING to match the log statements in the permissions
-		 * checks.
-		 */
-		if (!IsAutoVacuumWorkerProcess())
-			elevel = WARNING;
-		else if (params->log_min_duration >= 0)
-			elevel = LOG;
-		else
-			return;
+		elevel = get_elevel_for_skipped_relation(relation, params);
 
-		if (!rel_lock)
-			ereport(elevel,
-					(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-					 errmsg("skipping analyze of \"%s\" --- lock not available",
-							relation->relname)));
-		else
-			ereport(elevel,
-					(errcode(ERRCODE_UNDEFINED_TABLE),
-					 errmsg("skipping analyze of \"%s\" --- relation no longer exists",
-							relation->relname)));
+		if (elevel != 0)
+		{
+			if (!rel_lock)
+				ereport(elevel,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("skipping analyze of \"%s\" --- lock not available",
+								relation->relname)));
+			else
+				ereport(elevel,
+						(errcode(ERRCODE_UNDEFINED_TABLE),
+						 errmsg("skipping analyze of \"%s\" --- relation no longer exists",
+								relation->relname)));
+		}
 
 		return;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d539e33b8b..1e70806a51 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1393,28 +1393,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 */
 	if (!onerel)
 	{
-		int			elevel = 0;
-
-		/*
-		 * Determine the log level.
-		 *
-		 * If the RangeVar is not defined, we do not have enough information
-		 * to provide a meaningful log statement.  Chances are that
-		 * vacuum_rel's caller has intentionally not provided this information
-		 * so that this logging is skipped, anyway.
-		 *
-		 * Otherwise, for autovacuum logs, we emit a LOG if
-		 * log_autovacuum_min_duration is not disabled.  For manual VACUUM, we
-		 * emit a WARNING to match the log statements in the permission
-		 * checks.
-		 */
-		if (relation != NULL)
-		{
-			if (!IsAutoVacuumWorkerProcess())
-				elevel = WARNING;
-			else if (params->log_min_duration >= 0)
-				elevel = LOG;
-		}
+		int			elevel = get_elevel_for_skipped_relation(relation, params);
 
 		if (elevel != 0)
 		{
@@ -1724,3 +1703,35 @@ vacuum_delay_point(void)
 		CHECK_FOR_INTERRUPTS();
 	}
 }
+
+/*
+ * get_elevel_for_skip_locked
+ *
+ * This is a helper function for determining the appropriate logging level for
+ * messages related to SKIP LOCKED or concurrently dropped relations.
+ *
+ * If the RangeVar is not defined, we do not have enough information to provide
+ * a meaningful log statement, and 0 is returned.  Chances are that the caller
+ * has intentionally not provided this information so that the logging is
+ * skipped anyway.
+ *
+ * Otherwise, for autovacuum logs, we emit a LOG if log_autovacuum_min_duration
+ * is not disabled.  For manual commands, we emit a WARNING to match the log
+ * statements in the permission checks for VACUUM and ANALYZE.
+ */
+int
+get_elevel_for_skipped_relation(RangeVar *relation, VacuumParams *params)
+{
+	Assert(params != NULL);
+
+	if (relation == NULL)
+		return 0;
+
+	if (!IsAutoVacuumWorkerProcess())
+		return WARNING;
+
+	if (params->log_min_duration >= 0)
+		return LOG;
+
+	return 0;
+}
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 69b1e384ef..64215ea60a 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -186,6 +186,7 @@ extern void vacuum_set_xid_limits(Relation rel,
 					  MultiXactId *mxactFullScanLimit);
 extern void vac_update_datfrozenxid(void);
 extern void vacuum_delay_point(void);
+extern int	get_elevel_for_skipped_relation(RangeVar *relation, VacuumParams *params);
 
 /* in commands/vacuumlazy.c */
 extern void lazy_vacuum_rel(Relation onerel, int options,
-- 
2.16.2

v8-0007-Create-a-helper-function-for-cleaning-up-from-do_.patchapplication/octet-stream; name=v8-0007-Create-a-helper-function-for-cleaning-up-from-do_.patchDownload
From dfbfafbc762d2bc2e951e30538762fc498069f4a Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Thu, 5 Apr 2018 17:37:54 +0000
Subject: [PATCH v8 07/12] Create a helper function for cleaning up from
 do_analyze_rel().

---
 src/backend/commands/analyze.c | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 3db80c7d51..7c4c0025a3 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -102,6 +102,9 @@ static void update_attstats(Oid relid, bool inh,
 				int natts, VacAttrStats **vacattrstats);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
+static void do_analyze_rel_cleanup(int save_nestlevel, Oid save_userid,
+					   int save_sec_context, MemoryContext caller_context,
+					   RangeVar *rangeVar, VacuumParams *params);
 
 
 /*
@@ -705,6 +708,29 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
 							pg_rusage_show(&ru0))));
 	}
 
+	/* pass NULL for the RangeVar to avoid the SKIP LOCKED message */
+	do_analyze_rel_cleanup(save_nestlevel, save_userid, save_sec_context,
+						   caller_context, NULL, params);
+}
+
+/*
+ * Convenience function for cleaning up before exiting do_analyze_rel().
+ *
+ * This also emits a "skipping analyze" log statement based on the rangeVar and
+ * params provided.  If rangeVar is NULL, no such log statement will be emitted.
+ */
+static void
+do_analyze_rel_cleanup(int save_nestlevel, Oid save_userid, int save_sec_context,
+					   MemoryContext caller_context, RangeVar *rangeVar, VacuumParams *params)
+{
+	int			elevel = get_elevel_for_skipped_relation(rangeVar, params);
+
+	if (elevel != 0)
+		ereport(elevel,
+				(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+				 errmsg("skipping analyze of \"%s\" --- lock not available",
+						rangeVar->relname)));
+
 	/* Roll back any GUC changes executed by index functions */
 	AtEOXact_GUC(false, save_nestlevel);
 
-- 
2.16.2

v8-0008-Skip-VACUUM-ANALYZE-with-VACOPT_SKIP_LOCKED-if-in.patchapplication/octet-stream; name=v8-0008-Skip-VACUUM-ANALYZE-with-VACOPT_SKIP_LOCKED-if-in.patchDownload
From ccb160136a7a0853c8a20bcc9fbfb449311d8f34 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Thu, 5 Apr 2018 19:00:40 +0000
Subject: [PATCH v8 08/12] Skip VACUUM/ANALYZE with VACOPT_SKIP_LOCKED if
 indexes can't be opened.

---
 src/backend/commands/analyze.c    | 29 ++++++++++++++++++++++++-----
 src/backend/commands/vacuum.c     | 23 +++++++++++++++++++----
 src/backend/commands/vacuumlazy.c | 23 +++++++++++++++++++++--
 src/include/commands/vacuum.h     |  2 +-
 4 files changed, 65 insertions(+), 12 deletions(-)

diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 7c4c0025a3..6b473e7d1d 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -84,7 +84,7 @@ static BufferAccessStrategy vac_strategy;
 static void do_analyze_rel(Relation onerel, int options,
 			   VacuumParams *params, List *va_cols,
 			   AcquireSampleRowsFunc acquirefunc, BlockNumber relpages,
-			   bool inh, bool in_outer_xact, int elevel);
+			   bool inh, bool in_outer_xact, int elevel, RangeVar *rangeVar);
 static void compute_index_stats(Relation onerel, double totalrows,
 					AnlIndexData *indexdata, int nindexes,
 					HeapTuple *rows, int numrows,
@@ -294,14 +294,14 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 */
 	if (onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		do_analyze_rel(onerel, options, params, va_cols, acquirefunc,
-					   relpages, false, in_outer_xact, elevel);
+					   relpages, false, in_outer_xact, elevel, relation);
 
 	/*
 	 * If there are child tables, do recursive ANALYZE.
 	 */
 	if (onerel->rd_rel->relhassubclass)
 		do_analyze_rel(onerel, options, params, va_cols, acquirefunc, relpages,
-					   true, in_outer_xact, elevel);
+					   true, in_outer_xact, elevel, relation);
 
 	/*
 	 * Close source relation now, but keep lock so that no one deletes it
@@ -331,7 +331,7 @@ static void
 do_analyze_rel(Relation onerel, int options, VacuumParams *params,
 			   List *va_cols, AcquireSampleRowsFunc acquirefunc,
 			   BlockNumber relpages, bool inh, bool in_outer_xact,
-			   int elevel)
+			   int elevel, RangeVar *rangeVar)
 {
 	int			attr_cnt,
 				tcnt,
@@ -454,7 +454,26 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
 	 * all.
 	 */
 	if (!inh)
-		vac_open_indexes(onerel, AccessShareLock, &nindexes, &Irel, false, NULL);
+	{
+		bool		skipped = false;
+
+		vac_open_indexes(onerel, AccessShareLock, &nindexes, &Irel,
+						 (options & VACOPT_SKIP_LOCKED), &skipped);
+
+		/*
+		 * If SKIP LOCKED is specified and we would have to wait for a lock on
+		 * an index, skip analyzing the relation.
+		 */
+		if (skipped)
+		{
+			do_analyze_rel_cleanup(save_nestlevel, save_userid,
+								   save_sec_context, caller_context,
+								   rangeVar, params);
+
+			return;
+		}
+
+	}
 	else
 	{
 		Irel = NULL;
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 1e70806a51..6185dee681 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1314,6 +1314,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	int			save_sec_context;
 	int			save_nestlevel;
 	bool		rel_lock = true;
+	bool		skipped = false;
 
 	Assert(params != NULL);
 
@@ -1539,7 +1540,18 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 					(options & VACOPT_VERBOSE) != 0);
 	}
 	else
-		lazy_vacuum_rel(onerel, options, params, vac_strategy);
+		skipped = !lazy_vacuum_rel(onerel, options, params, vac_strategy);
+
+	if (skipped)
+	{
+		int			elevel = get_elevel_for_skipped_relation(relation, params);
+
+		if (elevel != 0)
+			ereport(elevel,
+					(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+					 errmsg("skipping vacuum of \"%s\" --- lock not available",
+							relation->relname)));
+	}
 
 	/* Roll back any GUC changes executed by index functions */
 	AtEOXact_GUC(false, save_nestlevel);
@@ -1563,8 +1575,11 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 * "analyze" will not get done on the toast table.  This is good, because
 	 * the toaster always uses hardcoded index access and statistics are
 	 * totally unimportant for toast relations.
+	 *
+	 * If we skipped vacuuming the main relation, skip vacuuming the secondary
+	 * toast rel as well.
 	 */
-	if (toast_relid != InvalidOid)
+	if (!skipped && toast_relid != InvalidOid)
 		vacuum_rel(toast_relid, NULL, options, params);
 
 	/*
@@ -1572,8 +1587,8 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 */
 	UnlockRelationIdForSession(&onerelid, lmode);
 
-	/* Report that we really did it. */
-	return true;
+	/* Report if we really did it. */
+	return !skipped;
 }
 
 
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 47cc0536dd..45e5844ad9 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -185,8 +185,12 @@ static bool heap_page_is_all_visible(Relation rel, Buffer buf,
  *
  *		At entry, we have already established a transaction and opened
  *		and locked the relation.
+ *
+ *		If SKIP LOCKED is specified in the options and lazy_vacuum_rel()
+ *		skips the relation due to a conflicting lock, false is returned.
+ *		Else, true is returned.
  */
-void
+bool
 lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
 				BufferAccessStrategy bstrategy)
 {
@@ -208,6 +212,7 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
 	double		new_live_tuples;
 	TransactionId new_frozen_xid;
 	MultiXactId new_min_multi;
+	bool		skipped = false;
 
 	Assert(params != NULL);
 
@@ -258,7 +263,19 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
 	vacrelstats->lock_waiter_detected = false;
 
 	/* Open all indexes of the relation */
-	vac_open_indexes(onerel, RowExclusiveLock, &nindexes, &Irel, false, NULL);
+	vac_open_indexes(onerel, RowExclusiveLock, &nindexes, &Irel,
+					 (options & VACOPT_SKIP_LOCKED), &skipped);
+
+	/*
+	 * If SKIP LOCKED is specified and we would have to wait for a lock on an
+	 * index, skip vacuuming the relation.
+	 */
+	if (skipped)
+	{
+		pgstat_progress_end_command();
+		return false;
+	}
+
 	vacrelstats->hasindex = (nindexes > 0);
 
 	/* Do the vacuuming */
@@ -408,6 +425,8 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
 			pfree(buf.data);
 		}
 	}
+
+	return true;
 }
 
 /*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 64215ea60a..23fd8392c5 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -189,7 +189,7 @@ extern void vacuum_delay_point(void);
 extern int	get_elevel_for_skipped_relation(RangeVar *relation, VacuumParams *params);
 
 /* in commands/vacuumlazy.c */
-extern void lazy_vacuum_rel(Relation onerel, int options,
+extern bool lazy_vacuum_rel(Relation onerel, int options,
 				VacuumParams *params, BufferAccessStrategy bstrategy);
 
 /* in commands/analyze.c */
-- 
2.16.2

v8-0009-Skip-ANALYZE-with-VACOPT_SKIP_LOCKED-if-acquire_i.patchapplication/octet-stream; name=v8-0009-Skip-ANALYZE-with-VACOPT_SKIP_LOCKED-if-acquire_i.patchDownload
From 86543d174eda785305efc29f222ee833c7784b93 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Thu, 5 Apr 2018 18:43:45 +0000
Subject: [PATCH v8 09/12] Skip ANALYZE with VACOPT_SKIP_LOCKED if
 acquire_inherited_sample_rows() blocks.

---
 src/backend/commands/analyze.c | 54 ++++++++++++++++++++++++++++++++++++------
 1 file changed, 47 insertions(+), 7 deletions(-)

diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 6b473e7d1d..33f85e3f4d 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -97,7 +97,8 @@ static int acquire_sample_rows(Relation onerel, int elevel,
 static int	compare_rows(const void *a, const void *b);
 static int acquire_inherited_sample_rows(Relation onerel, int elevel,
 							  HeapTuple *rows, int targrows,
-							  double *totalrows, double *totaldeadrows);
+							  double *totalrows, double *totaldeadrows,
+							  bool skip_locked, bool *skipped);
 static void update_attstats(Oid relid, bool inh,
 				int natts, VacAttrStats **vacattrstats);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
@@ -353,6 +354,7 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
+	bool		skipped = false;
 
 	if (inh)
 		ereport(elevel,
@@ -550,9 +552,23 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
 	 */
 	rows = (HeapTuple *) palloc(targrows * sizeof(HeapTuple));
 	if (inh)
+	{
 		numrows = acquire_inherited_sample_rows(onerel, elevel,
 												rows, targrows,
-												&totalrows, &totaldeadrows);
+												&totalrows, &totaldeadrows,
+												(options & VACOPT_SKIP_LOCKED), &skipped);
+
+		/*
+		 * If SKIP LOCKED is specified and we would have to wait for a lock on
+		 * a child relation, skip analyzing the relation.
+		 */
+		if (skipped)
+		{
+			do_analyze_rel_cleanup(save_nestlevel, save_userid, save_sec_context,
+								   caller_context, rangeVar, params);
+			return;
+		}
+	}
 	else
 		numrows = (*acquirefunc) (onerel, elevel,
 								  rows, targrows,
@@ -1337,15 +1353,20 @@ compare_rows(const void *a, const void *b)
 /*
  * acquire_inherited_sample_rows -- acquire sample rows from inheritance tree
  *
- * This has the same API as acquire_sample_rows, except that rows are
+ * This has roughly the same API as acquire_sample_rows, except that rows are
  * collected from all inheritance children as well as the specified table.
- * We fail and return zero if there are no inheritance children, or if all
- * children are foreign tables that don't support ANALYZE.
+ * We fail and return zero if there are no inheritance children, if all
+ * children are foreign tables that don't support ANALYZE, or if skip_locked
+ * is true and we cannot obtain a lock on all children without waiting.
+ *
+ * If skipped is supplied, it will be set to true if skip_locked takes effect
+ * (false otherwise).
  */
 static int
 acquire_inherited_sample_rows(Relation onerel, int elevel,
 							  HeapTuple *rows, int targrows,
-							  double *totalrows, double *totaldeadrows)
+							  double *totalrows, double *totaldeadrows,
+							  bool skip_locked, bool *skipped)
 {
 	List	   *tableOIDs;
 	Relation   *rels;
@@ -1363,7 +1384,26 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 	 * the children.
 	 */
 	tableOIDs =
-		find_all_inheritors(RelationGetRelid(onerel), AccessShareLock, NULL, false);
+		find_all_inheritors(RelationGetRelid(onerel), AccessShareLock, NULL, skip_locked);
+
+	/*
+	 * If tableOIDs is NIL, skip_locked took effect.  In that case, we should
+	 * fail, being sure to set skipped if it is provided.
+	 */
+	if (tableOIDs == NIL)
+	{
+		if (skipped != NULL)
+			*skipped = true;
+
+		return 0;
+	}
+
+	/*
+	 * All skip_locked-related logic precedes this point, so we can just set
+	 * skipped to false if it is provided.
+	 */
+	if (skipped != NULL)
+		*skipped = false;
 
 	/*
 	 * Check that there's at least one descendant, else fail.  This could
-- 
2.16.2

v8-0010-Skip-VACUUM-ANALYZE-with-VACOPT_SKIP_LOCKED-if-ex.patchapplication/octet-stream; name=v8-0010-Skip-VACUUM-ANALYZE-with-VACOPT_SKIP_LOCKED-if-ex.patchDownload
From 737b1fe34deff9791f3c3eab54c2a38a9d883b55 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Thu, 5 Apr 2018 18:52:11 +0000
Subject: [PATCH v8 10/12] Skip VACUUM/ANALYZE with VACOPT_SKIP_LOCKED if
 expand_vacuum_rel() would block.

---
 src/backend/commands/vacuum.c | 30 ++++++++++++++++++++++++++----
 1 file changed, 26 insertions(+), 4 deletions(-)

diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 6185dee681..e2b8cb6214 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -68,7 +68,7 @@ static BufferAccessStrategy vac_strategy;
 
 
 /* non-export function prototypes */
-static List *expand_vacuum_rel(VacuumRelation *vrel);
+static List *expand_vacuum_rel(VacuumRelation *vrel, int options);
 static List *get_all_vacuum_rels(void);
 static void vac_truncate_clog(TransactionId frozenXID,
 				  MultiXactId minMulti,
@@ -257,7 +257,7 @@ vacuum(int options, List *relations, VacuumParams *params,
 			List	   *sublist;
 			MemoryContext old_context;
 
-			sublist = expand_vacuum_rel(vrel);
+			sublist = expand_vacuum_rel(vrel, options);
 			old_context = MemoryContextSwitchTo(vac_context);
 			newrels = list_concat(newrels, sublist);
 			MemoryContextSwitchTo(old_context);
@@ -423,7 +423,7 @@ vacuum(int options, List *relations, VacuumParams *params,
  * are made in vac_context.
  */
 static List *
-expand_vacuum_rel(VacuumRelation *vrel)
+expand_vacuum_rel(VacuumRelation *vrel, int options)
 {
 	List	   *vacrels = NIL;
 	MemoryContext oldcontext;
@@ -454,7 +454,29 @@ expand_vacuum_rel(VacuumRelation *vrel)
 		 * below, as well as find_all_inheritors's expectation that the caller
 		 * holds some lock on the starting relation.
 		 */
-		relid = RangeVarGetRelid(vrel->relation, AccessShareLock, false);
+		relid = RangeVarGetRelidExtended(vrel->relation,
+										 AccessShareLock,
+										 (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0,
+										 NULL, NULL);
+
+		/*
+		 * If the lock is unavailable, emit the same log statement that
+		 * vacuum_rel() and analyze_rel() would.
+		 */
+		if (!OidIsValid(relid))
+		{
+			if (options & VACOPT_VACUUM)
+				ereport(WARNING,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("skipping vacuum of \"%s\" --- lock not available",
+								vrel->relation->relname)));
+			else
+				ereport(WARNING,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("skipping analyze of \"%s\" --- lock not available",
+								vrel->relation->relname)));
+			return vacrels;
+		}
 
 		/*
 		 * Make a returnable VacuumRelation for this rel.
-- 
2.16.2

v8-0011-Skip-VACUUM-FULL-with-VACOPT_SKIP_LOCKED-if-clust.patchapplication/octet-stream; name=v8-0011-Skip-VACUUM-FULL-with-VACOPT_SKIP_LOCKED-if-clust.patchDownload
From f61a11aac3e3430acb33bcd10d4a7fe51390d672 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Thu, 5 Apr 2018 19:04:25 +0000
Subject: [PATCH v8 11/12] Skip VACUUM FULL with VACOPT_SKIP_LOCKED if
 cluster_rel() would block.

---
 src/backend/commands/cluster.c | 37 +++++++++++++++++++++++++------------
 src/backend/commands/vacuum.c  |  5 +++--
 src/include/commands/cluster.h |  4 ++--
 3 files changed, 30 insertions(+), 16 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 482d463420..db1d338100 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -186,7 +186,7 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
 		heap_close(rel, NoLock);
 
 		/* Do the job. */
-		cluster_rel(tableOid, indexOid, false, stmt->verbose);
+		cluster_rel(tableOid, indexOid, false, stmt->verbose, false);
 	}
 	else
 	{
@@ -234,7 +234,7 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
 			/* functions in indexes may want a snapshot set */
 			PushActiveSnapshot(GetTransactionSnapshot());
 			/* Do the job. */
-			cluster_rel(rvtc->tableOid, rvtc->indexOid, true, stmt->verbose);
+			cluster_rel(rvtc->tableOid, rvtc->indexOid, true, stmt->verbose, false);
 			PopActiveSnapshot();
 			CommitTransactionCommand();
 		}
@@ -263,9 +263,15 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
  * If indexOid is InvalidOid, the table will be rewritten in physical order
  * instead of index order.  This is the new implementation of VACUUM FULL,
  * and error messages should refer to the operation as VACUUM not CLUSTER.
+ *
+ * If skip_locked is true and the relation cannot be immediately locked,
+ * processing will be skipped and false will be returned (otherwise, true is
+ * returned).  Note that skip_locked will not prevent blocking on the index
+ * provided.  Since this functionality is only used for VACUUM FULL, this
+ * omission has no effect.
  */
-void
-cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose)
+bool
+cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose, bool skip_locked)
 {
 	Relation	OldHeap;
 
@@ -278,11 +284,16 @@ cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose)
 	 * case, since cluster() already did it.)  The index lock is taken inside
 	 * check_index_is_clusterable.
 	 */
-	OldHeap = try_relation_open(tableOid, AccessExclusiveLock);
+	if (!skip_locked)
+		OldHeap = try_relation_open(tableOid, AccessExclusiveLock);
+	else if (ConditionalLockRelationOid(tableOid, AccessExclusiveLock))
+		OldHeap = try_relation_open(tableOid, NoLock);
+	else
+		return false;
 
 	/* If the table has gone away, we can skip processing it */
 	if (!OldHeap)
-		return;
+		return true;
 
 	/*
 	 * Since we may open a new transaction for each relation, we have to check
@@ -301,7 +312,7 @@ cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose)
 		if (!pg_class_ownercheck(tableOid, GetUserId()))
 		{
 			relation_close(OldHeap, AccessExclusiveLock);
-			return;
+			return true;
 		}
 
 		/*
@@ -315,7 +326,7 @@ cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose)
 		if (RELATION_IS_OTHER_TEMP(OldHeap))
 		{
 			relation_close(OldHeap, AccessExclusiveLock);
-			return;
+			return true;
 		}
 
 		if (OidIsValid(indexOid))
@@ -326,7 +337,7 @@ cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose)
 			if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(indexOid)))
 			{
 				relation_close(OldHeap, AccessExclusiveLock);
-				return;
+				return true;
 			}
 
 			/*
@@ -336,14 +347,14 @@ cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose)
 			if (!HeapTupleIsValid(tuple))	/* probably can't happen */
 			{
 				relation_close(OldHeap, AccessExclusiveLock);
-				return;
+				return true;
 			}
 			indexForm = (Form_pg_index) GETSTRUCT(tuple);
 			if (!indexForm->indisclustered)
 			{
 				ReleaseSysCache(tuple);
 				relation_close(OldHeap, AccessExclusiveLock);
-				return;
+				return true;
 			}
 			ReleaseSysCache(tuple);
 		}
@@ -397,7 +408,7 @@ cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose)
 		!RelationIsPopulated(OldHeap))
 	{
 		relation_close(OldHeap, AccessExclusiveLock);
-		return;
+		return true;
 	}
 
 	/*
@@ -412,6 +423,8 @@ cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose)
 	rebuild_relation(OldHeap, indexOid, verbose);
 
 	/* NB: rebuild_relation does heap_close() on OldHeap */
+
+	return true;
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index e2b8cb6214..f65109a8f9 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1558,8 +1558,9 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 		onerel = NULL;
 
 		/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
-		cluster_rel(relid, InvalidOid, false,
-					(options & VACOPT_VERBOSE) != 0);
+		skipped = !cluster_rel(relid, InvalidOid, false,
+							   (options & VACOPT_VERBOSE) != 0,
+							   (options & VACOPT_SKIP_LOCKED) != 0);
 	}
 	else
 		skipped = !lazy_vacuum_rel(onerel, options, params, vac_strategy);
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index b338cb1098..49bb35fafe 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -19,8 +19,8 @@
 
 
 extern void cluster(ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, bool recheck,
-			bool verbose);
+extern bool cluster_rel(Oid tableOid, Oid indexOid, bool recheck,
+			bool verbose, bool skip_locked);
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 						   bool recheck, LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
-- 
2.16.2

v8-0012-Open-up-VACOPT_SKIP_LOCKED-to-users.patchapplication/octet-stream; name=v8-0012-Open-up-VACOPT_SKIP_LOCKED-to-users.patchDownload
From 48ded11553a9b4297a8e20d89b949a5496a6b3e4 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Wed, 4 Apr 2018 21:53:48 +0000
Subject: [PATCH v8 12/12] Open up VACOPT_SKIP_LOCKED to users.

---
 doc/src/sgml/ref/analyze.sgml                      |  12 ++
 doc/src/sgml/ref/vacuum.sgml                       |  12 ++
 src/backend/parser/gram.y                          |   2 +
 src/include/nodes/parsenodes.h                     |   2 +-
 src/test/isolation/expected/vacuum-skip-locked.out | 171 +++++++++++++++++++++
 src/test/isolation/isolation_schedule              |   1 +
 src/test/isolation/specs/vacuum-skip-locked.spec   |  59 +++++++
 src/test/regress/expected/vacuum.out               |  10 ++
 src/test/regress/sql/vacuum.sql                    |  10 ++
 9 files changed, 278 insertions(+), 1 deletion(-)
 create mode 100644 src/test/isolation/expected/vacuum-skip-locked.out
 create mode 100644 src/test/isolation/specs/vacuum-skip-locked.spec

diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 10b3a9a673..4e6ae38a52 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -27,6 +27,7 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
 <phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
 
     VERBOSE
+    SKIP LOCKED
 
 <phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
 
@@ -76,6 +77,17 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>SKIP LOCKED</literal></term>
+    <listitem>
+     <para>
+      Specifies that <command>ANALYZE</command> should not wait for any
+      conflicting locks to be released: if a relation cannot be locked
+      immediately without waiting, the relation is skipped.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">table_name</replaceable></term>
     <listitem>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index b760e8ede1..1ef5bc9d4a 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -31,6 +31,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
     VERBOSE
     ANALYZE
     DISABLE_PAGE_SKIPPING
+    SKIP LOCKED
 
 <phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
 
@@ -160,6 +161,17 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>SKIP LOCKED</literal></term>
+    <listitem>
+     <para>
+      Specifies that <command>VACUUM</command> should not wait for any
+      conflicting locks to be released: if a relation cannot be locked
+      immediately without waiting, the relation is skipped.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">table_name</replaceable></term>
     <listitem>
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 90dfac2cb1..d37f65d822 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10558,6 +10558,7 @@ vacuum_option_elem:
 							 errmsg("unrecognized VACUUM option \"%s\"", $1),
 									 parser_errposition(@1)));
 				}
+			| SKIP LOCKED		{ $$ = VACOPT_SKIP_LOCKED; }
 		;
 
 AnalyzeStmt: analyze_keyword opt_verbose opt_vacuum_relation_list
@@ -10585,6 +10586,7 @@ analyze_option_list:
 
 analyze_option_elem:
 			VERBOSE				{ $$ = VACOPT_VERBOSE; }
+			| SKIP LOCKED		{ $$ = VACOPT_SKIP_LOCKED; }
 		;
 
 analyze_keyword:
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b907f1efef..edae59a22b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3135,7 +3135,7 @@ typedef enum VacuumOption
 	VACOPT_VERBOSE = 1 << 2,	/* print progress info */
 	VACOPT_FREEZE = 1 << 3,		/* FREEZE option */
 	VACOPT_FULL = 1 << 4,		/* FULL (non-concurrent) vacuum */
-	VACOPT_SKIP_LOCKED = 1 << 5,	/* don't wait to get lock (autovacuum only) */
+	VACOPT_SKIP_LOCKED = 1 << 5,	/* don't wait to get lock */
 	VACOPT_SKIPTOAST = 1 << 6,	/* don't process the TOAST table, if any */
 	VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7	/* don't skip any pages */
 } VacuumOption;
diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out
new file mode 100644
index 0000000000..a6fe617db1
--- /dev/null
+++ b/src/test/isolation/expected/vacuum-skip-locked.out
@@ -0,0 +1,171 @@
+Parsed test spec with 2 sessions
+
+starting permutation: lock_share vac_specified commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_specified: VACUUM (SKIP LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share vac_all_parts commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+step vac_all_parts: VACUUM (SKIP LOCKED) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share analyze_specified commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+WARNING:  skipping analyze of "part1" --- lock not available
+step analyze_specified: ANALYZE (SKIP LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share analyze_all_parts commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+step analyze_all_parts: ANALYZE (SKIP LOCKED) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share vac_analyze_specified commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_analyze_specified: VACUUM (ANALYZE, SKIP LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share vac_analyze_all_parts commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP LOCKED) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share vac_full_specified commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_full_specified: VACUUM (SKIP LOCKED, FULL) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share vac_full_all_parts commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+step vac_full_all_parts: VACUUM (SKIP LOCKED, FULL) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_specified commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_specified: VACUUM (SKIP LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_all_parts commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+step vac_all_parts: VACUUM (SKIP LOCKED) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive analyze_specified commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+WARNING:  skipping analyze of "part1" --- lock not available
+step analyze_specified: ANALYZE (SKIP LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive analyze_all_parts commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+WARNING:  skipping analyze of "parted" --- lock not available
+step analyze_all_parts: ANALYZE (SKIP LOCKED) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_analyze_specified commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_analyze_specified: VACUUM (ANALYZE, SKIP LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_analyze_all_parts commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+WARNING:  skipping analyze of "parted" --- lock not available
+step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP LOCKED) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_full_specified commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_full_specified: VACUUM (SKIP LOCKED, FULL) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_full_all_parts commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+step vac_full_all_parts: VACUUM (SKIP LOCKED, FULL) parted;
+step commit: 
+	COMMIT;
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 0e997215a8..0b19857b24 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -75,3 +75,4 @@ test: partition-key-update-1
 test: partition-key-update-2
 test: partition-key-update-3
 test: plpgsql-toast
+test: vacuum-skip-locked
diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec
new file mode 100644
index 0000000000..3e9e968798
--- /dev/null
+++ b/src/test/isolation/specs/vacuum-skip-locked.spec
@@ -0,0 +1,59 @@
+# Test for the SKIP LOCKED option for VACUUM and ANALYZE commands.
+#
+# This also verifies that log messages are not emitted for skipped relations
+# that were not specified in the VACUUM or ANALYZE command.
+
+setup
+{
+	CREATE TABLE parted (a INT) PARTITION BY LIST (a);
+	CREATE TABLE part1 PARTITION OF parted FOR VALUES IN (1);
+	CREATE TABLE part2 PARTITION OF parted FOR VALUES IN (2);
+}
+
+teardown
+{
+	DROP TABLE IF EXISTS parted;
+}
+
+session "s1"
+step "lock_share"
+{
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+}
+step "lock_access_exclusive"
+{
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+}
+step "commit"
+{
+	COMMIT;
+}
+
+session "s2"
+step "vac_specified"		{ VACUUM (SKIP LOCKED) part1, part2; }
+step "vac_all_parts"		{ VACUUM (SKIP LOCKED) parted; }
+step "analyze_specified"	{ ANALYZE (SKIP LOCKED) part1, part2; }
+step "analyze_all_parts"	{ ANALYZE (SKIP LOCKED) parted; }
+step "vac_analyze_specified"	{ VACUUM (ANALYZE, SKIP LOCKED) part1, part2; }
+step "vac_analyze_all_parts"	{ VACUUM (ANALYZE, SKIP LOCKED) parted; }
+step "vac_full_specified"	{ VACUUM (SKIP LOCKED, FULL) part1, part2; }
+step "vac_full_all_parts"	{ VACUUM (SKIP LOCKED, FULL) parted; }
+
+permutation "lock_share" "vac_specified" "commit"
+permutation "lock_share" "vac_all_parts" "commit"
+permutation "lock_share" "analyze_specified" "commit"
+permutation "lock_share" "analyze_all_parts" "commit"
+permutation "lock_share" "vac_analyze_specified" "commit"
+permutation "lock_share" "vac_analyze_all_parts" "commit"
+permutation "lock_share" "vac_full_specified" "commit"
+permutation "lock_share" "vac_full_all_parts" "commit"
+permutation "lock_access_exclusive" "vac_specified" "commit"
+permutation "lock_access_exclusive" "vac_all_parts" "commit"
+permutation "lock_access_exclusive" "analyze_specified" "commit"
+permutation "lock_access_exclusive" "analyze_all_parts" "commit"
+permutation "lock_access_exclusive" "vac_analyze_specified" "commit"
+permutation "lock_access_exclusive" "vac_analyze_all_parts" "commit"
+permutation "lock_access_exclusive" "vac_full_specified" "commit"
+permutation "lock_access_exclusive" "vac_full_all_parts" "commit"
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index d66e2aa3b7..5d4a1e51c0 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -119,6 +119,16 @@ ANALYZE (nonexistant-arg) does_not_exist;
 ERROR:  syntax error at or near "nonexistant"
 LINE 1: ANALYZE (nonexistant-arg) does_not_exist;
                  ^
+-- ensure argument order independence, and that SKIP LOCKED on non-existing
+-- relation still errors out.
+ANALYZE (SKIP LOCKED, VERBOSE) does_not_exist;
+ERROR:  relation "does_not_exist" does not exist
+ANALYZE (VERBOSE, SKIP LOCKED) does_not_exist;
+ERROR:  relation "does_not_exist" does not exist
+-- SKIP LOCKED option
+VACUUM (SKIP LOCKED) vactst;
+VACUUM (SKIP LOCKED, FULL) vactst;
+ANALYZE (SKIP LOCKED) vactst;
 DROP TABLE vaccluster;
 DROP TABLE vactst;
 DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 275ce2e270..15749ffd94 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -93,6 +93,16 @@ ANALYZE vactst (i), vacparted (does_not_exist);
 ANALYZE (VERBOSE) does_not_exist;
 ANALYZE (nonexistant-arg) does_not_exist;
 
+-- ensure argument order independence, and that SKIP LOCKED on non-existing
+-- relation still errors out.
+ANALYZE (SKIP LOCKED, VERBOSE) does_not_exist;
+ANALYZE (VERBOSE, SKIP LOCKED) does_not_exist;
+
+-- SKIP LOCKED option
+VACUUM (SKIP LOCKED) vactst;
+VACUUM (SKIP LOCKED, FULL) vactst;
+ANALYZE (SKIP LOCKED) vactst;
+
 DROP TABLE vaccluster;
 DROP TABLE vactst;
 DROP TABLE vacparted;
-- 
2.16.2

#2Michael Paquier
michael@paquier.xyz
In reply to: Bossart, Nathan (#1)
Re: Add SKIP LOCKED to VACUUM and ANALYZE

On Wed, Jun 13, 2018 at 08:29:12PM +0000, Bossart, Nathan wrote:

Previous thread: /messages/by-id/4BC0F3CD-F4B5-4F23-AADB-80607F9E4B4E@amazon.com

This is a new thread for tracking the work to add SKIP LOCKED to
VACUUM and ANALYZE. I've attached a rebased set of patches.

I am beginning to look at this stuff, and committed patch 4 and 5 on the
way as they make sense independently. I am still reviewing the rest,
which applies with some fuzz, and the tests proposed still pass.
--
Michael

#3Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#2)
Re: Add SKIP LOCKED to VACUUM and ANALYZE

On Thu, Jul 12, 2018 at 02:37:28PM +0900, Michael Paquier wrote:

On Wed, Jun 13, 2018 at 08:29:12PM +0000, Bossart, Nathan wrote:

Previous thread: /messages/by-id/4BC0F3CD-F4B5-4F23-AADB-80607F9E4B4E@amazon.com

This is a new thread for tracking the work to add SKIP LOCKED to
VACUUM and ANALYZE. I've attached a rebased set of patches.

I am beginning to look at this stuff, and committed patch 4 and 5 on the
way as they make sense independently. I am still reviewing the rest,
which applies with some fuzz, and the tests proposed still pass.

I have been looking at the rest of the patch set, and I find that the
way SKIP_LOCKED is handled is a bit inconsistent. First, if the manual
VACUUM does not specify a list of relations, which can never happen for
autovacuum, then we get to use get_all_vacuum_rels to decide the list of
relations to look at, and then it is up to vacuum_rel() to decide if a
relation can be skipped or not. If a list of relation is given by the
caller, then it is up to expand_vacuum_rel() to do the call.

In expand_vacuum_rel(), an OID could be defined for a relation, which is
something used by autovacuum. If no OID is defined, which happens for a
manual VACUUM, then we use directly RangeVarGetRelidExtended() at this
stage and see if the relation is available. If the relation listed in
the manual VACUUM is a partitioned table, then its full set of
partition OIDs (including down layers), are included in the list of
relations to include. And we definitely want to hold, then release once
AccessShareLock so as the partition tree lookup can happen. This uses
as well find_all_inheritors() with NoLock so as we rely on vacuum_rel()
to skip the relation or not.

The first thing which is striking me is that we may actually *not* want
to check for lock skipping within expand_vacuum_rel() as that's mainly a
function aimed at building the relations which are going to be vacuumed,
and that all the lock skipping operations should happen at the beginning
of vacuum_rel(). This way, even if the parent of a partition tree is
listed but cannot have its RangeVar opened immediately, then we have a
chance to have some of its partitions to be vacuumed after listing them.
This point is debatable as there are pros and cons. I would be of the
opinion to not change expand_vacuum_rel()'s mission to build the list of
relations to VACUUM, and actually complain about a lock not available
when the relation is ready to be processed individually. It is also
perfectly possible to skip locks for partitions by listing them
individually in the VACUUM command used. I am pretty sure that Andres
would complain at the sight of this paragraph as this is backwards with
the reason behind the refactoring of RangeVarGetRelidExtended but I like
the new API better :p

For this part, it seems to me that we can do better than what is in
HEAD:
- Call RangeVarGetRelidExtended without lock.
- Check for has_subclass(), which should be extended so as it does not
complain because of a missing relation so as vacuum.c does the error
handling.
- Call RangeVarGetRelidExtended a second time if a lookup with
find_all_inheritors is necessary. If the relation goes missing
in-between then we just get an error as the current behavior.

I am also questioning the fact of complicating vac_open_indexes() by
skipping a full relation vacuum if one of its indexes cannot be opened,
which would be the case for an ALTER INDEX for example. It seems to me
that skipping locks should just happen for the parent relation, so I
would not bother much about this case, and just document the behavior.
If somebody argues for this option, we could always have a SKIP_INDEXES
or such.

"SKIP LOCKED" should be named "SKIP_LOCKED", with an underscore for
consistency with the other options.

-void
-cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose)
+bool
+cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose,
-bool skip_locked)
 {
Some refactoring of CLUSTER on the way here?  It would be nice to avoid
having three boolean arguments here, and opens the door for an extended
CLUSTER grammar with parenthesis as well as a SKIP_LOCKED option for
it.  And this could be refactored first.  VACUUM FULL being a variant of
CLUSTER, we could just reuse the same options...  Now using separate
option names could be cleaner.

The stuff of get_elevel_for_skipped_relation could be refactored into
something used as well by cluster_rel as the same logic is used in three
places (vacuum_rel, analyze_rel and cluster_rel with try_relation_open).

I think that it would be most interesting to get the refactoring around
get_elevel_for_skip_locked and cluster_rel done first. The improvement
for RangeVarGetRelidExtended with relations not having subclasses could
be worth done separately as well.
--
Michael

#4Robert Haas
robertmhaas@gmail.com
In reply to: Michael Paquier (#3)
Re: Add SKIP LOCKED to VACUUM and ANALYZE

On Tue, Jul 17, 2018 at 2:21 AM, Michael Paquier <michael@paquier.xyz> wrote:

For this part, it seems to me that we can do better than what is in
HEAD:
- Call RangeVarGetRelidExtended without lock.

I haven't been following this work closely, but I just want to point
out that the reason why RangeVarGetRelidExtended exists is because:

(1) we cannot lock a relation without looking up its OID, and should
not lock it without doing permissions checks first, and at least as
currently designed, those functions expect an OID, but

(2) we cannot look up a relation first and only lock it afterwards,
because DDL might happen in the middle and leave us locking the wrong
relation

When RangeVarGetRelidExtended is called with an argument other than
NoLock, it effectively makes locking, permissions checking, and the
name lookup happen simultaneously, so that it is neither possible to
lock a relation for which you don't have permissions, nor to change
the identity of the relation after the name lookup has been done and
before the lock is acquired. On the other hand, when you call it with
NoLock, you don't get those nice behaviors.

So I'm inclined to think that any place in the source code that calls
RangeVarGetRelidExtended with NoLock is a bug, and we should be trying
to get rid of the cases we still have, not adding more.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#5Bossart, Nathan
bossartn@amazon.com
In reply to: Michael Paquier (#3)
Re: Add SKIP LOCKED to VACUUM and ANALYZE

Hi Michael,

Thanks for taking a look.

On 7/17/18, 1:22 AM, "Michael Paquier" <michael@paquier.xyz> wrote:

The first thing which is striking me is that we may actually *not* want
to check for lock skipping within expand_vacuum_rel() as that's mainly a
function aimed at building the relations which are going to be vacuumed,
and that all the lock skipping operations should happen at the beginning
of vacuum_rel(). This way, even if the parent of a partition tree is
listed but cannot have its RangeVar opened immediately, then we have a
chance to have some of its partitions to be vacuumed after listing them.
This point is debatable as there are pros and cons. I would be of the
opinion to not change expand_vacuum_rel()'s mission to build the list of
relations to VACUUM, and actually complain about a lock not available
when the relation is ready to be processed individually. It is also
perfectly possible to skip locks for partitions by listing them
individually in the VACUUM command used. I am pretty sure that Andres
would complain at the sight of this paragraph as this is backwards with
the reason behind the refactoring of RangeVarGetRelidExtended but I like
the new API better :p

I see your point. The only time that expand_vacuum_rel() will block
is due to a conflicting AccessExclusiveLock on the relation, and the
reason we take a lock on the relation temporarily in
expand_vacuum_rel() is documented as follows:

/*
* We transiently take AccessShareLock to protect the syscache lookup
* below, as well as find_all_inheritors's expectation that the caller
* holds some lock on the starting relation.
*/

For this part, it seems to me that we can do better than what is in
HEAD:
- Call RangeVarGetRelidExtended without lock.
- Check for has_subclass(), which should be extended so as it does not
complain because of a missing relation so as vacuum.c does the error
handling.
- Call RangeVarGetRelidExtended a second time if a lookup with
find_all_inheritors is necessary. If the relation goes missing
in-between then we just get an error as the current behavior.

Perhaps we could extend RangeVarGetRelidExtended() to only lock if
has_subclass() is true. However, I also understand Robert's position
on calling RangeVarGetRelidExtended() with NoLock, and I'm not sure if
this locking optimization is worth the complexity.

I am also questioning the fact of complicating vac_open_indexes() by
skipping a full relation vacuum if one of its indexes cannot be opened,
which would be the case for an ALTER INDEX for example. It seems to me
that skipping locks should just happen for the parent relation, so I
would not bother much about this case, and just document the behavior.
If somebody argues for this option, we could always have a SKIP_INDEXES
or such.

As long as we document this behavior, that seems reasonable to me.

"SKIP LOCKED" should be named "SKIP_LOCKED", with an underscore for
consistency with the other options.

I chose SKIP LOCKED with a space because both words are already
reserved and SELECT uses it this way. I'm fine with adding an
underscore for consistency with DISABLE_PAGE_SKIPPING, though.

-void
-cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose)
+bool
+cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose,
-bool skip_locked)
{
Some refactoring of CLUSTER on the way here?  It would be nice to avoid
having three boolean arguments here, and opens the door for an extended
CLUSTER grammar with parenthesis as well as a SKIP_LOCKED option for
it.  And this could be refactored first.  VACUUM FULL being a variant of
CLUSTER, we could just reuse the same options...  Now using separate
option names could be cleaner.

Refactoring the boolean arguments of cluster_rel() into an options
argument seems like a good idea.

The stuff of get_elevel_for_skipped_relation could be refactored into
something used as well by cluster_rel as the same logic is used in three
places (vacuum_rel, analyze_rel and cluster_rel with try_relation_open).

This seems like a good idea if we intend to add SKIP LOCKED to CLUSTER
eventually, and it would be nice to cut down on some of the duplicated
ereport() calls. I'll look into it.

Nathan

#6Michael Paquier
michael@paquier.xyz
In reply to: Bossart, Nathan (#5)
Re: Add SKIP LOCKED to VACUUM and ANALYZE

On Wed, Jul 18, 2018 at 04:58:48PM +0000, Bossart, Nathan wrote:

On 7/17/18, 1:22 AM, "Michael Paquier" <michael@paquier.xyz> wrote:
Perhaps we could extend RangeVarGetRelidExtended() to only lock if
has_subclass() is true. However, I also understand Robert's position
on calling RangeVarGetRelidExtended() with NoLock, and I'm not sure if
this locking optimization is worth the complexity.

Yeah, I am having as well second-thoughts about this remark of mine.
This would introduce more complexity so it may be better to just do as
you suggested previously to skip the parent locked. Getting that
documented would be nice, in the lines of for example "If a partitioned
relation is listed in the set provided to VACUUM, then the whole tree of
relations to vacuum will be considered. When using SKIP_LOCKED, if the
parent cannot be locked when building this relation list, then none of
its partitions are vacuumed and the only the parent is skipped. If the
partitioned relation can be locked at its set of partitions listed, then
all partitions will be considered for vacuuming, and each partition
will be considered by SKIP_LOCKED individually when its VACUUM begins".

I am pretty sure you would come up with better words than those written
quickly.

Refactoring the boolean arguments of cluster_rel() into an options
argument seems like a good idea.

The stuff of get_elevel_for_skipped_relation could be refactored into
something used as well by cluster_rel as the same logic is used in three
places (vacuum_rel, analyze_rel and cluster_rel with try_relation_open).

This seems like a good idea if we intend to add SKIP LOCKED to CLUSTER
eventually, and it would be nice to cut down on some of the duplicated
ereport() calls. I'll look into it.

If you can get this refactoring done, let's look into getting those two
parts committed first to simplify the next steps. No need to extend the
grammar of cluster either. The set of options for CLUSTER should be a
separate enum list on top of ClusterStmt in parsenodes.h, as VACUUM has
no recheck option. I would recommend to get cluster_rel reshaped first,
and the ereport() calls done after.
--
Michael

#7Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#6)
1 attachment(s)
Re: Add SKIP LOCKED to VACUUM and ANALYZE

On Thu, Jul 19, 2018 at 10:54:23AM +0900, Michael Paquier wrote:

If you can get this refactoring done, let's look into getting those two
parts committed first to simplify the next steps. No need to extend the
grammar of cluster either. The set of options for CLUSTER should be a
separate enum list on top of ClusterStmt in parsenodes.h, as VACUUM has
no recheck option. I would recommend to get cluster_rel reshaped first,
and the ereport() calls done after.

The refactoring for CLUSTER is pretty obvious, and makes the API a bit
cleaner, so attached is a proposal of patch to do so. Thoughts?
--
Michael

Attachments:

0001-Refactor-ClusterStmt-to-handle-more-options.patchtext/x-diff; charset=us-asciiDownload
From ce4cd47c78f917827d94b13bac99457bc9d17223 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 23 Jul 2018 12:10:15 +0900
Subject: [PATCH] Refactor ClusterStmt to handle more options

This extends cluster_rel() in such a way that more options can be added
in the future, which will reduce the amount of chunk code for an
upcoming SKIP_LOCKED aimed for VACUUM.  As VACUUM FULL is a different
flavor of CLUSTER, we want to make that extensible as well to ease
integration.

This only reworks the API and its callers, without providing anything
user-facing.  Two options are present now: verbose mode and relation
recheck when doing the cluster work across multiple transactions.
---
 src/backend/commands/cluster.c |  9 ++++++---
 src/backend/commands/vacuum.c  |  8 ++++++--
 src/backend/nodes/copyfuncs.c  |  2 +-
 src/backend/nodes/equalfuncs.c |  2 +-
 src/backend/parser/gram.y      | 12 +++++++++---
 src/include/commands/cluster.h |  3 +--
 src/include/nodes/parsenodes.h |  8 +++++++-
 7 files changed, 31 insertions(+), 13 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 0112a87224..68be470977 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -186,7 +186,7 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
 		heap_close(rel, NoLock);
 
 		/* Do the job. */
-		cluster_rel(tableOid, indexOid, false, stmt->verbose);
+		cluster_rel(tableOid, indexOid, stmt->options);
 	}
 	else
 	{
@@ -234,7 +234,8 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
 			/* functions in indexes may want a snapshot set */
 			PushActiveSnapshot(GetTransactionSnapshot());
 			/* Do the job. */
-			cluster_rel(rvtc->tableOid, rvtc->indexOid, true, stmt->verbose);
+			cluster_rel(rvtc->tableOid, rvtc->indexOid,
+						stmt->options | CLUOPT_RECHECK);
 			PopActiveSnapshot();
 			CommitTransactionCommand();
 		}
@@ -265,9 +266,11 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
  * and error messages should refer to the operation as VACUUM not CLUSTER.
  */
 void
-cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose)
+cluster_rel(Oid tableOid, Oid indexOid, int options)
 {
 	Relation	OldHeap;
+	bool		verbose = ((options & CLUOPT_VERBOSE) != 0);
+	bool		recheck = ((options & CLUOPT_RECHECK) != 0);
 
 	/* Check for user-requested abort. */
 	CHECK_FOR_INTERRUPTS();
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index bd0f04c7e2..5736f12b8f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1551,13 +1551,17 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 */
 	if (options & VACOPT_FULL)
 	{
+		int			options = 0;
+
 		/* close relation before vacuuming, but hold lock until commit */
 		relation_close(onerel, NoLock);
 		onerel = NULL;
 
+		if ((options & VACOPT_VERBOSE) != 0)
+			options |= CLUOPT_VERBOSE;
+
 		/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
-		cluster_rel(relid, InvalidOid, false,
-					(options & VACOPT_VERBOSE) != 0);
+		cluster_rel(relid, InvalidOid, options);
 	}
 	else
 		lazy_vacuum_rel(onerel, options, params, vac_strategy);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 96836ef19c..17b650b8cb 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3284,7 +3284,7 @@ _copyClusterStmt(const ClusterStmt *from)
 
 	COPY_NODE_FIELD(relation);
 	COPY_STRING_FIELD(indexname);
-	COPY_SCALAR_FIELD(verbose);
+	COPY_SCALAR_FIELD(options);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 6a971d0141..378f2facb8 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1206,7 +1206,7 @@ _equalClusterStmt(const ClusterStmt *a, const ClusterStmt *b)
 {
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_STRING_FIELD(indexname);
-	COMPARE_SCALAR_FIELD(verbose);
+	COMPARE_SCALAR_FIELD(options);
 
 	return true;
 }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 90dfac2cb1..87f5e95827 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10478,7 +10478,9 @@ ClusterStmt:
 					ClusterStmt *n = makeNode(ClusterStmt);
 					n->relation = $3;
 					n->indexname = $4;
-					n->verbose = $2;
+					n->options = 0;
+					if ($2)
+						n->options |= CLUOPT_VERBOSE;
 					$$ = (Node*)n;
 				}
 			| CLUSTER opt_verbose
@@ -10486,7 +10488,9 @@ ClusterStmt:
 					ClusterStmt *n = makeNode(ClusterStmt);
 					n->relation = NULL;
 					n->indexname = NULL;
-					n->verbose = $2;
+					n->options = 0;
+					if ($2)
+						n->options |= CLUOPT_VERBOSE;
 					$$ = (Node*)n;
 				}
 			/* kept for pre-8.3 compatibility */
@@ -10495,7 +10499,9 @@ ClusterStmt:
 					ClusterStmt *n = makeNode(ClusterStmt);
 					n->relation = $5;
 					n->indexname = $3;
-					n->verbose = $2;
+					n->options = 0;
+					if ($2)
+						n->options |= CLUOPT_VERBOSE;
 					$$ = (Node*)n;
 				}
 		;
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index b338cb1098..f37a60c1c1 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -19,8 +19,7 @@
 
 
 extern void cluster(ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, bool recheck,
-			bool verbose);
+extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 						   bool recheck, LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 9a5d91a198..000064d642 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3112,12 +3112,18 @@ typedef struct AlterSystemStmt
  *		Cluster Statement (support pbrown's cluster index implementation)
  * ----------------------
  */
+typedef enum ClusterOption
+{
+	CLUOPT_RECHECK,				/* recheck relation state */
+	CLUOPT_VERBOSE				/* print progress info */
+}			ClusterOption;
+
 typedef struct ClusterStmt
 {
 	NodeTag		type;
 	RangeVar   *relation;		/* relation being indexed, or NULL if all */
 	char	   *indexname;		/* original index defined */
-	bool		verbose;		/* print progress info */
+	int			options;		/* OR of ClusterOption flags */
 } ClusterStmt;
 
 /* ----------------------
-- 
2.18.0

#8Bossart, Nathan
bossartn@amazon.com
In reply to: Michael Paquier (#7)
Re: Add SKIP LOCKED to VACUUM and ANALYZE

On 7/22/18, 10:12 PM, "Michael Paquier" <michael@paquier.xyz> wrote:

The refactoring for CLUSTER is pretty obvious, and makes the API a bit
cleaner, so attached is a proposal of patch to do so. Thoughts?

Sorry for the delay on these patches! This is nearly identical to
what I started writing last night, so it looks good to me.

+typedef enum ClusterOption
+{
+	CLUOPT_RECHECK,				/* recheck relation state */
+	CLUOPT_VERBOSE				/* print progress info */
+}			ClusterOption;

It looks like the last line here has a bit of extra whitespace
compared to the other code in parsenodes.h.

Nathan

#9Michael Paquier
michael@paquier.xyz
In reply to: Bossart, Nathan (#8)
Re: Add SKIP LOCKED to VACUUM and ANALYZE

On Mon, Jul 23, 2018 at 02:27:48PM +0000, Bossart, Nathan wrote:

Sorry for the delay on these patches! This is nearly identical to
what I started writing last night, so it looks good to me.

Thanks for double-checking. I have pushed this one to move on with the
rest of the feature.

+typedef enum ClusterOption
+{
+	CLUOPT_RECHECK,				/* recheck relation state */
+	CLUOPT_VERBOSE				/* print progress info */
+}			ClusterOption;

It looks like the last line here has a bit of extra whitespace
compared to the other code in parsenodes.h.

That came from pgindent. I have just updated typedefs.list to take care
of it. There could be an argument about moving recheck out of
cluster_rel(), but that would not be nice with any module for example
calling this API, so its contract is unchanged.
--
Michael

#10Bossart, Nathan
bossartn@amazon.com
In reply to: Michael Paquier (#9)
1 attachment(s)
Re: Add SKIP LOCKED to VACUUM and ANALYZE

On 7/18/18, 12:00 PM, "Bossart, Nathan" <bossartn@amazon.com> wrote:

On 7/17/18, 1:22 AM, "Michael Paquier" <michael@paquier.xyz> wrote:

The stuff of get_elevel_for_skipped_relation could be refactored into
something used as well by cluster_rel as the same logic is used in three
places (vacuum_rel, analyze_rel and cluster_rel with try_relation_open).

This seems like a good idea if we intend to add SKIP LOCKED to CLUSTER
eventually, and it would be nice to cut down on some of the duplicated
ereport() calls. I'll look into it.

Here is a patch for refactoring the ereport() calls out of
vacuum_rel() and analyze_rel(). I've kept all four possible log
statements separated for ease of translation. I considered
simplifying these statements by replacing "vacuum" and "analyze" with
"processing." However, that seems like it could lead to ambiguity for
commands like "VACUUM (ANALYZE, SKIP_LOCKED) table1 (a);" since both
VACUUM and ANALYZE could be skipped independently. If we add
SKIP_LOCKED to CLUSTER in the future, we will need to add two more log
statements to this function.

Nathan

Attachments:

0001-Refactor-logging-logic-for-skipped-relations-in-VACU.patchapplication/octet-stream; name=0001-Refactor-logging-logic-for-skipped-relations-in-VACU.patchDownload
From 5249eca2aa2cedc5e3e05e7e789cdceccfa7e552 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Tue, 24 Jul 2018 17:14:47 +0000
Subject: [PATCH 1/1] Refactor logging logic for skipped relations in VACUUM
 and ANALYZE.

This change refactors the logging logic for skipped relations in
VACUUM and ANALYZE so that it can be reused in other places (such
as expand_vacuum_rel()) as part of the upcoming SKIP_LOCKED
parameter.
---
 src/backend/commands/analyze.c   |  35 ++----------
 src/backend/commands/vacuum.c    | 111 +++++++++++++++++++++++++++------------
 src/include/commands/vacuum.h    |  10 ++++
 src/tools/pgindent/typedefs.list |   1 +
 4 files changed, 92 insertions(+), 65 deletions(-)

diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 3e148f03d0..8251205801 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -159,39 +159,14 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 */
 	if (!onerel)
 	{
-		/*
-		 * If the RangeVar is not defined, we do not have enough information
-		 * to provide a meaningful log statement.  Chances are that
-		 * analyze_rel's caller has intentionally not provided this
-		 * information so that this logging is skipped, anyway.
-		 */
-		if (relation == NULL)
-			return;
-
-		/*
-		 * Determine the log level.  For autovacuum logs, we emit a LOG if
-		 * log_autovacuum_min_duration is not disabled.  For manual ANALYZE,
-		 * we emit a WARNING to match the log statements in the permissions
-		 * checks.
-		 */
-		if (!IsAutoVacuumWorkerProcess())
-			elevel = WARNING;
-		else if (params->log_min_duration >= 0)
-			elevel = LOG;
-		else
-			return;
+		int			errcode;
 
-		if (!rel_lock)
-			ereport(elevel,
-					(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-					 errmsg("skipping analyze of \"%s\" --- lock not available",
-							relation->relname)));
+		if (rel_lock)
+			errcode = ERRCODE_UNDEFINED_TABLE;
 		else
-			ereport(elevel,
-					(errcode(ERRCODE_UNDEFINED_TABLE),
-					 errmsg("skipping analyze of \"%s\" --- relation no longer exists",
-							relation->relname)));
+			errcode = ERRCODE_LOCK_NOT_AVAILABLE;
 
+		report_skipped_relation(relation, params, errcode, SRS_ANALYZE);
 		return;
 	}
 
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5736f12b8f..b53f9c709c 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1393,43 +1393,14 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 */
 	if (!onerel)
 	{
-		int			elevel = 0;
+		int			errcode;
 
-		/*
-		 * Determine the log level.
-		 *
-		 * If the RangeVar is not defined, we do not have enough information
-		 * to provide a meaningful log statement.  Chances are that
-		 * vacuum_rel's caller has intentionally not provided this information
-		 * so that this logging is skipped, anyway.
-		 *
-		 * Otherwise, for autovacuum logs, we emit a LOG if
-		 * log_autovacuum_min_duration is not disabled.  For manual VACUUM, we
-		 * emit a WARNING to match the log statements in the permission
-		 * checks.
-		 */
-		if (relation != NULL)
-		{
-			if (!IsAutoVacuumWorkerProcess())
-				elevel = WARNING;
-			else if (params->log_min_duration >= 0)
-				elevel = LOG;
-		}
-
-		if (elevel != 0)
-		{
-			if (!rel_lock)
-				ereport(elevel,
-						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-						 errmsg("skipping vacuum of \"%s\" --- lock not available",
-								relation->relname)));
-			else
-				ereport(elevel,
-						(errcode(ERRCODE_UNDEFINED_TABLE),
-						 errmsg("skipping vacuum of \"%s\" --- relation no longer exists",
-								relation->relname)));
-		}
+		if (rel_lock)
+			errcode = ERRCODE_UNDEFINED_TABLE;
+		else
+			errcode = ERRCODE_LOCK_NOT_AVAILABLE;
 
+		report_skipped_relation(relation, params, errcode, SRS_VACUUM);
 		PopActiveSnapshot();
 		CommitTransactionCommand();
 		return false;
@@ -1705,3 +1676,73 @@ vacuum_delay_point(void)
 		CHECK_FOR_INTERRUPTS();
 	}
 }
+
+/*
+ * report_skipped_relation
+ *
+ * This function reports that a relation has been skipped using the given error
+ * code.
+ *
+ * If the RangeVar is not defined, we do not have enough information to provide
+ * a meaningful log statement, and nothing is emitted.  Chances are that the
+ * caller has intentionally not provided this information so that logging is
+ * skipped anyway.
+ *
+ * Otherwise, for autovacuum logs, we emit a LOG if log_autovacuum_min_duration
+ * is not disabled.  For manual commands, we emit a WARNING to match the log
+ * statements in the permission checks for VACUUM and ANALYZE.
+ *
+ * Note that this function currently only accepts the following SQL error codes:
+ * 	ERRCODE_LOCK_NOT_AVAILABLE
+ * 	ERRCODE_UNDEFINED_TABLE
+ */
+void
+report_skipped_relation(RangeVar *relation, VacuumParams *params,
+						int sqlerrcode, SkippedRelStmtType stmttype)
+{
+	int			elevel;
+
+	Assert(params != NULL);
+
+	if (relation == NULL)
+		return;
+	else if (!IsAutoVacuumWorkerProcess())
+		elevel = WARNING;
+	else if (params->log_min_duration >= 0)
+		elevel = LOG;
+	else
+		return;
+
+	if (sqlerrcode == ERRCODE_LOCK_NOT_AVAILABLE)
+	{
+		if (stmttype == SRS_VACUUM)
+			ereport(elevel,
+					(errcode(sqlerrcode),
+					 errmsg("skipping vacuum of \"%s\" --- lock not available",
+							relation->relname)));
+		else if (stmttype == SRS_ANALYZE)
+			ereport(elevel,
+					(errcode(sqlerrcode),
+					 errmsg("skipping analyze of \"%s\" --- lock not available",
+							relation->relname)));
+		else
+			elog(ERROR, "unrecognized statement type: %d", stmttype);
+	}
+	else if (sqlerrcode == ERRCODE_UNDEFINED_TABLE)
+	{
+		if (stmttype == SRS_VACUUM)
+			ereport(elevel,
+					(errcode(sqlerrcode),
+					 errmsg("skipping vacuum of \"%s\" --- relation no longer exists",
+							relation->relname)));
+		else if (stmttype == SRS_ANALYZE)
+			ereport(elevel,
+					(errcode(sqlerrcode),
+					 errmsg("skipping analyze of \"%s\" --- relation no longer exists",
+							relation->relname)));
+		else
+			elog(ERROR, "unrecognized statement type: %d", stmttype);
+	}
+	else
+		elog(ERROR, "unrecognized error code: %d", sqlerrcode);
+}
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 85d472f0a5..d74835ec12 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -147,6 +147,12 @@ typedef struct VacuumParams
 									 * to use default */
 } VacuumParams;
 
+typedef enum SkippedRelStmtType
+{
+	SRS_VACUUM,
+	SRS_ANALYZE
+} SkippedRelStmtType;
+
 /* GUC parameters */
 extern PGDLLIMPORT int default_statistics_target;	/* PGDLLIMPORT for PostGIS */
 extern int	vacuum_freeze_min_age;
@@ -185,6 +191,10 @@ extern void vacuum_set_xid_limits(Relation rel,
 					  MultiXactId *mxactFullScanLimit);
 extern void vac_update_datfrozenxid(void);
 extern void vacuum_delay_point(void);
+extern void report_skipped_relation(RangeVar *relation,
+						VacuumParams *params,
+						int sqlerrcode,
+						SkippedRelStmtType stmttype);
 
 /* in commands/vacuumlazy.c */
 extern void lazy_vacuum_rel(Relation onerel, int options,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 9fe950b29d..7ec4049deb 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2147,6 +2147,7 @@ SimpleStringList
 SimpleStringListCell
 SingleBoundSortItem
 Size
+SkippedRelStmtType
 SlabBlock
 SlabChunk
 SlabContext
-- 
2.16.2

#11Michael Paquier
michael@paquier.xyz
In reply to: Bossart, Nathan (#10)
Re: Add SKIP LOCKED to VACUUM and ANALYZE

On Tue, Jul 24, 2018 at 05:21:25PM +0000, Bossart, Nathan wrote:

Here is a patch for refactoring the ereport() calls out of
vacuum_rel() and analyze_rel(). I've kept all four possible log
statements separated for ease of translation. I considered
simplifying these statements by replacing "vacuum" and "analyze" with
"processing." However, that seems like it could lead to ambiguity for
commands like "VACUUM (ANALYZE, SKIP_LOCKED) table1 (a);" since both
VACUUM and ANALYZE could be skipped independently. If we add
SKIP_LOCKED to CLUSTER in the future, we will need to add two more log
statements to this function.

+typedef enum SkippedRelStmtType
+{
+ SRS_VACUUM,
+ SRS_ANALYZE
+} SkippedRelStmtType;
Hm... I have not imagined this part but adding a new layer is sort of
ugly, and an extra one would be needed for CLUSTER as well, in which
case adding cluster-related logs into vacuum.c introduces a circular
dependency with cluster.c. What about just skipping this refactoring
and move to the point where CLUSTER also gains its log queries directly
in cluster_rel? VACUUM FULL is also not going to run for autovacuum, so
that's less confusion with IsAutoVacuumWorkerProcess().

Another thing is the set of issues discussed in
/messages/by-id/20180724041403.GF4035@paquier.xyz
which are actually going to touch the same code areas that we are going
to change here, for actually rather similar purposes related to lock
handling. My gut feeling is to get the other issue fixed first, and
then rework this patch series so as we get the behavior that we want in
both the case of the previous thread and what we want here when building
a list of relations to VACUUM. There is as well the issue where a user
does not provide a list, so that's an extra argument for fixing the
current relid fetching properly first.
--
Michael

#12Bossart, Nathan
bossartn@amazon.com
In reply to: Michael Paquier (#11)
Re: Add SKIP LOCKED to VACUUM and ANALYZE

On 7/24/18, 8:07 PM, "Michael Paquier" <michael@paquier.xyz> wrote:

Hm... I have not imagined this part but adding a new layer is sort of
ugly, and an extra one would be needed for CLUSTER as well, in which
case adding cluster-related logs into vacuum.c introduces a circular
dependency with cluster.c. What about just skipping this refactoring
and move to the point where CLUSTER also gains its log queries directly
in cluster_rel? VACUUM FULL is also not going to run for autovacuum, so
that's less confusion with IsAutoVacuumWorkerProcess().

Since vacuum_rel() already obtains an AccessExclusiveLock on the
relation for VACUUM FULL, we might be able to skip altering
cluster_rel(). I think we will need to alter it if we are going to
add SKIP LOCKED to CLUSTER, though.

Nathan

#13Michael Paquier
michael@paquier.xyz
In reply to: Bossart, Nathan (#12)
Re: Add SKIP LOCKED to VACUUM and ANALYZE

On Sun, Jul 29, 2018 at 10:56:24PM +0000, Bossart, Nathan wrote:

Since vacuum_rel() already obtains an AccessExclusiveLock on the
relation for VACUUM FULL, we might be able to skip altering
cluster_rel(). I think we will need to alter it if we are going to
add SKIP LOCKED to CLUSTER, though.

Nathan, could you rebase your patch set? From what I can see the last
patch set applies with one conflict, and it would be nice for clarity to
split the routines for analyze, vacuum and cluster into separate places.
Similar to what is done with vacuum_is_relation_owner, having the same
set of logs for vacuum and analyze may be cleaner. The set of ownership
checks should happen after the skip lock checks to be consistent between
the ownership checks done when building the relation list (list
expansion for partitions and such) as well as for vacuum_rel() and
analyze_rel().

With all the work which has been done already, I don't think that we are
that far from getting something committable.
--
Michael

#14Bossart, Nathan
bossartn@amazon.com
In reply to: Michael Paquier (#13)
Re: Add SKIP LOCKED to VACUUM and ANALYZE

On 9/3/18, 6:20 PM, "Michael Paquier" <michael@paquier.xyz> wrote:

Nathan, could you rebase your patch set? From what I can see the last
patch set applies with one conflict, and it would be nice for clarity to
split the routines for analyze, vacuum and cluster into separate places.
Similar to what is done with vacuum_is_relation_owner, having the same
set of logs for vacuum and analyze may be cleaner. The set of ownership
checks should happen after the skip lock checks to be consistent between
the ownership checks done when building the relation list (list
expansion for partitions and such) as well as for vacuum_rel() and
analyze_rel().

Yes. I've started working on this again, but the new patch set is
probably still a few days out.

With all the work which has been done already, I don't think that we are
that far from getting something committable.

Great!

Nathan

#15Michael Paquier
michael@paquier.xyz
In reply to: Bossart, Nathan (#14)
Re: Add SKIP LOCKED to VACUUM and ANALYZE

On Tue, Sep 04, 2018 at 03:49:09PM +0000, Bossart, Nathan wrote:

Yes. I've started working on this again, but the new patch set is
probably still a few days out.

Thanks, Nathan.
--
Michael

#16Bossart, Nathan
bossartn@amazon.com
In reply to: Michael Paquier (#15)
7 attachment(s)
Re: Add SKIP LOCKED to VACUUM and ANALYZE

On 9/4/18, 1:32 PM, "Michael Paquier" <michael@paquier.xyz> wrote:

On Tue, Sep 04, 2018 at 03:49:09PM +0000, Bossart, Nathan wrote:

Yes. I've started working on this again, but the new patch set is
probably still a few days out.

Thanks, Nathan.

And here it is. Here is a summary of the notable changes:

1) Patches v8-0003 and v8-0008 have been discarded. These patches
added SKIP_LOCKED behavior when opening a relation's indexes.
Instead, I've documented that VACUUM and ANALYZE may still block
on indexes in v9-0007.
2) Patches v8-0004 and v8-0005 have been discarded, as they have
already been committed.
3) Patch v8-0011 has been discarded. As previously noted, VACUUM
(SKIP_LOCKED, FULL) is already handled in vacuum_rel(), so no
changed are required to cluster_rel(). However, we will need
something similar to v8-0011 if we ever add SKIP_LOCKED to
CLUSTER.
4) The option has been renamed to SKIP_LOCKED (with the underscore)
for consistency with the DISABLE_PAGE_SKIPPING option.
5) In the documentation, I've listed the caveats for SKIP_LOCKED and
partitioned tables. I tried to make all the new documentation as
concise as possible.

Nathan

Attachments:

v9-0007-Open-up-VACOPT_SKIP_LOCKED-to-users.patchapplication/octet-stream; name=v9-0007-Open-up-VACOPT_SKIP_LOCKED-to-users.patchDownload
From 32c15a6acb7b239181ce224308470ee7f57c70fa Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Wed, 5 Sep 2018 14:45:16 +0000
Subject: [PATCH v9 7/7] Open up VACOPT_SKIP_LOCKED to users.

---
 doc/src/sgml/ref/analyze.sgml                      |  19 +++
 doc/src/sgml/ref/vacuum.sgml                       |  17 ++
 src/backend/parser/gram.y                          |  12 ++
 src/include/nodes/parsenodes.h                     |   3 +-
 src/test/isolation/expected/vacuum-skip-locked.out | 171 +++++++++++++++++++++
 src/test/isolation/isolation_schedule              |   1 +
 src/test/isolation/specs/vacuum-skip-locked.spec   |  59 +++++++
 src/test/regress/expected/vacuum.out               |  12 +-
 src/test/regress/sql/vacuum.sql                    |  10 ++
 9 files changed, 301 insertions(+), 3 deletions(-)
 create mode 100644 src/test/isolation/expected/vacuum-skip-locked.out
 create mode 100644 src/test/isolation/specs/vacuum-skip-locked.spec

diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 10b3a9a673..40dec976e7 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -27,6 +27,7 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
 <phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
 
     VERBOSE
+    SKIP_LOCKED
 
 <phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
 
@@ -76,6 +77,24 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>SKIP_LOCKED</literal></term>
+    <listitem>
+     <para>
+      Specifies that <command>ANALYZE</command> should not wait for any
+      conflicting locks to be released: if a relation cannot be locked
+      immediately without waiting, the relation is skipped.  Note that even with
+      this option, <command>ANALYZE</command> can still block when opening the
+      relation's indexes.  Also, while <command>ANALYZE</command> ordinarily
+      processes all leaf partitions of partitioned tables, this option will
+      cause <command>ANALYZE</command> to skip all leaf partitions if there is a
+      conflicting lock on the partitioned table.  Similarly,
+      <command>ANALYZE</command> will skip partitioned tables if there is a
+      conflicting lock on any of its leaf partitions.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">table_name</replaceable></term>
     <listitem>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index b760e8ede1..85987916f5 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -31,6 +31,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
     VERBOSE
     ANALYZE
     DISABLE_PAGE_SKIPPING
+    SKIP_LOCKED
 
 <phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
 
@@ -160,6 +161,22 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>SKIP_LOCKED</literal></term>
+    <listitem>
+     <para>
+      Specifies that <command>VACUUM</command> should not wait for any
+      conflicting locks to be released: if a relation cannot be locked
+      immediately without waiting, the relation is skipped.  Note that even with
+      this option, <command>VACUUM</command> can still block when opening the
+      relation’s indexes.  Also, while <command>VACUUM</command> ordinarily
+      processes all leaf partitions of specified partitioned tables, this option
+      will cause <command>VACUUM</command> to skip all leaf partitions if there
+      is a conflicting lock on the partitioned table.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">table_name</replaceable></term>
     <listitem>
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 4bd2223f26..bbf7f0298d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10563,6 +10563,8 @@ vacuum_option_elem:
 				{
 					if (strcmp($1, "disable_page_skipping") == 0)
 						$$ = VACOPT_DISABLE_PAGE_SKIPPING;
+					else if (strcmp($1, "skip_locked") == 0)
+						$$ = VACOPT_SKIP_LOCKED;
 					else
 						ereport(ERROR,
 								(errcode(ERRCODE_SYNTAX_ERROR),
@@ -10596,6 +10598,16 @@ analyze_option_list:
 
 analyze_option_elem:
 			VERBOSE				{ $$ = VACOPT_VERBOSE; }
+			| IDENT
+				{
+					if (strcmp($1, "skip_locked") == 0)
+						$$ = VACOPT_SKIP_LOCKED;
+					else
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("unrecognized ANALYZE option \"%s\"", $1),
+									 parser_errposition(@1)));
+				}
 		;
 
 analyze_keyword:
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 07ab1a3dde..8aa536dc8d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3141,8 +3141,7 @@ typedef enum VacuumOption
 	VACOPT_VERBOSE = 1 << 2,	/* print progress info */
 	VACOPT_FREEZE = 1 << 3,		/* FREEZE option */
 	VACOPT_FULL = 1 << 4,		/* FULL (non-concurrent) vacuum */
-	VACOPT_SKIP_LOCKED = 1 << 5,	/* skip if cannot get lock (autovacuum
-									 * only) */
+	VACOPT_SKIP_LOCKED = 1 << 5,	/* skip if cannot get lock */
 	VACOPT_SKIPTOAST = 1 << 6,	/* don't process the TOAST table, if any */
 	VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7	/* don't skip any pages */
 } VacuumOption;
diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out
new file mode 100644
index 0000000000..42688d8803
--- /dev/null
+++ b/src/test/isolation/expected/vacuum-skip-locked.out
@@ -0,0 +1,171 @@
+Parsed test spec with 2 sessions
+
+starting permutation: lock_share vac_specified commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_specified: VACUUM (SKIP_LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share vac_all_parts commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+step vac_all_parts: VACUUM (SKIP_LOCKED) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share analyze_specified commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+WARNING:  skipping analyze of "part1" --- lock not available
+step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share analyze_all_parts commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share vac_analyze_specified commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share vac_analyze_all_parts commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share vac_full_specified commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share vac_full_all_parts commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_specified commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_specified: VACUUM (SKIP_LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_all_parts commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+step vac_all_parts: VACUUM (SKIP_LOCKED) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive analyze_specified commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+WARNING:  skipping analyze of "part1" --- lock not available
+step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive analyze_all_parts commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+WARNING:  skipping analyze of "parted" --- lock not available
+step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_analyze_specified commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_analyze_all_parts commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+WARNING:  skipping analyze of "parted" --- lock not available
+step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_full_specified commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_full_all_parts commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted;
+step commit: 
+	COMMIT;
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index c23b401225..dd57a96e78 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -67,6 +67,7 @@ test: vacuum-reltuples
 test: timeouts
 test: vacuum-concurrent-drop
 test: vacuum-conflict
+test: vacuum-skip-locked
 test: predicate-hash
 test: predicate-gist
 test: predicate-gin
diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec
new file mode 100644
index 0000000000..3f05141388
--- /dev/null
+++ b/src/test/isolation/specs/vacuum-skip-locked.spec
@@ -0,0 +1,59 @@
+# Test for the SKIP_LOCKED option for VACUUM and ANALYZE commands.
+#
+# This also verifies that log messages are not emitted for skipped relations
+# that were not specified in the VACUUM or ANALYZE command.
+
+setup
+{
+	CREATE TABLE parted (a INT) PARTITION BY LIST (a);
+	CREATE TABLE part1 PARTITION OF parted FOR VALUES IN (1);
+	CREATE TABLE part2 PARTITION OF parted FOR VALUES IN (2);
+}
+
+teardown
+{
+	DROP TABLE IF EXISTS parted;
+}
+
+session "s1"
+step "lock_share"
+{
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+}
+step "lock_access_exclusive"
+{
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+}
+step "commit"
+{
+	COMMIT;
+}
+
+session "s2"
+step "vac_specified"		{ VACUUM (SKIP_LOCKED) part1, part2; }
+step "vac_all_parts"		{ VACUUM (SKIP_LOCKED) parted; }
+step "analyze_specified"	{ ANALYZE (SKIP_LOCKED) part1, part2; }
+step "analyze_all_parts"	{ ANALYZE (SKIP_LOCKED) parted; }
+step "vac_analyze_specified"	{ VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; }
+step "vac_analyze_all_parts"	{ VACUUM (ANALYZE, SKIP_LOCKED) parted; }
+step "vac_full_specified"	{ VACUUM (SKIP_LOCKED, FULL) part1, part2; }
+step "vac_full_all_parts"	{ VACUUM (SKIP_LOCKED, FULL) parted; }
+
+permutation "lock_share" "vac_specified" "commit"
+permutation "lock_share" "vac_all_parts" "commit"
+permutation "lock_share" "analyze_specified" "commit"
+permutation "lock_share" "analyze_all_parts" "commit"
+permutation "lock_share" "vac_analyze_specified" "commit"
+permutation "lock_share" "vac_analyze_all_parts" "commit"
+permutation "lock_share" "vac_full_specified" "commit"
+permutation "lock_share" "vac_full_all_parts" "commit"
+permutation "lock_access_exclusive" "vac_specified" "commit"
+permutation "lock_access_exclusive" "vac_all_parts" "commit"
+permutation "lock_access_exclusive" "analyze_specified" "commit"
+permutation "lock_access_exclusive" "analyze_all_parts" "commit"
+permutation "lock_access_exclusive" "vac_analyze_specified" "commit"
+permutation "lock_access_exclusive" "vac_analyze_all_parts" "commit"
+permutation "lock_access_exclusive" "vac_full_specified" "commit"
+permutation "lock_access_exclusive" "vac_full_all_parts" "commit"
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 5993a90247..b342982089 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -116,9 +116,19 @@ ERROR:  column "does_not_exist" of relation "vacparted" does not exist
 ANALYZE (VERBOSE) does_not_exist;
 ERROR:  relation "does_not_exist" does not exist
 ANALYZE (nonexistant-arg) does_not_exist;
-ERROR:  syntax error at or near "nonexistant"
+ERROR:  unrecognized ANALYZE option "nonexistant"
 LINE 1: ANALYZE (nonexistant-arg) does_not_exist;
                  ^
+-- ensure argument order independence, and that SKIP_LOCKED on non-existing
+-- relation still errors out.
+ANALYZE (SKIP_LOCKED, VERBOSE) does_not_exist;
+ERROR:  relation "does_not_exist" does not exist
+ANALYZE (VERBOSE, SKIP_LOCKED) does_not_exist;
+ERROR:  relation "does_not_exist" does not exist
+-- SKIP_LOCKED option
+VACUUM (SKIP_LOCKED) vactst;
+VACUUM (SKIP_LOCKED, FULL) vactst;
+ANALYZE (SKIP_LOCKED) vactst;
 DROP TABLE vaccluster;
 DROP TABLE vactst;
 DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7f74da3cbd..bfdfd87744 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -93,6 +93,16 @@ ANALYZE vactst (i), vacparted (does_not_exist);
 ANALYZE (VERBOSE) does_not_exist;
 ANALYZE (nonexistant-arg) does_not_exist;
 
+-- ensure argument order independence, and that SKIP_LOCKED on non-existing
+-- relation still errors out.
+ANALYZE (SKIP_LOCKED, VERBOSE) does_not_exist;
+ANALYZE (VERBOSE, SKIP_LOCKED) does_not_exist;
+
+-- SKIP_LOCKED option
+VACUUM (SKIP_LOCKED) vactst;
+VACUUM (SKIP_LOCKED, FULL) vactst;
+ANALYZE (SKIP_LOCKED) vactst;
+
 DROP TABLE vaccluster;
 DROP TABLE vactst;
 DROP TABLE vacparted;
-- 
2.16.2

v9-0006-Skip-VACUUM-ANALYZE-with-VACOPT_SKIP_LOCKED-if-ex.patchapplication/octet-stream; name=v9-0006-Skip-VACUUM-ANALYZE-with-VACOPT_SKIP_LOCKED-if-ex.patchDownload
From 3674e267c86e39664d8281001a903b33eab8c9a9 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Wed, 5 Sep 2018 14:44:57 +0000
Subject: [PATCH v9 6/7] Skip VACUUM/ANALYZE with VACOPT_SKIP_LOCKED if
 expand_vacuum_rel() would block.

---
 src/backend/commands/vacuum.c | 24 +++++++++++++++++++++++-
 1 file changed, 23 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 764559f043..07c509646f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -528,7 +528,29 @@ expand_vacuum_rel(VacuumRelation *vrel, int options)
 		 * below, as well as find_all_inheritors's expectation that the caller
 		 * holds some lock on the starting relation.
 		 */
-		relid = RangeVarGetRelid(vrel->relation, AccessShareLock, false);
+		relid = RangeVarGetRelidExtended(vrel->relation,
+										 AccessShareLock,
+										 (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0,
+										 NULL, NULL);
+
+		/*
+		 * If the lock is unavailable, emit the same log statement that
+		 * vacuum_rel() and analyze_rel() would.
+		 */
+		if (!OidIsValid(relid))
+		{
+			if (options & VACOPT_VACUUM)
+				ereport(WARNING,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("skipping vacuum of \"%s\" --- lock not available",
+								vrel->relation->relname)));
+			else
+				ereport(WARNING,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("skipping analyze of \"%s\" --- lock not available",
+								vrel->relation->relname)));
+			return vacrels;
+		}
 
 		/*
 		 * To check whether the relation is a partitioned table and its
-- 
2.16.2

v9-0005-Skip-ANALYZE-with-VACOPT_SKIP_LOCKED-if-acquire_i.patchapplication/octet-stream; name=v9-0005-Skip-ANALYZE-with-VACOPT_SKIP_LOCKED-if-acquire_i.patchDownload
From c181744cdf9c7970209013787b6e62fb5de5dac7 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Wed, 5 Sep 2018 14:44:15 +0000
Subject: [PATCH v9 5/7] Skip ANALYZE with VACOPT_SKIP_LOCKED if
 acquire_inherited_sample_rows() blocks.

---
 src/backend/commands/analyze.c | 62 ++++++++++++++++++++++++++++++++++--------
 1 file changed, 51 insertions(+), 11 deletions(-)

diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 26fa7bcd55..bc215e7c00 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -84,7 +84,7 @@ static BufferAccessStrategy vac_strategy;
 static void do_analyze_rel(Relation onerel, int options,
 			   VacuumParams *params, List *va_cols,
 			   AcquireSampleRowsFunc acquirefunc, BlockNumber relpages,
-			   bool inh, bool in_outer_xact, int elevel);
+			   bool inh, bool in_outer_xact, int elevel, RangeVar *rangeVar);
 static void compute_index_stats(Relation onerel, double totalrows,
 					AnlIndexData *indexdata, int nindexes,
 					HeapTuple *rows, int numrows,
@@ -97,7 +97,8 @@ static int acquire_sample_rows(Relation onerel, int elevel,
 static int	compare_rows(const void *a, const void *b);
 static int acquire_inherited_sample_rows(Relation onerel, int elevel,
 							  HeapTuple *rows, int targrows,
-							  double *totalrows, double *totaldeadrows);
+							  double *totalrows, double *totaldeadrows,
+							  bool skip_locked, bool *skipped);
 static void update_attstats(Oid relid, bool inh,
 				int natts, VacAttrStats **vacattrstats);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
@@ -284,14 +285,14 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 */
 	if (onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		do_analyze_rel(onerel, options, params, va_cols, acquirefunc,
-					   relpages, false, in_outer_xact, elevel);
+					   relpages, false, in_outer_xact, elevel, relation);
 
 	/*
 	 * If there are child tables, do recursive ANALYZE.
 	 */
 	if (onerel->rd_rel->relhassubclass)
 		do_analyze_rel(onerel, options, params, va_cols, acquirefunc, relpages,
-					   true, in_outer_xact, elevel);
+					   true, in_outer_xact, elevel, relation);
 
 	/*
 	 * Close source relation now, but keep lock so that no one deletes it
@@ -321,7 +322,7 @@ static void
 do_analyze_rel(Relation onerel, int options, VacuumParams *params,
 			   List *va_cols, AcquireSampleRowsFunc acquirefunc,
 			   BlockNumber relpages, bool inh, bool in_outer_xact,
-			   int elevel)
+			   int elevel, RangeVar *rangeVar)
 {
 	int			attr_cnt,
 				tcnt,
@@ -343,6 +344,7 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
+	bool		skipped = false;
 
 	if (inh)
 		ereport(elevel,
@@ -521,9 +523,23 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
 	 */
 	rows = (HeapTuple *) palloc(targrows * sizeof(HeapTuple));
 	if (inh)
+	{
 		numrows = acquire_inherited_sample_rows(onerel, elevel,
 												rows, targrows,
-												&totalrows, &totaldeadrows);
+												&totalrows, &totaldeadrows,
+												(options & VACOPT_SKIP_LOCKED), &skipped);
+
+		/*
+		 * If SKIP LOCKED is specified and we would have to wait for a lock on
+		 * a child relation, skip analyzing the relation.
+		 */
+		if (skipped)
+		{
+			do_analyze_rel_cleanup(save_nestlevel, save_userid, save_sec_context,
+								   caller_context, rangeVar, params);
+			return;
+		}
+	}
 	else
 		numrows = (*acquirefunc) (onerel, elevel,
 								  rows, targrows,
@@ -1308,15 +1324,20 @@ compare_rows(const void *a, const void *b)
 /*
  * acquire_inherited_sample_rows -- acquire sample rows from inheritance tree
  *
- * This has the same API as acquire_sample_rows, except that rows are
+ * This has roughly the same API as acquire_sample_rows, except that rows are
  * collected from all inheritance children as well as the specified table.
- * We fail and return zero if there are no inheritance children, or if all
- * children are foreign tables that don't support ANALYZE.
+ * We fail and return zero if there are no inheritance children, if all
+ * children are foreign tables that don't support ANALYZE, or if skip_locked
+ * is true and we cannot obtain a lock on all children without waiting.
+ *
+ * If skipped is supplied, it will be set to true if skip_locked takes effect
+ * (false otherwise).
  */
 static int
 acquire_inherited_sample_rows(Relation onerel, int elevel,
 							  HeapTuple *rows, int targrows,
-							  double *totalrows, double *totaldeadrows)
+							  double *totalrows, double *totaldeadrows,
+							  bool skip_locked, bool *skipped)
 {
 	List	   *tableOIDs;
 	Relation   *rels;
@@ -1334,7 +1355,26 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 	 * the children.
 	 */
 	tableOIDs =
-		find_all_inheritors(RelationGetRelid(onerel), AccessShareLock, NULL, false);
+		find_all_inheritors(RelationGetRelid(onerel), AccessShareLock, NULL, skip_locked);
+
+	/*
+	 * If tableOIDs is NIL, skip_locked took effect.  In that case, we should
+	 * fail, being sure to set skipped if it is provided.
+	 */
+	if (tableOIDs == NIL)
+	{
+		if (skipped != NULL)
+			*skipped = true;
+
+		return 0;
+	}
+
+	/*
+	 * All skip_locked-related logic precedes this point, so we can just set
+	 * skipped to false if it is provided.
+	 */
+	if (skipped != NULL)
+		*skipped = false;
 
 	/*
 	 * Check that there's at least one descendant, else fail.  This could
-- 
2.16.2

v9-0004-Create-a-helper-function-for-cleaning-up-from-do_.patchapplication/octet-stream; name=v9-0004-Create-a-helper-function-for-cleaning-up-from-do_.patchDownload
From 4991912b680a20d309781c44f09fdaaefdbecaab Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Wed, 5 Sep 2018 14:43:44 +0000
Subject: [PATCH v9 4/7] Create a helper function for cleaning up from
 do_analyze_rel().

---
 src/backend/commands/analyze.c | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index dc73714819..26fa7bcd55 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -102,6 +102,9 @@ static void update_attstats(Oid relid, bool inh,
 				int natts, VacAttrStats **vacattrstats);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
+static void do_analyze_rel_cleanup(int save_nestlevel, Oid save_userid,
+					   int save_sec_context, MemoryContext caller_context,
+					   RangeVar *rangeVar, VacuumParams *params);
 
 
 /*
@@ -695,6 +698,29 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
 							pg_rusage_show(&ru0))));
 	}
 
+	/* pass NULL for the RangeVar to avoid the SKIP LOCKED message */
+	do_analyze_rel_cleanup(save_nestlevel, save_userid, save_sec_context,
+						   caller_context, NULL, params);
+}
+
+/*
+ * Convenience function for cleaning up before exiting do_analyze_rel().
+ *
+ * This also emits a "skipping analyze" log statement based on the rangeVar and
+ * params provided.  If rangeVar is NULL, no such log statement will be emitted.
+ */
+static void
+do_analyze_rel_cleanup(int save_nestlevel, Oid save_userid, int save_sec_context,
+					   MemoryContext caller_context, RangeVar *rangeVar, VacuumParams *params)
+{
+	int			elevel = get_elevel_for_skipped_relation(rangeVar, params);
+
+	if (elevel != 0)
+		ereport(elevel,
+				(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+				 errmsg("skipping analyze of \"%s\" --- lock not available",
+						rangeVar->relname)));
+
 	/* Roll back any GUC changes executed by index functions */
 	AtEOXact_GUC(false, save_nestlevel);
 
-- 
2.16.2

v9-0003-Add-skip_locked-argument-to-find_all_inheritors.patchapplication/octet-stream; name=v9-0003-Add-skip_locked-argument-to-find_all_inheritors.patchDownload
From 6ed57067316b788a1d76b7555f2f6c4835b43e04 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Wed, 5 Sep 2018 14:43:22 +0000
Subject: [PATCH v9 3/7] Add skip_locked argument to find_all_inheritors().

---
 contrib/sepgsql/dml.c                  |  2 +-
 src/backend/catalog/pg_inherits.c      | 30 +++++++++++++++++++++++++++---
 src/backend/commands/analyze.c         |  2 +-
 src/backend/commands/publicationcmds.c |  2 +-
 src/backend/commands/tablecmds.c       | 16 ++++++++--------
 src/backend/commands/trigger.c         |  2 +-
 src/backend/commands/vacuum.c          |  2 +-
 src/backend/executor/execPartition.c   |  2 +-
 src/backend/optimizer/prep/prepunion.c |  2 +-
 src/backend/partitioning/partbounds.c  |  2 +-
 src/backend/tcop/utility.c             |  3 ++-
 src/include/catalog/pg_inherits.h      |  2 +-
 12 files changed, 46 insertions(+), 21 deletions(-)

diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 9bdbd7b60f..bb7713380a 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -330,7 +330,7 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 		if (!rte->inh)
 			tableIds = list_make1_oid(rte->relid);
 		else
-			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
+			tableIds = find_all_inheritors(rte->relid, NoLock, NULL, false);
 
 		foreach(li, tableIds)
 		{
diff --git a/src/backend/catalog/pg_inherits.c b/src/backend/catalog/pg_inherits.c
index 1b936123c5..a64037a958 100644
--- a/src/backend/catalog/pg_inherits.c
+++ b/src/backend/catalog/pg_inherits.c
@@ -180,10 +180,12 @@ find_inheritance_children(Oid parentrelId, LOCKMODE lockmode, bool skip_locked,
  * The specified lock type is acquired on all child relations (but not on the
  * given rel; caller should already have locked it).  If lockmode is NoLock
  * then no locks are acquired, but caller must beware of race conditions
- * against possible DROPs of child relations.
+ * against possible DROPs of child relations.  If skip_locked is true and a
+ * child relation can not be locked immediately without waiting, both the
+ * returned OID list and *numparents will be set to NIL.
  */
 List *
-find_all_inheritors(Oid parentrelId, LOCKMODE lockmode, List **numparents)
+find_all_inheritors(Oid parentrelId, LOCKMODE lockmode, List **numparents, bool skip_locked)
 {
 	/* hash table for O(1) rel_oid -> rel_numparents cell lookup */
 	HTAB	   *seen_rels;
@@ -191,6 +193,7 @@ find_all_inheritors(Oid parentrelId, LOCKMODE lockmode, List **numparents)
 	List	   *rels_list,
 			   *rel_numparents;
 	ListCell   *l;
+	bool		skipped = false;
 
 	memset(&ctl, 0, sizeof(ctl));
 	ctl.keysize = sizeof(Oid);
@@ -219,7 +222,7 @@ find_all_inheritors(Oid parentrelId, LOCKMODE lockmode, List **numparents)
 		ListCell   *lc;
 
 		/* Get the direct children of this rel */
-		currentchildren = find_inheritance_children(currentrel, lockmode, false, NULL);
+		currentchildren = find_inheritance_children(currentrel, lockmode, skip_locked, &skipped);
 
 		/*
 		 * Add to the queue only those children not already seen. This avoids
@@ -248,6 +251,27 @@ find_all_inheritors(Oid parentrelId, LOCKMODE lockmode, List **numparents)
 				hash_entry->numparents_cell = rel_numparents->tail;
 			}
 		}
+
+		if (skipped)
+			break;
+	}
+
+	/* if a child relation was skipped, set the return lists to NIL */
+	if (skipped)
+	{
+		/* release all locks */
+		foreach(l, rels_list)
+		{
+			Oid			currentrel = lfirst_oid(l);
+
+			if (currentrel != parentrelId)
+				UnlockRelationOid(currentrel, lockmode);
+		}
+
+		list_free(rel_numparents);
+		list_free(rels_list);
+		rel_numparents = NIL;
+		rels_list = NIL;
 	}
 
 	if (numparents)
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index b6b78527f4..dc73714819 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1308,7 +1308,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 	 * the children.
 	 */
 	tableOIDs =
-		find_all_inheritors(RelationGetRelid(onerel), AccessShareLock, NULL);
+		find_all_inheritors(RelationGetRelid(onerel), AccessShareLock, NULL, false);
 
 	/*
 	 * Check that there's at least one descendant, else fail.  This could
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 6f7762a906..a5e01ea61e 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -530,7 +530,7 @@ OpenTableList(List *tables)
 			List	   *children;
 
 			children = find_all_inheritors(myrelid, ShareUpdateExclusiveLock,
-										   NULL);
+										   NULL, false);
 
 			foreach(child, children)
 			{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7520de1b56..693cffb1f0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1374,7 +1374,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 			ListCell   *child;
 			List	   *children;
 
-			children = find_all_inheritors(myrelid, AccessExclusiveLock, NULL);
+			children = find_all_inheritors(myrelid, AccessExclusiveLock, NULL, false);
 
 			foreach(child, children)
 			{
@@ -2797,7 +2797,7 @@ renameatt_internal(Oid myrelid,
 		 * outside the inheritance hierarchy being processed.
 		 */
 		child_oids = find_all_inheritors(myrelid, AccessExclusiveLock,
-										 &child_numparents);
+										 &child_numparents, false);
 
 		/*
 		 * find_all_inheritors does the recursive search of the inheritance
@@ -3007,7 +3007,7 @@ rename_constraint_internal(Oid myrelid,
 					   *li;
 
 			child_oids = find_all_inheritors(myrelid, AccessExclusiveLock,
-											 &child_numparents);
+											 &child_numparents, false);
 
 			forboth(lo, child_oids, li, child_numparents)
 			{
@@ -5067,7 +5067,7 @@ ATSimpleRecursion(List **wqueue, Relation rel,
 		ListCell   *child;
 		List	   *children;
 
-		children = find_all_inheritors(relid, lockmode, NULL);
+		children = find_all_inheritors(relid, lockmode, NULL, false);
 
 		/*
 		 * find_all_inheritors does the recursive search of the inheritance
@@ -8048,7 +8048,7 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 			 */
 			if (!recursing && !con->connoinherit)
 				children = find_all_inheritors(RelationGetRelid(rel),
-											   lockmode, NULL);
+											   lockmode, NULL, false);
 
 			/*
 			 * For CHECK constraints, we must ensure that we only mark the
@@ -9255,7 +9255,7 @@ ATPrepAlterColumnType(List **wqueue,
 		ListCell   *child;
 		List	   *children;
 
-		children = find_all_inheritors(relid, lockmode, NULL);
+		children = find_all_inheritors(relid, lockmode, NULL, false);
 
 		/*
 		 * find_all_inheritors does the recursive search of the inheritance
@@ -11479,7 +11479,7 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 	 * We use weakest lock we can on child's children, namely AccessShareLock.
 	 */
 	children = find_all_inheritors(RelationGetRelid(child_rel),
-								   AccessShareLock, NULL);
+								   AccessShareLock, NULL, false);
 
 	if (list_member_oid(children, RelationGetRelid(parent_rel)))
 		ereport(ERROR,
@@ -14171,7 +14171,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 * weaker lock now and the stronger one only when needed.
 	 */
 	attachrel_children = find_all_inheritors(RelationGetRelid(attachrel),
-											 AccessExclusiveLock, NULL);
+											 AccessExclusiveLock, NULL, false);
 	if (list_member_oid(attachrel_children, RelationGetRelid(rel)))
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_TABLE),
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index f2617cfb1d..806c7a271c 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -364,7 +364,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
 	if (partition_recurse)
 		list_free(find_all_inheritors(RelationGetRelid(rel),
-									  ShareRowExclusiveLock, NULL));
+									  ShareRowExclusiveLock, NULL, false));
 
 	/* Compute tgtype */
 	TRIGGER_CLEAR_TYPE(tgtype);
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index fc1081663c..764559f043 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -567,7 +567,7 @@ expand_vacuum_rel(VacuumRelation *vrel, int options)
 		 */
 		if (include_parts)
 		{
-			List	   *part_oids = find_all_inheritors(relid, NoLock, NULL);
+			List	   *part_oids = find_all_inheritors(relid, NoLock, NULL, false);
 			ListCell   *part_lc;
 
 			foreach(part_lc, part_oids)
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 1a9943c3aa..6bb0b1ba14 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -89,7 +89,7 @@ ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
 	 * Get the information about the partition tree after locking all the
 	 * partitions.
 	 */
-	(void) find_all_inheritors(RelationGetRelid(rel), RowExclusiveLock, NULL);
+	(void) find_all_inheritors(RelationGetRelid(rel), RowExclusiveLock, NULL, false);
 	proute = (PartitionTupleRouting *) palloc0(sizeof(PartitionTupleRouting));
 	proute->partition_dispatch_info =
 		RelationGetPartitionDispatchInfo(rel, &proute->num_dispatch,
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 690b6bbab7..4f9305d2ba 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -1563,7 +1563,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 		lockmode = AccessShareLock;
 
 	/* Scan for all members of inheritance set, acquire needed locks */
-	inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+	inhOIDs = find_all_inheritors(parentOID, lockmode, NULL, false);
 
 	/*
 	 * Check that there's at least one descendant, else treat as no-child
diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index 9015a05d32..0e7b627793 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -629,7 +629,7 @@ check_default_partition_contents(Relation parent, Relation default_rel,
 	 */
 	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
-										AccessExclusiveLock, NULL);
+										AccessExclusiveLock, NULL, false);
 	else
 		all_parts = list_make1_oid(RelationGetRelid(default_rel));
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index b5804f64ad..f2c9b46c10 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1325,7 +1325,8 @@ ProcessUtilitySlow(ParseState *pstate,
 						ListCell   *lc;
 						List	   *inheritors = NIL;
 
-						inheritors = find_all_inheritors(relid, lockmode, NULL);
+						inheritors = find_all_inheritors(relid, lockmode,
+															NULL, false);
 						foreach(lc, inheritors)
 						{
 							char		relkind = get_rel_relkind(lfirst_oid(lc));
diff --git a/src/include/catalog/pg_inherits.h b/src/include/catalog/pg_inherits.h
index a89b3f44a5..ccbf756ce5 100644
--- a/src/include/catalog/pg_inherits.h
+++ b/src/include/catalog/pg_inherits.h
@@ -47,7 +47,7 @@ typedef FormData_pg_inherits *Form_pg_inherits;
 extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode,
 						  bool skip_locked, bool *skipped);
 extern List *find_all_inheritors(Oid parentrelId, LOCKMODE lockmode,
-					List **parents);
+					List **parents, bool skip_locked);
 extern bool has_subclass(Oid relationId);
 extern bool has_superclass(Oid relationId);
 extern bool typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId);
-- 
2.16.2

v9-0002-Add-skip_locked-argument-to-find_inheritance_chil.patchapplication/octet-stream; name=v9-0002-Add-skip_locked-argument-to-find_inheritance_chil.patchDownload
From ad3cb733a4fa60f42734aeeddbaa82656e2e9085 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Wed, 5 Sep 2018 14:42:55 +0000
Subject: [PATCH v9 2/7] Add skip_locked argument to
 find_inheritance_children().

---
 src/backend/catalog/pg_inherits.c   | 26 +++++++++++++++++++++++---
 src/backend/commands/lockcmds.c     |  2 +-
 src/backend/commands/tablecmds.c    | 16 ++++++++--------
 src/backend/commands/trigger.c      |  2 +-
 src/backend/utils/cache/partcache.c |  2 +-
 src/include/catalog/pg_inherits.h   |  3 ++-
 6 files changed, 36 insertions(+), 15 deletions(-)

diff --git a/src/backend/catalog/pg_inherits.c b/src/backend/catalog/pg_inherits.c
index 85baca54cc..1b936123c5 100644
--- a/src/backend/catalog/pg_inherits.c
+++ b/src/backend/catalog/pg_inherits.c
@@ -51,9 +51,14 @@ typedef struct SeenRelsEntry
  * given rel; caller should already have locked it).  If lockmode is NoLock
  * then no locks are acquired, but caller must beware of race conditions
  * against possible DROPs of child relations.
+ *
+ * If skip_locked is true, any child relations that cannot be locked immediately
+ * without waiting are not added to the returned OID list, and skipped will be
+ * set to true if it is provided.  Otherwise, skipped will be set to false if it
+ * is provided.
  */
 List *
-find_inheritance_children(Oid parentrelId, LOCKMODE lockmode)
+find_inheritance_children(Oid parentrelId, LOCKMODE lockmode, bool skip_locked, bool *skipped)
 {
 	List	   *list = NIL;
 	Relation	relation;
@@ -65,13 +70,19 @@ find_inheritance_children(Oid parentrelId, LOCKMODE lockmode)
 	int			maxoids,
 				numoids,
 				i;
+	bool		did_skip = false;
 
 	/*
 	 * Can skip the scan if pg_class shows the relation has never had a
 	 * subclass.
 	 */
 	if (!has_subclass(parentrelId))
+	{
+		if (skipped != NULL)
+			*skipped = did_skip;
+
 		return NIL;
+	}
 
 	/*
 	 * Scan pg_inherits and build a working array of subclass OIDs.
@@ -124,7 +135,13 @@ find_inheritance_children(Oid parentrelId, LOCKMODE lockmode)
 		if (lockmode != NoLock)
 		{
 			/* Get the lock to synchronize against concurrent drop */
-			LockRelationOid(inhrelid, lockmode);
+			if (!skip_locked)
+				LockRelationOid(inhrelid, lockmode);
+			else if (!ConditionalLockRelationOid(inhrelid, lockmode))
+			{
+				did_skip = true;
+				continue;
+			}
 
 			/*
 			 * Now that we have the lock, double-check to see if the relation
@@ -145,6 +162,9 @@ find_inheritance_children(Oid parentrelId, LOCKMODE lockmode)
 
 	pfree(oidarr);
 
+	if (skipped != NULL)
+		*skipped = did_skip;
+
 	return list;
 }
 
@@ -199,7 +219,7 @@ find_all_inheritors(Oid parentrelId, LOCKMODE lockmode, List **numparents)
 		ListCell   *lc;
 
 		/* Get the direct children of this rel */
-		currentchildren = find_inheritance_children(currentrel, lockmode);
+		currentchildren = find_inheritance_children(currentrel, lockmode, false, NULL);
 
 		/*
 		 * Add to the queue only those children not already seen. This avoids
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 71278b38cf..ef4d4a8c9d 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -119,7 +119,7 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid)
 	List	   *children;
 	ListCell   *lc;
 
-	children = find_inheritance_children(reloid, NoLock);
+	children = find_inheritance_children(reloid, NoLock, false, NULL);
 
 	foreach(lc, children)
 	{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f9e83c2456..7520de1b56 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2824,7 +2824,7 @@ renameatt_internal(Oid myrelid,
 		 * expected_parents will only be 0 if we are not already recursing.
 		 */
 		if (expected_parents == 0 &&
-			find_inheritance_children(myrelid, NoLock) != NIL)
+			find_inheritance_children(myrelid, NoLock, false, NULL) != NIL)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("inherited column \"%s\" must be renamed in child tables too",
@@ -3023,7 +3023,7 @@ rename_constraint_internal(Oid myrelid,
 		else
 		{
 			if (expected_parents == 0 &&
-				find_inheritance_children(myrelid, NoLock) != NIL)
+				find_inheritance_children(myrelid, NoLock, false, NULL) != NIL)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("inherited constraint \"%s\" must be renamed in child tables too",
@@ -5478,7 +5478,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	 */
 	if (colDef->identity &&
 		recurse &&
-		find_inheritance_children(myrelid, NoLock) != NIL)
+		find_inheritance_children(myrelid, NoLock, false, NULL) != NIL)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("cannot recursively add identity column to table that has child tables")));
@@ -5720,7 +5720,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	 * routines, we have to do this one level of recursion at a time; we can't
 	 * use find_all_inheritors to do it in one pass.
 	 */
-	children = find_inheritance_children(RelationGetRelid(rel), lockmode);
+	children = find_inheritance_children(RelationGetRelid(rel), lockmode, false, NULL);
 
 	/*
 	 * If we are told not to recurse, there had better not be any child
@@ -6823,7 +6823,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	 * routines, we have to do this one level of recursion at a time; we can't
 	 * use find_all_inheritors to do it in one pass.
 	 */
-	children = find_inheritance_children(RelationGetRelid(rel), lockmode);
+	children = find_inheritance_children(RelationGetRelid(rel), lockmode, false, NULL);
 
 	if (children)
 	{
@@ -7275,7 +7275,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	 * routines, we have to do this one level of recursion at a time; we can't
 	 * use find_all_inheritors to do it in one pass.
 	 */
-	children = find_inheritance_children(RelationGetRelid(rel), lockmode);
+	children = find_inheritance_children(RelationGetRelid(rel), lockmode, false, NULL);
 
 	/*
 	 * Check if ONLY was specified with ALTER TABLE.  If so, allow the
@@ -8964,7 +8964,7 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 	 * use find_all_inheritors to do it in one pass.
 	 */
 	if (!is_no_inherit_constraint)
-		children = find_inheritance_children(RelationGetRelid(rel), lockmode);
+		children = find_inheritance_children(RelationGetRelid(rel), lockmode, false, NULL);
 	else
 		children = NIL;
 
@@ -9306,7 +9306,7 @@ ATPrepAlterColumnType(List **wqueue,
 		}
 	}
 	else if (!recursing &&
-			 find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
+			 find_inheritance_children(RelationGetRelid(rel), NoLock, false, NULL) != NIL)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("type of inherited column \"%s\" must be changed in child tables too",
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 2436692eb8..f2617cfb1d 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -1109,7 +1109,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 			ListCell   *l;
 			List	   *idxs = NIL;
 
-			idxs = find_inheritance_children(indexOid, ShareRowExclusiveLock);
+			idxs = find_inheritance_children(indexOid, ShareRowExclusiveLock, false, NULL);
 			foreach(l, idxs)
 				childTbls = lappend_oid(childTbls,
 										IndexGetRelation(lfirst_oid(l),
diff --git a/src/backend/utils/cache/partcache.c b/src/backend/utils/cache/partcache.c
index 115a9fe78f..c160e755e2 100644
--- a/src/backend/utils/cache/partcache.c
+++ b/src/backend/utils/cache/partcache.c
@@ -285,7 +285,7 @@ RelationBuildPartitionDesc(Relation rel)
 	PartitionRangeBound **rbounds = NULL;
 
 	/* Get partition oids from pg_inherits */
-	inhoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+	inhoids = find_inheritance_children(RelationGetRelid(rel), NoLock, false, NULL);
 
 	/* Collect bound spec nodes in a list */
 	i = 0;
diff --git a/src/include/catalog/pg_inherits.h b/src/include/catalog/pg_inherits.h
index 2a98e02c6a..a89b3f44a5 100644
--- a/src/include/catalog/pg_inherits.h
+++ b/src/include/catalog/pg_inherits.h
@@ -44,7 +44,8 @@ CATALOG(pg_inherits,2611,InheritsRelationId) BKI_WITHOUT_OIDS
 typedef FormData_pg_inherits *Form_pg_inherits;
 
 
-extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode);
+extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode,
+						  bool skip_locked, bool *skipped);
 extern List *find_all_inheritors(Oid parentrelId, LOCKMODE lockmode,
 					List **parents);
 extern bool has_subclass(Oid relationId);
-- 
2.16.2

v9-0001-Create-a-helper-function-for-determining-the-log-.patchapplication/octet-stream; name=v9-0001-Create-a-helper-function-for-determining-the-log-.patchDownload
From d9ea645ec9eb948e37fe60596872a6715f720126 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Wed, 5 Sep 2018 14:42:22 +0000
Subject: [PATCH v9 1/7] Create a helper function for determining the log level
 for skipped relations.

---
 src/backend/commands/analyze.c | 45 +++++++++++------------------------
 src/backend/commands/vacuum.c  | 53 ++++++++++++++++++++++++------------------
 src/include/commands/vacuum.h  |  1 +
 3 files changed, 46 insertions(+), 53 deletions(-)

diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index edbdce81f2..b6b78527f4 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -159,38 +159,21 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 */
 	if (!onerel)
 	{
-		/*
-		 * If the RangeVar is not defined, we do not have enough information
-		 * to provide a meaningful log statement.  Chances are that
-		 * analyze_rel's caller has intentionally not provided this
-		 * information so that this logging is skipped, anyway.
-		 */
-		if (relation == NULL)
-			return;
-
-		/*
-		 * Determine the log level.  For autovacuum logs, we emit a LOG if
-		 * log_autovacuum_min_duration is not disabled.  For manual ANALYZE,
-		 * we emit a WARNING to match the log statements in the permissions
-		 * checks.
-		 */
-		if (!IsAutoVacuumWorkerProcess())
-			elevel = WARNING;
-		else if (params->log_min_duration >= 0)
-			elevel = LOG;
-		else
-			return;
+		elevel = get_elevel_for_skipped_relation(relation, params);
 
-		if (!rel_lock)
-			ereport(elevel,
-					(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-					 errmsg("skipping analyze of \"%s\" --- lock not available",
-							relation->relname)));
-		else
-			ereport(elevel,
-					(errcode(ERRCODE_UNDEFINED_TABLE),
-					 errmsg("skipping analyze of \"%s\" --- relation no longer exists",
-							relation->relname)));
+		if (elevel != 0)
+		{
+			if (!rel_lock)
+				ereport(elevel,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("skipping analyze of \"%s\" --- lock not available",
+								relation->relname)));
+			else
+				ereport(elevel,
+						(errcode(ERRCODE_UNDEFINED_TABLE),
+						 errmsg("skipping analyze of \"%s\" --- relation no longer exists",
+								relation->relname)));
+		}
 
 		return;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index f166509734..fc1081663c 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1480,28 +1480,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 */
 	if (!onerel)
 	{
-		int			elevel = 0;
-
-		/*
-		 * Determine the log level.
-		 *
-		 * If the RangeVar is not defined, we do not have enough information
-		 * to provide a meaningful log statement.  Chances are that
-		 * vacuum_rel's caller has intentionally not provided this information
-		 * so that this logging is skipped, anyway.
-		 *
-		 * Otherwise, for autovacuum logs, we emit a LOG if
-		 * log_autovacuum_min_duration is not disabled.  For manual VACUUM, we
-		 * emit a WARNING to match the log statements in the permission
-		 * checks.
-		 */
-		if (relation != NULL)
-		{
-			if (!IsAutoVacuumWorkerProcess())
-				elevel = WARNING;
-			else if (params->log_min_duration >= 0)
-				elevel = LOG;
-		}
+		int			elevel = get_elevel_for_skipped_relation(relation, params);
 
 		if (elevel != 0)
 		{
@@ -1779,3 +1758,33 @@ vacuum_delay_point(void)
 		CHECK_FOR_INTERRUPTS();
 	}
 }
+
+/*
+ * get_elevel_for_skipped_relation
+ *
+ * This is a helper function for determining the appropriate logging level for
+ * messages related to SKIP_LOCKED or concurrently dropped relations.
+ *
+ * If the RangeVar is not defined, we do not have enough information to provide
+ * a meaningful log statement, and 0 is returned.  Chances are that the caller
+ * has intentionally not provided this information so that the logging is
+ * skipped anyway.
+ *
+ * Otherwise, for autovacuum logs, we emit a LOG if log_autovacuum_min_duration
+ * is not disabled.  For manual commands, we emit a WARNING to match the log
+ * statements in the permission checks for VACUUM and ANALYZE.
+ */
+int
+get_elevel_for_skipped_relation(RangeVar *relation, VacuumParams *params)
+{
+	Assert(params != NULL);
+
+	if (relation == NULL)
+		return 0;
+	else if (!IsAutoVacuumWorkerProcess())
+		return WARNING;
+	else if (params->log_min_duration >= 0)
+		return LOG;
+	else
+		return 0;
+}
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 5af96fdc8a..3cf7559728 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -188,6 +188,7 @@ extern void vac_update_datfrozenxid(void);
 extern void vacuum_delay_point(void);
 extern bool vacuum_is_relation_owner(Oid relid, Form_pg_class reltuple,
 						 int options);
+extern int	get_elevel_for_skipped_relation(RangeVar *relation, VacuumParams *params);
 
 /* in commands/vacuumlazy.c */
 extern void lazy_vacuum_rel(Relation onerel, int options,
-- 
2.16.2

#17Michael Paquier
michael@paquier.xyz
In reply to: Bossart, Nathan (#16)
1 attachment(s)
Re: Add SKIP LOCKED to VACUUM and ANALYZE

On Wed, Sep 05, 2018 at 03:24:21PM +0000, Bossart, Nathan wrote:

And here it is. Here is a summary of the notable changes:

1) Patches v8-0003 and v8-0008 have been discarded. These patches
added SKIP_LOCKED behavior when opening a relation's indexes.
Instead, I've documented that VACUUM and ANALYZE may still block
on indexes in v9-0007.
2) Patches v8-0004 and v8-0005 have been discarded, as they have
already been committed.
3) Patch v8-0011 has been discarded. As previously noted, VACUUM
(SKIP_LOCKED, FULL) is already handled in vacuum_rel(), so no
changed are required to cluster_rel(). However, we will need
something similar to v8-0011 if we ever add SKIP_LOCKED to
CLUSTER.
4) The option has been renamed to SKIP_LOCKED (with the underscore)
for consistency with the DISABLE_PAGE_SKIPPING option.
5) In the documentation, I've listed the caveats for SKIP_LOCKED and
partitioned tables. I tried to make all the new documentation as
concise as possible.

Thanks for the new patches. So I have begun looking at the full set,
beginning by 0001 which introduces a new common routine to get the
elevel associated to a skipped relation. I have been chewing on this
one, and I think that the patch could do more in terms of refactoring by
introducing one single routine able to open a relation which is going to
be vacuumed or analyzed. This removes quite a lot of duplication
between analyze_rel() and vacuum_rel() when it comes to using
try_relation_open(). The result is attached, and that makes the code
closer to what the recently-added vacuum_is_relation_owner does.
Nathan, what do you think?

Please note that I am on the edge of discarding the stuff in
find_all_inheritors and such, as well as not worrying about the case of
analyze which could block for some index columns, but I have not spent
much time studying this part yet. Still the potential issues around
complicating this code, particularly when things come to having a
partial analyze or vacuum done rather scares me.
--
Michael

Attachments:

vacuum-open-refactor.patchtext/x-diff; charset=us-asciiDownload
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index d11b559b20..da757f0974 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -120,7 +120,6 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	int			elevel;
 	AcquireSampleRowsFunc acquirefunc = NULL;
 	BlockNumber relpages = 0;
-	bool		rel_lock = true;
 
 	/* Select logging level */
 	if (options & VACOPT_VERBOSE)
@@ -142,58 +141,16 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 * concurrent VACUUM, which doesn't matter much at the moment but might
 	 * matter if we ever try to accumulate stats on dead tuples.) If the rel
 	 * has been dropped since we last saw it, we don't need to process it.
+	 *
+	 * Make sure to generate only logs for ANALYZE in this case.
 	 */
-	if (!(options & VACOPT_SKIP_LOCKED))
-		onerel = try_relation_open(relid, ShareUpdateExclusiveLock);
-	else if (ConditionalLockRelationOid(relid, ShareUpdateExclusiveLock))
-		onerel = try_relation_open(relid, NoLock);
-	else
-	{
-		onerel = NULL;
-		rel_lock = false;
-	}
+	onerel = vacuum_open_relation(relid, relation, params,
+								  options & ~(VACOPT_VACUUM),
+								  ShareUpdateExclusiveLock);
 
-	/*
-	 * If we failed to open or lock the relation, emit a log message before
-	 * exiting.
-	 */
+	/* leave if relation could not be opened or locked */
 	if (!onerel)
-	{
-		/*
-		 * If the RangeVar is not defined, we do not have enough information
-		 * to provide a meaningful log statement.  Chances are that
-		 * analyze_rel's caller has intentionally not provided this
-		 * information so that this logging is skipped, anyway.
-		 */
-		if (relation == NULL)
-			return;
-
-		/*
-		 * Determine the log level.  For autovacuum logs, we emit a LOG if
-		 * log_autovacuum_min_duration is not disabled.  For manual ANALYZE,
-		 * we emit a WARNING to match the log statements in the permissions
-		 * checks.
-		 */
-		if (!IsAutoVacuumWorkerProcess())
-			elevel = WARNING;
-		else if (params->log_min_duration >= 0)
-			elevel = LOG;
-		else
-			return;
-
-		if (!rel_lock)
-			ereport(elevel,
-					(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-					 errmsg("skipping analyze of \"%s\" --- lock not available",
-							relation->relname)));
-		else
-			ereport(elevel,
-					(errcode(ERRCODE_UNDEFINED_TABLE),
-					 errmsg("skipping analyze of \"%s\" --- relation no longer exists",
-							relation->relname)));
-
 		return;
-	}
 
 	/*
 	 * Check if relation needs to be skipped based on ownership.  This check
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index f166509734..4e3823b0f0 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -482,6 +482,112 @@ vacuum_is_relation_owner(Oid relid, Form_pg_class reltuple, int options)
 }
 
 
+/*
+ * vacuum_open_relation
+ *
+ * This routine is used for attempting to open and lock a relation which
+ * is going to be vacuumed or analyzed.  If the relation cannot be opened
+ * or locked, a log is emitted if possible.
+ */
+Relation
+vacuum_open_relation(Oid relid, RangeVar *relation, VacuumParams *params,
+					 int options, LOCKMODE lmode)
+{
+	Relation	onerel;
+	bool		rel_lock = true;
+	int			elevel;
+
+	Assert(params != NULL);
+	Assert((options & (VACOPT_VACUUM | VACOPT_ANALYZE)) != 0);
+
+	/*
+	 * Open the relation and get the appropriate lock on it.
+	 *
+	 * There's a race condition here: the relation may have gone away since
+	 * the last time we saw it.  If so, we don't need to vacuum or analyze it.
+	 *
+	 * If we've been asked not to wait for the relation lock, acquire it first
+	 * in non-blocking mode, before calling try_relation_open().
+	 */
+	if (!(options & VACOPT_SKIP_LOCKED))
+		onerel = try_relation_open(relid, lmode);
+	else if (ConditionalLockRelationOid(relid, lmode))
+		onerel = try_relation_open(relid, NoLock);
+	else
+	{
+		onerel = NULL;
+		rel_lock = false;
+	}
+
+	/* if relation is opened, leave */
+	if (onerel)
+		return onerel;
+
+	/*
+	 * Relation could not be opened, hence generate if possible a log
+	 * informing on the situation.
+	 *
+	 * If the RangeVar is not defined, we do not have enough information to
+	 * provide a meaningful log statement.  Chances are that the caller has
+	 * intentionally not provided this information so that this logging is
+	 * skipped, anyway.
+	 */
+	if (relation == NULL)
+		return NULL;
+
+	/*
+	 * Determine the log level.
+	 *
+	 * For autovacuum logs, we emit a LOG if log_autovacuum_min_duration is
+	 * not disabled.  For manual VACUUM or ANALYZE, we emit a WARNING to match
+	 * the log statements in the permission checks.
+	 */
+	if (!IsAutoVacuumWorkerProcess())
+		elevel = WARNING;
+	else if (params->log_min_duration >= 0)
+		elevel = LOG;
+	else
+		return NULL;
+
+	if ((options & VACOPT_VACUUM) != 0)
+	{
+		if (!rel_lock)
+			ereport(elevel,
+					(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+					 errmsg("skipping vacuum of \"%s\" --- lock not available",
+							relation->relname)));
+		else
+			ereport(elevel,
+					(errcode(ERRCODE_UNDEFINED_TABLE),
+					 errmsg("skipping vacuum of \"%s\" --- relation no longer exists",
+							relation->relname)));
+
+		/*
+		 * For VACUUM ANALYZE, both logs could show up, but just generate
+		 * information for VACUUM as that would be the first one to be
+		 * processed.
+		 */
+		return NULL;
+	}
+
+	if ((options & VACOPT_ANALYZE) != 0)
+	{
+		if (!rel_lock)
+			ereport(elevel,
+					(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+					 errmsg("skipping analyze of \"%s\" --- lock not available",
+							relation->relname)));
+		else
+			ereport(elevel,
+					(errcode(ERRCODE_UNDEFINED_TABLE),
+					 errmsg("skipping analyze of \"%s\" --- relation no longer exists",
+							relation->relname)));
+	}
+
+	return NULL;
+}
+
+
 /*
  * Given a VacuumRelation, fill in the table OID if it wasn't specified,
  * and optionally add VacuumRelations for partitions of the table.
@@ -1400,7 +1506,6 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
-	bool		rel_lock = true;
 
 	Assert(params != NULL);
 
@@ -1455,68 +1560,12 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 */
 	lmode = (options & VACOPT_FULL) ? AccessExclusiveLock : ShareUpdateExclusiveLock;
 
-	/*
-	 * Open the relation and get the appropriate lock on it.
-	 *
-	 * There's a race condition here: the rel may have gone away since the
-	 * last time we saw it.  If so, we don't need to vacuum it.
-	 *
-	 * If we've been asked not to wait for the relation lock, acquire it first
-	 * in non-blocking mode, before calling try_relation_open().
-	 */
-	if (!(options & VACOPT_SKIP_LOCKED))
-		onerel = try_relation_open(relid, lmode);
-	else if (ConditionalLockRelationOid(relid, lmode))
-		onerel = try_relation_open(relid, NoLock);
-	else
-	{
-		onerel = NULL;
-		rel_lock = false;
-	}
+	/* open the relation and get the appropriate lock on it */
+	onerel = vacuum_open_relation(relid, relation, params, options, lmode);
 
-	/*
-	 * If we failed to open or lock the relation, emit a log message before
-	 * exiting.
-	 */
+	/* leave if relation could not be opened or locked */
 	if (!onerel)
 	{
-		int			elevel = 0;
-
-		/*
-		 * Determine the log level.
-		 *
-		 * If the RangeVar is not defined, we do not have enough information
-		 * to provide a meaningful log statement.  Chances are that
-		 * vacuum_rel's caller has intentionally not provided this information
-		 * so that this logging is skipped, anyway.
-		 *
-		 * Otherwise, for autovacuum logs, we emit a LOG if
-		 * log_autovacuum_min_duration is not disabled.  For manual VACUUM, we
-		 * emit a WARNING to match the log statements in the permission
-		 * checks.
-		 */
-		if (relation != NULL)
-		{
-			if (!IsAutoVacuumWorkerProcess())
-				elevel = WARNING;
-			else if (params->log_min_duration >= 0)
-				elevel = LOG;
-		}
-
-		if (elevel != 0)
-		{
-			if (!rel_lock)
-				ereport(elevel,
-						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-						 errmsg("skipping vacuum of \"%s\" --- lock not available",
-								relation->relname)));
-			else
-				ereport(elevel,
-						(errcode(ERRCODE_UNDEFINED_TABLE),
-						 errmsg("skipping vacuum of \"%s\" --- relation no longer exists",
-								relation->relname)));
-		}
-
 		PopActiveSnapshot();
 		CommitTransactionCommand();
 		return false;
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 5af96fdc8a..2f4303e40d 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -188,6 +188,8 @@ extern void vac_update_datfrozenxid(void);
 extern void vacuum_delay_point(void);
 extern bool vacuum_is_relation_owner(Oid relid, Form_pg_class reltuple,
 						 int options);
+extern Relation vacuum_open_relation(Oid relid, RangeVar *relation,
+					 VacuumParams *params, int options, LOCKMODE lmode);
 
 /* in commands/vacuumlazy.c */
 extern void lazy_vacuum_rel(Relation onerel, int options,
#18Bossart, Nathan
bossartn@amazon.com
In reply to: Michael Paquier (#17)
Re: Add SKIP LOCKED to VACUUM and ANALYZE

On 9/27/18, 2:52 AM, "Michael Paquier" <michael@paquier.xyz> wrote:

Thanks for the new patches. So I have begun looking at the full set,
beginning by 0001 which introduces a new common routine to get the
elevel associated to a skipped relation. I have been chewing on this
one, and I think that the patch could do more in terms of refactoring by
introducing one single routine able to open a relation which is going to
be vacuumed or analyzed. This removes quite a lot of duplication
between analyze_rel() and vacuum_rel() when it comes to using
try_relation_open(). The result is attached, and that makes the code
closer to what the recently-added vacuum_is_relation_owner does.
Nathan, what do you think?

Good idea. This patch looks good to me.

Please note that I am on the edge of discarding the stuff in
find_all_inheritors and such, as well as not worrying about the case of
analyze which could block for some index columns, but I have not spent
much time studying this part yet. Still the potential issues around
complicating this code, particularly when things come to having a
partial analyze or vacuum done rather scares me.

Without the find_all_inheritors() stuff, I think we would just need to
modify the ANALYZE documentation patch to say something like

Specifies that ANALYZE should not wait for any conflicting locks
to be released: if a relation cannot be locked immediately without
waiting, the relation is skipped. Note that even with this
option, ANALYZE can still block when opening the relation's
indexes or when acquiring sample rows to prepare the statistics.
Also, while ANALYZE ordinarily processes all leaf partitions of
partitioned tables, this option will cause ANALYZE to skip all
leaf partitions if there is a conflicting lock on the partitioned
table.

Nathan

#19Michael Paquier
michael@paquier.xyz
In reply to: Bossart, Nathan (#18)
Re: Add SKIP LOCKED to VACUUM and ANALYZE

On Mon, Oct 01, 2018 at 03:37:01PM +0000, Bossart, Nathan wrote:

Good idea. This patch looks good to me.

Thanks, I have pushed this one now.

Without the find_all_inheritors() stuff, I think we would just need to
modify the ANALYZE documentation patch to say something like

Specifies that ANALYZE should not wait for any conflicting locks
to be released: if a relation cannot be locked immediately without
waiting, the relation is skipped. Note that even with this
option, ANALYZE can still block when opening the relation's
indexes or when acquiring sample rows to prepare the statistics.
Also, while ANALYZE ordinarily processes all leaf partitions of
partitioned tables, this option will cause ANALYZE to skip all
leaf partitions if there is a conflicting lock on the partitioned
table.

Yes, I think that we could live with something like that. I could give
this whole thing a shot, but this will have to wait until the commit
fest is closed as there are still many patches to classify. If you can
send a patch that's of course always helpful..
--
Michael

#20Bossart, Nathan
bossartn@amazon.com
In reply to: Michael Paquier (#19)
1 attachment(s)
Re: Add SKIP LOCKED to VACUUM and ANALYZE

On 10/1/18, 7:07 PM, "Michael Paquier" <michael@paquier.xyz> wrote:

On Mon, Oct 01, 2018 at 03:37:01PM +0000, Bossart, Nathan wrote:

Without the find_all_inheritors() stuff, I think we would just need to
modify the ANALYZE documentation patch to say something like

Specifies that ANALYZE should not wait for any conflicting locks
to be released: if a relation cannot be locked immediately without
waiting, the relation is skipped. Note that even with this
option, ANALYZE can still block when opening the relation's
indexes or when acquiring sample rows to prepare the statistics.
Also, while ANALYZE ordinarily processes all leaf partitions of
partitioned tables, this option will cause ANALYZE to skip all
leaf partitions if there is a conflicting lock on the partitioned
table.

Yes, I think that we could live with something like that. I could give
this whole thing a shot, but this will have to wait until the commit
fest is closed as there are still many patches to classify. If you can
send a patch that's of course always helpful..

Sure, here it is.

Nathan

Attachments:

vacuum-skip-locked-v10.patchapplication/octet-stream; name=vacuum-skip-locked-v10.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 10b3a9a673..e6e8772a91 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -27,6 +27,7 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
 <phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
 
     VERBOSE
+    SKIP_LOCKED
 
 <phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
 
@@ -76,6 +77,23 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>SKIP_LOCKED</literal></term>
+    <listitem>
+     <para>
+      Specifies that <command>ANALYZE</command> should not wait for any
+      conflicting locks to be released: if a relation cannot be locked
+      immediately without waiting, the relation is skipped.  Note that even with
+      this option, <command>ANALYZE</command> can still block when opening the
+      relation's indexes or when acquiring sample rows to prepare the
+      statistics.  Also, while <command>ANALYZE</command> ordinarily processes
+      all leaf partitions of specified partitioned tables, this option will
+      cause <command>ANALYZE</command> to skip all leaf partitions if there is a
+      conflicting lock on the partitioned table.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">table_name</replaceable></term>
     <listitem>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index b760e8ede1..85987916f5 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -31,6 +31,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
     VERBOSE
     ANALYZE
     DISABLE_PAGE_SKIPPING
+    SKIP_LOCKED
 
 <phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
 
@@ -160,6 +161,22 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>SKIP_LOCKED</literal></term>
+    <listitem>
+     <para>
+      Specifies that <command>VACUUM</command> should not wait for any
+      conflicting locks to be released: if a relation cannot be locked
+      immediately without waiting, the relation is skipped.  Note that even with
+      this option, <command>VACUUM</command> can still block when opening the
+      relation’s indexes.  Also, while <command>VACUUM</command> ordinarily
+      processes all leaf partitions of specified partitioned tables, this option
+      will cause <command>VACUUM</command> to skip all leaf partitions if there
+      is a conflicting lock on the partitioned table.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">table_name</replaceable></term>
     <listitem>
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 4e3823b0f0..6245d59d51 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -634,7 +634,29 @@ expand_vacuum_rel(VacuumRelation *vrel, int options)
 		 * below, as well as find_all_inheritors's expectation that the caller
 		 * holds some lock on the starting relation.
 		 */
-		relid = RangeVarGetRelid(vrel->relation, AccessShareLock, false);
+		relid = RangeVarGetRelidExtended(vrel->relation,
+										 AccessShareLock,
+										 (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0,
+										 NULL, NULL);
+
+		/*
+		 * If the lock is unavailable, emit the same log statement that
+		 * vacuum_rel() and analyze_rel() would.
+		 */
+		if (!OidIsValid(relid))
+		{
+			if (options & VACOPT_VACUUM)
+				ereport(WARNING,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("skipping vacuum of \"%s\" --- lock not available",
+								vrel->relation->relname)));
+			else
+				ereport(WARNING,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("skipping analyze of \"%s\" --- lock not available",
+								vrel->relation->relname)));
+			return vacrels;
+		}
 
 		/*
 		 * To check whether the relation is a partitioned table and its
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ce60e99cff..6d23bfb0b3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10538,6 +10538,8 @@ vacuum_option_elem:
 				{
 					if (strcmp($1, "disable_page_skipping") == 0)
 						$$ = VACOPT_DISABLE_PAGE_SKIPPING;
+					else if (strcmp($1, "skip_locked") == 0)
+						$$ = VACOPT_SKIP_LOCKED;
 					else
 						ereport(ERROR,
 								(errcode(ERRCODE_SYNTAX_ERROR),
@@ -10571,6 +10573,16 @@ analyze_option_list:
 
 analyze_option_elem:
 			VERBOSE				{ $$ = VACOPT_VERBOSE; }
+			| IDENT
+				{
+					if (strcmp($1, "skip_locked") == 0)
+						$$ = VACOPT_SKIP_LOCKED;
+					else
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("unrecognized ANALYZE option \"%s\"", $1),
+									 parser_errposition(@1)));
+				}
 		;
 
 analyze_keyword:
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 200df8e659..aa4a0dba2a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3154,8 +3154,7 @@ typedef enum VacuumOption
 	VACOPT_VERBOSE = 1 << 2,	/* print progress info */
 	VACOPT_FREEZE = 1 << 3,		/* FREEZE option */
 	VACOPT_FULL = 1 << 4,		/* FULL (non-concurrent) vacuum */
-	VACOPT_SKIP_LOCKED = 1 << 5,	/* skip if cannot get lock (autovacuum
-									 * only) */
+	VACOPT_SKIP_LOCKED = 1 << 5,	/* skip if cannot get lock */
 	VACOPT_SKIPTOAST = 1 << 6,	/* don't process the TOAST table, if any */
 	VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7	/* don't skip any pages */
 } VacuumOption;
diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out
new file mode 100644
index 0000000000..95ca4569cc
--- /dev/null
+++ b/src/test/isolation/expected/vacuum-skip-locked.out
@@ -0,0 +1,171 @@
+Parsed test spec with 2 sessions
+
+starting permutation: lock_share vac_specified commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_specified: VACUUM (SKIP_LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share vac_all_parts commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+step vac_all_parts: VACUUM (SKIP_LOCKED) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share analyze_specified commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+WARNING:  skipping analyze of "part1" --- lock not available
+step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share analyze_all_parts commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share vac_analyze_specified commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share vac_analyze_all_parts commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share vac_full_specified commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share vac_full_all_parts commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_specified commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_specified: VACUUM (SKIP_LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_all_parts commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+step vac_all_parts: VACUUM (SKIP_LOCKED) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive analyze_specified commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+WARNING:  skipping analyze of "part1" --- lock not available
+step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive analyze_all_parts commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; <waiting ...>
+step commit: 
+	COMMIT;
+
+step analyze_all_parts: <... completed>
+
+starting permutation: lock_access_exclusive vac_analyze_specified commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_analyze_all_parts commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; <waiting ...>
+step commit: 
+	COMMIT;
+
+step vac_analyze_all_parts: <... completed>
+
+starting permutation: lock_access_exclusive vac_full_specified commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_full_all_parts commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted;
+step commit: 
+	COMMIT;
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index c23b401225..dd57a96e78 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -67,6 +67,7 @@ test: vacuum-reltuples
 test: timeouts
 test: vacuum-concurrent-drop
 test: vacuum-conflict
+test: vacuum-skip-locked
 test: predicate-hash
 test: predicate-gist
 test: predicate-gin
diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec
new file mode 100644
index 0000000000..3f05141388
--- /dev/null
+++ b/src/test/isolation/specs/vacuum-skip-locked.spec
@@ -0,0 +1,59 @@
+# Test for the SKIP_LOCKED option for VACUUM and ANALYZE commands.
+#
+# This also verifies that log messages are not emitted for skipped relations
+# that were not specified in the VACUUM or ANALYZE command.
+
+setup
+{
+	CREATE TABLE parted (a INT) PARTITION BY LIST (a);
+	CREATE TABLE part1 PARTITION OF parted FOR VALUES IN (1);
+	CREATE TABLE part2 PARTITION OF parted FOR VALUES IN (2);
+}
+
+teardown
+{
+	DROP TABLE IF EXISTS parted;
+}
+
+session "s1"
+step "lock_share"
+{
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+}
+step "lock_access_exclusive"
+{
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+}
+step "commit"
+{
+	COMMIT;
+}
+
+session "s2"
+step "vac_specified"		{ VACUUM (SKIP_LOCKED) part1, part2; }
+step "vac_all_parts"		{ VACUUM (SKIP_LOCKED) parted; }
+step "analyze_specified"	{ ANALYZE (SKIP_LOCKED) part1, part2; }
+step "analyze_all_parts"	{ ANALYZE (SKIP_LOCKED) parted; }
+step "vac_analyze_specified"	{ VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; }
+step "vac_analyze_all_parts"	{ VACUUM (ANALYZE, SKIP_LOCKED) parted; }
+step "vac_full_specified"	{ VACUUM (SKIP_LOCKED, FULL) part1, part2; }
+step "vac_full_all_parts"	{ VACUUM (SKIP_LOCKED, FULL) parted; }
+
+permutation "lock_share" "vac_specified" "commit"
+permutation "lock_share" "vac_all_parts" "commit"
+permutation "lock_share" "analyze_specified" "commit"
+permutation "lock_share" "analyze_all_parts" "commit"
+permutation "lock_share" "vac_analyze_specified" "commit"
+permutation "lock_share" "vac_analyze_all_parts" "commit"
+permutation "lock_share" "vac_full_specified" "commit"
+permutation "lock_share" "vac_full_all_parts" "commit"
+permutation "lock_access_exclusive" "vac_specified" "commit"
+permutation "lock_access_exclusive" "vac_all_parts" "commit"
+permutation "lock_access_exclusive" "analyze_specified" "commit"
+permutation "lock_access_exclusive" "analyze_all_parts" "commit"
+permutation "lock_access_exclusive" "vac_analyze_specified" "commit"
+permutation "lock_access_exclusive" "vac_analyze_all_parts" "commit"
+permutation "lock_access_exclusive" "vac_full_specified" "commit"
+permutation "lock_access_exclusive" "vac_full_all_parts" "commit"
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 5993a90247..fa9d663abd 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -115,10 +115,20 @@ ERROR:  column "does_not_exist" of relation "vacparted" does not exist
 -- parenthesized syntax for ANALYZE
 ANALYZE (VERBOSE) does_not_exist;
 ERROR:  relation "does_not_exist" does not exist
-ANALYZE (nonexistant-arg) does_not_exist;
-ERROR:  syntax error at or near "nonexistant"
-LINE 1: ANALYZE (nonexistant-arg) does_not_exist;
+ANALYZE (nonexistent-arg) does_not_exist;
+ERROR:  unrecognized ANALYZE option "nonexistent"
+LINE 1: ANALYZE (nonexistent-arg) does_not_exist;
                  ^
+-- ensure argument order independence, and that SKIP_LOCKED on non-existing
+-- relation still errors out.
+ANALYZE (SKIP_LOCKED, VERBOSE) does_not_exist;
+ERROR:  relation "does_not_exist" does not exist
+ANALYZE (VERBOSE, SKIP_LOCKED) does_not_exist;
+ERROR:  relation "does_not_exist" does not exist
+-- SKIP_LOCKED option
+VACUUM (SKIP_LOCKED) vactst;
+VACUUM (SKIP_LOCKED, FULL) vactst;
+ANALYZE (SKIP_LOCKED) vactst;
 DROP TABLE vaccluster;
 DROP TABLE vactst;
 DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7f74da3cbd..9defa0d8b2 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -91,7 +91,17 @@ ANALYZE vactst (i), vacparted (does_not_exist);
 
 -- parenthesized syntax for ANALYZE
 ANALYZE (VERBOSE) does_not_exist;
-ANALYZE (nonexistant-arg) does_not_exist;
+ANALYZE (nonexistent-arg) does_not_exist;
+
+-- ensure argument order independence, and that SKIP_LOCKED on non-existing
+-- relation still errors out.
+ANALYZE (SKIP_LOCKED, VERBOSE) does_not_exist;
+ANALYZE (VERBOSE, SKIP_LOCKED) does_not_exist;
+
+-- SKIP_LOCKED option
+VACUUM (SKIP_LOCKED) vactst;
+VACUUM (SKIP_LOCKED, FULL) vactst;
+ANALYZE (SKIP_LOCKED) vactst;
 
 DROP TABLE vaccluster;
 DROP TABLE vactst;
#21Michael Paquier
michael@paquier.xyz
In reply to: Bossart, Nathan (#20)
Re: Add SKIP LOCKED to VACUUM and ANALYZE

On Tue, Oct 02, 2018 at 02:22:42AM +0000, Bossart, Nathan wrote:

On 10/1/18, 7:07 PM, "Michael Paquier" <michael@paquier.xyz> wrote:

On Mon, Oct 01, 2018 at 03:37:01PM +0000, Bossart, Nathan wrote:

Without the find_all_inheritors() stuff, I think we would just need to
modify the ANALYZE documentation patch to say something like

Specifies that ANALYZE should not wait for any conflicting locks
to be released: if a relation cannot be locked immediately without
waiting, the relation is skipped. Note that even with this
option, ANALYZE can still block when opening the relation's
indexes or when acquiring sample rows to prepare the statistics.
Also, while ANALYZE ordinarily processes all leaf partitions of
partitioned tables, this option will cause ANALYZE to skip all
leaf partitions if there is a conflicting lock on the partitioned
table.

Yes, I think that we could live with something like that. I could give
this whole thing a shot, but this will have to wait until the commit
fest is closed as there are still many patches to classify. If you can
send a patch that's of course always helpful..

Sure, here it is.

I was just playing with autovacuum and some inheritance trees, and
actually locking a child would cause an autovacuum worker to block if it
is analyzing the parent when acquiring row samples even now. Anyway,
coming back to the patch...

My comments are mostly about the documentation bits added:
- Inheritance trees have the same problem, so it would be nice to
mention them as well.
- One workaround, which we could document (?), would be to list leaves
of a partition tree individually so as their locks are checked one by
one.

+      Specifies that <command>ANALYZE</command> should not wait for any
+      conflicting locks to be released: if a relation cannot be locked

Perhaps it would be more precise to tell when "beginning to work on a
relation" because the restrictions in row samplings?

The rest of the patch looks clean to me.
--
Michael

#22Bossart, Nathan
bossartn@amazon.com
In reply to: Michael Paquier (#21)
1 attachment(s)
Re: Add SKIP LOCKED to VACUUM and ANALYZE

On 10/3/18, 12:54 AM, "Michael Paquier" <michael@paquier.xyz> wrote:

- Inheritance trees have the same problem, so it would be nice to
mention them as well.

Done.

- One workaround, which we could document (?), would be to list leaves
of a partition tree individually so as their locks are checked one by
one.

Done.

+      Specifies that <command>ANALYZE</command> should not wait for any
+      conflicting locks to be released: if a relation cannot be locked

Perhaps it would be more precise to tell when "beginning to work on a
relation" because the restrictions in row samplings?

Done.

Here is what I have so far for the docs:

Specifies that ANALYZE should not wait for any conflicting
locks to be released before beginning work on a relation: if a
relation cannot be locked immediately without waiting, the
relation is skipped. Note that even with this option, ANALYZE
may still block when opening the relation's indexes or when
acquiring sample rows from leaf partitions, table inheritance
children, and some types of foreign tables. Also, while
ANALYZE ordinarily processes all leaf partitions of specified
partitioned tables, this option will cause ANALYZE to skip all
leaf partitions if there is a conflicting lock on the
partitioned table. Therefore, it is recommended to list each
leaf partition individually in the ANALYZE command when using
the SKIP_LOCKED option.

Specifies that VACUUM should not wait for any conflicting
locks to be released before beginning work on a relation: if a
relation cannot be locked immediately without waiting, the
relation is skipped. Note that even with this option, VACUUM
may still block when opening the relation's indexes. Also,
while VACUUM ordinarily processes all leaf partitions of
specified partitioned tables, this option will cause VACUUM to
skip all leaf partitions if there is a conflicting lock on the
partitioned table. Therefore, it is recommended to list each
leaf partition individually in the VACUUM command when using
the SKIP_LOCKED option.

Nathan

Attachments:

vacuum-skip-locked-v11.patchapplication/octet-stream; name=vacuum-skip-locked-v11.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 10b3a9a673..ccbaa96b42 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -27,6 +27,7 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
 <phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
 
     VERBOSE
+    SKIP_LOCKED
 
 <phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
 
@@ -76,6 +77,27 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>SKIP_LOCKED</literal></term>
+    <listitem>
+     <para>
+      Specifies that <command>ANALYZE</command> should not wait for any
+      conflicting locks to be released before beginning work on a relation: if a
+      relation cannot be locked immediately without waiting, the relation is
+      skipped.  Note that even with this option, <command>ANALYZE</command> may
+      still block when opening the relation's indexes or when acquiring sample
+      rows from leaf partitions, table inheritance children, and some types of
+      foreign tables.  Also, while <command>ANALYZE</command> ordinarily
+      processes all leaf partitions of specified partitioned tables, this option
+      will cause <command>ANALYZE</command> to skip all leaf partitions if there
+      is a conflicting lock on the partitioned table.  Therefore, it is
+      recommended to list each leaf partition individually in the
+      <command>ANALYZE</command> command when using the
+      <literal>SKIP_LOCKED</literal> option.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">table_name</replaceable></term>
     <listitem>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index b760e8ede1..4469f2c026 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -31,6 +31,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
     VERBOSE
     ANALYZE
     DISABLE_PAGE_SKIPPING
+    SKIP_LOCKED
 
 <phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
 
@@ -160,6 +161,25 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>SKIP_LOCKED</literal></term>
+    <listitem>
+     <para>
+      Specifies that <command>VACUUM</command> should not wait for any
+      conflicting locks to be released before beginning work on a relation: if a
+      relation cannot be locked immediately without waiting, the relation is
+      skipped.  Note that even with this option, <command>VACUUM</command> may
+      still block when opening the relation's indexes.  Also, while
+      <command>VACUUM</command> ordinarily processes all leaf partitions of
+      specified partitioned tables, this option will cause
+      <command>VACUUM</command> to skip all leaf partitions if there is a
+      conflicting lock on the partitioned table.  Therefore, it is recommended
+      to list each leaf partition individually in the <command>VACUUM</command>
+      command when using the <literal>SKIP_LOCKED</literal> option.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">table_name</replaceable></term>
     <listitem>
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 4e3823b0f0..6245d59d51 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -634,7 +634,29 @@ expand_vacuum_rel(VacuumRelation *vrel, int options)
 		 * below, as well as find_all_inheritors's expectation that the caller
 		 * holds some lock on the starting relation.
 		 */
-		relid = RangeVarGetRelid(vrel->relation, AccessShareLock, false);
+		relid = RangeVarGetRelidExtended(vrel->relation,
+										 AccessShareLock,
+										 (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0,
+										 NULL, NULL);
+
+		/*
+		 * If the lock is unavailable, emit the same log statement that
+		 * vacuum_rel() and analyze_rel() would.
+		 */
+		if (!OidIsValid(relid))
+		{
+			if (options & VACOPT_VACUUM)
+				ereport(WARNING,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("skipping vacuum of \"%s\" --- lock not available",
+								vrel->relation->relname)));
+			else
+				ereport(WARNING,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("skipping analyze of \"%s\" --- lock not available",
+								vrel->relation->relname)));
+			return vacrels;
+		}
 
 		/*
 		 * To check whether the relation is a partitioned table and its
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ce60e99cff..6d23bfb0b3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10538,6 +10538,8 @@ vacuum_option_elem:
 				{
 					if (strcmp($1, "disable_page_skipping") == 0)
 						$$ = VACOPT_DISABLE_PAGE_SKIPPING;
+					else if (strcmp($1, "skip_locked") == 0)
+						$$ = VACOPT_SKIP_LOCKED;
 					else
 						ereport(ERROR,
 								(errcode(ERRCODE_SYNTAX_ERROR),
@@ -10571,6 +10573,16 @@ analyze_option_list:
 
 analyze_option_elem:
 			VERBOSE				{ $$ = VACOPT_VERBOSE; }
+			| IDENT
+				{
+					if (strcmp($1, "skip_locked") == 0)
+						$$ = VACOPT_SKIP_LOCKED;
+					else
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("unrecognized ANALYZE option \"%s\"", $1),
+									 parser_errposition(@1)));
+				}
 		;
 
 analyze_keyword:
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 200df8e659..aa4a0dba2a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3154,8 +3154,7 @@ typedef enum VacuumOption
 	VACOPT_VERBOSE = 1 << 2,	/* print progress info */
 	VACOPT_FREEZE = 1 << 3,		/* FREEZE option */
 	VACOPT_FULL = 1 << 4,		/* FULL (non-concurrent) vacuum */
-	VACOPT_SKIP_LOCKED = 1 << 5,	/* skip if cannot get lock (autovacuum
-									 * only) */
+	VACOPT_SKIP_LOCKED = 1 << 5,	/* skip if cannot get lock */
 	VACOPT_SKIPTOAST = 1 << 6,	/* don't process the TOAST table, if any */
 	VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7	/* don't skip any pages */
 } VacuumOption;
diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out
new file mode 100644
index 0000000000..95ca4569cc
--- /dev/null
+++ b/src/test/isolation/expected/vacuum-skip-locked.out
@@ -0,0 +1,171 @@
+Parsed test spec with 2 sessions
+
+starting permutation: lock_share vac_specified commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_specified: VACUUM (SKIP_LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share vac_all_parts commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+step vac_all_parts: VACUUM (SKIP_LOCKED) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share analyze_specified commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+WARNING:  skipping analyze of "part1" --- lock not available
+step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share analyze_all_parts commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share vac_analyze_specified commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share vac_analyze_all_parts commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share vac_full_specified commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_share vac_full_all_parts commit
+step lock_share: 
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+
+step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_specified commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_specified: VACUUM (SKIP_LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_all_parts commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+step vac_all_parts: VACUUM (SKIP_LOCKED) parted;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive analyze_specified commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+WARNING:  skipping analyze of "part1" --- lock not available
+step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive analyze_all_parts commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; <waiting ...>
+step commit: 
+	COMMIT;
+
+step analyze_all_parts: <... completed>
+
+starting permutation: lock_access_exclusive vac_analyze_specified commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_analyze_all_parts commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; <waiting ...>
+step commit: 
+	COMMIT;
+
+step vac_analyze_all_parts: <... completed>
+
+starting permutation: lock_access_exclusive vac_full_specified commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+WARNING:  skipping vacuum of "part1" --- lock not available
+step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2;
+step commit: 
+	COMMIT;
+
+
+starting permutation: lock_access_exclusive vac_full_all_parts commit
+step lock_access_exclusive: 
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+
+step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted;
+step commit: 
+	COMMIT;
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index c23b401225..dd57a96e78 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -67,6 +67,7 @@ test: vacuum-reltuples
 test: timeouts
 test: vacuum-concurrent-drop
 test: vacuum-conflict
+test: vacuum-skip-locked
 test: predicate-hash
 test: predicate-gist
 test: predicate-gin
diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec
new file mode 100644
index 0000000000..3f05141388
--- /dev/null
+++ b/src/test/isolation/specs/vacuum-skip-locked.spec
@@ -0,0 +1,59 @@
+# Test for the SKIP_LOCKED option for VACUUM and ANALYZE commands.
+#
+# This also verifies that log messages are not emitted for skipped relations
+# that were not specified in the VACUUM or ANALYZE command.
+
+setup
+{
+	CREATE TABLE parted (a INT) PARTITION BY LIST (a);
+	CREATE TABLE part1 PARTITION OF parted FOR VALUES IN (1);
+	CREATE TABLE part2 PARTITION OF parted FOR VALUES IN (2);
+}
+
+teardown
+{
+	DROP TABLE IF EXISTS parted;
+}
+
+session "s1"
+step "lock_share"
+{
+	BEGIN;
+	LOCK part1 IN SHARE MODE;
+}
+step "lock_access_exclusive"
+{
+	BEGIN;
+	LOCK part1 IN ACCESS EXCLUSIVE MODE;
+}
+step "commit"
+{
+	COMMIT;
+}
+
+session "s2"
+step "vac_specified"		{ VACUUM (SKIP_LOCKED) part1, part2; }
+step "vac_all_parts"		{ VACUUM (SKIP_LOCKED) parted; }
+step "analyze_specified"	{ ANALYZE (SKIP_LOCKED) part1, part2; }
+step "analyze_all_parts"	{ ANALYZE (SKIP_LOCKED) parted; }
+step "vac_analyze_specified"	{ VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; }
+step "vac_analyze_all_parts"	{ VACUUM (ANALYZE, SKIP_LOCKED) parted; }
+step "vac_full_specified"	{ VACUUM (SKIP_LOCKED, FULL) part1, part2; }
+step "vac_full_all_parts"	{ VACUUM (SKIP_LOCKED, FULL) parted; }
+
+permutation "lock_share" "vac_specified" "commit"
+permutation "lock_share" "vac_all_parts" "commit"
+permutation "lock_share" "analyze_specified" "commit"
+permutation "lock_share" "analyze_all_parts" "commit"
+permutation "lock_share" "vac_analyze_specified" "commit"
+permutation "lock_share" "vac_analyze_all_parts" "commit"
+permutation "lock_share" "vac_full_specified" "commit"
+permutation "lock_share" "vac_full_all_parts" "commit"
+permutation "lock_access_exclusive" "vac_specified" "commit"
+permutation "lock_access_exclusive" "vac_all_parts" "commit"
+permutation "lock_access_exclusive" "analyze_specified" "commit"
+permutation "lock_access_exclusive" "analyze_all_parts" "commit"
+permutation "lock_access_exclusive" "vac_analyze_specified" "commit"
+permutation "lock_access_exclusive" "vac_analyze_all_parts" "commit"
+permutation "lock_access_exclusive" "vac_full_specified" "commit"
+permutation "lock_access_exclusive" "vac_full_all_parts" "commit"
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 5993a90247..fa9d663abd 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -115,10 +115,20 @@ ERROR:  column "does_not_exist" of relation "vacparted" does not exist
 -- parenthesized syntax for ANALYZE
 ANALYZE (VERBOSE) does_not_exist;
 ERROR:  relation "does_not_exist" does not exist
-ANALYZE (nonexistant-arg) does_not_exist;
-ERROR:  syntax error at or near "nonexistant"
-LINE 1: ANALYZE (nonexistant-arg) does_not_exist;
+ANALYZE (nonexistent-arg) does_not_exist;
+ERROR:  unrecognized ANALYZE option "nonexistent"
+LINE 1: ANALYZE (nonexistent-arg) does_not_exist;
                  ^
+-- ensure argument order independence, and that SKIP_LOCKED on non-existing
+-- relation still errors out.
+ANALYZE (SKIP_LOCKED, VERBOSE) does_not_exist;
+ERROR:  relation "does_not_exist" does not exist
+ANALYZE (VERBOSE, SKIP_LOCKED) does_not_exist;
+ERROR:  relation "does_not_exist" does not exist
+-- SKIP_LOCKED option
+VACUUM (SKIP_LOCKED) vactst;
+VACUUM (SKIP_LOCKED, FULL) vactst;
+ANALYZE (SKIP_LOCKED) vactst;
 DROP TABLE vaccluster;
 DROP TABLE vactst;
 DROP TABLE vacparted;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 7f74da3cbd..9defa0d8b2 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -91,7 +91,17 @@ ANALYZE vactst (i), vacparted (does_not_exist);
 
 -- parenthesized syntax for ANALYZE
 ANALYZE (VERBOSE) does_not_exist;
-ANALYZE (nonexistant-arg) does_not_exist;
+ANALYZE (nonexistent-arg) does_not_exist;
+
+-- ensure argument order independence, and that SKIP_LOCKED on non-existing
+-- relation still errors out.
+ANALYZE (SKIP_LOCKED, VERBOSE) does_not_exist;
+ANALYZE (VERBOSE, SKIP_LOCKED) does_not_exist;
+
+-- SKIP_LOCKED option
+VACUUM (SKIP_LOCKED) vactst;
+VACUUM (SKIP_LOCKED, FULL) vactst;
+ANALYZE (SKIP_LOCKED) vactst;
 
 DROP TABLE vaccluster;
 DROP TABLE vactst;
#23Michael Paquier
michael@paquier.xyz
In reply to: Bossart, Nathan (#22)
Re: Add SKIP LOCKED to VACUUM and ANALYZE

On Wed, Oct 03, 2018 at 04:20:42PM +0000, Bossart, Nathan wrote:

Here is what I have so far for the docs:

[snip]

Thanks, I have committed the patch, after minor tweaks to the docs.
Mainly, in the VACUUM page, I have added a mention that VACUUM ANALYZE
can block for row sampling. I have also removed for now the set of
recommendations the patch added, as we still gather statistics on the
partitioned tables from the row samples gathered from the partitions, so
recommending to list only the leaves is not completely correct either
all the time.

We could perhaps add something about such recommendations. A separate
section covering more general things may be interesting.
--
Michael

#24Bossart, Nathan
bossartn@amazon.com
In reply to: Michael Paquier (#23)
Re: Add SKIP LOCKED to VACUUM and ANALYZE

On 10/3/18, 7:10 PM, "Michael Paquier" <michael@paquier.xyz> wrote:

Thanks, I have committed the patch, after minor tweaks to the docs.

Thanks!

Mainly, in the VACUUM page, I have added a mention that VACUUM ANALYZE
can block for row sampling. I have also removed for now the set of
recommendations the patch added, as we still gather statistics on the
partitioned tables from the row samples gathered from the partitions, so
recommending to list only the leaves is not completely correct either
all the time.

Good catch. The committed version of the documentation looks good to
me.

We could perhaps add something about such recommendations. A separate
section covering more general things may be interesting.

Agreed, this could be a good addition to the "Notes" sections.

Nathan