Alter index rename concurrently to

Started by Andrey Klychkovover 7 years ago38 messages
#1Andrey Klychkov
aaklychkov@mail.ru
1 attachment(s)

Dear hackers!
I have an idea to facilitate work with index rebuilding.
Usually if we want to rebuild an index without table locks we should do the queries below:
1. create index concurrently... (with different name on the same columns)
2. drop index concurrently <old index>
3. alter index <new_index> rename to <like_old_index>
As you can see, only the last query in this simple algorithm locks table.
Commonly this steps are included to a more complex script and run by cron or manually.
When you have high load databases, maybe some problems appear:
1. it’s dangerous and unacceptable for production to wait unknown
number of minutes or even hours while a table is locked
2. we must write a complex script that sets statement timeout to rename and
implement several checks there to prevent stopping of production at nights
3. it’s uncomfortable to find indexes that couldn’t be renamed during script executing
Then we must execute renaming manually and interrupt it again and
again if it can’t execute in allowable time.

I made a patch to solve this issue (see the attachment).
It allows to avoid locks by a query like this:
“alter index <new_index> rename CONCURRENTLY to <old_index_name>”.
Briefly, I did it by using similar way in the index_create() and index_drop() functions
that set ShareUpdateExclusiveLock instead of AccessShareLock
when the structure DropStmt/CreateStmt has “true” in the stmt->concurrent field.
(In other words, when it "see" "concurretly" in query). 
In all other cases (alter table, sequence, etc) I initialized this field as “false” respectively.
I ran it when another transactions to the same table are started but not finished.
Also I used a script that made SELECT\DML queries in a loop to that test tables and
"ALTER INDEX ... RENAME CONCURRENTLY TO ..." works without any errors and
indexes were valid after renaming.
Maybe it’s interesting for someone.
I’ll be very thankful for any ideas!
--
Kind regards,
Andrew Klychkov

Attachments:

rename_concurrent.patchapplication/x-patch; name="=?UTF-8?B?cmVuYW1lX2NvbmN1cnJlbnQucGF0Y2g=?="Download
# Author: Andrew Klychkov <aaklyckov@mail.ru>
# 16-07-2018

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 482d463..47e20d5 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -1658,14 +1658,14 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 			snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u",
 					 OIDOldHeap);
 			RenameRelationInternal(newrel->rd_rel->reltoastrelid,
-								   NewToastName, true);
+								   NewToastName, true, false);
 
 			/* ... and its valid index too. */
 			snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u_index",
 					 OIDOldHeap);
 
 			RenameRelationInternal(toastidx,
-								   NewToastName, true);
+								   NewToastName, true, false);
 		}
 		relation_close(newrel, NoLock);
 	}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 22e81e7..ead4f26 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -3012,7 +3012,7 @@ rename_constraint_internal(Oid myrelid,
 			|| con->contype == CONSTRAINT_UNIQUE
 			|| con->contype == CONSTRAINT_EXCLUSION))
 		/* rename the index; this renames the constraint as well */
-		RenameRelationInternal(con->conindid, newconname, false);
+		RenameRelationInternal(con->conindid, newconname, false, false);
 	else
 		RenameConstraintById(constraintOid, newconname);
 
@@ -3082,7 +3082,9 @@ RenameRelation(RenameStmt *stmt)
 {
 	Oid			relid;
 	ObjectAddress address;
+    LOCKMODE    lockmode;
 
+    lockmode = stmt->concurrent ? ShareUpdateExclusiveLock : AccessExclusiveLock;
 	/*
 	 * Grab an exclusive lock on the target table, index, sequence, view,
 	 * materialized view, or foreign table, which we will NOT release until
@@ -3091,7 +3093,7 @@ RenameRelation(RenameStmt *stmt)
 	 * Lock level used here should match RenameRelationInternal, to avoid lock
 	 * escalation.
 	 */
-	relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
+	relid = RangeVarGetRelidExtended(stmt->relation, lockmode,
 									 stmt->missing_ok ? RVR_MISSING_OK : 0,
 									 RangeVarCallbackForAlterRelation,
 									 (void *) stmt);
@@ -3105,7 +3107,7 @@ RenameRelation(RenameStmt *stmt)
 	}
 
 	/* Do the work */
-	RenameRelationInternal(relid, stmt->newname, false);
+	RenameRelationInternal(relid, stmt->newname, false, stmt->concurrent);
 
 	ObjectAddressSet(address, RelationRelationId, relid);
 
@@ -3122,20 +3124,22 @@ RenameRelation(RenameStmt *stmt)
  *			  sequence, AFAIK there's no need for it to be there.
  */
 void
-RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal)
+RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bool concurrent)
 {
 	Relation	targetrelation;
 	Relation	relrelation;	/* for RELATION relation */
 	HeapTuple	reltup;
 	Form_pg_class relform;
 	Oid			namespaceId;
+    LOCKMODE    lockmode;
 
 	/*
 	 * Grab an exclusive lock on the target table, index, sequence, view,
 	 * materialized view, or foreign table, which we will NOT release until
 	 * end of transaction.
 	 */
-	targetrelation = relation_open(myrelid, AccessExclusiveLock);
+    lockmode = concurrent ? ShareUpdateExclusiveLock : AccessExclusiveLock;
+	targetrelation = relation_open(myrelid, lockmode);
 	namespaceId = RelationGetNamespace(targetrelation);
 
 	/*
@@ -7039,7 +7043,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
 		ereport(NOTICE,
 				(errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index \"%s\" to \"%s\"",
 						indexName, constraintName)));
-		RenameRelationInternal(index_oid, constraintName, false);
+		RenameRelationInternal(index_oid, constraintName, false, false);
 	}
 
 	/* Extra checks needed if making primary key */
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 175ecc8..f7f3ce2 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3280,7 +3280,7 @@ RenameType(RenameStmt *stmt)
 	 * RenameRelationInternal will call RenameTypeInternal automatically.
 	 */
 	if (typTup->typtype == TYPTYPE_COMPOSITE)
-		RenameRelationInternal(typTup->typrelid, newTypeName, false);
+		RenameRelationInternal(typTup->typrelid, newTypeName, false, false);
 	else
 		RenameTypeInternal(typeOid, newTypeName,
 						   typTup->typnamespace);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 90dfac2..ed6658b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -8457,6 +8457,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->object = (Node *) $3;
 					n->newname = $6;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER COLLATION any_name RENAME TO name
@@ -8466,6 +8467,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->object = (Node *) $3;
 					n->newname = $6;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER CONVERSION_P any_name RENAME TO name
@@ -8475,6 +8477,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->object = (Node *) $3;
 					n->newname = $6;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER DATABASE database_name RENAME TO database_name
@@ -8484,6 +8487,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = $3;
 					n->newname = $6;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER DOMAIN_P any_name RENAME TO name
@@ -8493,6 +8497,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->object = (Node *) $3;
 					n->newname = $6;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER DOMAIN_P any_name RENAME CONSTRAINT name TO name
@@ -8502,6 +8507,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->object = (Node *) $3;
 					n->subname = $6;
 					n->newname = $8;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER FOREIGN DATA_P WRAPPER name RENAME TO name
@@ -8511,6 +8517,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->object = (Node *) makeString($5);
 					n->newname = $8;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER FUNCTION function_with_argtypes RENAME TO name
@@ -8520,6 +8527,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->object = (Node *) $3;
 					n->newname = $6;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER GROUP_P RoleId RENAME TO RoleId
@@ -8529,6 +8537,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = $3;
 					n->newname = $6;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER opt_procedural LANGUAGE name RENAME TO name
@@ -8538,6 +8547,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->object = (Node *) makeString($4);
 					n->newname = $7;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER OPERATOR CLASS any_name USING access_method RENAME TO name
@@ -8547,6 +8557,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->object = (Node *) lcons(makeString($6), $4);
 					n->newname = $9;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER OPERATOR FAMILY any_name USING access_method RENAME TO name
@@ -8556,6 +8567,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->object = (Node *) lcons(makeString($6), $4);
 					n->newname = $9;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER POLICY name ON qualified_name RENAME TO name
@@ -8566,6 +8578,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = $3;
 					n->newname = $8;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER POLICY IF_P EXISTS name ON qualified_name RENAME TO name
@@ -8576,6 +8589,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = $5;
 					n->newname = $10;
 					n->missing_ok = true;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER PROCEDURE function_with_argtypes RENAME TO name
@@ -8585,6 +8599,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->object = (Node *) $3;
 					n->newname = $6;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER PUBLICATION name RENAME TO name
@@ -8594,6 +8609,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->object = (Node *) makeString($3);
 					n->newname = $6;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER ROUTINE function_with_argtypes RENAME TO name
@@ -8603,6 +8619,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->object = (Node *) $3;
 					n->newname = $6;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER SCHEMA name RENAME TO name
@@ -8612,6 +8629,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = $3;
 					n->newname = $6;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER SERVER name RENAME TO name
@@ -8621,6 +8639,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->object = (Node *) makeString($3);
 					n->newname = $6;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER SUBSCRIPTION name RENAME TO name
@@ -8630,6 +8649,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->object = (Node *) makeString($3);
 					n->newname = $6;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER TABLE relation_expr RENAME TO name
@@ -8640,6 +8660,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = NULL;
 					n->newname = $6;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER TABLE IF_P EXISTS relation_expr RENAME TO name
@@ -8650,6 +8671,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = NULL;
 					n->newname = $8;
 					n->missing_ok = true;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER SEQUENCE qualified_name RENAME TO name
@@ -8660,6 +8682,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = NULL;
 					n->newname = $6;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER SEQUENCE IF_P EXISTS qualified_name RENAME TO name
@@ -8670,6 +8693,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = NULL;
 					n->newname = $8;
 					n->missing_ok = true;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER VIEW qualified_name RENAME TO name
@@ -8680,6 +8704,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = NULL;
 					n->newname = $6;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER VIEW IF_P EXISTS qualified_name RENAME TO name
@@ -8690,6 +8715,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = NULL;
 					n->newname = $8;
 					n->missing_ok = true;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER MATERIALIZED VIEW qualified_name RENAME TO name
@@ -8700,6 +8726,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = NULL;
 					n->newname = $7;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER MATERIALIZED VIEW IF_P EXISTS qualified_name RENAME TO name
@@ -8710,6 +8737,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = NULL;
 					n->newname = $9;
 					n->missing_ok = true;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER INDEX qualified_name RENAME TO name
@@ -8720,6 +8748,18 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = NULL;
 					n->newname = $6;
 					n->missing_ok = false;
+                    n->concurrent = false;
+					$$ = (Node *)n;
+				}
+			| ALTER INDEX qualified_name RENAME CONCURRENTLY TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+					n->renameType = OBJECT_INDEX;
+					n->relation = $3;
+					n->subname = NULL;
+					n->newname = $7;
+					n->missing_ok = false;
+                    n->concurrent = true;
 					$$ = (Node *)n;
 				}
 			| ALTER INDEX IF_P EXISTS qualified_name RENAME TO name
@@ -8730,6 +8770,18 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = NULL;
 					n->newname = $8;
 					n->missing_ok = true;
