Partitioned index can be not dumped
Hi.
I've seen the following effect on PostgreSQL 14 stable branch.
Index, created on partitioned table, disappears from pg_dump or psql \d
output.
This seems to begin after analyze. Partitoned relation relhasindex
pg_class field suddenly becomes false.
The issue happens after
commit 0e69f705cc1a3df273b38c9883fb5765991e04fe (HEAD, refs/bisect/bad)
Author: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri Apr 9 11:29:08 2021 -0400
Set pg_class.reltuples for partitioned tables
When commit 0827e8af70f4 added auto-analyze support for partitioned
tables, it included code to obtain reltuples for the partitioned
table
as a number of catalog accesses to read pg_class.reltuples for each
partition. That's not only very inefficient, but also problematic
because autovacuum doesn't hold any locks on any of those tables --
and
doesn't want to. Replace that code with a read of
pg_class.reltuples
for the partitioned table, and make sure ANALYZE and TRUNCATE
properly
maintain that value.
I found no code that would be affected by the change of relpages
from
zero to non-zero for partitioned tables, and no other code that
should
be maintaining it, but if there is, hopefully it'll be an easy fix.
Per buildfarm.
Author: Álvaro Herrera <alvherre@alvh.no-ip.org>
Reviewed-by: Zhihong Yu <zyu@yugabyte.com>
It seems that in this commit we unconditionally overwrite this data with
0.
I've tried to fix it by getting this information when inh is true and
ignoring nindexes when inh is not true.
--
Best regards,
Alexander Pyhalov,
Postgres Professional
Attachments:
0001-Set-relhasindex-for-partitioned-tables-correctly.patchtext/x-diff; name=0001-Set-relhasindex-for-partitioned-tables-correctly.patchDownload
From 0e66025a32c6e848b2b77355631b06b4b8d4dd08 Mon Sep 17 00:00:00 2001
From: Alexander Pyhalov <a.pyhalov@postgrespro.ru>
Date: Wed, 30 Jun 2021 17:22:37 +0300
Subject: [PATCH] Set relhasindex for partitioned tables correctly.
The issue appeared after 0e69f705cc1a3df273b38c9883fb5765991e04fe:
in this commit we unconditionally set nindexes to 0 for partitioned
relations.
---
src/backend/commands/analyze.c | 53 ++++++++++++++++------------
src/test/regress/expected/vacuum.out | 18 ++++++++++
src/test/regress/sql/vacuum.sql | 12 +++++++
3 files changed, 60 insertions(+), 23 deletions(-)
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 426c1e67109..9c8913a1619 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -424,8 +424,10 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
* doing a recursive scan, we don't want to touch the parent's indexes at
* all.
*/
- if (!inh)
+ if (!inh || onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
vac_open_indexes(onerel, AccessShareLock, &nindexes, &Irel);
+ }
else
{
Irel = NULL;
@@ -433,7 +435,7 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
}
hasindex = (nindexes > 0);
indexdata = NULL;
- if (hasindex)
+ if (hasindex && !inh)
{
indexdata = (AnlIndexData *) palloc0(nindexes * sizeof(AnlIndexData));
for (ind = 0; ind < nindexes; ind++)
@@ -487,14 +489,17 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
if (targrows < vacattrstats[i]->minrows)
targrows = vacattrstats[i]->minrows;
}
- for (ind = 0; ind < nindexes; ind++)
+ if (!inh)
{
- AnlIndexData *thisdata = &indexdata[ind];
-
- for (i = 0; i < thisdata->attr_cnt; i++)
+ for (ind = 0; ind < nindexes; ind++)
{
- if (targrows < thisdata->vacattrstats[i]->minrows)
- targrows = thisdata->vacattrstats[i]->minrows;
+ AnlIndexData *thisdata = &indexdata[ind];
+
+ for (i = 0; i < thisdata->attr_cnt; i++)
+ {
+ if (targrows < thisdata->vacattrstats[i]->minrows)
+ targrows = thisdata->vacattrstats[i]->minrows;
+ }
}
}
@@ -572,7 +577,7 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
MemoryContextResetAndDeleteChildren(col_context);
}
- if (hasindex)
+ if (hasindex & !inh)
compute_index_stats(onerel, totalrows,
indexdata, nindexes,
rows, numrows,
@@ -589,23 +594,25 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
update_attstats(RelationGetRelid(onerel), inh,
attr_cnt, vacattrstats);
- for (ind = 0; ind < nindexes; ind++)
+ if (!inh)
{
- AnlIndexData *thisdata = &indexdata[ind];
+ for (ind = 0; ind < nindexes; ind++)
+ {
+ AnlIndexData *thisdata = &indexdata[ind];
- update_attstats(RelationGetRelid(Irel[ind]), false,
- thisdata->attr_cnt, thisdata->vacattrstats);
- }
+ update_attstats(RelationGetRelid(Irel[ind]), false,
+ thisdata->attr_cnt, thisdata->vacattrstats);
+ }
- /*
- * Build extended statistics (if there are any).
- *
- * For now we only build extended statistics on individual relations,
- * not for relations representing inheritance trees.
- */
- if (!inh)
+ /*
+ * Build extended statistics (if there are any).
+ *
+ * For now we only build extended statistics on individual
+ * relations, not for relations representing inheritance trees.
+ */
BuildRelationExtStatistics(onerel, totalrows, numrows, rows,
attr_cnt, vacattrstats);
+ }
}
pgstat_progress_update_param(PROGRESS_ANALYZE_PHASE,
@@ -663,7 +670,7 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
* for auto-analyze to work properly.
*/
vac_update_relstats(onerel, -1, totalrows,
- 0, false, InvalidTransactionId,
+ 0, hasindex, InvalidTransactionId,
InvalidMultiXactId,
in_outer_xact);
}
@@ -704,7 +711,7 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
* amvacuumcleanup() when called in ANALYZE-only mode. The only exception
* among core index AMs is GIN/ginvacuumcleanup().
*/
- if (!(params->options & VACOPT_VACUUM))
+ if (!(params->options & VACOPT_VACUUM) && !inh)
{
for (ind = 0; ind < nindexes; ind++)
{
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index e5771462d57..405c6cca94b 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -199,6 +199,24 @@ VACUUM ANALYZE vacparted(a,b,a);
ERROR: column "a" of relation "vacparted" appears more than once
ANALYZE vacparted(a,b,b);
ERROR: column "b" of relation "vacparted" appears more than once
+-- partitioned table with index
+CREATE TABLE vacparted_i (a int primary key, b varchar(100)) PARTITION BY HASH (a);
+CREATE TABLE vacparted_i1 PARTITION OF vacparted_i FOR VALUES WITH ( MODULUS 2, REMAINDER 0);
+CREATE TABLE vacparted_i2 PARTITION OF vacparted_i FOR VALUES WITH ( MODULUS 2, REMAINDER 1);
+INSERT INTO vacparted SELECT i, 'test_'|| i from generate_series(1,10) i;
+ERROR: value too long for type character(1)
+VACUUM (ANALYZE) vacparted_i;
+VACUUM (FULL) vacparted_i;
+VACUUM (FREEZE) vacparted_i;
+SELECT relname,relhasindex from pg_class where relname LIKE 'vacparted_i%' and relkind in ('p','r') ORDER BY relname;
+ relname | relhasindex
+--------------+-------------
+ vacparted_i | t
+ vacparted_i1 | t
+ vacparted_i2 | t
+(3 rows)
+
+DROP TABLE vacparted_i;
-- multiple tables specified
VACUUM vaccluster, vactst;
VACUUM vacparted, does_not_exist;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index f220fc28a70..94d69192487 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -166,10 +166,22 @@ VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
-- check behavior with duplicate column mentions
VACUUM ANALYZE vacparted(a,b,a);
ANALYZE vacparted(a,b,b);
+-- partitioned table with index
+CREATE TABLE vacparted_i (a int primary key, b varchar(100)) PARTITION BY HASH (a);
+CREATE TABLE vacparted_i1 PARTITION OF vacparted_i FOR VALUES WITH ( MODULUS 2, REMAINDER 0);
+CREATE TABLE vacparted_i2 PARTITION OF vacparted_i FOR VALUES WITH ( MODULUS 2, REMAINDER 1);
+INSERT INTO vacparted SELECT i, 'test_'|| i from generate_series(1,10) i;
+VACUUM (ANALYZE) vacparted_i;
+VACUUM (FULL) vacparted_i;
+VACUUM (FREEZE) vacparted_i;
+SELECT relname,relhasindex from pg_class where relname LIKE 'vacparted_i%' and relkind in ('p','r') ORDER BY relname;
+DROP TABLE vacparted_i;
+
-- multiple tables specified
VACUUM vaccluster, vactst;
VACUUM vacparted, does_not_exist;
--
2.25.1
Alexander Pyhalov писал 2021-06-30 17:26:
Hi.
Sorry, test had an issue.
--
Best regards,
Alexander Pyhalov,
Postgres Professional
Attachments:
0001-Set-relhasindex-for-partitioned-tables-correctly-v2.patchtext/x-diff; name=0001-Set-relhasindex-for-partitioned-tables-correctly-v2.patchDownload
From 2aabf5e8e86d222e6a73b25ccc652fe645e12fc4 Mon Sep 17 00:00:00 2001
From: Alexander Pyhalov <a.pyhalov@postgrespro.ru>
Date: Wed, 30 Jun 2021 17:22:37 +0300
Subject: [PATCH] Set relhasindex for partitioned tables correctly.
The issue appeared after 0e69f705cc1a3df273b38c9883fb5765991e04fe:
in this commit we unconditionally set nindexes to 0 for partitioned
relations.
---
src/backend/commands/analyze.c | 53 ++++++++++++++++------------
src/test/regress/expected/vacuum.out | 17 +++++++++
src/test/regress/sql/vacuum.sql | 12 +++++++
3 files changed, 59 insertions(+), 23 deletions(-)
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 426c1e67109..9c8913a1619 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -424,8 +424,10 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
* doing a recursive scan, we don't want to touch the parent's indexes at
* all.
*/
- if (!inh)
+ if (!inh || onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
vac_open_indexes(onerel, AccessShareLock, &nindexes, &Irel);
+ }
else
{
Irel = NULL;
@@ -433,7 +435,7 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
}
hasindex = (nindexes > 0);
indexdata = NULL;
- if (hasindex)
+ if (hasindex && !inh)
{
indexdata = (AnlIndexData *) palloc0(nindexes * sizeof(AnlIndexData));
for (ind = 0; ind < nindexes; ind++)
@@ -487,14 +489,17 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
if (targrows < vacattrstats[i]->minrows)
targrows = vacattrstats[i]->minrows;
}
- for (ind = 0; ind < nindexes; ind++)
+ if (!inh)
{
- AnlIndexData *thisdata = &indexdata[ind];
-
- for (i = 0; i < thisdata->attr_cnt; i++)
+ for (ind = 0; ind < nindexes; ind++)
{
- if (targrows < thisdata->vacattrstats[i]->minrows)
- targrows = thisdata->vacattrstats[i]->minrows;
+ AnlIndexData *thisdata = &indexdata[ind];
+
+ for (i = 0; i < thisdata->attr_cnt; i++)
+ {
+ if (targrows < thisdata->vacattrstats[i]->minrows)
+ targrows = thisdata->vacattrstats[i]->minrows;
+ }
}
}
@@ -572,7 +577,7 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
MemoryContextResetAndDeleteChildren(col_context);
}
- if (hasindex)
+ if (hasindex & !inh)
compute_index_stats(onerel, totalrows,
indexdata, nindexes,
rows, numrows,
@@ -589,23 +594,25 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
update_attstats(RelationGetRelid(onerel), inh,
attr_cnt, vacattrstats);
- for (ind = 0; ind < nindexes; ind++)
+ if (!inh)
{
- AnlIndexData *thisdata = &indexdata[ind];
+ for (ind = 0; ind < nindexes; ind++)
+ {
+ AnlIndexData *thisdata = &indexdata[ind];
- update_attstats(RelationGetRelid(Irel[ind]), false,
- thisdata->attr_cnt, thisdata->vacattrstats);
- }
+ update_attstats(RelationGetRelid(Irel[ind]), false,
+ thisdata->attr_cnt, thisdata->vacattrstats);
+ }
- /*
- * Build extended statistics (if there are any).
- *
- * For now we only build extended statistics on individual relations,
- * not for relations representing inheritance trees.
- */
- if (!inh)
+ /*
+ * Build extended statistics (if there are any).
+ *
+ * For now we only build extended statistics on individual
+ * relations, not for relations representing inheritance trees.
+ */
BuildRelationExtStatistics(onerel, totalrows, numrows, rows,
attr_cnt, vacattrstats);
+ }
}
pgstat_progress_update_param(PROGRESS_ANALYZE_PHASE,
@@ -663,7 +670,7 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
* for auto-analyze to work properly.
*/
vac_update_relstats(onerel, -1, totalrows,
- 0, false, InvalidTransactionId,
+ 0, hasindex, InvalidTransactionId,
InvalidMultiXactId,
in_outer_xact);
}
@@ -704,7 +711,7 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
* amvacuumcleanup() when called in ANALYZE-only mode. The only exception
* among core index AMs is GIN/ginvacuumcleanup().
*/
- if (!(params->options & VACOPT_VACUUM))
+ if (!(params->options & VACOPT_VACUUM) && !inh)
{
for (ind = 0; ind < nindexes; ind++)
{
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index e5771462d57..e3d462b66fa 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -199,6 +199,23 @@ VACUUM ANALYZE vacparted(a,b,a);
ERROR: column "a" of relation "vacparted" appears more than once
ANALYZE vacparted(a,b,b);
ERROR: column "b" of relation "vacparted" appears more than once
+-- partitioned table with index
+CREATE TABLE vacparted_i (a int primary key, b varchar(100)) PARTITION BY HASH (a);
+CREATE TABLE vacparted_i1 PARTITION OF vacparted_i FOR VALUES WITH ( MODULUS 2, REMAINDER 0);
+CREATE TABLE vacparted_i2 PARTITION OF vacparted_i FOR VALUES WITH ( MODULUS 2, REMAINDER 1);
+INSERT INTO vacparted_i SELECT i, 'test_'|| i from generate_series(1,10) i;
+VACUUM (ANALYZE) vacparted_i;
+VACUUM (FULL) vacparted_i;
+VACUUM (FREEZE) vacparted_i;
+SELECT relname,relhasindex from pg_class where relname LIKE 'vacparted_i%' and relkind in ('p','r') ORDER BY relname;
+ relname | relhasindex
+--------------+-------------
+ vacparted_i | t
+ vacparted_i1 | t
+ vacparted_i2 | t
+(3 rows)
+
+DROP TABLE vacparted_i;
-- multiple tables specified
VACUUM vaccluster, vactst;
VACUUM vacparted, does_not_exist;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index f220fc28a70..b670612b548 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -166,10 +166,22 @@ VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
-- check behavior with duplicate column mentions
VACUUM ANALYZE vacparted(a,b,a);
ANALYZE vacparted(a,b,b);
+-- partitioned table with index
+CREATE TABLE vacparted_i (a int primary key, b varchar(100)) PARTITION BY HASH (a);
+CREATE TABLE vacparted_i1 PARTITION OF vacparted_i FOR VALUES WITH ( MODULUS 2, REMAINDER 0);
+CREATE TABLE vacparted_i2 PARTITION OF vacparted_i FOR VALUES WITH ( MODULUS 2, REMAINDER 1);
+INSERT INTO vacparted_i SELECT i, 'test_'|| i from generate_series(1,10) i;
+VACUUM (ANALYZE) vacparted_i;
+VACUUM (FULL) vacparted_i;
+VACUUM (FREEZE) vacparted_i;
+SELECT relname,relhasindex from pg_class where relname LIKE 'vacparted_i%' and relkind in ('p','r') ORDER BY relname;
+DROP TABLE vacparted_i;
+
-- multiple tables specified
VACUUM vaccluster, vactst;
VACUUM vacparted, does_not_exist;
--
2.25.1
On 2021-Jun-30, Alexander Pyhalov wrote:
Hi.
I've seen the following effect on PostgreSQL 14 stable branch.
Index, created on partitioned table, disappears from pg_dump or psql \d
output.
This seems to begin after analyze. Partitoned relation relhasindex pg_class
field suddenly becomes false.
Uh, ouch.
I'll look into this shortly.
--
�lvaro Herrera Valdivia, Chile
https://www.EnterpriseDB.com/
On 2021-Jun-30, Alexander Pyhalov wrote:
I've seen the following effect on PostgreSQL 14 stable branch.
Index, created on partitioned table, disappears from pg_dump or psql \d
output.
This seems to begin after analyze. Partitoned relation relhasindex pg_class
field suddenly becomes false.
Yeah, that seems correct.
I didn't verify your test case, but after looking at the code I thought
there was a bit too much churn and the new conditions looked quite messy
and unexplained. It seems simpler to be explicit at the start about
what we're doing, and keep nindexes=0 for partitioned tables; with that,
the code works unchanged because the "for" loops do nothing without
having to check for anything. My proposal is attached.
I did run the tests and they do pass, but I didn't look very closely at
what the tests are actually doing.
I noticed that part of that comment seems to be a leftover from ... I
don't know when: "We do not analyze index columns if there was an
explicit column list in the ANALYZE command, however." I suppose this
is about some code that was removed, but I didn't dig into it.
--
�lvaro Herrera Valdivia, Chile
"How strange it is to find the words "Perl" and "saner" in such close
proximity, with no apparent sense of irony. I doubt that Larry himself
could have managed it." (ncm, http://lwn.net/Articles/174769/)
Attachments:
v3-0001-Set-relhasindex-for-partitioned-tables-correctly.patchtext/x-diff; charset=us-asciiDownload
From 05ca1eed3e2e1632477586ee5bc4d7b24ad045aa Mon Sep 17 00:00:00 2001
From: Alexander Pyhalov <a.pyhalov@postgrespro.ru>
Date: Wed, 30 Jun 2021 17:22:37 +0300
Subject: [PATCH v3] Set relhasindex for partitioned tables correctly.
The issue appeared after 0e69f705cc1a3df273b38c9883fb5765991e04fe:
in this commit we unconditionally set nindexes to 0 for partitioned
relations.
---
src/backend/commands/analyze.c | 29 ++++++++++++++++++----------
src/test/regress/expected/vacuum.out | 17 ++++++++++++++++
src/test/regress/sql/vacuum.sql | 12 ++++++++++++
3 files changed, 48 insertions(+), 10 deletions(-)
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 426c1e6710..3980baa41c 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -419,21 +419,30 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
/*
* Open all indexes of the relation, and see if there are any analyzable
- * columns in the indexes. We do not analyze index columns if there was
- * an explicit column list in the ANALYZE command, however. If we are
- * doing a recursive scan, we don't want to touch the parent's indexes at
- * all.
+ * columns in the indexes. If we are doing a recursive scan, we don't
+ * want to touch the parent's indexes at all. If we're processing a
+ * partitioned table, we need to know if there are any indexes, but we
+ * don't want to process them.
*/
- if (!inh)
+ if (!inh || onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
vac_open_indexes(onerel, AccessShareLock, &nindexes, &Irel);
+ hasindex = nindexes > 0;
+ if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ vac_close_indexes(nindexes, Irel, NoLock);
+ nindexes = 0;
+ Irel = NULL;
+ }
+ }
else
{
Irel = NULL;
nindexes = 0;
+ hasindex = false;
}
- hasindex = (nindexes > 0);
indexdata = NULL;
- if (hasindex)
+ if (nindexes > 0)
{
indexdata = (AnlIndexData *) palloc0(nindexes * sizeof(AnlIndexData));
for (ind = 0; ind < nindexes; ind++)
@@ -572,7 +581,7 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
MemoryContextResetAndDeleteChildren(col_context);
}
- if (hasindex)
+ if (nindexes > 0)
compute_index_stats(onerel, totalrows,
indexdata, nindexes,
rows, numrows,
@@ -660,10 +669,10 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
/*
* Partitioned tables don't have storage, so we don't set any fields
* in their pg_class entries except for reltuples, which is necessary
- * for auto-analyze to work properly.
+ * for auto-analyze to work properly, and relhasindex.
*/
vac_update_relstats(onerel, -1, totalrows,
- 0, false, InvalidTransactionId,
+ 0, hasindex, InvalidTransactionId,
InvalidMultiXactId,
in_outer_xact);
}
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index e5771462d5..e3d462b66f 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -199,6 +199,23 @@ VACUUM ANALYZE vacparted(a,b,a);
ERROR: column "a" of relation "vacparted" appears more than once
ANALYZE vacparted(a,b,b);
ERROR: column "b" of relation "vacparted" appears more than once
+-- partitioned table with index
+CREATE TABLE vacparted_i (a int primary key, b varchar(100)) PARTITION BY HASH (a);
+CREATE TABLE vacparted_i1 PARTITION OF vacparted_i FOR VALUES WITH ( MODULUS 2, REMAINDER 0);
+CREATE TABLE vacparted_i2 PARTITION OF vacparted_i FOR VALUES WITH ( MODULUS 2, REMAINDER 1);
+INSERT INTO vacparted_i SELECT i, 'test_'|| i from generate_series(1,10) i;
+VACUUM (ANALYZE) vacparted_i;
+VACUUM (FULL) vacparted_i;
+VACUUM (FREEZE) vacparted_i;
+SELECT relname,relhasindex from pg_class where relname LIKE 'vacparted_i%' and relkind in ('p','r') ORDER BY relname;
+ relname | relhasindex
+--------------+-------------
+ vacparted_i | t
+ vacparted_i1 | t
+ vacparted_i2 | t
+(3 rows)
+
+DROP TABLE vacparted_i;
-- multiple tables specified
VACUUM vaccluster, vactst;
VACUUM vacparted, does_not_exist;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index f220fc28a7..b670612b54 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -166,10 +166,22 @@ VACUUM (ANALYZE) vacparted;
VACUUM (FULL) vacparted;
VACUUM (FREEZE) vacparted;
+
-- check behavior with duplicate column mentions
VACUUM ANALYZE vacparted(a,b,a);
ANALYZE vacparted(a,b,b);
+-- partitioned table with index
+CREATE TABLE vacparted_i (a int primary key, b varchar(100)) PARTITION BY HASH (a);
+CREATE TABLE vacparted_i1 PARTITION OF vacparted_i FOR VALUES WITH ( MODULUS 2, REMAINDER 0);
+CREATE TABLE vacparted_i2 PARTITION OF vacparted_i FOR VALUES WITH ( MODULUS 2, REMAINDER 1);
+INSERT INTO vacparted_i SELECT i, 'test_'|| i from generate_series(1,10) i;
+VACUUM (ANALYZE) vacparted_i;
+VACUUM (FULL) vacparted_i;
+VACUUM (FREEZE) vacparted_i;
+SELECT relname,relhasindex from pg_class where relname LIKE 'vacparted_i%' and relkind in ('p','r') ORDER BY relname;
+DROP TABLE vacparted_i;
+
-- multiple tables specified
VACUUM vaccluster, vactst;
VACUUM vacparted, does_not_exist;
--
2.30.2
On Wed, Jun 30, 2021 at 11:54 AM Álvaro Herrera <alvherre@alvh.no-ip.org>
wrote:
On 2021-Jun-30, Alexander Pyhalov wrote:
I've seen the following effect on PostgreSQL 14 stable branch.
Index, created on partitioned table, disappears from pg_dump or psql \d
output.
This seems to begin after analyze. Partitoned relation relhasindexpg_class
field suddenly becomes false.
Yeah, that seems correct.
I didn't verify your test case, but after looking at the code I thought
there was a bit too much churn and the new conditions looked quite messy
and unexplained. It seems simpler to be explicit at the start about
what we're doing, and keep nindexes=0 for partitioned tables; with that,
the code works unchanged because the "for" loops do nothing without
having to check for anything. My proposal is attached.I did run the tests and they do pass, but I didn't look very closely at
what the tests are actually doing.I noticed that part of that comment seems to be a leftover from ... I
don't know when: "We do not analyze index columns if there was an
explicit column list in the ANALYZE command, however." I suppose this
is about some code that was removed, but I didn't dig into it.--
Álvaro Herrera Valdivia, Chile
"How strange it is to find the words "Perl" and "saner" in such close
proximity, with no apparent sense of irony. I doubt that Larry himself
could have managed it." (ncm, http://lwn.net/Articles/174769/)
Hi,
nit:
- if (hasindex)
+ if (nindexes > 0)
It seems hasindex is no longer needed since nindexes is checked.
Cheers
Álvaro Herrera писал 2021-06-30 21:54:
On 2021-Jun-30, Alexander Pyhalov wrote:
I've seen the following effect on PostgreSQL 14 stable branch.
Index, created on partitioned table, disappears from pg_dump or psql
\d
output.
This seems to begin after analyze. Partitoned relation relhasindex
pg_class
field suddenly becomes false.Yeah, that seems correct.
I didn't verify your test case, but after looking at the code I thought
there was a bit too much churn and the new conditions looked quite
messy
and unexplained. It seems simpler to be explicit at the start about
what we're doing, and keep nindexes=0 for partitioned tables; with
that,
the code works unchanged because the "for" loops do nothing without
having to check for anything. My proposal is attached.I did run the tests and they do pass, but I didn't look very closely at
what the tests are actually doing.I noticed that part of that comment seems to be a leftover from ... I
don't know when: "We do not analyze index columns if there was an
explicit column list in the ANALYZE command, however." I suppose this
is about some code that was removed, but I didn't dig into it.
Looks good. It seems this comment refers to line 455.
445 if (nindexes > 0)
446 {
447 indexdata = (AnlIndexData *) palloc0(nindexes *
sizeof(AnlIndexData));
448 for (ind = 0; ind < nindexes; ind++)
449 {
450 AnlIndexData *thisdata = &indexdata[ind];
451 IndexInfo *indexInfo;
452
453 thisdata->indexInfo = indexInfo =
BuildIndexInfo(Irel[ind]);
454 thisdata->tupleFract = 1.0; /* fix later if partial */
455 if (indexInfo->ii_Expressions != NIL && va_cols == NIL)
456 {
457 ListCell *indexpr_item =
list_head(indexInfo->ii_Expressions);
458
459 thisdata->vacattrstats = (VacAttrStats **)
460 palloc(indexInfo->ii_NumIndexAttrs *
sizeof(VacAttrStats *));
Also I've added non-necessary new line in test.
Restored comment and removed new line.
--
Best regards,
Alexander Pyhalov,
Postgres Professional
Attachments:
v4-0001-Set-relhasindex-for-partitioned-tables-correctly.patchtext/x-diff; name=v4-0001-Set-relhasindex-for-partitioned-tables-correctly.patchDownload
From 0677fca372349ab2a1f5bc2e69a91c72ae7dfaa3 Mon Sep 17 00:00:00 2001
From: Alexander Pyhalov <a.pyhalov@postgrespro.ru>
Date: Wed, 30 Jun 2021 22:24:56 +0300
Subject: [PATCH] Set relhasindex for partitioned tables correctly.
The issue appeared after 0e69f705cc1a3df273b38c9883fb5765991e04fe:
in this commit we unconditionally set nindexes to 0 for partitioned
relations.
---
src/backend/commands/analyze.c | 24 +++++++++++++++++-------
src/test/regress/expected/vacuum.out | 17 +++++++++++++++++
src/test/regress/sql/vacuum.sql | 11 +++++++++++
3 files changed, 45 insertions(+), 7 deletions(-)
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 426c1e67109..c21cb7da3cf 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -422,18 +422,28 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
* columns in the indexes. We do not analyze index columns if there was
* an explicit column list in the ANALYZE command, however. If we are
* doing a recursive scan, we don't want to touch the parent's indexes at
- * all.
+ * all. If we're processing a partitioned table, we need to know if there
+ * are any indexes, but we don't want to process them.
*/
- if (!inh)
+ if (!inh || onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
vac_open_indexes(onerel, AccessShareLock, &nindexes, &Irel);
+ hasindex = nindexes > 0;
+ if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ vac_close_indexes(nindexes, Irel, NoLock);
+ nindexes = 0;
+ Irel = NULL;
+ }
+ }
else
{
Irel = NULL;
nindexes = 0;
+ hasindex = false;
}
- hasindex = (nindexes > 0);
indexdata = NULL;
- if (hasindex)
+ if (nindexes > 0)
{
indexdata = (AnlIndexData *) palloc0(nindexes * sizeof(AnlIndexData));
for (ind = 0; ind < nindexes; ind++)
@@ -572,7 +582,7 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
MemoryContextResetAndDeleteChildren(col_context);
}
- if (hasindex)
+ if (nindexes > 0)
compute_index_stats(onerel, totalrows,
indexdata, nindexes,
rows, numrows,
@@ -660,10 +670,10 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
/*
* Partitioned tables don't have storage, so we don't set any fields
* in their pg_class entries except for reltuples, which is necessary
- * for auto-analyze to work properly.
+ * for auto-analyze to work properly, and relhasindex.
*/
vac_update_relstats(onerel, -1, totalrows,
- 0, false, InvalidTransactionId,
+ 0, hasindex, InvalidTransactionId,
InvalidMultiXactId,
in_outer_xact);
}
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index e5771462d57..e3d462b66fa 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -199,6 +199,23 @@ VACUUM ANALYZE vacparted(a,b,a);
ERROR: column "a" of relation "vacparted" appears more than once
ANALYZE vacparted(a,b,b);
ERROR: column "b" of relation "vacparted" appears more than once
+-- partitioned table with index
+CREATE TABLE vacparted_i (a int primary key, b varchar(100)) PARTITION BY HASH (a);
+CREATE TABLE vacparted_i1 PARTITION OF vacparted_i FOR VALUES WITH ( MODULUS 2, REMAINDER 0);
+CREATE TABLE vacparted_i2 PARTITION OF vacparted_i FOR VALUES WITH ( MODULUS 2, REMAINDER 1);
+INSERT INTO vacparted_i SELECT i, 'test_'|| i from generate_series(1,10) i;
+VACUUM (ANALYZE) vacparted_i;
+VACUUM (FULL) vacparted_i;
+VACUUM (FREEZE) vacparted_i;
+SELECT relname,relhasindex from pg_class where relname LIKE 'vacparted_i%' and relkind in ('p','r') ORDER BY relname;
+ relname | relhasindex
+--------------+-------------
+ vacparted_i | t
+ vacparted_i1 | t
+ vacparted_i2 | t
+(3 rows)
+
+DROP TABLE vacparted_i;
-- multiple tables specified
VACUUM vaccluster, vactst;
VACUUM vacparted, does_not_exist;
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index f220fc28a70..5c4c4159ac9 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -170,6 +170,17 @@ VACUUM (FREEZE) vacparted;
VACUUM ANALYZE vacparted(a,b,a);
ANALYZE vacparted(a,b,b);
+-- partitioned table with index
+CREATE TABLE vacparted_i (a int primary key, b varchar(100)) PARTITION BY HASH (a);
+CREATE TABLE vacparted_i1 PARTITION OF vacparted_i FOR VALUES WITH ( MODULUS 2, REMAINDER 0);
+CREATE TABLE vacparted_i2 PARTITION OF vacparted_i FOR VALUES WITH ( MODULUS 2, REMAINDER 1);
+INSERT INTO vacparted_i SELECT i, 'test_'|| i from generate_series(1,10) i;
+VACUUM (ANALYZE) vacparted_i;
+VACUUM (FULL) vacparted_i;
+VACUUM (FREEZE) vacparted_i;
+SELECT relname,relhasindex from pg_class where relname LIKE 'vacparted_i%' and relkind in ('p','r') ORDER BY relname;
+DROP TABLE vacparted_i;
+
-- multiple tables specified
VACUUM vaccluster, vactst;
VACUUM vacparted, does_not_exist;
--
2.25.1
On 2021-Jun-30, Zhihong Yu wrote:
Hi, nit: - if (hasindex) + if (nindexes > 0)It seems hasindex is no longer needed since nindexes is checked.
It's still used to call vac_update_relstats(). We want nindexes to be 0
for partitioned tables, but still pass true when there are indexes.
Please don't forget to trim the text of the email you're replying to.
--
�lvaro Herrera Valdivia, Chile
https://www.EnterpriseDB.com/
On Wed, Jun 30, 2021 at 2:32 PM Álvaro Herrera <alvherre@alvh.no-ip.org>
wrote:
On 2021-Jun-30, Zhihong Yu wrote:
Hi, nit: - if (hasindex) + if (nindexes > 0)It seems hasindex is no longer needed since nindexes is checked.
It's still used to call vac_update_relstats(). We want nindexes to be 0
for partitioned tables, but still pass true when there are indexes.
Hi,
In that case, I wonder whether nindexes can be negated following the call
to vac_open_indexes().
vac_open_indexes(onerel, AccessShareLock, &nindexes, &Irel);
+ nindexes = -nindexes;
That way, hasindex can be dropped.
vac_update_relstats() call would become:
vac_update_relstats(onerel, -1, totalrows,
- 0, false, InvalidTransactionId,
+ 0, nindexes != 0, InvalidTransactionId,
My thinking is that without hasindex, the code is easier to maintain.
Thanks
Show quoted text
Please don't forget to trim the text of the email you're replying to.
--
Álvaro Herrera Valdivia, Chile
https://www.EnterpriseDB.com/
On 2021-Jun-30, Zhihong Yu wrote:
Hi,
In that case, I wonder whether nindexes can be negated following the call
to vac_open_indexes().vac_open_indexes(onerel, AccessShareLock, &nindexes, &Irel);
+ nindexes = -nindexes;That way, hasindex can be dropped.
vac_update_relstats() call would become:vac_update_relstats(onerel, -1, totalrows, - 0, false, InvalidTransactionId, + 0, nindexes != 0, InvalidTransactionId,
Perhaps this works, but I don't think it's a readability improvement.
My thinking is that without hasindex, the code is easier to maintain.
You have one less variable but one additional concept (negative
nindexes). It doesn't seem easier to me, TBH, rather the opposite.
--
�lvaro Herrera 39�49'30"S 73�17'W
https://www.EnterpriseDB.com/
On 2021-Jun-30, Alexander Pyhalov wrote:
Looks good. It seems this comment refers to line 455.
Ah, so it does.
I realized that we don't need to do vac_open_indexes for partitioned
tables: it is sufficient to know whether there are any indexes at all.
So I replaced that with RelationGetIndexList() and checking if the list
is nonempty, specifically for the partitioned table case. Pushed now.
Thanks for reporting and fixing this, and to Zhihong Yu for reviewing.
--
Álvaro Herrera Valdivia, Chile — https://www.EnterpriseDB.com/
"Investigación es lo que hago cuando no sé lo que estoy haciendo"
(Wernher von Braun)