Skip to content

Commit bb30542

Browse files
committed
Move I/O before the index_update_stats() buffer lock region.
Commit a07e03f enlarged the work done here under the pg_class heap buffer lock. Two preexisting actions are best done before holding that lock. Both RelationGetNumberOfBlocks() and visibilitymap_count() do I/O, and the latter might exclusive-lock a visibility map buffer. Moving these reduces contention and risk of undetected LWLock deadlock. Back-patch to v12, like that commit. Discussion: https://postgr.es/m/20241031200139.b4@rfd.leadboat.com
1 parent 4c70887 commit bb30542

File tree

1 file changed

+36
-18
lines changed

1 file changed

+36
-18
lines changed

src/backend/catalog/index.c

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2792,6 +2792,9 @@ index_update_stats(Relation rel,
27922792
bool hasindex,
27932793
double reltuples)
27942794
{
2795+
bool update_stats;
2796+
BlockNumber relpages;
2797+
BlockNumber relallvisible;
27952798
Oid relid = RelationGetRelid(rel);
27962799
Relation pg_class;
27972800
ScanKeyData key[1];
@@ -2800,6 +2803,38 @@ index_update_stats(Relation rel,
28002803
Form_pg_class rd_rel;
28012804
bool dirty;
28022805

2806+
/*
2807+
* As a special hack, if we are dealing with an empty table and the
2808+
* existing reltuples is -1, we leave that alone. This ensures that
2809+
* creating an index as part of CREATE TABLE doesn't cause the table to
2810+
* prematurely look like it's been vacuumed. The rd_rel we modify may
2811+
* differ from rel->rd_rel due to e.g. commit of concurrent GRANT, but the
2812+
* commands that change reltuples take locks conflicting with ours. (Even
2813+
* if a command changed reltuples under a weaker lock, this affects only
2814+
* statistics for an empty table.)
2815+
*/
2816+
if (reltuples == 0 && rel->rd_rel->reltuples < 0)
2817+
reltuples = -1;
2818+
2819+
update_stats = reltuples >= 0;
2820+
2821+
/*
2822+
* Finish I/O and visibility map buffer locks before
2823+
* systable_inplace_update_begin() locks the pg_class buffer. The rd_rel
2824+
* we modify may differ from rel->rd_rel due to e.g. commit of concurrent
2825+
* GRANT, but no command changes a relkind from non-index to index. (Even
2826+
* if one did, relallvisible doesn't break functionality.)
2827+
*/
2828+
if (update_stats)
2829+
{
2830+
relpages = RelationGetNumberOfBlocks(rel);
2831+
2832+
if (rel->rd_rel->relkind != RELKIND_INDEX)
2833+
visibilitymap_count(rel, &relallvisible, NULL);
2834+
else /* don't bother for indexes */
2835+
relallvisible = 0;
2836+
}
2837+
28032838
/*
28042839
* We always update the pg_class row using a non-transactional,
28052840
* overwrite-in-place update. There are several reasons for this:
@@ -2844,15 +2879,6 @@ index_update_stats(Relation rel,
28442879
/* Should this be a more comprehensive test? */
28452880
Assert(rd_rel->relkind != RELKIND_PARTITIONED_INDEX);
28462881

2847-
/*
2848-
* As a special hack, if we are dealing with an empty table and the
2849-
* existing reltuples is -1, we leave that alone. This ensures that
2850-
* creating an index as part of CREATE TABLE doesn't cause the table to
2851-
* prematurely look like it's been vacuumed.
2852-
*/
2853-
if (reltuples == 0 && rd_rel->reltuples < 0)
2854-
reltuples = -1;
2855-
28562882
/* Apply required updates, if any, to copied tuple */
28572883

28582884
dirty = false;
@@ -2862,16 +2888,8 @@ index_update_stats(Relation rel,
28622888
dirty = true;
28632889
}
28642890

2865-
if (reltuples >= 0)
2891+
if (update_stats)
28662892
{
2867-
BlockNumber relpages = RelationGetNumberOfBlocks(rel);
2868-
BlockNumber relallvisible;
2869-
2870-
if (rd_rel->relkind != RELKIND_INDEX)
2871-
visibilitymap_count(rel, &relallvisible, NULL);
2872-
else /* don't bother for indexes */
2873-
relallvisible = 0;
2874-
28752893
if (rd_rel->relpages != (int32) relpages)
28762894
{
28772895
rd_rel->relpages = (int32) relpages;

0 commit comments

Comments
 (0)