+                    n->concurrent = false;
+					$$ = (Node *)n;
+				}
+			| ALTER INDEX IF_P EXISTS qualified_name RENAME CONCURRENTLY TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+					n->renameType = OBJECT_INDEX;
+					n->relation = $5;
+					n->subname = NULL;
+					n->newname = $9;
+					n->missing_ok = true;
+                    n->concurrent = true;
 					$$ = (Node *)n;
 				}
 			| ALTER FOREIGN TABLE relation_expr RENAME TO name
@@ -8740,6 +8792,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = NULL;
 					n->newname = $7;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER FOREIGN TABLE IF_P EXISTS relation_expr RENAME TO name
@@ -8750,6 +8803,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = NULL;
 					n->newname = $9;
 					n->missing_ok = true;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER TABLE relation_expr RENAME opt_column name TO name
@@ -8761,6 +8815,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = $6;
 					n->newname = $8;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER TABLE IF_P EXISTS relation_expr RENAME opt_column name TO name
@@ -8772,6 +8827,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = $8;
 					n->newname = $10;
 					n->missing_ok = true;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER MATERIALIZED VIEW qualified_name RENAME opt_column name TO name
@@ -8783,6 +8839,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = $7;
 					n->newname = $9;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER MATERIALIZED VIEW IF_P EXISTS qualified_name RENAME opt_column name TO name
@@ -8794,6 +8851,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = $9;
 					n->newname = $11;
 					n->missing_ok = true;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER TABLE relation_expr RENAME CONSTRAINT name TO name
@@ -8804,6 +8862,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = $6;
 					n->newname = $8;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER TABLE IF_P EXISTS relation_expr RENAME CONSTRAINT name TO name
@@ -8814,6 +8873,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = $8;
 					n->newname = $10;
 					n->missing_ok = true;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER FOREIGN TABLE relation_expr RENAME opt_column name TO name
@@ -8825,6 +8885,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = $7;
 					n->newname = $9;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER FOREIGN TABLE IF_P EXISTS relation_expr RENAME opt_column name TO name
@@ -8836,6 +8897,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = $9;
 					n->newname = $11;
 					n->missing_ok = true;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER RULE name ON qualified_name RENAME TO name
@@ -8846,6 +8908,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = $3;
 					n->newname = $8;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER TRIGGER name ON qualified_name RENAME TO name
@@ -8856,6 +8919,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = $3;
 					n->newname = $8;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER EVENT TRIGGER name RENAME TO name
@@ -8864,6 +8928,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->renameType = OBJECT_EVENT_TRIGGER;
 					n->object = (Node *) makeString($4);
 					n->newname = $7;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER ROLE RoleId RENAME TO RoleId
@@ -8873,6 +8938,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = $3;
 					n->newname = $6;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER USER RoleId RENAME TO RoleId
@@ -8882,6 +8948,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = $3;
 					n->newname = $6;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER TABLESPACE name RENAME TO name
@@ -8891,6 +8958,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->subname = $3;
 					n->newname = $6;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER STATISTICS any_name RENAME TO name
@@ -8900,6 +8968,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->object = (Node *) $3;
 					n->newname = $6;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER TEXT_P SEARCH PARSER any_name RENAME TO name
@@ -8909,6 +8978,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->object = (Node *) $5;
 					n->newname = $8;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER TEXT_P SEARCH DICTIONARY any_name RENAME TO name
@@ -8918,6 +8988,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->object = (Node *) $5;
 					n->newname = $8;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER TEXT_P SEARCH TEMPLATE any_name RENAME TO name
@@ -8927,6 +8998,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->object = (Node *) $5;
 					n->newname = $8;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER TEXT_P SEARCH CONFIGURATION any_name RENAME TO name
@@ -8936,6 +9008,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->object = (Node *) $5;
 					n->newname = $8;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER TYPE_P any_name RENAME TO name
@@ -8945,6 +9018,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->object = (Node *) $3;
 					n->newname = $6;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 			| ALTER TYPE_P any_name RENAME ATTRIBUTE name TO name opt_drop_behavior
@@ -8957,6 +9031,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->newname = $8;
 					n->behavior = $9;
 					n->missing_ok = false;
