Skip to content

Commit

Permalink
Revert changes in HOT handling of BRIN indexes
Browse files Browse the repository at this point in the history
This reverts commits 5753d4e and fe60b67 that modified HOT to
ignore BRIN indexes. The commit message for 5753d4e claims that:

    When determining whether an index update may be skipped by using
    HOT, we can ignore attributes indexed only by BRIN indexes. There
    are no index pointers to individual tuples in BRIN, and the page
    range summary will be updated anyway as it relies on visibility
    info.

This is partially incorrect - it's true BRIN indexes don't point to
individual tuples, so HOT chains are not an issue, but the visibitlity
info is not sufficient to keep the index up to date. This can easily
result in corrupted indexes, as demonstrated in the hackers thread.

This does not mean relaxing the HOT restrictions for BRIN is a lost
cause, but it needs to handle the two aspects (allowing HOT chains and
updating the page range summaries) as separate. But that requires a
major changes, and it's too late for that in the current dev cycle.

Reported-by: Tomas Vondra
Discussion: https://postgr.es/m/05ebcb44-f383-86e3-4f31-0a97a55634cf@enterprisedb.com
  • Loading branch information
tvondra committed Jun 16, 2022
1 parent 664da2a commit e3fcca0
Show file tree
Hide file tree
Showing 17 changed files with 27 additions and 502 deletions.
11 changes: 0 additions & 11 deletions doc/src/sgml/indexam.sgml
Expand Up @@ -126,8 +126,6 @@ typedef struct IndexAmRoutine
bool amcaninclude;
/* does AM use maintenance_work_mem? */
bool amusemaintenanceworkmem;
/* does AM block HOT update? */
bool amhotblocking;
/* OR of parallel vacuum flags */
uint8 amparallelvacuumoptions;
/* type of data stored in index, or InvalidOid if variable */
Expand Down Expand Up @@ -248,15 +246,6 @@ typedef struct IndexAmRoutine
null, independently of <structfield>amoptionalkey</structfield>.
</para>

<para>
The <structfield>amhotblocking</structfield> flag indicates whether the
access method blocks <acronym>HOT</acronym> when an indexed attribute is
updated. Access methods without pointers to individual tuples (like
<acronym>BRIN</acronym>) may allow <acronym>HOT</acronym> even in this
case. This does not apply to attributes referenced in index predicates;
an update of such attribute always disables <acronym>HOT</acronym>.
</para>

</sect1>

