On Wed, Aug 26, 2020 at 09:08:51PM +0900, Michael Paquier wrote:
> I'll go through this part again tomorrow, it is late here.

There is actually a huge gotcha here that exists since replica
identities exist: relation_mark_replica_identity() only considers
valid indexes!  So, on HEAD, if DROP INDEX CONCURRENTLY fails in the
second transaction doing the physical drop, then we would finish with
a catalog entry that has indisreplident and !indisinvalid.  If you
reindex it afterwards and switch the index back to be valid, there
can be more than one valid index marked with indisreplident, which
messes up with the logic in tablecmds.c because we use
RelationGetIndexList(), that discards invalid indexes.  This case is
actually rather similar to the handling of indisclustered.

So we have a set of two issues here:
1) indisreplident should be switched to false when we clear the valid
flag of an index for INDEX_DROP_CLEAR_VALID.  This issue exists since
9.4.
2) To never finish in a state where we have REPLICA IDENTITY INDEX
without an index marked as indisreplident, we need to reset the
replident of the parent table in the same transaction as the one
clearing indisvalid for the concurrent case.  That's a problem of the
patch proposed upthread.

Attached is a patch for 1) and 2) grouped together, to ease review for
now.  I think that we had better fix 1) separately though, so I am
going to start a new thread about that with a separate patch as the
current thread is misleading.
--
Michael
From 8893acae8b4d14b1cccb3bdcf8c23e46215d2060 Mon Sep 17 00:00:00 2001
From: Michael Paquier <mich...@paquier.xyz>
Date: Thu, 27 Aug 2020 11:27:04 +0900
Subject: [PATCH v4] Reset pg_class.relreplident when associated replica index
 is dropped

When an index associated to REPLICA IDENTITY USING INDEX gets dropped,
relreplident gets set to 'i', which can be confusing as the catalogs
have a state inconsistent with pg_index that lacks an associated entry
marked with indisreplident.  This changes the logic so as such an index
dropped leads to 'n' to be set for the parent table, equivalent to
REPLICA IDENTITY NOTHING.

Author: Michael Paquier
Reviewed-by: Masahiko Sawada, Rahila Syed, Euler Taveira
Discussion: https://postgr.es/m/20200522035028.go2...@paquier.xyz
---
 src/include/commands/tablecmds.h              |  2 +
 src/backend/catalog/index.c                   | 43 +++++++++++++-
 src/backend/commands/tablecmds.c              | 58 ++++++++++++-------
 .../regress/expected/replica_identity.out     | 30 ++++++++++
 src/test/regress/sql/replica_identity.sql     | 22 +++++++
 doc/src/sgml/catalogs.sgml                    |  3 +-
 6 files changed, 132 insertions(+), 26 deletions(-)

diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index c1581ad178..b163a82c52 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -61,6 +61,8 @@ extern void ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_
 
 extern void SetRelationHasSubclass(Oid relationId, bool relhassubclass);
 
+extern void SetRelationReplIdent(Oid relationId, char ri_type);
+
 extern ObjectAddress renameatt(RenameStmt *stmt);
 
 extern ObjectAddress RenameConstraint(RenameStmt *stmt);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 1be27eec52..ccdda7561a 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2005,6 +2005,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	Relation	indexRelation;
 	HeapTuple	tuple;
 	bool		hasexprs;
+	bool		resetreplident;
 	LockRelId	heaprelid,
 				indexrelid;
 	LOCKTAG		heaplocktag;
@@ -2048,6 +2049,15 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/*
+	 * Check if the REPLICA IDENTITY of the parent relation needs to be
+	 * reset.  This should be done only if the index to drop here is
+	 * marked as used in a replica identity.  We cannot rely on the
+	 * contents of pg_index for the index either as a concurrent drop
+	 * may have changed indisreplident already.
+	 */
+	resetreplident = userIndexRelation->rd_index->indisreplident;
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2159,10 +2169,32 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 
 		/* Finish invalidation of index and mark it as dead */
 		index_concurrently_set_dead(heapId, indexId);
+	}
 