+                    n->concurrent = false;
 					$$ = (Node *)n;
 				}
 		;
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 138de84..0e44958 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -67,7 +67,8 @@ extern ObjectAddress RenameConstraint(RenameStmt *stmt);
 extern ObjectAddress RenameRelation(RenameStmt *stmt);
 
 extern void RenameRelationInternal(Oid myrelid,
-					   const char *newrelname, bool is_internal);
+					   const char *newrelname, bool is_internal,
+                       bool concurrent);
 
 extern void find_composite_type_dependencies(Oid typeOid,
 								 Relation origRelation,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 2a2b17d..7609633 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2849,6 +2849,7 @@ typedef struct RenameStmt
 	char	   *newname;		/* the new name */
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 	bool		missing_ok;		/* skip error if missing? */
+    bool        concurrent;     /* for "RENAME CONCURRENTLY TO" */
 } RenameStmt;
 
 /* ----------------------
#2Andrey Borodin
x4mmm@yandex-team.ru
In reply to: Andrey Klychkov (#1)
Re: Alter index rename concurrently to

Hi!

16 июля 2018 г., в 22:58, Andrey Klychkov <aaklychkov@mail.ru> написал(а):
Dear hackers!

I have an idea to facilitate work with index rebuilding.
....
"ALTER INDEX ... RENAME CONCURRENTLY TO ..."

The idea seems useful. I'm not an expert in CIC, but your post do not cover one very important topic. When we use CREATE INDEX CONCURRENTLY we pay for less intrusive lock by scanning data twice. What is the price of RENAME CONCURRENTLY? Should this mode be default?

Best regards, Andrey Borodin.

#3Victor Yegorov
vyegorov@gmail.com
In reply to: Andrey Klychkov (#1)
Re: Alter index rename concurrently to

пн, 16 июл. 2018 г. в 21:58, Andrey Klychkov <aaklychkov@mail.ru>:

I made a patch to solve this issue (see the attachment).
It allows to avoid locks by a query like this:
“alter index <new_index> rename CONCURRENTLY to <old_index_name>”.

Please, have a look at previous discussions on the subject:
- 2012
/messages/by-id/CAB7nPqTys6JUQDxUczbJb0BNW0kPrW8WdZuk11KaxQq6o98PJg@mail.gmail.com
- 2013
/messages/by-id/CAB7nPqSTFkuc7dZxCDX4HOTU63YXHRroNv2aoUzyD-Zz_8Z_Zg@mail.gmail.com
- https://commitfest.postgresql.org/16/1276/

--
Victor Yegorov

#4Andrey Klychkov
aaklychkov@mail.ru
In reply to: Victor Yegorov (#3)
Re[2]: Alter index rename concurrently to

Понедельник, 16 июля 2018, 22:40 +03:00 от Victor Yegorov <vyegorov@gmail.com>:

пн, 16 июл. 2018 г. в 21:58, Andrey Klychkov < aaklychkov@mail.ru >:

I made a patch to solve this issue (see the attachment).
It allows to avoid locks by a query like this:
“alter index <new_index> rename CONCURRENTLY to <old_index_name>”.

Please, have a look at previous discussions on the subject:
- 2012 /messages/by-id/CAB7nPqTys6JUQDxUczbJb0BNW0kPrW8WdZuk11KaxQq6o98PJg@mail.gmail.com
- 2013  /messages/by-id/CAB7nPqSTFkuc7dZxCDX4HOTU63YXHRroNv2aoUzyD-Zz_8Z_Zg@mail.gmail.com
https://commitfest.postgresql.org/16/1276/

Hello,
Thank you for this information!

In the first discussion the concurrent alter was mentioned.
In the next link and commitfest info I only saw "Reindex concurrently 2.0".
It sounds great!
If this component will be added to core it certainly facilitates index rebuilding.

What about "alter index ... rename to" in the concurrent mode?
Does "Reindex concurrently 2.0" add it?
From time to time we need just to rename some indexes.
Without concurrent mode this "alter index" makes queues.
It may be a problem on high load databases.

--
Kind regards,
Andrey Klychkov

#5Andrey Klychkov
aaklychkov@mail.ru
In reply to: Andrey Borodin (#2)
Re[2]: Alter index rename concurrently to

Понедельник, 16 июля 2018, 22:19 +03:00 от Andrey Borodin <x4mmm@yandex-team.ru>:

Hi!

16 июля 2018 г., в 22:58, Andrey Klychkov < aaklychkov@mail.ru > написал(а):
Dear hackers!

I have an idea to facilitate work with index rebuilding.
....
"ALTER INDEX ... RENAME CONCURRENTLY TO ..."

The idea seems useful. I'm not an expert in CIC, but your post do not cover one very important topic. When we use CREATE INDEX CONCURRENTLY we pay for less intrusive lock by scanning data twice. What is the price of RENAME CONCURRENTLY? Should this mode be default?

Hello and thank you for the answer!

I don't think "alter index ... rename concurrently to ..." has large overheads
cause it just locks table and copies a new name instead of an old name
to the field relform->relname of the FormData_pg_class struct.

./src/include/catalog/pg_class.h: typedef of FormData_pg_class
./src/backend/commands/tablecmds.c: RenameRelation() and RenameRelationInternal()

As I wrote before, in my patch I've added the opportunity to change a locking
AccessShareLock -> ShareUpdateExclusiveLock
by passing "concurrently" in "alter", it's similar to the way of index_drop()/index_create()
functions.

It changes only one name field and nothing more.
I believe it is safe.
Also I ran tests again: select/insert queries in loops in several sessions and changed
an index name concurrently in parallel many times.
After that, index was valid and its index_scan was increased.

However, I don't have much experience and maybe I am wrong.

--
Kind regards,
Andrey Klychkov

#6Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Andrey Klychkov (#4)
Re: Alter index rename concurrently to

On 17.07.18 13:48, Andrey Klychkov wrote:

Please, have a look at previous discussions on the subject:
- 2012
/messages/by-id/CAB7nPqTys6JUQDxUczbJb0BNW0kPrW8WdZuk11KaxQq6o98PJg@mail.gmail.com
-
2013 /messages/by-id/CAB7nPqSTFkuc7dZxCDX4HOTU63YXHRroNv2aoUzyD-Zz_8Z_Zg@mail.gmail.com
https://commitfest.postgresql.org/16/1276/

[the above are discussions on REINDEX CONCURRENTLY]

In the first discussion the concurrent alter was mentioned.
In the next link and commitfest info I only saw "Reindex concurrently 2.0".
It sounds great!
If this component will be added to core it certainly facilitates index
rebuilding.

What about "alter index ... rename to" in the concurrent mode?
Does "Reindex concurrently 2.0" add it?

The latest posted REINDEX CONCURRENTLY patch implements swapping index
dependencies and name with a ShareUpdateExclusiveLock, which is
effectively what your patch is doing also, for renaming only.

In the 2012 thread, it was mentioned several times that some thought
that renaming without an exclusive lock was unsafe, but without any
further reasons. I think that was before MVCC catalog snapshots were
implemented, so at that time, any catalog change without an exclusive
lock would have been risky. After that was changed, the issue hasn't
been mentioned again in the 2013 and beyond thread, and it seems that
everyone assumed that it was OK.

It we think that it is safe, then I think we should just lower the lock
level of RENAME by default.

In your patch, the effect of the CONCURRENTLY keyword is just to change
the lock level, without any further changes. That doesn't make much
sense. If we think the lower lock level is OK, then we should just use
it always.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#7Andrey Klychkov
aaklychkov@mail.ru
In reply to: Peter Eisentraut (#6)
Re[2]: Alter index rename concurrently to

Среда, 18 июля 2018, 12:21 +03:00 от Peter Eisentraut <peter.eisentraut@2ndquadrant.com>:

If we think the lower lock level is OK, then we should just use
it always.

Hi, I absolutely agree with you.
If lower locking is safe and possible to be used by default in renaming it will be great.
What stage is solving of this issue? Does anybody do it?

Thank you!

--
Kind regards,
Andrey Klychkov

#8Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Andrey Klychkov (#7)
Re: Alter index rename concurrently to

On 18.07.18 11:44, Andrey Klychkov wrote:

If lower locking is safe and possible to be used by default in renaming
it will be great.
What stage is solving of this issue? Does anybody do it?

We wait to see if anyone has any concerns about this change.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#9Andrey Klychkov
aaklychkov@mail.ru
In reply to: Peter Eisentraut (#6)
Re[2]: Alter index rename concurrently to

Среда, 18 июля 2018, 12:21 +03:00 от Peter Eisentraut <peter.eisentraut@2ndquadrant.com>:

In your patch, the effect of the CONCURRENTLY keyword is just to change
the lock level, without any further changes. That doesn't make much
sense. If we think the lower lock level is OK, then we should just use
it always.

I was thinking about it and I seem that AccessShareExclusiveLock by default is a better way than
the lower lock level because it corresponds to the principle of more strict implementation
of changing database schema (by default).
I think if some users need to rename a relation without query locking they should
say it definitely by using "concurrently" keyword like in the drop/create index cases.

Otherwise, to set the lower lock level we must also embody new keyword for "alter.. rename" to allow user
to set up stronger lock level for this action (not only indexes but tables and so on).

Moreover, if you rename Table without query locking, it may crushes your services that
do queries at the same time, therefore , this is unlikely that someone will be do it
with concurrent queries to renamed table, in other words, with running production.
So, I think it doesn't have real sense to use the lower lock for example for tables (at least by default).
However, renaming Indexes with the lower lock is safe for database consumers
because they don't know anything about them.

As I wrote early, in the patch I added the 'concurrent' field in the RenameStmt structure.
However, the "concurrently" keyword is realized there for index renaming only
because I only see need to rename indexes in concurrent mode.
It also may be implemented for tables, etc if it will be really needed for someone in the future.

--
Kind regards,
Andrey Klychkov

#10Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Andrey Klychkov (#9)
Re: Alter index rename concurrently to

On 23.07.18 15:14, Andrey Klychkov wrote:

Moreover, if you rename Table without query locking, it may crushes your
services that
do queries at the same time, therefore, this is unlikely that someone
will be do it
with concurrent queries to renamed table, in other words, with running
production.
So, I think it doesn't have real sense to use the lower lock for example
for tables (at least by default).
However, renaming Indexes with the lower lock is safe for database consumers
because they don't know anything about them.

You appear to be saying that you think that renaming an index
concurrently is not safe. In that case, this patch should be rejected.
However, I don't think it necessarily is unsafe. What we need is some
reasoning about the impact, not a bunch of different options that we
don't understand.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#11Andrey Klychkov
aaklychkov@mail.ru
In reply to: Andrey Klychkov (#9)
Fwd: Re[2]: Alter index rename concurrently to

Понедельник, 23 июля 2018, 18:06 +03:00 от Peter Eisentraut < peter.eisentraut@2ndquadrant.com >:

You appear to be saying that you think that renaming an index
concurrently is not safe

No, I didn't say it about renaming indexes.
I tried to say that it does not make sense exactly to rename a table (view, functions) concurrently
(and implement the lower lock level respectively) because probably no one will do this on production.
Because names of that objects are used in application's configuration.
I don't remember any cases I need to do this for tables but for indexes very often.
Renaming indexes is safe for client's applications because their names are not used in texts of SQL queries.
I think it's safe for relations too because, as we know, the database server works with indexes by using identifiers, not names.
Moreover, I tested it.

Ok, Peter, thanks for your answer!

--
Kind regards,
Andrey Klychkov

----------------------------------------------------------------------

--
Kind regards,
Andrey Klychkov

#12Corey Huinker
corey.huinker@gmail.com
In reply to: Peter Eisentraut (#10)
Re: Alter index rename concurrently to

You appear to be saying that you think that renaming an index
concurrently is not safe. In that case, this patch should be rejected.
However, I don't think it necessarily is unsafe. What we need is some
reasoning about the impact, not a bunch of different options that we
don't understand.

I've had this same need, and dreamed this same solution before. I also
thought about a syntax like ALTER INDEX foo RENAME TO
WHATEVER-IT-WOULD-HAVE-BEEN-NAMED-BY-DEFAULT to aid this situation.

But all of those needs fade if we have REINDEX CONCURRENTLY. I think that's
where we should focus our efforts.

A possible side effort into something like a VACUUM FULL CONCURRENTLY,
which would essentially do what pg_repack does, but keeping the same oid
and the stats that go with it, but even that's a nice-to-have add-on to
REINDEX CONCURRENTLY.

#13Robert Haas
robertmhaas@gmail.com
In reply to: Peter Eisentraut (#6)
Re: Alter index rename concurrently to

On Wed, Jul 18, 2018 at 5:20 AM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

In the 2012 thread, it was mentioned several times that some thought
that renaming without an exclusive lock was unsafe, but without any
further reasons. I think that was before MVCC catalog snapshots were
implemented, so at that time, any catalog change without an exclusive
lock would have been risky. After that was changed, the issue hasn't
been mentioned again in the 2013 and beyond thread, and it seems that
everyone assumed that it was OK.

It we think that it is safe, then I think we should just lower the lock
level of RENAME by default.

In your patch, the effect of the CONCURRENTLY keyword is just to change
the lock level, without any further changes. That doesn't make much
sense. If we think the lower lock level is OK, then we should just use
it always.

Right. I think that, in the world of MVCC catalog scans, there are
basically two main concerns around reducing the lock levels for DDL.
The first is the way that the shared invalidation queue works. If
people reading a certain piece of information take a lock X, and
people changing it take a lock Y which conflicts with X, then the
shared invalidation machinery guarantees that everyone always sees the
latest version. If not, some backends may continue to use the old
version for an unbounded period of time -- there doesn't seem to be a
guarantee that AcceptInvalidationMessages() even runs once per
command, if say the transaction already holds all the locks the query
needs. Moreover, there is no predictability to when the new
information may become visible -- it may happen just by chance that
the changes become visible almost at once. I think it would be
worthwhile for somebody to consider adjusting or maybe even
significantly rewriting the invalidation code to provide some
less-unpredictable behavior in this area. It would be much more
palatable to make non-critical changes under lesser lock levels if you
could describe in an understandable way when those changes would take
effect. If you could say something like "normally within a second" or
"no later than the start of the next query" it would be a lot better.

The second problem is the relation cache. Lots of things cache
pointers to the RelationData structure or its subsidiary structures,
and if any of that gets rebuild, the pointer addresses may change,
leaving those pointers pointing to garbage. This is handled in
different ways in different places. One approach is -- if we do a
rebuild of something, say the partition descriptor, and find that the
new one is identical to the old one, then free the new one and keep
the old one. This means that it's safe to rely on the pointer not
changing underneath you provided you hold a lock strong enough to
prevent any DDL that might change the partition descriptor. But note
that something like this is essential for any concurrent DDL to work
at all. Without this, concurrent DDL that changes some completely
unrelated property of the table (e.g. the reloptions) would cause a
relcache rebuild that would invalidate cached pointers to the
partition descriptor, leading to crashes.

With respect to this particular patch, I don't know whether there are
any hazards of the second type. What I'd check, if it were me, is
what structures in the index's relcache entry are going to get rebuilt
when the index name changes. If any of those look like things that
something that somebody could hold a pointer to during the course of
query execution, or more generally be relying on not to change during
the course of query execution, then you've got a problem. As to the
first category, I suspect it's possible to construct cases where the
fact that the index's name hasn't change fails to get noticed for an
alarmingly long period of time after the change has happened. I also
suspect that an appropriate fix might be to ensure that
AcceptInvalidationMessages() is run at least once at the beginning of
parse analysis. I don't think that will make this kind of thing
race-free, but if you can't demonstrate a case where the new name
fails to be noticed immediately without resorting to setting
breakpoints with a debugger, it's probably good enough. We might need
to worry about whether the extra call to AcceptInvalidationMessages()
costs enough to be noticeable, but I hope it doesn't.

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

#14Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Robert Haas (#13)
Re: Alter index rename concurrently to

On 27/07/2018 16:16, Robert Haas wrote:

With respect to this particular patch, I don't know whether there are
any hazards of the second type. What I'd check, if it were me, is
what structures in the index's relcache entry are going to get rebuilt
when the index name changes. If any of those look like things that
something that somebody could hold a pointer to during the course of
query execution, or more generally be relying on not to change during
the course of query execution, then you've got a problem.

I have investigated this, and I think it's safe. Relcache reloads for
open indexes are already handled specially in RelationReloadIndexInfo().
The only pointers that get replaced there are rd_amcache and
rd_options. I have checked where those are used, and it looks like they
are not used across possible relcache reloads. The code structure in
those two cases make this pretty unlikely even in the future. Also,
violations would probably be caught by CLOBBER_CACHE_ALWAYS.

As to the
first category, I suspect it's possible to construct cases where the
fact that the index's name hasn't change fails to get noticed for an
alarmingly long period of time after the change has happened. I also
suspect that an appropriate fix might be to ensure that
AcceptInvalidationMessages() is run at least once at the beginning of
parse analysis.

Why don't we just do that? I have run some performance tests and there
was no impact (when no invalidation events are generated). The code
path in the case where there are no events queued up looks pretty
harmless, certainly compared to all the other stuff that goes on per
command.

Then again, I don't know why you consider this such a big problem in the
first place. If a concurrent transaction doesn't get the new index name
until the next transaction, what's the problem?

Then again, I don't know why we don't just call
AcceptInvalidationMessages() before each command or at least before each
top-level command? That would make way too much sense.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#15Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#14)
Re: Alter index rename concurrently to

Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:

On 27/07/2018 16:16, Robert Haas wrote:

I also suspect that an appropriate fix might be to ensure that
AcceptInvalidationMessages() is run at least once at the beginning of
parse analysis.

Why don't we just do that?

Don't we do that already? Certainly it should get run in advance of
any relation name lookup. There is one at transaction start also,
if memory serves.

regards, tom lane

#16Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Tom Lane (#15)
Re: Alter index rename concurrently to

On 31/07/2018 23:25, Tom Lane wrote:

Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:

On 27/07/2018 16:16, Robert Haas wrote:

I also suspect that an appropriate fix might be to ensure that
AcceptInvalidationMessages() is run at least once at the beginning of
parse analysis.

Why don't we just do that?

Don't we do that already? Certainly it should get run in advance of
any relation name lookup. There is one at transaction start also,
if memory serves.

Right, we do it at transaction start and when opening a relation with a
lock that you don't already have. Which I suppose in practice is almost
equivalent to at least once per command, but you can construct cases
where subsequent commands in a transaction use the all same tables as
the previous commands, in which case they don't run AIM() again.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#17Robert Haas
robertmhaas@gmail.com
In reply to: Peter Eisentraut (#16)
Re: Alter index rename concurrently to

On Wed, Aug 1, 2018 at 3:04 AM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

On 31/07/2018 23:25, Tom Lane wrote:

Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:

On 27/07/2018 16:16, Robert Haas wrote:

I also suspect that an appropriate fix might be to ensure that
AcceptInvalidationMessages() is run at least once at the beginning of
parse analysis.

Why don't we just do that?

Don't we do that already? Certainly it should get run in advance of
any relation name lookup. There is one at transaction start also,
if memory serves.

Right, we do it at transaction start and when opening a relation with a
lock that you don't already have. Which I suppose in practice is almost
equivalent to at least once per command, but you can construct cases
where subsequent commands in a transaction use the all same tables as
the previous commands, in which case they don't run AIM() again.

Right. If nobody sees a reason not to change that, I think we should.
It would make the behavior more predictable with, I hope, no real
loss.

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

#18Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#17)
Re: Alter index rename concurrently to

On 2018-08-01 15:33:09 -0400, Robert Haas wrote:

On Wed, Aug 1, 2018 at 3:04 AM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

On 31/07/2018 23:25, Tom Lane wrote:

Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:

On 27/07/2018 16:16, Robert Haas wrote:

I also suspect that an appropriate fix might be to ensure that
AcceptInvalidationMessages() is run at least once at the beginning of
parse analysis.

Why don't we just do that?

Don't we do that already? Certainly it should get run in advance of
any relation name lookup. There is one at transaction start also,
if memory serves.

Right, we do it at transaction start and when opening a relation with a
lock that you don't already have. Which I suppose in practice is almost
equivalent to at least once per command, but you can construct cases
where subsequent commands in a transaction use the all same tables as
the previous commands, in which case they don't run AIM() again.

Right. If nobody sees a reason not to change that, I think we should.
It would make the behavior more predictable with, I hope, no real
loss.

What precisely are you proposing?

Greetings,

Andres Freund

#19Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#18)
Re: Alter index rename concurrently to

On Wed, Aug 1, 2018 at 3:36 PM, Andres Freund <andres@anarazel.de> wrote:

Right. If nobody sees a reason not to change that, I think we should.
It would make the behavior more predictable with, I hope, no real
loss.

What precisely are you proposing?

Inserting AcceptInvalidationMessages() in some location that
guarantees it will be executed at least once per SQL statement. I
tentatively propose the beginning of parse_analyze(), but I am open to
suggestions.

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

#20Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#19)
Re: Alter index rename concurrently to

Hi,

On 2018-08-02 15:57:13 -0400, Robert Haas wrote:

On Wed, Aug 1, 2018 at 3:36 PM, Andres Freund <andres@anarazel.de> wrote:

Right. If nobody sees a reason not to change that, I think we should.
It would make the behavior more predictable with, I hope, no real
loss.

What precisely are you proposing?

Inserting AcceptInvalidationMessages() in some location that
guarantees it will be executed at least once per SQL statement. I
tentatively propose the beginning of parse_analyze(), but I am open to
suggestions.

I'm inclined to think that that doesn't really actually solve anything,
but makes locking issues harder to find, because the window is smaller,
but decidedly non-zero. Can you describe why this'd make things more
"predictable" precisely?

Greetings,

Andres Freund

#21Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#19)
Re: Alter index rename concurrently to

Robert Haas <robertmhaas@gmail.com> writes:

On Wed, Aug 1, 2018 at 3:36 PM, Andres Freund <andres@anarazel.de> wrote:

What precisely are you proposing?

Inserting AcceptInvalidationMessages() in some location that
guarantees it will be executed at least once per SQL statement. I
tentatively propose the beginning of parse_analyze(), but I am open to
suggestions.

What will that accomplish that the existing call in transaction start
doesn't? It might make the window for concurrency issues a bit narrower,
but it certainly doesn't close it.

regards, tom lane

#22Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#20)
Re: Alter index rename concurrently to

On Thu, Aug 2, 2018 at 4:02 PM, Andres Freund <andres@anarazel.de> wrote:

Inserting AcceptInvalidationMessages() in some location that
guarantees it will be executed at least once per SQL statement. I
tentatively propose the beginning of parse_analyze(), but I am open to
suggestions.

I'm inclined to think that that doesn't really actually solve anything,
but makes locking issues harder to find, because the window is smaller,
but decidedly non-zero. Can you describe why this'd make things more
"predictable" precisely?

Sure. I'd like to be able to explain in the documentation in simple
words when a given DDL change is likely to take effect. For changes
made under AccessExclusiveLock, there's really nothing to say: the
change will definitely take effect immediately. If you're able to do
anything at all with the relevant table, you must have got some kind
of lock on it, and that means you must have done
AcceptInvalidationMessages(), and so you will definitely see the
change. With DDL changes made under less than AccessExclusiveLock,
the situation is more complicated. Right now, we can say that a new
transaction will definitely see the changes, because it will have to
acquire a lock on that relation which it doesn't already have and will
thus have to do AcceptInvalidationMessages(). A new statement within
the same transaction may see the changes, or it may not. If it
mentions any relations not previously mentioned or if it does
something like UPDATE a relation where we previously only did a
SELECT, thus triggering a new lock acquisition, it will see the
changes. If a catchup interrupt gets sent to it, it will see the
changes. Otherwise, it won't. It's even possible that we'll start to
see the changes in the middle of a statement, because of a sinval
reset or something. To summarize, at present, all we can really say
is that changes made by concurrent DDL which doesn't take AEL may or
may not affect already-running queries, may or may not affect new
queries, and will affect new transactions.

With this change, we'd be able to say that new statements will
definitely see the results of concurrent DDL. That's a clear, easy to
understand rule which I think users will like. It would be even
better if we could say something stronger, e.g. "Concurrent DDL will
affect new SQL statements, but not those already in progress." Or
"Concurrent DDL will affect new SQL statements, but SQL statements
that are already running may take up to 10 seconds to react to the
changes". Or whatever. I'm not sure there's really a way to get to a
really solid guarantee, but being able to tell users that, at a
minimum, the next SQL statement will notice that things have changed
would be good. Otherwise, we'll have conversations like this:

User: I have a usage pattern where I run a DDL command to rename an
object, and then in another session I tried to refer to it by the new
name, and it sometimes it works and sometimes it doesn't. Why does
that happen?

Postgres Expert: Well, there are several possible reasons. If you
already had a transaction in progress in the second window and it
already had a lock on the object strong enough for the operation you
attempted to perform and no sinval resets occurred and nothing else
triggered invalidation processing, then it would still know that
object under the old name. Otherwise it would know about it under the
new name.

User: Well, I did have a transaction open and it may have had some
kind of lock on that object already, but how do I know whether
invalidation processing happened?

Postgres Expert: There's really know way to know. If something else
on the system were doing a lot of DDL operations, then it might fill
up the invalidation queue enough to trigger catchup interrupts or
sinval resets, but if not, it could be deferred for an arbitrarily
long period of time.

User: So you're saying that if I have two PostgreSQL sessions, and I
execute the same commands in those sessions in the same order, just
like the isolationtester does, I can get different answers depending
on whether some third session creates a lot of unrelated temporary
tables in a different database while that's happening?

Postgres Expert: Yes.

I leave it to your imagination to fill in what this imaginary user
will say next, but I bet it will be snarky.

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

#23Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#22)
Re: Alter index rename concurrently to

Hi,

On 2018-08-02 16:30:42 -0400, Robert Haas wrote:

With this change, we'd be able to say that new statements will
definitely see the results of concurrent DDL.

I don't think it really gets you that far. Because you're suggesting to
do it at the parse stage, you suddenly have issues with prepared
statements. Some client libraries, and a lot more frameworks,
automatically use prepared statements when they see the same query text
multiple times. And this'll not improve anything on that.

ISTM, if you want to increase consistency in this area, you've to go
further. E.g. processing invalidations in StartTransactionCommand() in
all states, which'd give you a lot more consistency.

Greetings,

Andres Freund

#24Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#22)
Re: Alter index rename concurrently to

Robert Haas <robertmhaas@gmail.com> writes:

[ reasons why DDL under less than AEL sucks ]

Unfortunately, none of these problems are made to go away with an
AcceptInvalidationMessages at statement start. That just makes the
window smaller. But DDL effects could still be seen - or not -
partway through a statement, with just as much ensuing hilarity
as in your example. Maybe more.

The real issue here, and the reason why I'm very unhappy with the mad rush
in certain quarters to try to reduce locking levels for DDL, is exactly
that it generally results in uncertainty about when the effects will be
seen. I do not think your proposal does much more than put a fig leaf
over that problem.

Barring somebody having a great idea about resolving that, I think we
just need to be very clear that any DDL done with less than AEL has
exactly this issue. In the case at hand, couldn't we just document
that "the effects of RENAME CONCURRENTLY may not be seen in other
sessions right away; at worst, not till they begin a new transaction"?
If you don't like that, don't use CONCURRENTLY.

regards, tom lane

#25Andres Freund
andres@anarazel.de
In reply to: Tom Lane (#24)
Re: Alter index rename concurrently to

On 2018-08-02 16:51:10 -0400, Tom Lane wrote:

Robert Haas <robertmhaas@gmail.com> writes:

[ reasons why DDL under less than AEL sucks ]

Unfortunately, none of these problems are made to go away with an
AcceptInvalidationMessages at statement start. That just makes the
window smaller. But DDL effects could still be seen - or not -
partway through a statement, with just as much ensuing hilarity
as in your example. Maybe more.

I think it's a lot easier to explain that every newly issued statement
sees the effect of DDL, and already running statements may see it, than
something else. I don't agree that parse analysis is a good hook to
force that, as I've written.

The real issue here, and the reason why I'm very unhappy with the mad rush
in certain quarters to try to reduce locking levels for DDL, is exactly
that it generally results in uncertainty about when the effects will be
seen. I do not think your proposal does much more than put a fig leaf
over that problem.

I think it's a significant issue operationally, which is why that
pressure exists. I don't know what makes it a "mad rush", given these
discussions have been going on for *years*?

Greetings,

Andres Freund

#26Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#24)
Re: Alter index rename concurrently to

On Thu, Aug 2, 2018 at 4:51 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

[ reasons why DDL under less than AEL sucks ]

Unfortunately, none of these problems are made to go away with an
AcceptInvalidationMessages at statement start. That just makes the
window smaller. But DDL effects could still be seen - or not -
partway through a statement, with just as much ensuing hilarity
as in your example. Maybe more.

Making the window a lot smaller -- so that it's a statement rather
than a transaction -- has a lot of value, I think. What's
particularly obnoxious about the transaction thing is that in many
cases the next statement will see the changes, but sometimes (if it
only references previously-referenced tables in previously-acquired
lock modes) it won't. I can't see any good reason to keep that
inconsistency around. It's true that the changes might or might not
see by a concurrently-running transaction, and it would be nice to do
something about that, too, but it's not clear that there is any
straightforward change that would accomplish that. OTOH, ensuring
that AcceptInvalidationMessages() runs at least once per statement
seems pretty straightforward.

The real issue here, and the reason why I'm very unhappy with the mad rush
in certain quarters to try to reduce locking levels for DDL, is exactly
that it generally results in uncertainty about when the effects will be
seen. I do not think your proposal does much more than put a fig leaf
over that problem.

Let's not treat wanting to reduce locking levels for DDL as a
character flaw. As Andres says, it's a huge operational problem. I
don't think we should be irresponsible about lowering DDL lock levels
and accept random breakage along the way, but I do think we need,
rather desperately actually, to reduce lock levels. We need to figure
out which things are problems and what can be done about them, not
just say "no" to everything.

Barring somebody having a great idea about resolving that, I think we
just need to be very clear that any DDL done with less than AEL has
exactly this issue. In the case at hand, couldn't we just document
that "the effects of RENAME CONCURRENTLY may not be seen in other
sessions right away; at worst, not till they begin a new transaction"?
If you don't like that, don't use CONCURRENTLY.

Sure. On the other hand, we could ALSO insert an
AcceptInvalidationMessages() call once per statement and document that
they might not be seen until you begin a new statement, which seems
like it has basically no downside but is much better for users,
particularly because the name of an object tends -- in the great
majority of cases -- to matter only at the beginning of statement
processing. You can construct SQL queries that perform name lookups
midway through, but that's not typical.

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

#27Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#23)
Re: Alter index rename concurrently to

On Thu, Aug 2, 2018 at 4:44 PM, Andres Freund <andres@anarazel.de> wrote:

ISTM, if you want to increase consistency in this area, you've to go
further. E.g. processing invalidations in StartTransactionCommand() in
all states, which'd give you a lot more consistency.

Hmm, that seems like a pretty good idea.

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

#28Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Peter Eisentraut (#14)
1 attachment(s)
Re: Alter index rename concurrently to

On 31/07/2018 23:10, Peter Eisentraut wrote:

On 27/07/2018 16:16, Robert Haas wrote:

With respect to this particular patch, I don't know whether there are
any hazards of the second type. What I'd check, if it were me, is
what structures in the index's relcache entry are going to get rebuilt
when the index name changes. If any of those look like things that
something that somebody could hold a pointer to during the course of
query execution, or more generally be relying on not to change during
the course of query execution, then you've got a problem.

I have investigated this, and I think it's safe. Relcache reloads for
open indexes are already handled specially in RelationReloadIndexInfo().
The only pointers that get replaced there are rd_amcache and
rd_options. I have checked where those are used, and it looks like they
are not used across possible relcache reloads. The code structure in
those two cases make this pretty unlikely even in the future. Also,
violations would probably be caught by CLOBBER_CACHE_ALWAYS.

Based on these assertions, here is my proposed patch. It lowers the
lock level for index renaming to ShareUpdateExclusiveLock.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

v1-0001-Lower-lock-level-for-renaming-indexes.patchtext/plain; charset=UTF-8; name=v1-0001-Lower-lock-level-for-renaming-indexes.patch; x-mac-creator=0; x-mac-type=0Download
From febe62543115a99eae00747a864cd922fde71875 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Mon, 13 Aug 2018 22:38:36 +0200
Subject: [PATCH v1] Lower lock level for renaming indexes

Change lock level for renaming index (either ALTER INDEX or implicitly
via some other commands) from AccessExclusiveLock to
ShareUpdateExclusiveLock.

The reason we need a strong lock at all for relation renaming is that
the name change causes a rebuild of the relcache entry.  Concurrent
sessions that have the relation open might not be able to handle the
relcache entry changing underneath them.  Therefore, we need to lock the
relation in a way that no one can have the relation open concurrently.
But for indexes, the relcache handles reloads specially in
RelationReloadIndexInfo() in a way that keeps changes in the relcache
entry to a minimum.  As long as no one keeps pointers to rd_amcache and
rd_options around across possible relcache flushes, which is the case,
this ought to be safe.

One could perhaps argue that this could all be done with an
AccessShareLock then.  But we standardize here for consistency on
ShareUpdateExclusiveLock, which is already used by other DDL commands
that want to operate in a concurrent manner.  For example, renaming an
index concurrently with CREATE INDEX CONCURRENTLY might be trouble.

The reason this is interesting at all is that renaming an index is a
typical part of a concurrent reindexing workflow (CREATE INDEX
CONCURRENTLY new + DROP INDEX CONCURRENTLY old + rename back).  And
indeed a future built-in REINDEX CONCURRENTLY might rely on the ability
to do concurrent renames as well.
---
 doc/src/sgml/mvcc.sgml            | 12 ++++++------
 doc/src/sgml/ref/alter_index.sgml |  9 ++++++++-
 src/backend/commands/cluster.c    |  4 ++--
 src/backend/commands/tablecmds.c  | 24 ++++++++++++++----------
 src/backend/commands/typecmds.c   |  2 +-
 src/include/commands/tablecmds.h  |  3 ++-
 6 files changed, 33 insertions(+), 21 deletions(-)

diff --git a/doc/src/sgml/mvcc.sgml b/doc/src/sgml/mvcc.sgml
index 73934e5cf3..bedd9a008d 100644
--- a/doc/src/sgml/mvcc.sgml
+++ b/doc/src/sgml/mvcc.sgml
@@ -926,10 +926,10 @@ <title>Table-level Lock Modes</title>
         <para>
          Acquired by <command>VACUUM</command> (without <option>FULL</option>),
          <command>ANALYZE</command>, <command>CREATE INDEX CONCURRENTLY</command>,
-         <command>CREATE STATISTICS</command> and
-         <command>ALTER TABLE VALIDATE</command> and other
-         <command>ALTER TABLE</command> variants (for full details see
-         <xref linkend="sql-altertable"/>).
+         <command>CREATE STATISTICS</command>, and certain <command>ALTER
+         INDEX</command> and <command>ALTER TABLE</command> variants (for full
+         details see <xref linkend="sql-alterindex"/> and <xref
+         linkend="sql-altertable"/>).
         </para>
        </listitem>
       </varlistentry>
@@ -970,7 +970,7 @@ <title>Table-level Lock Modes</title>
         </para>
 
         <para>
-         Acquired by <command>CREATE TRIGGER</command> and many forms of
+         Acquired by <command>CREATE TRIGGER</command> and some forms of
          <command>ALTER TABLE</command> (see <xref linkend="sql-altertable"/>).
         </para>
        </listitem>
@@ -1020,7 +1020,7 @@ <title>Table-level Lock Modes</title>
          <command>CLUSTER</command>, <command>VACUUM FULL</command>,
          and <command>REFRESH MATERIALIZED VIEW</command> (without
          <option>CONCURRENTLY</option>)
-         commands. Many forms of <command>ALTER TABLE</command> also acquire
+         commands. Many forms of <command>ALTER INDEX</command> and <command>ALTER TABLE</command> also acquire
          a lock at this level. This is also the default lock mode for
          <command>LOCK TABLE</command> statements that do not specify
          a mode explicitly.
diff --git a/doc/src/sgml/ref/alter_index.sgml b/doc/src/sgml/ref/alter_index.sgml
index 7290d9a5bd..73094fd179 100644
--- a/doc/src/sgml/ref/alter_index.sgml
+++ b/doc/src/sgml/ref/alter_index.sgml
@@ -39,7 +39,10 @@ <title>Description</title>
 
   <para>
    <command>ALTER INDEX</command> changes the definition of an existing index.
-   There are several subforms:
+   There are several subforms described below. Note that the lock level required
+   may differ for each subform. An <literal>ACCESS EXCLUSIVE</literal> lock is held
+   unless explicitly noted. When multiple subcommands are listed, the lock
+   held will be the strictest one required from any subcommand.
 
   <variablelist>
 
@@ -50,6 +53,10 @@ <title>Description</title>
       The <literal>RENAME</literal> form changes the name of the index.
       There is no effect on the stored data.
      </para>
+     <para>
+      Renaming an index acquires a <literal>SHARE UPDATE EXCLUSIVE</literal>
+      lock.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 68be470977..5ecd2565b4 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -1661,14 +1661,14 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 			snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u",
 					 OIDOldHeap);
 			RenameRelationInternal(newrel->rd_rel->reltoastrelid,
-								   NewToastName, true);
+								   NewToastName, true, false);
 
 			/* ... and its valid index too. */
 			snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u_index",
 					 OIDOldHeap);
 
 			RenameRelationInternal(toastidx,
-								   NewToastName, true);
+								   NewToastName, true, true);
 		}
 		relation_close(newrel, NoLock);
 	}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f6210226e9..e1905dfe60 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -3038,7 +3038,7 @@ rename_constraint_internal(Oid myrelid,
 			|| con->contype == CONSTRAINT_UNIQUE
 			|| con->contype == CONSTRAINT_EXCLUSION))
 		/* rename the index; this renames the constraint as well */
