🌐 AI搜索 & 代理 主页
Skip to content

Commit df93f94

Browse files
committed
amcheck: Fix snapshot usage in bt_index_parent_check
We were using SnapshotAny to do some index checks, but that's wrong and causes spurious errors when used on indexes created by CREATE INDEX CONCURRENTLY. Fix it to use an MVCC snapshot, and add a test for it. This problem came in with commit 5ae2087, which introduced uniqueness check. Backpatch to 17. Author: Mihail Nikalayeu <mihailnikalayeu@gmail.com> Reviewed-by: Andrey Borodin <x4mmm@yandex-team.ru> Backpatch-through: 17 Discussion: https://postgr.es/m/CANtu0ojmVd27fEhfpST7RG2KZvwkX=dMyKUqg0KM87FkOSdz8Q@mail.gmail.com
1 parent e46041f commit df93f94

File tree

3 files changed

+60
-51
lines changed

3 files changed

+60
-51
lines changed

contrib/amcheck/t/002_cic.pl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,28 @@
6464
)
6565
});
6666

67+
# Test bt_index_parent_check() with indexes created with
68+
# CREATE INDEX CONCURRENTLY.
69+
$node->safe_psql('postgres', q(CREATE TABLE quebec(i int primary key)));
70+
# Insert two rows into index
71+
$node->safe_psql('postgres',
72+
q(INSERT INTO quebec SELECT i FROM generate_series(1, 2) s(i);));
73+
74+
# start background transaction
75+
my $in_progress_h = $node->background_psql('postgres');
76+
$in_progress_h->query_safe(q(BEGIN; SELECT pg_current_xact_id();));
77+
78+
# delete one row from table, while background transaction is in progress
79+
$node->safe_psql('postgres', q(DELETE FROM quebec WHERE i = 1;));
80+
# create index concurrently, which will skip the deleted row
81+
$node->safe_psql('postgres', q(CREATE INDEX CONCURRENTLY oscar ON quebec(i);));
82+
83+
# check index using bt_index_parent_check
84+
my $result = $node->psql('postgres',
85+
q(SELECT bt_index_parent_check('oscar', heapallindexed => true)));
86+
is($result, '0', 'bt_index_parent_check for CIC after removed row');
87+
88+
$in_progress_h->quit;
89+
6790
$node->stop;
6891
done_testing();

contrib/amcheck/verify_nbtree.c

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

469464
/*
470-
* We need a snapshot to check the uniqueness of the index. For better
471-
* performance take it once per index check. If snapshot already taken
472-
* reuse it.
465+
* We need a snapshot to check the uniqueness of the index. For better
466+
* performance, take it once per index check. If one was already taken
467+
* above, use that.
473468
*/
474469
if (state->checkunique)
475470
{
476471
state->indexinfo = BuildIndexInfo(state->rel);
477-
if (state->indexinfo->ii_Unique)
478-
{
479-
if (snapshot != SnapshotAny)
480-
state->snapshot = snapshot;
481-
else
482-
state->snapshot = RegisterSnapshot(GetTransactionSnapshot());
483-
}
472+
473+
if (state->indexinfo->ii_Unique && state->snapshot == InvalidSnapshot)
474+
state->snapshot = RegisterSnapshot(GetTransactionSnapshot());
484475
}
485476

486477
Assert(!state->rootdescend || state->readonly);
@@ -555,30 +546,28 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
555546
/*
556547
* Create our own scan for table_index_build_scan(), rather than
557548
* 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.
549+
* actually use the MVCC snapshot registered earlier.
560550
*
561551
* Note that table_index_build_scan() calls heap_endscan() for us.
562552
*/
563553
scan = table_beginscan_strat(state->heaprel, /* relation */
564-
snapshot, /* snapshot */
554+
state->snapshot, /* snapshot */
565555
0, /* number of keys */
566556
NULL, /* scan key */
567557
true, /* buffer access strategy OK */
568558
true); /* syncscan OK? */
569559

570560
/*
571561
* Scan will behave as the first scan of a CREATE INDEX CONCURRENTLY
572-
* behaves in !readonly case.
562+
* behaves.
573563
*
574564
* It's okay that we don't actually use the same lock strength for the
575-
* heap relation as any other ii_Concurrent caller would in !readonly
576-
* case. We have no reason to care about a concurrent VACUUM
577-
* operation, since there isn't going to be a second scan of the heap
578-
* that needs to be sure that there was no concurrent recycling of
579-
* TIDs.
565+
* heap relation as any other ii_Concurrent caller would. We have no
566+
* reason to care about a concurrent VACUUM operation, since there
567+
* isn't going to be a second scan of the heap that needs to be sure
568+
* that there was no concurrent recycling of TIDs.
580569
*/
581-
indexinfo->ii_Concurrent = !state->readonly;
570+
indexinfo->ii_Concurrent = true;
582571

583572
/*
584573
* Don't wait for uncommitted tuple xact commit/abort when index is a
@@ -602,14 +591,11 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
602591
state->heaptuplespresent, RelationGetRelationName(heaprel),
603592
100.0 * bloom_prop_bits_set(state->filter))));
604593

605-
if (snapshot != SnapshotAny)
606-
UnregisterSnapshot(snapshot);
607-
608594
bloom_free(state->filter);
609595
}
610596

611597
/* Be tidy: */
612-
if (snapshot == SnapshotAny && state->snapshot != InvalidSnapshot)
598+
if (state->snapshot != InvalidSnapshot)
613599
UnregisterSnapshot(state->snapshot);
614600
MemoryContextDelete(state->targetcontext);
615601
}

doc/src/sgml/amcheck.sgml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ SET client_min_messages = DEBUG1;
382382
verification functions is <literal>true</literal>, an additional
383383
phase of verification is performed against the table associated with
384384
the target index relation. This consists of a <quote>dummy</quote>
385-
<command>CREATE INDEX</command> operation, which checks for the
385+
<command>CREATE INDEX CONCURRENTLY</command> operation, which checks for the
386386
presence of all hypothetical new index tuples against a temporary,
387387
in-memory summarizing structure (this is built when needed during
388388
the basic first phase of verification). The summarizing structure

0 commit comments

Comments
 (0)