+	/*
+	 * If the index to be dropped was marked as indisreplident (it could
+	 * have been updated when clearing indisvalid previously), reset
+	 * pg_class.relreplident for the parent table.  Note that this part
+	 * needs to be done in the same transaction as the one marking the
+	 * index as invalid, so as we never finish with the case where the
+	 * parent's replica identity is based on an index, without any index
+	 * marked with indisreplident.
+	 */
+	if (resetreplident)
+	{
+		SetRelationReplIdent(heapId, REPLICA_IDENTITY_NOTHING);
+
+		/* make the update visible, mandatory for the non-concurrent case */
+		CommandCounterIncrement();
+	}
+
+	/* continue the concurrent process */
+	if (concurrent)
+	{
 		/*
-		 * Again, commit the transaction to make the pg_index update visible
-		 * to other sessions.
+		 * Again, commit the transaction to make the pg_index and pg_class
+		 * (in the case where indisreplident is set) updates are visible to
+		 * other sessions.
 		 */
 		CommitTransactionCommand();
 		StartTransactionCommand();
@@ -3349,10 +3381,13 @@ index_set_state_flags(Oid indexId, IndexStateFlagsAction action)
 			 * CONCURRENTLY that failed partway through.)
 			 *
 			 * Note: the CLUSTER logic assumes that indisclustered cannot be
-			 * set on any invalid index, so clear that flag too.
+			 * set on any invalid index, so clear that flag too.  Similarly,
+			 * ALTER TABLE assumes that indisreplident cannot be set for
+			 * invalid indexes.
 			 */
 			indexForm->indisvalid = false;
 			indexForm->indisclustered = false;
+			indexForm->indisreplident = false;
 			break;
 		case INDEX_DROP_SET_DEAD:
 
@@ -3364,6 +3399,8 @@ index_set_state_flags(Oid indexId, IndexStateFlagsAction action)
 			 * the index at all.
 			 */
 			Assert(!indexForm->indisvalid);
+			Assert(!indexForm->indisclustered);
+			Assert(!indexForm->indisreplident);
 			indexForm->indisready = false;
 			indexForm->indislive = false;
 			break;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d2b15a3387..968cb41f98 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -3020,6 +3020,41 @@ SetRelationHasSubclass(Oid relationId, bool relhassubclass)
 	table_close(relationRelation, RowExclusiveLock);
 }
 
+/*
+ * SetRelationReplIdent
+ *		Set the value of the relation's relreplident field in pg_class.
+ *
+ * NOTE: caller must be holding an appropriate lock on the relation.
+ * ShareUpdateExclusiveLock is sufficient.
+ */
+void
+SetRelationReplIdent(Oid relationId, char ri_type)
+{
+	Relation	relationRelation;
+	HeapTuple	tuple;
+	Form_pg_class classtuple;
+
+	/*
+	 * Fetch a modifiable copy of the tuple, modify it, update pg_class.
+	 */
+	relationRelation = table_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(relationId));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation \"%s\"",
+			 get_rel_name(relationId));
+	classtuple = (Form_pg_class) GETSTRUCT(tuple);
+
+	if (classtuple->relreplident != ri_type)
+	{
+		classtuple->relreplident = ri_type;
+		CatalogTupleUpdate(relationRelation, &tuple->t_self, tuple);
+	}
+
+	heap_freetuple(tuple);
+	table_close(relationRelation, RowExclusiveLock);
+}
+
 /*
  *		renameatt_check			- basic sanity checks before attribute rename
  */