-		RenameRelationInternal(con->conindid, newconname, false);
+		RenameRelationInternal(con->conindid, newconname, false, true);
 	else
 		RenameConstraintById(constraintOid, newconname);
 
@@ -3106,6 +3106,7 @@ RenameConstraint(RenameStmt *stmt)
 ObjectAddress
 RenameRelation(RenameStmt *stmt)
 {
+	bool		is_index = stmt->renameType == OBJECT_INDEX;
 	Oid			relid;
 	ObjectAddress address;
 
@@ -3117,7 +3118,8 @@ RenameRelation(RenameStmt *stmt)
 	 * Lock level used here should match RenameRelationInternal, to avoid lock
 	 * escalation.
 	 */
-	relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
+	relid = RangeVarGetRelidExtended(stmt->relation,
+									 is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock,
 									 stmt->missing_ok ? RVR_MISSING_OK : 0,
 									 RangeVarCallbackForAlterRelation,
 									 (void *) stmt);
@@ -3131,7 +3133,7 @@ RenameRelation(RenameStmt *stmt)
 	}
 
 	/* Do the work */
-	RenameRelationInternal(relid, stmt->newname, false);
+	RenameRelationInternal(relid, stmt->newname, false, is_index);
 
 	ObjectAddressSet(address, RelationRelationId, relid);
 