<sect1 id="index-functions">
Expand Down
1 change: 0 additions & 1 deletion src/backend/access/brin/brin.c
Expand Up @@ -108,7 +108,6 @@ brinhandler(PG_FUNCTION_ARGS)
amroutine->amcanparallel = false;
amroutine->amcaninclude = false;
amroutine->amusemaintenanceworkmem = false;
amroutine->amhotblocking = false;
amroutine->amparallelvacuumoptions =
VACUUM_OPTION_PARALLEL_CLEANUP;
amroutine->amkeytype = InvalidOid;
Expand Down
1 change: 0 additions & 1 deletion src/backend/access/gin/ginutil.c
Expand Up @@ -56,7 +56,6 @@ ginhandler(PG_FUNCTION_ARGS)
amroutine->amcanparallel = false;
amroutine->amcaninclude = false;
amroutine->amusemaintenanceworkmem = true;
amroutine->amhotblocking = true;
amroutine->amparallelvacuumoptions =
VACUUM_OPTION_PARALLEL_BULKDEL | VACUUM_OPTION_PARALLEL_CLEANUP;
amroutine->amkeytype = InvalidOid;
Expand Down
1 change: 0 additions & 1 deletion src/backend/access/gist/gist.c
Expand Up @@ -78,7 +78,6 @@ gisthandler(PG_FUNCTION_ARGS)
amroutine->amcanparallel = false;
amroutine->amcaninclude = true;
amroutine->amusemaintenanceworkmem = false;
amroutine->amhotblocking = true;
amroutine->amparallelvacuumoptions =
VACUUM_OPTION_PARALLEL_BULKDEL | VACUUM_OPTION_PARALLEL_COND_CLEANUP;
amroutine->amkeytype = InvalidOid;
Expand Down
1 change: 0 additions & 1 deletion src/backend/access/hash/hash.c
Expand Up @@ -75,7 +75,6 @@ hashhandler(PG_FUNCTION_ARGS)
amroutine->amcanparallel = false;
amroutine->amcaninclude = false;
amroutine->amusemaintenanceworkmem = false;
amroutine->amhotblocking = true;
amroutine->amparallelvacuumoptions =
VACUUM_OPTION_PARALLEL_BULKDEL;
amroutine->amkeytype = INT4OID;
Expand Down
2 changes: 1 addition & 1 deletion src/backend/access/heap/heapam.c
Expand Up @@ -3190,7 +3190,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
* Note that we get copies of each bitmap, so we need not worry about
* relcache flush happening midway through.
*/
hot_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_HOT_BLOCKING);
hot_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_ALL);
key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
id_attrs = RelationGetIndexAttrBitmap(relation,
INDEX_ATTR_BITMAP_IDENTITY_KEY);
Expand Down
1 change: 0 additions & 1 deletion src/backend/access/nbtree/nbtree.c
Expand Up @@ -114,7 +114,6 @@ bthandler(PG_FUNCTION_ARGS)
amroutine->amcanparallel = true;
amroutine->amcaninclude = true;
amroutine->amusemaintenanceworkmem = false;
amroutine->amhotblocking = true;
amroutine->amparallelvacuumoptions =
VACUUM_OPTION_PARALLEL_BULKDEL | VACUUM_OPTION_PARALLEL_COND_CLEANUP;
amroutine->amkeytype = InvalidOid;
Expand Down
1 change: 0 additions & 1 deletion src/backend/access/spgist/spgutils.c
Expand Up @@ -62,7 +62,6 @@ spghandler(PG_FUNCTION_ARGS)
amroutine->amcanparallel = false;
amroutine->amcaninclude = true;
amroutine->amusemaintenanceworkmem = false;
amroutine->amhotblocking = true;
amroutine->amparallelvacuumoptions =
VACUUM_OPTION_PARALLEL_BULKDEL | VACUUM_OPTION_PARALLEL_COND_CLEANUP;
amroutine->amkeytype = InvalidOid;
Expand Down
53 changes: 23 additions & 30 deletions src/backend/utils/cache/relcache.c
Expand Up @@ -2439,10 +2439,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
list_free_deep(relation->rd_fkeylist);
list_free(relation->rd_indexlist);
list_free(relation->rd_statlist);
bms_free(relation->rd_indexattr);
bms_free(relation->rd_keyattr);
bms_free(relation->rd_pkattr);
bms_free(relation->rd_idattr);
bms_free(relation->rd_hotblockingattr);
if (relation->rd_pubdesc)
pfree(relation->rd_pubdesc);
if (relation->rd_options)
Expand Down Expand Up @@ -5104,10 +5104,10 @@ RelationGetIndexPredicate(Relation relation)
Bitmapset *
RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
{
Bitmapset *indexattrs; /* indexed columns */
Bitmapset *uindexattrs; /* columns in unique indexes */
Bitmapset *pkindexattrs; /* columns in the primary index */
Bitmapset *idindexattrs; /* columns in the replica identity */
Bitmapset *hotblockingattrs; /* columns with HOT blocking indexes */
List *indexoidlist;
List *newindexoidlist;
Oid relpkindex;
Expand All @@ -5116,18 +5116,18 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
MemoryContext oldcxt;

/* Quick exit if we already computed the result. */
if (relation->rd_attrsvalid)
if (relation->rd_indexattr != NULL)
{
switch (attrKind)
{
case INDEX_ATTR_BITMAP_ALL:
return bms_copy(relation->rd_indexattr);
case INDEX_ATTR_BITMAP_KEY:
return bms_copy(relation->rd_keyattr);
case INDEX_ATTR_BITMAP_PRIMARY_KEY:
return bms_copy(relation->rd_pkattr);
case INDEX_ATTR_BITMAP_IDENTITY_KEY:
return bms_copy(relation->rd_idattr);
case INDEX_ATTR_BITMAP_HOT_BLOCKING:
return bms_copy(relation->rd_hotblockingattr);
default:
elog(ERROR, "unknown attrKind %u", attrKind);
}
Expand Down Expand Up @@ -5158,7 +5158,7 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
relreplindex = relation->rd_replidindex;

/*
* For each index, add referenced attributes to appropriate bitmaps.
* For each index, add referenced attributes to indexattrs.
*
* Note: we consider all indexes returned by RelationGetIndexList, even if
* they are not indisready or indisvalid. This is important because an
Expand All @@ -5167,10 +5167,10 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
* CONCURRENTLY is far enough along that we should ignore the index, it
* won't be returned at all by RelationGetIndexList.
*/
indexattrs = NULL;
uindexattrs = NULL;
pkindexattrs = NULL;
idindexattrs = NULL;
hotblockingattrs = NULL;
foreach(l, indexoidlist)
{
Oid indexOid = lfirst_oid(l);
Expand Down Expand Up @@ -5235,9 +5235,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
*/
if (attrnum != 0)
{
if (indexDesc->rd_indam->amhotblocking)
hotblockingattrs = bms_add_member(hotblockingattrs,
attrnum - FirstLowInvalidHeapAttributeNumber);
indexattrs = bms_add_member(indexattrs,
attrnum - FirstLowInvalidHeapAttributeNumber);

if (isKey && i < indexDesc->rd_index->indnkeyatts)
uindexattrs = bms_add_member(uindexattrs,
Expand All @@ -5254,15 +5253,10 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
}

/* Collect all attributes used in expressions, too */
if (indexDesc->rd_indam->amhotblocking)
pull_varattnos(indexExpressions, 1, &hotblockingattrs);
pull_varattnos(indexExpressions, 1, &indexattrs);

/*
* Collect all attributes in the index predicate, too. We have to
* ignore amhotblocking flag, because the row might become indexable,
* in which case we have to add it to the index.
*/
pull_varattnos(indexPredicate, 1, &hotblockingattrs);
/* Collect all attributes in the index predicate, too */
pull_varattnos(indexPredicate, 1, &indexattrs);

index_close(indexDesc, AccessShareLock);
}
Expand Down Expand Up @@ -5290,46 +5284,46 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
bms_free(uindexattrs);
bms_free(pkindexattrs);
bms_free(idindexattrs);
bms_free(hotblockingattrs);
bms_free(indexattrs);

goto restart;
}

/* Don't leak the old values of these bitmaps, if any */
bms_free(relation->rd_indexattr);
relation->rd_indexattr = NULL;
bms_free(relation->rd_keyattr);
relation->rd_keyattr = NULL;
bms_free(relation->rd_pkattr);
relation->rd_pkattr = NULL;
bms_free(relation->rd_idattr);
relation->rd_idattr = NULL;
bms_free(relation->rd_hotblockingattr);
relation->rd_hotblockingattr = NULL;

/*
* Now save copies of the bitmaps in the relcache entry. We intentionally
* set rd_attrsvalid last, because that's what signals validity of the
* values; if we run out of memory before making that copy, we won't leave
* the relcache entry looking like the other ones are valid but empty.
* set rd_indexattr last, because that's the one that signals validity of
* the values; if we run out of memory before making that copy, we won't
* leave the relcache entry looking like the other ones are valid but
* empty.
*/
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
relation->rd_keyattr = bms_copy(uindexattrs);
relation->rd_pkattr = bms_copy(pkindexattrs);
relation->rd_idattr = bms_copy(idindexattrs);
relation->rd_hotblockingattr = bms_copy(hotblockingattrs);
relation->rd_attrsvalid = true;
relation->rd_indexattr = bms_copy(indexattrs);
MemoryContextSwitchTo(oldcxt);

/* We return our original working copy for caller to play with */
switch (attrKind)
{
case INDEX_ATTR_BITMAP_ALL:
return indexattrs;
case INDEX_ATTR_BITMAP_KEY:
return uindexattrs;
case INDEX_ATTR_BITMAP_PRIMARY_KEY:
return pkindexattrs;
case INDEX_ATTR_BITMAP_IDENTITY_KEY:
return idindexattrs;
case INDEX_ATTR_BITMAP_HOT_BLOCKING:
return hotblockingattrs;
default:
elog(ERROR, "unknown attrKind %u", attrKind);
return NULL;
Expand Down Expand Up @@ -6250,11 +6244,10 @@ load_relcache_init_file(bool shared)
rel->rd_indexlist = NIL;
rel->rd_pkindex = InvalidOid;
rel->rd_replidindex = InvalidOid;
rel->rd_attrsvalid = false;
rel->rd_indexattr = NULL;
rel->rd_keyattr = NULL;
rel->rd_pkattr = NULL;
rel->rd_idattr = NULL;
rel->rd_hotblockingattr = NULL;
rel->rd_pubdesc = NULL;
rel->rd_statvalid = false;
rel->rd_statlist = NIL;
Expand Down
2 changes: 0 additions & 2 deletions src/include/access/amapi.h
Expand Up @@ -244,8 +244,6 @@ typedef struct IndexAmRoutine
bool amcaninclude;
/* does AM use maintenance_work_mem? */
bool amusemaintenanceworkmem;
/* does AM block HOT update? */
bool amhotblocking;
/* OR of parallel vacuum flags. See vacuum.h for flags. */
uint8 amparallelvacuumoptions;
/* type of data stored in index, or InvalidOid if variable */
Expand Down
3 changes: 1 addition & 2 deletions src/include/utils/rel.h
Expand Up @@ -155,11 +155,10 @@ typedef struct RelationData
List *rd_statlist; /* list of OIDs of extended stats */

/* data managed by RelationGetIndexAttrBitmap: */
bool rd_attrsvalid; /* are bitmaps of attrs valid? */
Bitmapset *rd_indexattr; /* identifies columns used in indexes */
Bitmapset *rd_keyattr; /* cols that can be ref'd by foreign keys */
Bitmapset *rd_pkattr; /* cols included in primary key */
Bitmapset *rd_idattr; /* included in replica identity index */
Bitmapset *rd_hotblockingattr; /* cols blocking HOT update */

PublicationDesc *rd_pubdesc; /* publication descriptor, or NULL */

Expand Down
4 changes: 2 additions & 2 deletions src/include/utils/relcache.h
Expand Up @@ -55,10 +55,10 @@ extern bytea **RelationGetIndexAttOptions(Relation relation, bool copy);

typedef enum IndexAttrBitmapKind
{
INDEX_ATTR_BITMAP_ALL,
INDEX_ATTR_BITMAP_KEY,
INDEX_ATTR_BITMAP_PRIMARY_KEY,
INDEX_ATTR_BITMAP_IDENTITY_KEY,
INDEX_ATTR_BITMAP_HOT_BLOCKING
INDEX_ATTR_BITMAP_IDENTITY_KEY
} IndexAttrBitmapKind;

extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
Expand Down
1 change: 0 additions & 1 deletion src/test/modules/dummy_index_am/dummy_index_am.c
Expand Up @@ -298,7 +298,6 @@ dihandler(PG_FUNCTION_ARGS)
amroutine->amcanparallel = false;
amroutine->amcaninclude = false;
amroutine->amusemaintenanceworkmem = false;
amroutine->amhotblocking = true;
amroutine->amparallelvacuumoptions = VACUUM_OPTION_NO_PARALLEL;
amroutine->amkeytype = InvalidOid;

Expand Down
58 changes: 0 additions & 58 deletions src/test/regress/expected/brin.out
Expand Up @@ -567,61 +567,3 @@ SELECT * FROM brintest_3 WHERE b < '0';

DROP TABLE brintest_3;
RESET enable_seqscan;
-- Test handling of index predicates - updating attributes in predicates
-- should block HOT even for BRIN. We update a row that was not indexed
-- due to the index predicate, and becomes indexable.
CREATE TABLE brin_hot_2 (a int, b int);
INSERT INTO brin_hot_2 VALUES (1, 100);
CREATE INDEX ON brin_hot_2 USING brin (b) WHERE a = 2;
UPDATE brin_hot_2 SET a = 2;
EXPLAIN (COSTS OFF) SELECT * FROM brin_hot_2 WHERE a = 2 AND b = 100;
QUERY PLAN
-----------------------------------
Seq Scan on brin_hot_2
Filter: ((a = 2) AND (b = 100))
(2 rows)

SELECT COUNT(*) FROM brin_hot_2 WHERE a = 2 AND b = 100;
count
-------
1
(1 row)

SET enable_seqscan = off;
EXPLAIN (COSTS OFF) SELECT * FROM brin_hot_2 WHERE a = 2 AND b = 100;
QUERY PLAN
---------------------------------------------
Bitmap Heap Scan on brin_hot_2
Recheck Cond: ((b = 100) AND (a = 2))
-> Bitmap Index Scan on brin_hot_2_b_idx
Index Cond: (b = 100)
(4 rows)

SELECT COUNT(*) FROM brin_hot_2 WHERE a = 2 AND b = 100;
count
-------
1
(1 row)

-- test BRIN index doesn't block HOT update
CREATE TABLE brin_hot (
id integer PRIMARY KEY,
val integer NOT NULL
) WITH (autovacuum_enabled = off, fillfactor = 70);
INSERT INTO brin_hot SELECT *, 0 FROM generate_series(1, 235);
CREATE INDEX val_brin ON brin_hot using brin(val);
UPDATE brin_hot SET val = -3 WHERE id = 42;
-- ensure pending stats are flushed
SELECT pg_stat_force_next_flush();
pg_stat_force_next_flush
--------------------------

(1 row)

SELECT pg_stat_get_tuples_hot_updated('brin_hot'::regclass::oid);
pg_stat_get_tuples_hot_updated
--------------------------------
1
(1 row)

DROP TABLE brin_hot;

0 comments on commit e3fcca0

Please sign in to comment.