Fix potential assertion failure when reindexing a pg_class index.
authorAndres Freund <[email protected]>
Tue, 30 Apr 2019 02:39:36 +0000 (19:39 -0700)
committerAndres Freund <[email protected]>
Tue, 30 Apr 2019 02:48:58 +0000 (19:48 -0700)
When reindexing individual indexes on pg_class it was possible to
either trigger an assertion failure:
TRAP: FailedAssertion("!(!ReindexIsProcessingIndex(((index)->rd_id)))

That's because reindex_index() called SetReindexProcessing() - which
enables an asserts ensuring no index insertions happen into the index
- before calling RelationSetNewRelfilenode(). That not correct for
indexes on pg_class, because RelationSetNewRelfilenode() updates the
relevant pg_class row, which needs to update the indexes.

The are two reasons this wasn't noticed earlier. Firstly the bug
doesn't trigger when reindexing all of pg_class, as reindex_relation
has code "hiding" all yet-to-be-reindexed indexes. Secondly, the bug
only triggers when the the update to pg_class doesn't turn out to be a
HOT update - otherwise there's no index insertion to trigger the
bug. Most of the time there's enough space, making this bug hard to
trigger.

To fix, move RelationSetNewRelfilenode() to before the
SetReindexProcessing() (and, together with some other code, to outside
of the PG_TRY()).

To make sure the error checking intended by SetReindexProcessing() is
more robust, modify CatalogIndexInsert() to check
ReindexIsProcessingIndex() even when the update is a HOT update.

Also add a few regression tests for REINDEXing of system catalogs.

The last two improvements would have prevented some of the issues
fixed in 5c1560606dc4c from being introduced in the first place.

Reported-By: Michael Paquier
Diagnosed-By: Tom Lane and Andres Freund
Author: Andres Freund
Reviewed-By: Tom Lane
Discussion: https://postgr.es/m/20190418011430[email protected]
Backpatch: 9.4-, the bug is present in all branches

contrib/test_decoding/expected/rewrite.out
contrib/test_decoding/sql/rewrite.sql
src/backend/catalog/index.c
src/backend/catalog/indexing.c
src/test/regress/expected/create_index.out
src/test/regress/sql/create_index.sql

index 3bf2afa9315f5a1cbc0f7d6f332aa93aeb201c73..28998b86f9e91b8863341d532cb0a97181be987a 100644 (file)
@@ -103,6 +103,10 @@ COMMIT;
  -- repeated rewrites in different transactions
 VACUUM FULL pg_class;
 VACUUM FULL pg_class;
+-- reindexing of important relations / indexes
+REINDEX TABLE pg_class;
+REINDEX INDEX pg_class_oid_index;
+REINDEX INDEX pg_class_tblspc_relfilenode_index;
 INSERT INTO replication_example(somedata, testcolumn1) VALUES (5, 3);
 BEGIN;
 INSERT INTO replication_example(somedata, testcolumn1) VALUES (6, 4);
index 4271b82bead4b290357f4853afd2e85028b8a94b..c9503a0da59b98c20a1632190362d40d33d49ca5 100644 (file)
@@ -74,6 +74,11 @@ COMMIT;
 VACUUM FULL pg_class;
 VACUUM FULL pg_class;
 
+-- reindexing of important relations / indexes
+REINDEX TABLE pg_class;
+REINDEX INDEX pg_class_oid_index;
+REINDEX INDEX pg_class_tblspc_relfilenode_index;
+
 INSERT INTO replication_example(somedata, testcolumn1) VALUES (5, 3);
 
 BEGIN;
index 074f4ec9bc80b9252c15356a8c2fe020091e791b..5bd5511ce707129c16d3b6fef6d9d105fcd79e81 100644 (file)
@@ -3143,29 +3143,35 @@ reindex_index(Oid indexId, bool skip_constraint_checks)
     */
    TransferPredicateLocksToHeapRelation(iRel);
 
+   /* Fetch info needed for index_build */
+   indexInfo = BuildIndexInfo(iRel);
+
+   /* If requested, skip checking uniqueness/exclusion constraints */
+   if (skip_constraint_checks)
+   {
+       if (indexInfo->ii_Unique || indexInfo->ii_ExclusionOps != NULL)
+           skipped_constraint = true;
+       indexInfo->ii_Unique = false;
+       indexInfo->ii_ExclusionOps = NULL;
+       indexInfo->ii_ExclusionProcs = NULL;
+       indexInfo->ii_ExclusionStrats = NULL;
+   }
+
+   /*
+    * Build a new physical relation for the index. Need to do that before
+    * "officially" starting the reindexing with SetReindexProcessing -
+    * otherwise the necessary pg_class changes cannot be made with
+    * encountering assertions.
+    */
+   RelationSetNewRelfilenode(iRel, InvalidTransactionId,
+                             InvalidMultiXactId);
+
+   /* ensure SetReindexProcessing state isn't leaked */
    PG_TRY();
    {
        /* Suppress use of the target index while rebuilding it */
        SetReindexProcessing(heapId, indexId);
 
-       /* Fetch info needed for index_build */
-       indexInfo = BuildIndexInfo(iRel);
-
-       /* If requested, skip checking uniqueness/exclusion constraints */
-       if (skip_constraint_checks)
-       {
-           if (indexInfo->ii_Unique || indexInfo->ii_ExclusionOps != NULL)
-               skipped_constraint = true;
-           indexInfo->ii_Unique = false;
-           indexInfo->ii_ExclusionOps = NULL;
-           indexInfo->ii_ExclusionProcs = NULL;
-           indexInfo->ii_ExclusionStrats = NULL;
-       }
-
-       /* We'll build a new physical relation for the index */
-       RelationSetNewRelfilenode(iRel, InvalidTransactionId,
-                                 InvalidMultiXactId);
-
        /* Initialize the index and rebuild */
        /* Note: we do not need to re-establish pkey setting */
        index_build(heapRelation, iRel, indexInfo, false, true);
index 05aa56e8593d23521b0114832d5dbc459c8ff00d..8ae8f470df47f3c9e1b8ef5b911ba369f12ff7aa 100644 (file)
@@ -80,9 +80,15 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
    Datum       values[INDEX_MAX_KEYS];
    bool        isnull[INDEX_MAX_KEYS];
 
-   /* HOT update does not require index inserts */
+   /*
+    * HOT update does not require index inserts. But with asserts enabled we
+    * want to check that it'd be legal to currently insert into the
+    * table/index.
+    */
+#ifndef USE_ASSERT_CHECKING
    if (HeapTupleIsHeapOnly(heapTuple))
        return;
+#endif
 
    /*
     * Get information from the state structure.  Fall out if nothing to do.
@@ -104,8 +110,10 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
    for (i = 0; i < numIndexes; i++)
    {
        IndexInfo  *indexInfo;
+       Relation    index;
 
        indexInfo = indexInfoArray[i];
+       index = relationDescs[i];
 
        /* If the index is marked as read-only, ignore it */
        if (!indexInfo->ii_ReadyForInserts)
@@ -118,7 +126,16 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
        Assert(indexInfo->ii_Expressions == NIL);
        Assert(indexInfo->ii_Predicate == NIL);
        Assert(indexInfo->ii_ExclusionOps == NULL);
-       Assert(relationDescs[i]->rd_index->indimmediate);
+       Assert(index->rd_index->indimmediate);
+
+       /* see earlier check above */
+#ifdef USE_ASSERT_CHECKING
+       if (HeapTupleIsHeapOnly(heapTuple))
+       {
+           Assert(!ReindexIsProcessingIndex(RelationGetRelid(index)));
+           continue;
+       }
+#endif /* USE_ASSERT_CHECKING */
 
        /*
         * FormIndexDatum fills in its values and isnull parameters with the
index 728cd1eabe7e41a3487d2813f653f6c84cf89fd9..d123d43d807f810760e5c380df6928b2abbee6ef 100644 (file)
@@ -2804,3 +2804,21 @@ explain (costs off)
    Index Cond: ((thousand = 1) AND (tenthous = 1001))
 (2 rows)
 
+--
+-- check that system tables can be reindexed
+--
+-- whole tables
+REINDEX TABLE pg_class; -- mapped, non-shared, critical
+REINDEX TABLE pg_index; -- non-mapped, non-shared, critical
+REINDEX TABLE pg_operator; -- non-mapped, non-shared, critical
+REINDEX TABLE pg_database; -- mapped, shared, critical
+REINDEX TABLE pg_shdescription; -- mapped, shared non-critical
+-- Check that individual system indexes can be reindexed. That's a bit
+-- different from the entire-table case because reindex_relation
+-- treats e.g. pg_class special.
+REINDEX INDEX pg_class_oid_index; -- mapped, non-shared, critical
+REINDEX INDEX pg_class_relname_nsp_index; -- mapped, non-shared, non-critical
+REINDEX INDEX pg_index_indexrelid_index; -- non-mapped, non-shared, critical
+REINDEX INDEX pg_index_indrelid_index; -- non-mapped, non-shared, non-critical
+REINDEX INDEX pg_database_oid_index; -- mapped, shared, critical
+REINDEX INDEX pg_shdescription_o_c_index; -- mapped, shared, non-critical
index 5008e981ab0dff24c7ceb32ed619a9abbb22a8ba..0c6e38637387ac1f4d41ca74776e2bc5c665cb91 100644 (file)
@@ -951,3 +951,24 @@ RESET enable_indexonlyscan;
 
 explain (costs off)
   select * from tenk1 where (thousand, tenthous) in ((1,1001), (null,null));
+
+--
+-- check that system tables can be reindexed
+--
+
+-- whole tables
+REINDEX TABLE pg_class; -- mapped, non-shared, critical
+REINDEX TABLE pg_index; -- non-mapped, non-shared, critical
+REINDEX TABLE pg_operator; -- non-mapped, non-shared, critical
+REINDEX TABLE pg_database; -- mapped, shared, critical
+REINDEX TABLE pg_shdescription; -- mapped, shared non-critical
+
+-- Check that individual system indexes can be reindexed. That's a bit
+-- different from the entire-table case because reindex_relation
+-- treats e.g. pg_class special.
+REINDEX INDEX pg_class_oid_index; -- mapped, non-shared, critical
+REINDEX INDEX pg_class_relname_nsp_index; -- mapped, non-shared, non-critical
+REINDEX INDEX pg_index_indexrelid_index; -- non-mapped, non-shared, critical
+REINDEX INDEX pg_index_indrelid_index; -- non-mapped, non-shared, non-critical
+REINDEX INDEX pg_database_oid_index; -- mapped, shared, critical
+REINDEX INDEX pg_shdescription_o_c_index; -- mapped, shared, non-critical