@@ -3142,7 +3144,7 @@ RenameRelation(RenameStmt *stmt)
  *		RenameRelationInternal - change the name of a relation
  */
 void
-RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal)
+RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bool is_index)
 {
 	Relation	targetrelation;
 	Relation	relrelation;	/* for RELATION relation */
@@ -3151,11 +3153,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal)
 	Oid			namespaceId;
 
 	/*
-	 * Grab an exclusive lock on the target table, index, sequence, view,
-	 * materialized view, or foreign table, which we will NOT release until
-	 * end of transaction.
+	 * Grab a lock on the target relation, which we will NOT release until end
+	 * of transaction.  The lock here guards mainly against triggering
+	 * relcache reloads in concurrent sessions, which might not handle this
+	 * information changing under them.  For indexes, we can use a reduced
+	 * lock level because RelationReloadIndexInfo() handles indexes specially.
 	 */
-	targetrelation = relation_open(myrelid, AccessExclusiveLock);
+	targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
 	namespaceId = RelationGetNamespace(targetrelation);
 
 	/*
@@ -3208,7 +3212,7 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal)
 	}
 
 	/*
-	 * Close rel, but keep exclusive lock!
+	 * Close rel, but keep lock!
 	 */
 	relation_close(targetrelation, NoLock);
 }
