Skip to content

Commit bf9a60e

Browse files
committed
Fix interaction between CREATE INDEX and "snapshot too old".
Since indexes are created without valid LSNs, an index created while a snapshot older than old_snapshot_threshold existed could cause queries to return incorrect results when those old snapshots were used, if any relevant rows had been subject to early pruning before the index was built. Prevent usage of a newly created index until all such snapshots are released, for relations where this can happen. Questions about the interaction of "snapshot too old" with index creation were initially raised by Andres Freund. Reviewed by Robert Haas.
1 parent cae1c78 commit bf9a60e

File tree

4 files changed

+36
-13
lines changed

4 files changed

+36
-13
lines changed

src/backend/catalog/index.c

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2040,18 +2040,24 @@ index_build(Relation heapRelation,
20402040
/*
20412041
* If we found any potentially broken HOT chains, mark the index as not
20422042
* being usable until the current transaction is below the event horizon.
2043-
* See src/backend/access/heap/README.HOT for discussion.
2043+
* See src/backend/access/heap/README.HOT for discussion. Also set this
2044+
* if early pruning/vacuuming is enabled for the heap relation. While it
2045+
* might become safe to use the index earlier based on actual cleanup
2046+
* activity and other active transactions, the test for that would be much
2047+
* more complex and would require some form of blocking, so keep it simple
2048+
* and fast by just using the current transaction.
20442049
*
20452050
* However, when reindexing an existing index, we should do nothing here.
20462051
* Any HOT chains that are broken with respect to the index must predate
20472052
* the index's original creation, so there is no need to change the
20482053
* index's usability horizon. Moreover, we *must not* try to change the
20492054
* index's pg_index entry while reindexing pg_index itself, and this
2050-
* optimization nicely prevents that.
2055+
* optimization nicely prevents that. The more complex rules needed for a
2056+
* reindex are handled separately after this function returns.
20512057
*
20522058
* We also need not set indcheckxmin during a concurrent index build,
20532059
* because we won't set indisvalid true until all transactions that care
2054-
* about the broken HOT chains are gone.
2060+
* about the broken HOT chains or early pruning/vacuuming are gone.
20552061
*
20562062
* Therefore, this code path can only be taken during non-concurrent
20572063
* CREATE INDEX. Thus the fact that heap_update will set the pg_index
@@ -2060,7 +2066,8 @@ index_build(Relation heapRelation,
20602066
* about any concurrent readers of the tuple; no other transaction can see
20612067
* it yet.
20622068
*/
2063-
if (indexInfo->ii_BrokenHotChain && !isreindex &&
2069+
if ((indexInfo->ii_BrokenHotChain || EarlyPruningEnabled(heapRelation)) &&
2070+
!isreindex &&
20642071
!indexInfo->ii_Concurrent)
20652072
{
20662073
Oid indexId = RelationGetRelid(indexRelation);
@@ -3389,13 +3396,19 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
33893396
* reindexing pg_index itself, we must not try to update tuples in it.
33903397
* pg_index's indexes should always have these flags in their clean state,
33913398
* so that won't happen.
3399+
*
3400+
* If early pruning/vacuuming is enabled for the heap relation, the
3401+
* usability horizon must be advanced to the current transaction on every
3402+
* build or rebuild. pg_index is OK in this regard because catalog tables
3403+
* are not subject to early cleanup.
33923404
*/
33933405
if (!skipped_constraint)
33943406
{
33953407
Relation pg_index;
33963408
HeapTuple indexTuple;
33973409
Form_pg_index indexForm;
33983410
bool index_bad;
3411+
bool early_vacuum_enabled = EarlyPruningEnabled(heapRelation);
33993412

34003413
pg_index = heap_open(IndexRelationId, RowExclusiveLock);
34013414

@@ -3409,11 +3422,12 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
34093422
!indexForm->indisready ||
34103423
!indexForm->indislive);
34113424
if (index_bad ||
3412-
(indexForm->indcheckxmin && !indexInfo->ii_BrokenHotChain))
3425+
(indexForm->indcheckxmin && !indexInfo->ii_BrokenHotChain) ||
3426+
early_vacuum_enabled)
34133427
{
3414-
if (!indexInfo->ii_BrokenHotChain)
3428+
if (!indexInfo->ii_BrokenHotChain && !early_vacuum_enabled)
34153429
indexForm->indcheckxmin = false;
3416-
else if (index_bad)
3430+
else if (index_bad || early_vacuum_enabled)
34173431
indexForm->indcheckxmin = true;
34183432
indexForm->indisvalid = true;
34193433
indexForm->indisready = true;

src/backend/storage/buffer/bufmgr.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4290,8 +4290,7 @@ IssuePendingWritebacks(WritebackContext *context)
42904290
void
42914291
TestForOldSnapshot_impl(Snapshot snapshot, Relation relation)
42924292
{
4293-
if (!IsCatalogRelation(relation)
4294-
&& !RelationIsAccessibleInLogicalDecoding(relation)
4293+
if (RelationAllowsEarlyPruning(relation)
42954294
&& (snapshot)->whenTaken < GetOldSnapshotThresholdTimestamp())
42964295
ereport(ERROR,
42974296
(errcode(ERRCODE_SNAPSHOT_TOO_OLD),

src/backend/utils/time/snapmgr.c

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,10 +1596,7 @@ TransactionIdLimitedForOldSnapshots(TransactionId recentXmin,
15961596
{
15971597
if (TransactionIdIsNormal(recentXmin)
15981598
&& old_snapshot_threshold >= 0
1599-
&& RelationNeedsWAL(relation)
1600-
&& !IsCatalogRelation(relation)
1601-
&& !RelationIsAccessibleInLogicalDecoding(relation)
1602-
&& !RelationHasUnloggedIndex(relation))
1599+
&& RelationAllowsEarlyPruning(relation))
16031600
{
16041601
int64 ts = GetSnapshotCurrentTimestamp();
16051602
TransactionId xlimit = recentXmin;

src/include/utils/snapmgr.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@
3131
#define OLD_SNAPSHOT_PADDING_ENTRIES 10
3232
#define OLD_SNAPSHOT_TIME_MAP_ENTRIES (old_snapshot_threshold + OLD_SNAPSHOT_PADDING_ENTRIES)
3333

34+
/*
35+
* Common definition of relation properties that allow early pruning/vacuuming
36+
* when old_snapshot_threshold >= 0.
37+
*/
38+
#define RelationAllowsEarlyPruning(rel) \
39+
( \
40+
RelationNeedsWAL(rel) \
41+
&& !IsCatalogRelation(rel) \
42+
&& !RelationIsAccessibleInLogicalDecoding(rel) \
43+
&& !RelationHasUnloggedIndex(rel) \
44+
)
45+
46+
#define EarlyPruningEnabled(rel) (old_snapshot_threshold >= 0 && RelationAllowsEarlyPruning(rel))
3447

3548
/* GUC variables */
3649
extern PGDLLIMPORT int old_snapshot_threshold;

0 commit comments

Comments
 (0)