Preserve index data in pg_statistic across REINDEX CONCURRENTLY
authorMichael Paquier <michael@paquier.xyz>
Sun, 1 Nov 2020 12:22:07 +0000 (21:22 +0900)
committerMichael Paquier <michael@paquier.xyz>
Sun, 1 Nov 2020 12:22:07 +0000 (21:22 +0900)
Statistics associated to an index got lost after running REINDEX
CONCURRENTLY, while the non-concurrent case preserves these correctly.
The concurrent and non-concurrent operations need to be consistent for
the end-user, and missing statistics would force to wait for a new
analyze to happen, which could take some time depending on the activity
of the existing autovacuum workers.  This issue is fixed by copying any
existing entries in pg_statistic associated to the old index to the new
one.  Note that this copy is already done with the data of the index in
the stats collector.

Reported-by: Fabrízio de Royes Mello
Author: Michael Paquier, Fabrízio de Royes Mello
Reviewed-by: Justin Pryzby
Discussion: https://postgr.es/m/CAFcNs+qpFPmiHd1oTXvcPdvAHicJDA9qBUSujgAhUMJyUMb+SA@mail.gmail.com
Backpatch-through: 12

src/backend/catalog/heap.c
src/backend/catalog/index.c
src/include/catalog/heap.h
src/test/regress/expected/create_index.out
src/test/regress/sql/create_index.sql

index 67144aa3c94408edebb9bb7f2bf10757bf8d0df3..9ccf378d45b81dbee384e993a94c6436c6e44d82 100644 (file)
@@ -3142,6 +3142,47 @@ cookConstraint(ParseState *pstate,
    return expr;
 }
 
+/*
+ * CopyStatistics --- copy entries in pg_statistic from one rel to another
+ */
+void
+CopyStatistics(Oid fromrelid, Oid torelid)
+{
+   HeapTuple   tup;
+   SysScanDesc scan;
+   ScanKeyData key[1];
+   Relation    statrel;
+
+   statrel = table_open(StatisticRelationId, RowExclusiveLock);
+
+   /* Now search for stat records */
+   ScanKeyInit(&key[0],
+               Anum_pg_statistic_starelid,
+               BTEqualStrategyNumber, F_OIDEQ,
+               ObjectIdGetDatum(fromrelid));
+
+   scan = systable_beginscan(statrel, StatisticRelidAttnumInhIndexId,
+                             true, NULL, 1, key);
+
+   while (HeapTupleIsValid((tup = systable_getnext(scan))))
+   {
+       Form_pg_statistic statform;
+
+       /* make a modifiable copy */
+       tup = heap_copytuple(tup);
+       statform = (Form_pg_statistic) GETSTRUCT(tup);
+
+       /* update the copy of the tuple and insert it */
+       statform->starelid = torelid;
+       CatalogTupleInsert(statrel, tup);
+
+       heap_freetuple(tup);
+   }
+
+   systable_endscan(scan);
+
+   table_close(statrel, RowExclusiveLock);
+}
 
 /*
  * RemoveStatistics --- remove entries in pg_statistic for a rel or column
index 0974f3e23a23726b63246cd3a1347e10923dd541..a18cc7cad309f392bd46612c7f227697712b65c0 100644 (file)
@@ -1711,6 +1711,9 @@ index_concurrently_swap(Oid newIndexId, Oid oldIndexId, const char *oldName)
        }
    }
 
+   /* Copy data of pg_statistic from the old index to the new one */
+   CopyStatistics(oldIndexId, newIndexId);
+
    /* Close relations */
    table_close(pg_class, RowExclusiveLock);
    table_close(pg_index, RowExclusiveLock);
index d31141c1a218f0c5b8d77ab47f2c0ec65a87bf96..f94defff3ce807a53ce3a2db7b729b5237b145f2 100644 (file)
@@ -134,6 +134,7 @@ extern void RemoveAttributeById(Oid relid, AttrNumber attnum);
 extern void RemoveAttrDefault(Oid relid, AttrNumber attnum,
                              DropBehavior behavior, bool complain, bool internal);
 extern void RemoveAttrDefaultById(Oid attrdefId);
+extern void CopyStatistics(Oid fromrelid, Oid torelid);
 extern void RemoveStatistics(Oid relid, AttrNumber attnum);
 
 extern const FormData_pg_attribute *SystemAttributeDefinition(AttrNumber attno);