@@ -7066,7 +7070,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
 		ereport(NOTICE,
 				(errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index \"%s\" to \"%s\"",
 						indexName, constraintName)));
-		RenameRelationInternal(index_oid, constraintName, false);
+		RenameRelationInternal(index_oid, constraintName, false, true);
 	}
 
 	/* Extra checks needed if making primary key */
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 175ecc8b48..f7f3ce2d08 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3280,7 +3280,7 @@ RenameType(RenameStmt *stmt)
 	 * RenameRelationInternal will call RenameTypeInternal automatically.
 	 */
 	if (typTup->typtype == TYPTYPE_COMPOSITE)
-		RenameRelationInternal(typTup->typrelid, newTypeName, false);
+		RenameRelationInternal(typTup->typrelid, newTypeName, false, false);
 	else
 		RenameTypeInternal(typeOid, newTypeName,
 						   typTup->typnamespace);
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 138de84e83..2afcd5be44 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -67,7 +67,8 @@ extern ObjectAddress RenameConstraint(RenameStmt *stmt);
 extern ObjectAddress RenameRelation(RenameStmt *stmt);
 
 extern void RenameRelationInternal(Oid myrelid,
-					   const char *newrelname, bool is_internal);
+					   const char *newrelname, bool is_internal,
+					   bool is_index);
 
 extern void find_composite_type_dependencies(Oid typeOid,
 								 Relation origRelation,
-- 
2.18.0

#29Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Robert Haas (#27)
Re: Alter index rename concurrently to

On 03/08/2018 15:00, Robert Haas wrote:

On Thu, Aug 2, 2018 at 4:44 PM, Andres Freund <andres@anarazel.de> wrote:

ISTM, if you want to increase consistency in this area, you've to go
further. E.g. processing invalidations in StartTransactionCommand() in
all states, which'd give you a lot more consistency.

Hmm, that seems like a pretty good idea.

That would only affect top-level commands, not things like SPI. Is that
what we want? Or we could sprinkle additional
AcceptInvalidationMessages() calls in spi.c.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#30Andres Freund
andres@anarazel.de
In reply to: Peter Eisentraut (#29)
Re: Alter index rename concurrently to

Hi,

On 2018-08-14 08:44:46 +0200, Peter Eisentraut wrote:

On 03/08/2018 15:00, Robert Haas wrote:

On Thu, Aug 2, 2018 at 4:44 PM, Andres Freund <andres@anarazel.de> wrote:

ISTM, if you want to increase consistency in this area, you've to go
further. E.g. processing invalidations in StartTransactionCommand() in
all states, which'd give you a lot more consistency.

Hmm, that seems like a pretty good idea.

That would only affect top-level commands, not things like SPI. Is that
what we want? Or we could sprinkle additional
AcceptInvalidationMessages() calls in spi.c.

I'd say it's not unreasonable to *not* to guarantee spi (and various
other functions) immediately take concurrent activity into account,
unless locking rules guarantee that independently. Definining it as
"commands sent by the client see effects of previously issued
non-conflicting DDL, others might see them earlier" doesn't seem insane.

If you want to take account of SPI and PLs it gets more comoplicated
quickly, because we don't always parse functions afresh. So you'd need
invalidations in a lot more places.

Greetings,

Andres Freund

#31Andrey Klychkov
aaklychkov@mail.ru
In reply to: Peter Eisentraut (#28)
Re[2]: Alter index rename concurrently to

Based on these assertions, here is my proposed patch. It lowers the
lock level for index renaming to ShareUpdateExclusiveLock.

Hi, Peter

I made small review for your patch:
Server source code got from https://github.com/postgres/postgres.git
1. Patch was applied without any errors except a part related to documentation:
error: patch failed: doc/src/sgml/ref/alter_index.sgml:50
error: doc/src/sgml/ref/alter_index.sgml: patch does not apply
2. The code has been compiled successfully, configured by:
# ./configure CFLAGS="-O0" --enable-debug --enable-cassert --enable-depend --without-zlib
3. 'make' / 'make install' successfully made and complete.
4. The compiled instance has been started without any errors.
5. I realized several tests by the pgbench (with -c 4 -j 4 -T 360) that modified data into columns, indexed by pk and common btree.
In the same time there was a running script that was making renaming indexes multiple times in transactions with pg_sleep(1).
After several tests no errors were found.
6. pg_upgrade from 10 to 12 (patched) has been done without any errors / warnings
7. Code style:
+RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bool is_index)
This line is longer than 80 chars.
Thank you

Вторник, 14 августа 2018, 9:33 +03:00 от Peter Eisentraut <peter.eisentraut@2ndquadrant.com>:

On 31/07/2018 23:10, Peter Eisentraut wrote:

On 27/07/2018 16:16, Robert Haas wrote:

With respect to this particular patch, I don't know whether there are
any hazards of the second type. What I'd check, if it were me, is
what structures in the index's relcache entry are going to get rebuilt
when the index name changes. If any of those look like things that
something that somebody could hold a pointer to during the course of
query execution, or more generally be relying on not to change during
the course of query execution, then you've got a problem.

I have investigated this, and I think it's safe. Relcache reloads for
open indexes are already handled specially in RelationReloadIndexInfo().
The only pointers that get replaced there are rd_amcache and
rd_options. I have checked where those are used, and it looks like they
are not used across possible relcache reloads. The code structure in
those two cases make this pretty unlikely even in the future. Also,
violations would probably be caught by CLOBBER_CACHE_ALWAYS.

Based on these assertions, here is my proposed patch. It lowers the
lock level for index renaming to ShareUpdateExclusiveLock.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Regards,
Andrey Klychkov

#32Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Andrey Klychkov (#31)
1 attachment(s)
Re: Alter index rename concurrently to

On 03/10/2018 13:51, Andrey Klychkov wrote:

1. Patch was applied without any errors except a part related to
documentation:
error: patch failed: doc/src/sgml/ref/alter_index.sgml:50
error: doc/src/sgml/ref/alter_index.sgml: patch does not apply

Attached is an updated patch.

2. The code has been compiled successfully, configured by:
# ./configure CFLAGS="-O0" --enable-debug --enable-cassert
--enable-depend --without-zlib

Not sure why you use -O0 here. It's not a good idea for development,
because it might miss interesting warnings.

7. Code style:
+RenameRelationInternal(Oid myrelid, const char *newrelname, bool
is_internal, bool is_index)
This line is longer than 80 chars.

pgindent leaves this line alone.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

v2-0001-Lower-lock-level-for-renaming-indexes.patchtext/plain; charset=UTF-8; name=v2-0001-Lower-lock-level-for-renaming-indexes.patch; x-mac-creator=0; x-mac-type=0Download
From 37591a06901e2d694e3928b7e1cddfcfd84f6267 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Mon, 13 Aug 2018 22:38:36 +0200
Subject: [PATCH v2] Lower lock level for renaming indexes

Change lock level for renaming index (either ALTER INDEX or implicitly
via some other commands) from AccessExclusiveLock to
ShareUpdateExclusiveLock.

The reason we need a strong lock at all for relation renaming is that
the name change causes a rebuild of the relcache entry.  Concurrent
sessions that have the relation open might not be able to handle the
relcache entry changing underneath them.  Therefore, we need to lock the
relation in a way that no one can have the relation open concurrently.
But for indexes, the relcache handles reloads specially in
RelationReloadIndexInfo() in a way that keeps changes in the relcache
entry to a minimum.  As long as no one keeps pointers to rd_amcache and
rd_options around across possible relcache flushes, which is the case,
this ought to be safe.

One could perhaps argue that this could all be done with an
AccessShareLock then.  But we standardize here for consistency on
ShareUpdateExclusiveLock, which is already used by other DDL commands
that want to operate in a concurrent manner.  For example, renaming an
index concurrently with CREATE INDEX CONCURRENTLY might be trouble.

The reason this is interesting at all is that renaming an index is a
typical part of a concurrent reindexing workflow (CREATE INDEX
CONCURRENTLY new + DROP INDEX CONCURRENTLY old + rename back).  And
indeed a future built-in REINDEX CONCURRENTLY might rely on the ability
to do concurrent renames as well.
---
 doc/src/sgml/mvcc.sgml            | 12 ++++++------
 doc/src/sgml/ref/alter_index.sgml |  9 ++++++++-
 src/backend/commands/cluster.c    |  4 ++--
 src/backend/commands/tablecmds.c  | 24 ++++++++++++++----------
 src/backend/commands/typecmds.c   |  2 +-
 src/include/commands/tablecmds.h  |  3 ++-
 6 files changed, 33 insertions(+), 21 deletions(-)

diff --git a/doc/src/sgml/mvcc.sgml b/doc/src/sgml/mvcc.sgml
index 73934e5cf3..bedd9a008d 100644
--- a/doc/src/sgml/mvcc.sgml
+++ b/doc/src/sgml/mvcc.sgml
@@ -926,10 +926,10 @@ <title>Table-level Lock Modes</title>
         <para>
          Acquired by <command>VACUUM</command> (without <option>FULL</option>),
          <command>ANALYZE</command>, <command>CREATE INDEX CONCURRENTLY</command>,
-         <command>CREATE STATISTICS</command> and
-         <command>ALTER TABLE VALIDATE</command> and other
-         <command>ALTER TABLE</command> variants (for full details see
-         <xref linkend="sql-altertable"/>).
+         <command>CREATE STATISTICS</command>, and certain <command>ALTER
+         INDEX</command> and <command>ALTER TABLE</command> variants (for full
+         details see <xref linkend="sql-alterindex"/> and <xref
+         linkend="sql-altertable"/>).
         </para>
        </listitem>
       </varlistentry>
@@ -970,7 +970,7 @@ <title>Table-level Lock Modes</title>
         </para>
 
         <para>
-         Acquired by <command>CREATE TRIGGER</command> and many forms of
+         Acquired by <command>CREATE TRIGGER</command> and some forms of
          <command>ALTER TABLE</command> (see <xref linkend="sql-altertable"/>).
         </para>
        </listitem>
@@ -1020,7 +1020,7 @@ <title>Table-level Lock Modes</title>
          <command>CLUSTER</command>, <command>VACUUM FULL</command>,
          and <command>REFRESH MATERIALIZED VIEW</command> (without
          <option>CONCURRENTLY</option>)
-         commands. Many forms of <command>ALTER TABLE</command> also acquire
+         commands. Many forms of <command>ALTER INDEX</command> and <command>ALTER TABLE</command> also acquire
          a lock at this level. This is also the default lock mode for
          <command>LOCK TABLE</command> statements that do not specify
          a mode explicitly.
diff --git a/doc/src/sgml/ref/alter_index.sgml b/doc/src/sgml/ref/alter_index.sgml
index d0a6212358..6d34dbb74e 100644
--- a/doc/src/sgml/ref/alter_index.sgml
+++ b/doc/src/sgml/ref/alter_index.sgml
@@ -39,7 +39,10 @@ <title>Description</title>
 
   <para>
    <command>ALTER INDEX</command> changes the definition of an existing index.
-   There are several subforms:
+   There are several subforms described below. Note that the lock level required
+   may differ for each subform. An <literal>ACCESS EXCLUSIVE</literal> lock is held
+   unless explicitly noted. When multiple subcommands are listed, the lock
+   held will be the strictest one required from any subcommand.
 
   <variablelist>
 
@@ -53,6 +56,10 @@ <title>Description</title>
       or <literal>EXCLUDE</literal>), the constraint is renamed as well.
       There is no effect on the stored data.
      </para>
+     <para>
+      Renaming an index acquires a <literal>SHARE UPDATE EXCLUSIVE</literal>
+      lock.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 68be470977..5ecd2565b4 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -1661,14 +1661,14 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 			snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u",
 					 OIDOldHeap);
 			RenameRelationInternal(newrel->rd_rel->reltoastrelid,
-								   NewToastName, true);
+								   NewToastName, true, false);
 
 			/* ... and its valid index too. */
 			snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u_index",
 					 OIDOldHeap);
 
 			RenameRelationInternal(toastidx,
-								   NewToastName, true);
+								   NewToastName, true, true);
 		}
 		relation_close(newrel, NoLock);
 	}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c145385f84..b1c1181e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -3042,7 +3042,7 @@ rename_constraint_internal(Oid myrelid,
 			|| con->contype == CONSTRAINT_UNIQUE
 			|| con->contype == CONSTRAINT_EXCLUSION))
 		/* rename the index; this renames the constraint as well */
