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

Commit be6e311

Browse files
author
Commitfest Bot
committed
[CF 5438] v2 - bt_index_parent_check false alarm for indexes created concurrently
This branch was automatically generated by a robot using patches from an email thread registered at: https://commitfest.postgresql.org/patch/5438 The branch will be overwritten each time a new patch version is posted to the thread, and also periodically to check for bitrot caused by changes on the master branch. Patch(es): https://www.postgresql.org/message-id/CANtu0og_7Jvyfu2p5PJJQZ7yNY97v5cB0zan=GR_fxu8j-_7WA@mail.gmail.com Author(s): Michail Nikolaev, Mihail Nikalayeu
2 parents 44ce4e1 + e16e2a8 commit be6e311

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)