index 6ace7662ee1f7c9105772708e7c334de5b7d0da6..012c1eb0676cd82b16265891e7ee06c7aa2df9ea 100644 (file)
@@ -2551,6 +2551,17 @@ CREATE UNIQUE INDEX concur_exprs_index_pred ON concur_exprs_tab (c1)
 CREATE UNIQUE INDEX concur_exprs_index_pred_2
   ON concur_exprs_tab ((1 / c1))
   WHERE ('-H') >= (c2::TEXT) COLLATE "C";
+ANALYZE concur_exprs_tab;
+SELECT starelid::regclass, count(*) FROM pg_statistic WHERE starelid IN (
+  'concur_exprs_index_expr'::regclass,
+  'concur_exprs_index_pred'::regclass,
+  'concur_exprs_index_pred_2'::regclass)
+  GROUP BY starelid ORDER BY starelid::regclass::text;
+        starelid         | count 
+-------------------------+-------
+ concur_exprs_index_expr |     1
+(1 row)
+
 SELECT pg_get_indexdef('concur_exprs_index_expr'::regclass);
                                                 pg_get_indexdef                                                
 ---------------------------------------------------------------------------------------------------------------
@@ -2608,6 +2619,17 @@ SELECT pg_get_indexdef('concur_exprs_index_pred_2'::regclass);
  CREATE UNIQUE INDEX concur_exprs_index_pred_2 ON public.concur_exprs_tab USING btree (((1 / c1))) WHERE ('-H'::text >= (c2 COLLATE "C"))
 (1 row)
 
+-- Statistics should remain intact.
+SELECT starelid::regclass, count(*) FROM pg_statistic WHERE starelid IN (
+  'concur_exprs_index_expr'::regclass,
+  'concur_exprs_index_pred'::regclass,
+  'concur_exprs_index_pred_2'::regclass)
+  GROUP BY starelid ORDER BY starelid::regclass::text;
+        starelid         | count 
+-------------------------+-------
+ concur_exprs_index_expr |     1
+(1 row)
+
 DROP TABLE concur_exprs_tab;
 -- Temporary tables and on-commit actions, where CONCURRENTLY is ignored.
 -- ON COMMIT PRESERVE ROWS, the default.
index 37f7259da9e6840383129852e0f8e2fc58a9416c..4e45b186131bcad4366cedeb5352b8252f402b4d 100644 (file)
@@ -1079,6 +1079,12 @@ CREATE UNIQUE INDEX concur_exprs_index_pred ON concur_exprs_tab (c1)
 CREATE UNIQUE INDEX concur_exprs_index_pred_2
   ON concur_exprs_tab ((1 / c1))
   WHERE ('-H') >= (c2::TEXT) COLLATE "C";
+ANALYZE concur_exprs_tab;
+SELECT starelid::regclass, count(*) FROM pg_statistic WHERE starelid IN (
+  'concur_exprs_index_expr'::regclass,
+  'concur_exprs_index_pred'::regclass,
+  'concur_exprs_index_pred_2'::regclass)
+  GROUP BY starelid ORDER BY starelid::regclass::text;
 SELECT pg_get_indexdef('concur_exprs_index_expr'::regclass);
 SELECT pg_get_indexdef('concur_exprs_index_pred'::regclass);
 SELECT pg_get_indexdef('concur_exprs_index_pred_2'::regclass);
@@ -1091,6 +1097,12 @@ ALTER TABLE concur_exprs_tab ALTER c2 TYPE TEXT;
 SELECT pg_get_indexdef('concur_exprs_index_expr'::regclass);
 SELECT pg_get_indexdef('concur_exprs_index_pred'::regclass);
 SELECT pg_get_indexdef('concur_exprs_index_pred_2'::regclass);
+-- Statistics should remain intact.
+SELECT starelid::regclass, count(*) FROM pg_statistic WHERE starelid IN (
+  'concur_exprs_index_expr'::regclass,
+  'concur_exprs_index_pred'::regclass,
+  'concur_exprs_index_pred_2'::regclass)
+  GROUP BY starelid ORDER BY starelid::regclass::text;
 DROP TABLE concur_exprs_tab;
 
 -- Temporary tables and on-commit actions, where CONCURRENTLY is ignored.