Preserve index data in pg_statistic across REINDEX CONCURRENTLY
authorMichael Paquier
Sun, 1 Nov 2020 12:24:15 +0000 (21:24 +0900)
committerMichael Paquier
Sun, 1 Nov 2020 12:24:15 +0000 (21:24 +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 76169b64adc791bf4fff96d64eefd03afa52abdb..4ac05adc73da82d53b2fec4111f28fb4d461659c 100644 (file)
@@ -3113,6 +3113,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 d290c7e980e8c7cfc73e92bf7e0523318500962b..5b2507cd29690426f6e76d6ba18b9bffc965946b 100644 (file)
@@ -1714,6 +1714,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 2e63137897100a0217707a37592f7c0aa190d1b5..39f0271d47fae4fa25bf21e0c6d5fa2e2d514104 100644 (file)
@@ -132,6 +132,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 c3bf910e023c19f93866a6d4a44a6a0708d16a9d..f51c4ec20f9b198580edb8d906c90c6c3c073af5 100644 (file)
@@ -2406,6 +2406,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                                                
 ---------------------------------------------------------------------------------------------------------------
@@ -2463,6 +2474,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 56887526b82278cd18ebc4f10dc2f7cda827cfe1..566931bd4f4058c7321e1abcf2c71282ef22e3ec 100644 (file)
@@ -997,6 +997,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);
@@ -1009,6 +1015,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.