Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit e16e2a8

Browse files
nkeyCommitfest Bot
nkey
authored and
Commitfest Bot
committed
amcheck: Fix bt_index_parent_check behavior with CREATE INDEX CONCURRENTLY
Modify snapshot handling in verify_nbtree to properly handle indexes created with CREATE INDEX CONCURRENTLY. Previously, the function could fail to find some tuples in bloom when verifying such indexes because it was using the SnapshotAny, while indexes were build using MVCC snapshot. This could lead to false alarms in bt_index_parent_check's validation. Add a regression test for the the issue. Discussion: https://postgr.es/m/CANtu0ojmVd27fEhfpST7RG2KZvwkX%3DdMyKUqg0KM87FkOSdz8Q%40mail.gmail.com
1 parent 44ce4e1 commit e16e2a8

File tree

3 files changed

+69
-39
lines changed

3 files changed

+69
-39
lines changed

contrib/amcheck/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ tests += {
4949
't/003_cic_2pc.pl',
5050
't/004_verify_nbtree_unique.pl',
5151
't/005_pitr.pl',
52+
't/006_cic_bt_index_parent_check.pl',
5253
],
5354
},
5455
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Copyright (c) 2025, PostgreSQL Global Development Group
2+
3+
# Test bt_index_parent_check with index created with CREATE INDEX CONCURRENTLY
4+
use strict;
5+
use warnings FATAL => 'all';
6+
7+
use PostgreSQL::Test::Cluster;
8+
use PostgreSQL::Test::Utils;
9+
10+
use Test::More;
11+
12+
my ($node, $result);
13+
14+
#
15+
# Test set-up
16+
#
17+
$node = PostgreSQL::Test::Cluster->new('CIC_bt_index_parent_check_test');
18+
$node->init;
19+
$node->start;
20+
$node->safe_psql('postgres', q(CREATE EXTENSION amcheck));
21+
$node->safe_psql('postgres', q(CREATE TABLE tbl(i int primary key)));
22+
# Insert two rows into index
23+
$node->safe_psql('postgres', q(INSERT INTO tbl SELECT i FROM generate_series(1, 2) s(i);));
24+
25+
# start background transaction
26+
my $in_progress_h = $node->background_psql('postgres');
27+
$in_progress_h->query_safe(q(BEGIN; SELECT pg_current_xact_id();));
28+
29+
# delete one row from table, while background transaction is in progress
30+
$node->safe_psql('postgres', q(DELETE FROM tbl WHERE i = 1;));
31+
# create index concurrently, which will skip the deleted row
32+
$node->safe_psql('postgres', q(CREATE INDEX CONCURRENTLY idx ON tbl(i);));
33+
34+
# check index using bt_index_parent_check
35+
$result = $node->psql('postgres', q(SELECT bt_index_parent_check('idx', heapallindexed => true)));
36+
is($result, '0', 'bt_index_parent_check for CIC after removed row');
37+
38+
$in_progress_h->quit;
39+
done_testing();

contrib/amcheck/verify_nbtree.c

Lines changed: 29 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,6 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
382382
BTMetaPageData *metad;
383383
uint32 previouslevel;
384384
BtreeLevel current;
385-
Snapshot snapshot = SnapshotAny;
386385