@@ -14487,31 +14522,12 @@ relation_mark_replica_identity(Relation rel, char ri_type, Oid indexOid,
 							   bool is_internal)
 {
 	Relation	pg_index;
-	Relation	pg_class;
-	HeapTuple	pg_class_tuple;
 	HeapTuple	pg_index_tuple;
-	Form_pg_class pg_class_form;
 	Form_pg_index pg_index_form;
-
 	ListCell   *index;
 
-	/*
-	 * Check whether relreplident has changed, and update it if so.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
-	pg_class_tuple = SearchSysCacheCopy1(RELOID,
-										 ObjectIdGetDatum(RelationGetRelid(rel)));
-	if (!HeapTupleIsValid(pg_class_tuple))
-		elog(ERROR, "cache lookup failed for relation \"%s\"",
-			 RelationGetRelationName(rel));
-	pg_class_form = (Form_pg_class) GETSTRUCT(pg_class_tuple);
-	if (pg_class_form->relreplident != ri_type)
-	{
-		pg_class_form->relreplident = ri_type;
-		CatalogTupleUpdate(pg_class, &pg_class_tuple->t_self, pg_class_tuple);
-	}
-	table_close(pg_class, RowExclusiveLock);
-	heap_freetuple(pg_class_tuple);
+	/* update relreplident if necessary */
+	SetRelationReplIdent(RelationGetRelid(rel), ri_type);
 
 	/*
 	 * Check whether the correct index is marked indisreplident; if so, we're
diff --git a/src/test/regress/expected/replica_identity.out b/src/test/regress/expected/replica_identity.out
index 79002197a7..86053c5bb4 100644
--- a/src/test/regress/expected/replica_identity.out
+++ b/src/test/regress/expected/replica_identity.out
@@ -227,3 +227,33 @@ DROP TABLE test_replica_identity;
 DROP TABLE test_replica_identity2;
 DROP TABLE test_replica_identity3;
 DROP TABLE test_replica_identity_othertable;
+--
+-- Test that DROP INDEX on a index used for REPLICA IDENTITY sets
+-- relreplident back to NOTHING.
+--
+CREATE TABLE test_replica_identity_4 (id int NOT NULL);
+CREATE UNIQUE INDEX test_replica_index_4 ON test_replica_identity_4(id);
+ALTER TABLE test_replica_identity_4 REPLICA IDENTITY
+  USING INDEX test_replica_index_4;
+DROP INDEX CONCURRENTLY test_replica_index_4;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity_4'::regclass;
+ relreplident 
+--------------
+ n
+(1 row)
+
+DROP TABLE test_replica_identity_4;
+-- Partitioned table
+CREATE TABLE test_replica_part (id int NOT NULL)
+  PARTITION BY RANGE (id);
+CREATE UNIQUE INDEX test_replica_part_index ON test_replica_part(id);
+ALTER TABLE test_replica_part REPLICA IDENTITY
+  USING INDEX test_replica_part_index;
+DROP INDEX test_replica_part_index;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_part'::regclass;
+ relreplident 
+--------------
+ n
+(1 row)
+
+DROP TABLE test_replica_part;
diff --git a/src/test/regress/sql/replica_identity.sql b/src/test/regress/sql/replica_identity.sql
index a5ac8f5567..6dd34dc39d 100644
--- a/src/test/regress/sql/replica_identity.sql
+++ b/src/test/regress/sql/replica_identity.sql
@@ -98,3 +98,25 @@ DROP TABLE test_replica_identity;
 DROP TABLE test_replica_identity2;
 DROP TABLE test_replica_identity3;
 DROP TABLE test_replica_identity_othertable;
+
+--
+-- Test that DROP INDEX on a index used for REPLICA IDENTITY sets
+-- relreplident back to NOTHING.
+--
+
+CREATE TABLE test_replica_identity_4 (id int NOT NULL);
+CREATE UNIQUE INDEX test_replica_index_4 ON test_replica_identity_4(id);
+ALTER TABLE test_replica_identity_4 REPLICA IDENTITY
+  USING INDEX test_replica_index_4;
+DROP INDEX CONCURRENTLY test_replica_index_4;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity_4'::regclass;
+DROP TABLE test_replica_identity_4;
+-- Partitioned table
+CREATE TABLE test_replica_part (id int NOT NULL)
+  PARTITION BY RANGE (id);
+CREATE UNIQUE INDEX test_replica_part_index ON test_replica_part(id);
+ALTER TABLE test_replica_part REPLICA IDENTITY
+  USING INDEX test_replica_part_index;
+DROP INDEX test_replica_part_index;
+SELECT relreplident FROM pg_class WHERE oid = 'test_replica_part'::regclass;
+DROP TABLE test_replica_part;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9fe260ecff..601b3d448c 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2144,8 +2144,7 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        <literal>n</literal> = nothing,
        <literal>f</literal> = all columns,
        <literal>i</literal> = index with
-       <structfield>indisreplident</structfield> set (same as nothing if the
-       index used has been dropped)
+       <structfield>indisreplident</structfield> set
       </para></entry>
      </row>
 
-- 
2.28.0

Attachment: signature.asc
Description: PGP signature

Reply via email to