-		RenameRelationInternal(con->conindid, newconname, false);
+		RenameRelationInternal(con->conindid, newconname, false, true);
 	else
 		RenameConstraintById(constraintOid, newconname);
 
@@ -3110,6 +3110,7 @@ RenameConstraint(RenameStmt *stmt)
 ObjectAddress
 RenameRelation(RenameStmt *stmt)
 {
+	bool		is_index = stmt->renameType == OBJECT_INDEX;
 	Oid			relid;
 	ObjectAddress address;
 
@@ -3121,7 +3122,8 @@ RenameRelation(RenameStmt *stmt)
 	 * Lock level used here should match RenameRelationInternal, to avoid lock
 	 * escalation.
 	 */
-	relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
+	relid = RangeVarGetRelidExtended(stmt->relation,
+									 is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock,
 									 stmt->missing_ok ? RVR_MISSING_OK : 0,
 									 RangeVarCallbackForAlterRelation,
 									 (void *) stmt);
@@ -3135,7 +3137,7 @@ RenameRelation(RenameStmt *stmt)
 	}
 
 	/* Do the work */
-	RenameRelationInternal(relid, stmt->newname, false);
+	RenameRelationInternal(relid, stmt->newname, false, is_index);
 
 	ObjectAddressSet(address, RelationRelationId, relid);
 
@@ -3146,7 +3148,7 @@ RenameRelation(RenameStmt *stmt)
  *		RenameRelationInternal - change the name of a relation
  */
 void
-RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal)
+RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bool is_index)
 {
 	Relation	targetrelation;
 	Relation	relrelation;	/* for RELATION relation */
@@ -3155,11 +3157,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal)
 	Oid			namespaceId;
 
 	/*
-	 * Grab an exclusive lock on the target table, index, sequence, view,
-	 * materialized view, or foreign table, which we will NOT release until
-	 * end of transaction.
+	 * Grab a lock on the target relation, which we will NOT release until end
+	 * of transaction.  The lock here guards mainly against triggering
+	 * relcache reloads in concurrent sessions, which might not handle this
+	 * information changing under them.  For indexes, we can use a reduced
+	 * lock level because RelationReloadIndexInfo() handles indexes specially.
 	 */
-	targetrelation = relation_open(myrelid, AccessExclusiveLock);
+	targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
 	namespaceId = RelationGetNamespace(targetrelation);
 
 	/*
@@ -3212,7 +3216,7 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal)
 	}
 
 	/*
-	 * Close rel, but keep exclusive lock!
+	 * Close rel, but keep lock!
 	 */
 	relation_close(targetrelation, NoLock);
 }
@@ -7069,7 +7073,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
 		ereport(NOTICE,
 				(errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index \"%s\" to \"%s\"",
 						indexName, constraintName)));
-		RenameRelationInternal(index_oid, constraintName, false);
+		RenameRelationInternal(index_oid, constraintName, false, true);
 	}
 
 	/* Extra checks needed if making primary key */
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index b018585aef..6506db80b0 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3283,7 +3283,7 @@ RenameType(RenameStmt *stmt)
 	 * RenameRelationInternal will call RenameTypeInternal automatically.
 	 */
 	if (typTup->typtype == TYPTYPE_COMPOSITE)
-		RenameRelationInternal(typTup->typrelid, newTypeName, false);
+		RenameRelationInternal(typTup->typrelid, newTypeName, false, false);
 	else
 		RenameTypeInternal(typeOid, newTypeName,
 						   typTup->typnamespace);
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 138de84e83..2afcd5be44 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -67,7 +67,8 @@ extern ObjectAddress RenameConstraint(RenameStmt *stmt);
 extern ObjectAddress RenameRelation(RenameStmt *stmt);
 
 extern void RenameRelationInternal(Oid myrelid,
-					   const char *newrelname, bool is_internal);
+					   const char *newrelname, bool is_internal,
+					   bool is_index);
 
 extern void find_composite_type_dependencies(Oid typeOid,
 								 Relation origRelation,

base-commit: 803b1301e8c9aac478abeec62824a5d09664ffff
-- 
2.19.0

#33Andrey Klychkov
aaklychkov@mail.ru
In reply to: Peter Eisentraut (#32)
Re[2]: Alter index rename concurrently to

 Attached is an updated patch.

That's OK now, the patch applying is without any errors.
I have no more remarks.

Пятница, 5 октября 2018, 13:04 +03:00 от Peter Eisentraut <peter.eisentraut@2ndquadrant.com>:

On 03/10/2018 13:51, Andrey Klychkov wrote:

1. Patch was applied without any errors except a part related to
documentation:
error: patch failed: doc/src/sgml/ref/alter_index.sgml:50
error: doc/src/sgml/ref/alter_index.sgml: patch does not apply

Attached is an updated patch.

2. The code has been compiled successfully, configured by:
# ./configure CFLAGS="-O0" --enable-debug --enable-cassert
--enable-depend --without-zlib

Not sure why you use -O0 here. It's not a good idea for development,
because it might miss interesting warnings.

7. Code style:
+RenameRelationInternal(Oid myrelid, const char *newrelname, bool
is_internal, bool is_index)
This line is longer than 80 chars.

pgindent leaves this line alone.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Regards,
Andrey Klychkov

#34Andres Freund
andres@anarazel.de
In reply to: Peter Eisentraut (#32)
Re: Alter index rename concurrently to

Hi,

On 2018-10-05 12:03:54 +0200, Peter Eisentraut wrote:

From 37591a06901e2d694e3928b7e1cddfcfd84f6267 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Mon, 13 Aug 2018 22:38:36 +0200
Subject: [PATCH v2] Lower lock level for renaming indexes

Change lock level for renaming index (either ALTER INDEX or implicitly
via some other commands) from AccessExclusiveLock to
ShareUpdateExclusiveLock.

The reason we need a strong lock at all for relation renaming is that
the name change causes a rebuild of the relcache entry. Concurrent
sessions that have the relation open might not be able to handle the
relcache entry changing underneath them. Therefore, we need to lock the
relation in a way that no one can have the relation open concurrently.
But for indexes, the relcache handles reloads specially in
RelationReloadIndexInfo() in a way that keeps changes in the relcache
entry to a minimum. As long as no one keeps pointers to rd_amcache and
rd_options around across possible relcache flushes, which is the case,
this ought to be safe.

One could perhaps argue that this could all be done with an
AccessShareLock then. But we standardize here for consistency on
ShareUpdateExclusiveLock, which is already used by other DDL commands
that want to operate in a concurrent manner. For example, renaming an
index concurrently with CREATE INDEX CONCURRENTLY might be trouble.

I don't see how this could be argued. It has to be a self-conflicting
lockmode, otherwise you'd end up doing renames of tables where you
cannot see the previous state. And you'd get weird errors about updating
invisible rows etc.

@@ -3155,11 +3157,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal)
Oid namespaceId;

/*
-	 * Grab an exclusive lock on the target table, index, sequence, view,
-	 * materialized view, or foreign table, which we will NOT release until
-	 * end of transaction.
+	 * Grab a lock on the target relation, which we will NOT release until end
+	 * of transaction.  The lock here guards mainly against triggering
+	 * relcache reloads in concurrent sessions, which might not handle this
+	 * information changing under them.  For indexes, we can use a reduced
+	 * lock level because RelationReloadIndexInfo() handles indexes specially.
*/

I don't buy this description. Imo it's a fundamental correctness
thing. Without it concurrent DDL would potentially overwrite the rename,
because they could start updating while still seeing the old version.

Greetings,

Andres Freund

#35Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Andres Freund (#34)
Re: Alter index rename concurrently to

On 13/10/2018 04:01, Andres Freund wrote:

I don't see how this could be argued. It has to be a self-conflicting
lockmode, otherwise you'd end up doing renames of tables where you
cannot see the previous state. And you'd get weird errors about updating
invisible rows etc.

I don't buy this description. Imo it's a fundamental correctness
thing. Without it concurrent DDL would potentially overwrite the rename,
because they could start updating while still seeing the old version.

OK, I can refine those descriptions/comments. Do you have any concerns
about the underlying principle of this patch?

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#36Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Peter Eisentraut (#35)
1 attachment(s)
Re: Alter index rename concurrently to

On 17/10/2018 23:11, Peter Eisentraut wrote:

On 13/10/2018 04:01, Andres Freund wrote:

I don't see how this could be argued. It has to be a self-conflicting
lockmode, otherwise you'd end up doing renames of tables where you
cannot see the previous state. And you'd get weird errors about updating
invisible rows etc.

I don't buy this description. Imo it's a fundamental correctness
thing. Without it concurrent DDL would potentially overwrite the rename,
because they could start updating while still seeing the old version.

OK, I can refine those descriptions/comments. Do you have any concerns
about the underlying principle of this patch?

Patch with updated comments to reflect your input.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

v3-0001-Lower-lock-level-for-renaming-indexes.patchtext/plain; charset=UTF-8; name=v3-0001-Lower-lock-level-for-renaming-indexes.patch; x-mac-creator=0; x-mac-type=0Download
From 3188b76cb4a2851b87361193addb6cbe326d4a64 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Thu, 25 Oct 2018 08:33:17 +0100
Subject: [PATCH v3] Lower lock level for renaming indexes

Change lock level for renaming index (either ALTER INDEX or implicitly
via some other commands) from AccessExclusiveLock to
ShareUpdateExclusiveLock.

One reason we need a strong lock for relation renaming is that the
name change causes a rebuild of the relcache entry.  Concurrent
sessions that have the relation open might not be able to handle the
relcache entry changing underneath them.  Therefore, we need to lock
the relation in a way that no one can have the relation open
concurrently.  But for indexes, the relcache handles reloads specially
in RelationReloadIndexInfo() in a way that keeps changes in the
relcache entry to a minimum.  As long as no one keeps pointers to
rd_amcache and rd_options around across possible relcache flushes,
which is the case, this ought to be safe.

We also want to use a self-exclusive lock for correctness, so that
concurrent DDL doesn't overwrite the rename if they start updating
while still seeing the old version.  Therefore, we use
ShareUpdateExclusiveLock, which is already used by other DDL commands
that want to operate in a concurrent manner.

The reason this is interesting at all is that renaming an index is a
typical part of a concurrent reindexing workflow (CREATE INDEX
CONCURRENTLY new + DROP INDEX CONCURRENTLY old + rename back).  And
indeed a future built-in REINDEX CONCURRENTLY might rely on the ability
to do concurrent renames as well.
---
 doc/src/sgml/mvcc.sgml            | 12 ++++++------
 doc/src/sgml/ref/alter_index.sgml |  9 ++++++++-
 src/backend/commands/cluster.c    |  4 ++--
 src/backend/commands/tablecmds.c  | 27 +++++++++++++++++----------
 src/backend/commands/typecmds.c   |  2 +-
 src/include/commands/tablecmds.h  |  3 ++-
 6 files changed, 36 insertions(+), 21 deletions(-)

diff --git a/doc/src/sgml/mvcc.sgml b/doc/src/sgml/mvcc.sgml
index 73934e5cf3..bedd9a008d 100644
--- a/doc/src/sgml/mvcc.sgml
+++ b/doc/src/sgml/mvcc.sgml
@@ -926,10 +926,10 @@ <title>Table-level Lock Modes</title>
         <para>
          Acquired by <command>VACUUM</command> (without <option>FULL</option>),
          <command>ANALYZE</command>, <command>CREATE INDEX CONCURRENTLY</command>,
-         <command>CREATE STATISTICS</command> and
-         <command>ALTER TABLE VALIDATE</command> and other
-         <command>ALTER TABLE</command> variants (for full details see
-         <xref linkend="sql-altertable"/>).
+         <command>CREATE STATISTICS</command>, and certain <command>ALTER
+         INDEX</command> and <command>ALTER TABLE</command> variants (for full
+         details see <xref linkend="sql-alterindex"/> and <xref
+         linkend="sql-altertable"/>).
         </para>
        </listitem>
       </varlistentry>
@@ -970,7 +970,7 @@ <title>Table-level Lock Modes</title>
         </para>
 
         <para>
-         Acquired by <command>CREATE TRIGGER</command> and many forms of
+         Acquired by <command>CREATE TRIGGER</command> and some forms of
          <command>ALTER TABLE</command> (see <xref linkend="sql-altertable"/>).
         </para>
        </listitem>
@@ -1020,7 +1020,7 @@ <title>Table-level Lock Modes</title>
          <command>CLUSTER</command>, <command>VACUUM FULL</command>,
          and <command>REFRESH MATERIALIZED VIEW</command> (without
          <option>CONCURRENTLY</option>)
-         commands. Many forms of <command>ALTER TABLE</command> also acquire
+         commands. Many forms of <command>ALTER INDEX</command> and <command>ALTER TABLE</command> also acquire
          a lock at this level. This is also the default lock mode for
          <command>LOCK TABLE</command> statements that do not specify
          a mode explicitly.
diff --git a/doc/src/sgml/ref/alter_index.sgml b/doc/src/sgml/ref/alter_index.sgml
index d0a6212358..6d34dbb74e 100644
--- a/doc/src/sgml/ref/alter_index.sgml
+++ b/doc/src/sgml/ref/alter_index.sgml
@@ -39,7 +39,10 @@ <title>Description</title>
 
   <para>
    <command>ALTER INDEX</command> changes the definition of an existing index.
-   There are several subforms:
+   There are several subforms described below. Note that the lock level required
+   may differ for each subform. An <literal>ACCESS EXCLUSIVE</literal> lock is held
+   unless explicitly noted. When multiple subcommands are listed, the lock
+   held will be the strictest one required from any subcommand.
 
   <variablelist>
 
@@ -53,6 +56,10 @@ <title>Description</title>
       or <literal>EXCLUDE</literal>), the constraint is renamed as well.
       There is no effect on the stored data.
      </para>
+     <para>
+      Renaming an index acquires a <literal>SHARE UPDATE EXCLUSIVE</literal>
+      lock.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 68be470977..5ecd2565b4 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -1661,14 +1661,14 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 			snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u",
 					 OIDOldHeap);
 			RenameRelationInternal(newrel->rd_rel->reltoastrelid,
-								   NewToastName, true);
+								   NewToastName, true, false);
 
 			/* ... and its valid index too. */
 			snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u_index",
 					 OIDOldHeap);
 
 			RenameRelationInternal(toastidx,
-								   NewToastName, true);
+								   NewToastName, true, true);
 		}
 		relation_close(newrel, NoLock);
 	}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 153aec263e..0a7f4a587e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -3042,7 +3042,7 @@ rename_constraint_internal(Oid myrelid,
 			|| con->contype == CONSTRAINT_UNIQUE
 			|| con->contype == CONSTRAINT_EXCLUSION))
 		/* rename the index; this renames the constraint as well */