387386
if (!readonly)
388387
elog(DEBUG1, "verifying consistency of tree structure for index \"%s\"",
@@ -433,38 +432,35 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
433432
state->heaptuplespresent = 0;
434433

435434
/*
436-
* Register our own snapshot in !readonly case, rather than asking
435+
* Register our own snapshot for heapallindexed, rather than asking
437436
* table_index_build_scan() to do this for us later. This needs to
438437
* happen before index fingerprinting begins, so we can later be
439438
* certain that index fingerprinting should have reached all tuples
440439
* returned by table_index_build_scan().
441440
*/
442-
if (!state->readonly)
443-
{
444-
snapshot = RegisterSnapshot(GetTransactionSnapshot());
441+
state->snapshot = RegisterSnapshot(GetTransactionSnapshot());
445442

446-
/*
447-
* GetTransactionSnapshot() always acquires a new MVCC snapshot in
448-
* READ COMMITTED mode. A new snapshot is guaranteed to have all
449-
* the entries it requires in the index.
450-
*
451-
* We must defend against the possibility that an old xact
452-
* snapshot was returned at higher isolation levels when that
453-
* snapshot is not safe for index scans of the target index. This
454-
* is possible when the snapshot sees tuples that are before the
455-
* index's indcheckxmin horizon. Throwing an error here should be
456-
* very rare. It doesn't seem worth using a secondary snapshot to
457-
* avoid this.
458-
*/
459-
if (IsolationUsesXactSnapshot() && rel->rd_index->indcheckxmin &&
460-
!TransactionIdPrecedes(HeapTupleHeaderGetXmin(rel->rd_indextuple->t_data),
461-
snapshot->xmin))
462-
ereport(ERROR,
463-
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
464-
errmsg("index \"%s\" cannot be verified using transaction snapshot",
465-
RelationGetRelationName(rel))));
466-
}
467-
}
443+
/*
444+
* GetTransactionSnapshot() always acquires a new MVCC snapshot in
445+
* READ COMMITTED mode. A new snapshot is guaranteed to have all
446+
* the entries it requires in the index.
447+
*
448+
* We must defend against the possibility that an old xact
449+
* snapshot was returned at higher isolation levels when that
450+
* snapshot is not safe for index scans of the target index. This
451+
* is possible when the snapshot sees tuples that are before the
452+
* index's indcheckxmin horizon. Throwing an error here should be
453+
* very rare. It doesn't seem worth using a secondary snapshot to
454+
* avoid this.
455+
*/
456+
if (IsolationUsesXactSnapshot() && rel->rd_index->indcheckxmin &&
457+
!TransactionIdPrecedes(HeapTupleHeaderGetXmin(rel->rd_indextuple->t_data),
458+
state->snapshot->xmin))
459+
ereport(ERROR,
460+
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
461+
errmsg("index \"%s\" cannot be verified using transaction snapshot",
462+
RelationGetRelationName(rel))));
463+
}
468464

469465
/*
470466
* We need a snapshot to check the uniqueness of the index. For better
@@ -476,9 +472,7 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
476472
state->indexinfo = BuildIndexInfo(state->rel);
477473
if (state->indexinfo->ii_Unique)
478474
{
479-
if (snapshot != SnapshotAny)
480-
state->snapshot = snapshot;
481-
else
475+
if (state->snapshot == InvalidSnapshot)
482476
state->snapshot = RegisterSnapshot(GetTransactionSnapshot());
483477
}
484478
}
@@ -555,21 +549,20 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
555549
/*
556550
* Create our own scan for table_index_build_scan(), rather than
557551
* getting it to do so for us. This is required so that we can
558-
* actually use the MVCC snapshot registered earlier in !readonly
559-
* case.
552+
* actually use the MVCC snapshot registered earlier.
560553
*
561554
* Note that table_index_build_scan() calls heap_endscan() for us.
562555
*/
563556
scan = table_beginscan_strat(state->heaprel, /* relation */
564-
snapshot, /* snapshot */
557+
state->snapshot, /* snapshot */
565558
0, /* number of keys */
566559
NULL, /* scan key */
567560
true, /* buffer access strategy OK */
568561
true); /* syncscan OK? */
569562

570563
/*
571564
* Scan will behave as the first scan of a CREATE INDEX CONCURRENTLY
572-
* behaves in !readonly case.
565+
* behaves.
573566
*
574567
* It's okay that we don't actually use the same lock strength for the
575568
* heap relation as any other ii_Concurrent caller would in !readonly
@@ -578,7 +571,7 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
578571
* that needs to be sure that there was no concurrent recycling of
579572
* TIDs.
580573
*/
581-
indexinfo->ii_Concurrent = !state->readonly;
574+
indexinfo->ii_Concurrent = true;
582575

583576
/*
584577
* Don't wait for uncommitted tuple xact commit/abort when index is a
@@ -602,14 +595,11 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
602595
state->heaptuplespresent, RelationGetRelationName(heaprel),
603596
100.0 * bloom_prop_bits_set(state->filter))));
604597

605-
if (snapshot != SnapshotAny)
606-
UnregisterSnapshot(snapshot);
607-
608598
bloom_free(state->filter);
609599
}
610600

611601
/* Be tidy: */
612-
if (snapshot == SnapshotAny && state->snapshot != InvalidSnapshot)
602+
if (state->snapshot != InvalidSnapshot)
613603
UnregisterSnapshot(state->snapshot);
614604
MemoryContextDelete(state->targetcontext);
615605
}

0 commit comments

Comments
 (0)