-		RenameRelationInternal(con->conindid, newconname, false);
+		RenameRelationInternal(con->conindid, newconname, false, true);
 	else
 		RenameConstraintById(constraintOid, newconname);
 
@@ -3110,6 +3110,7 @@ RenameConstraint(RenameStmt *stmt)
 ObjectAddress
 RenameRelation(RenameStmt *stmt)
 {
+	bool		is_index = stmt->renameType == OBJECT_INDEX;
 	Oid			relid;
 	ObjectAddress address;
 
@@ -3121,7 +3122,8 @@ RenameRelation(RenameStmt *stmt)
 	 * Lock level used here should match RenameRelationInternal, to avoid lock
 	 * escalation.
 	 */
-	relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
+	relid = RangeVarGetRelidExtended(stmt->relation,
+									 is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock,
 									 stmt->missing_ok ? RVR_MISSING_OK : 0,
 									 RangeVarCallbackForAlterRelation,
 									 (void *) stmt);
@@ -3135,7 +3137,7 @@ RenameRelation(RenameStmt *stmt)
 	}
 
 	/* Do the work */
-	RenameRelationInternal(relid, stmt->newname, false);
+	RenameRelationInternal(relid, stmt->newname, false, is_index);
 
 	ObjectAddressSet(address, RelationRelationId, relid);
 
@@ -3146,7 +3148,7 @@ RenameRelation(RenameStmt *stmt)
  *		RenameRelationInternal - change the name of a relation
  */
 void
-RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal)
+RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bool is_index)
 {
 	Relation	targetrelation;
 	Relation	relrelation;	/* for RELATION relation */
@@ -3155,11 +3157,16 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal)
 	Oid			namespaceId;
 
 	/*
-	 * Grab an exclusive lock on the target table, index, sequence, view,
-	 * materialized view, or foreign table, which we will NOT release until
-	 * end of transaction.
+	 * Grab a lock on the target relation, which we will NOT release until end
+	 * of transaction.  We need at least a self-exclusive lock so that
+	 * concurrent DDL doesn't overwrite the rename if they start updating
+	 * while still seeing the old version.  The lock also guards against
+	 * triggering relcache reloads in concurrent sessions, which might not
+	 * handle this information changing under them.  For indexes, we can use a
+	 * reduced lock level because RelationReloadIndexInfo() handles indexes
+	 * specially.
 	 */
-	targetrelation = relation_open(myrelid, AccessExclusiveLock);
+	targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
 	namespaceId = RelationGetNamespace(targetrelation);
 
 	/*
@@ -3212,7 +3219,7 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal)
 	}
 
 	/*
-	 * Close rel, but keep exclusive lock!
+	 * Close rel, but keep lock!
 	 */
 	relation_close(targetrelation, NoLock);
 }
@@ -7070,7 +7077,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
 		ereport(NOTICE,
 				(errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index \"%s\" to \"%s\"",
 						indexName, constraintName)));
-		RenameRelationInternal(index_oid, constraintName, false);
+		RenameRelationInternal(index_oid, constraintName, false, true);
 	}
 
 	/* Extra checks needed if making primary key */
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 3271962a7a..69429a8b62 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3262,7 +3262,7 @@ RenameType(RenameStmt *stmt)
 	 * RenameRelationInternal will call RenameTypeInternal automatically.
 	 */
 	if (typTup->typtype == TYPTYPE_COMPOSITE)
-		RenameRelationInternal(typTup->typrelid, newTypeName, false);
+		RenameRelationInternal(typTup->typrelid, newTypeName, false, false);
 	else
 		RenameTypeInternal(typeOid, newTypeName,
 						   typTup->typnamespace);
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 138de84e83..2afcd5be44 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -67,7 +67,8 @@ extern ObjectAddress RenameConstraint(RenameStmt *stmt);
 extern ObjectAddress RenameRelation(RenameStmt *stmt);
 
 extern void RenameRelationInternal(Oid myrelid,
-					   const char *newrelname, bool is_internal);
+					   const char *newrelname, bool is_internal,
+					   bool is_index);
 
 extern void find_composite_type_dependencies(Oid typeOid,
 								 Relation origRelation,

base-commit: 10074651e3355e2405015f6253602be8344bc829
-- 
2.19.1

#37Fabrízio de Royes Mello
fabriziomello@gmail.com
In reply to: Peter Eisentraut (#36)
Re: Alter index rename concurrently to

On Thu, Oct 25, 2018 at 4:41 AM Peter Eisentraut <
peter.eisentraut@2ndquadrant.com> wrote:

On 17/10/2018 23:11, Peter Eisentraut wrote:

On 13/10/2018 04:01, Andres Freund wrote:

I don't see how this could be argued. It has to be a self-conflicting
lockmode, otherwise you'd end up doing renames of tables where you
cannot see the previous state. And you'd get weird errors about

updating

invisible rows etc.

I don't buy this description. Imo it's a fundamental correctness
thing. Without it concurrent DDL would potentially overwrite the

rename,

because they could start updating while still seeing the old version.

OK, I can refine those descriptions/comments. Do you have any concerns
about the underlying principle of this patch?

Patch with updated comments to reflect your input.

Great... this patch will help a lot so I took the liberty to perform some
review:

- the doc and code (simple) looks good
- the patch apply cleanly against current master
- all tests (check and check-world) past without any issue

I also perform some test using DDLs and DMLs in various sessions:

- renaming indexes / dml against same table
- creating and renaming indexes / altering tables in other session
- renaming indexes / dropping indexes

I didn't found any issue, so the patch looks in a very good shape.

Regards,

--
Fabrízio de Royes Mello Timbira - http://www.timbira.com.br/
PostgreSQL: Consultoria, Desenvolvimento, Suporte 24x7 e Treinamento

#38Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Fabrízio de Royes Mello (#37)
Re: Alter index rename concurrently to

On 25/10/2018 19:35, Fabrízio de Royes Mello wrote:

OK, I can refine those descriptions/comments.  Do you have any concerns
about the underlying principle of this patch?

Patch with updated comments to reflect your input.

I didn't found any issue, so the patch looks in a very good shape.

Committed, thanks all.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services