Skip to content

Commit ce2f575

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 8ba61bc commit ce2f575

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
@@ -60,5 +60,28 @@
6060
)
6161
});
6262

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

contrib/amcheck/verify_nbtree.c

Lines changed: 36 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,6 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
508508
BTMetaPageData *metad;
509509
uint32 previouslevel;
510510
BtreeLevel current;
511-
Snapshot snapshot = SnapshotAny;
512511

513512
if (!readonly)
514513
elog(DEBUG1, "verifying consistency of tree structure for index \"%s\"",
@@ -559,54 +558,46 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
559558
state->heaptuplespresent = 0;
560559

561560
/*
562-
* Register our own snapshot in !readonly case, rather than asking
561+
* Register our own snapshot for heapallindexed, rather than asking
563562
* table_index_build_scan() to do this for us later. This needs to
564563
* happen before index fingerprinting begins, so we can later be
565564
* certain that index fingerprinting should have reached all tuples
566565
* returned by table_index_build_scan().
567566
*/
568-
if (!state->readonly)
569-
{
570-
snapshot = RegisterSnapshot(GetTransactionSnapshot());
567+
state->snapshot = RegisterSnapshot(GetTransactionSnapshot());
571568

572-
/*
573-
* GetTransactionSnapshot() always acquires a new MVCC snapshot in
574-
* READ COMMITTED mode. A new snapshot is guaranteed to have all
575-
* the entries it requires in the index.
576-
*
577-
* We must defend against the possibility that an old xact
578-
* snapshot was returned at higher isolation levels when that
579-
* snapshot is not safe for index scans of the target index. This
580-
* is possible when the snapshot sees tuples that are before the
581-
* index's indcheckxmin horizon. Throwing an error here should be
582-
* very rare. It doesn't seem worth using a secondary snapshot to
583-
* avoid this.
584-
*/
585-
if (IsolationUsesXactSnapshot() && rel->rd_index->indcheckxmin &&
586-
!TransactionIdPrecedes(HeapTupleHeaderGetXmin(rel->rd_indextuple->t_data),
587-
snapshot->xmin))
588-
ereport(ERROR,
589-
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
590-
errmsg("index \"%s\" cannot be verified using transaction snapshot",
591-
RelationGetRelationName(rel))));
592-
}
569+
/*
570+
* GetTransactionSnapshot() always acquires a new MVCC snapshot in
571+
* READ COMMITTED mode. A new snapshot is guaranteed to have all the
572+
* entries it requires in the index.
573+
*
574+
* We must defend against the possibility that an old xact snapshot
575+
* was returned at higher isolation levels when that snapshot is not
576+
* safe for index scans of the target index. This is possible when
577+
* the snapshot sees tuples that are before the index's indcheckxmin
578+
* horizon. Throwing an error here should be very rare. It doesn't
579+
* seem worth using a secondary snapshot to avoid this.
580+
*/
581+
if (IsolationUsesXactSnapshot() && rel->rd_index->indcheckxmin &&
582+
!TransactionIdPrecedes(HeapTupleHeaderGetXmin(rel->rd_indextuple->t_data),
583+
state->snapshot->xmin))
584+
ereport(ERROR,
585+
errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
586+
errmsg("index \"%s\" cannot be verified using transaction snapshot",
587+
RelationGetRelationName(rel)));
593588
}
594589

595590
/*
596-
* We need a snapshot to check the uniqueness of the index. For better
597-
* performance take it once per index check. If snapshot already taken
598-
* reuse it.
591+
* We need a snapshot to check the uniqueness of the index. For better
592+
* performance, take it once per index check. If one was already taken
593+
* above, use that.
599594
*/
600595
if (state->checkunique)
601596
{
602597
state->indexinfo = BuildIndexInfo(state->rel);
603-
if (state->indexinfo->ii_Unique)
604-
{
605-
if (snapshot != SnapshotAny)
606-
state->snapshot = snapshot;
607-
else
608-
state->snapshot = RegisterSnapshot(GetTransactionSnapshot());
609-
}
598+
599+
if (state->indexinfo->ii_Unique && state->snapshot == InvalidSnapshot)
600+
state->snapshot = RegisterSnapshot(GetTransactionSnapshot());
610601
}
611602

612603
Assert(!state->rootdescend || state->readonly);
@@ -681,30 +672,28 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
681672
/*
682673
* Create our own scan for table_index_build_scan(), rather than
683674
* getting it to do so for us. This is required so that we can
684-
* actually use the MVCC snapshot registered earlier in !readonly
685-
* case.
675+
* actually use the MVCC snapshot registered earlier.
686676
*
687677
* Note that table_index_build_scan() calls heap_endscan() for us.
688678
*/
689679
scan = table_beginscan_strat(state->heaprel, /* relation */
690-
snapshot, /* snapshot */
680+
state->snapshot, /* snapshot */
691681
0, /* number of keys */
692682
NULL, /* scan key */
693683
true, /* buffer access strategy OK */
694684
true); /* syncscan OK? */
695685

696686
/*
697687
* Scan will behave as the first scan of a CREATE INDEX CONCURRENTLY
698-
* behaves in !readonly case.
688+
* behaves.
699689
*
700690
* It's okay that we don't actually use the same lock strength for the
701-
* heap relation as any other ii_Concurrent caller would in !readonly
702-
* case. We have no reason to care about a concurrent VACUUM
703-
* operation, since there isn't going to be a second scan of the heap
704-
* that needs to be sure that there was no concurrent recycling of
705-
* TIDs.
691+
* heap relation as any other ii_Concurrent caller would. We have no
692+
* reason to care about a concurrent VACUUM operation, since there
693+
* isn't going to be a second scan of the heap that needs to be sure
694+
* that there was no concurrent recycling of TIDs.
706695
*/
707-
indexinfo->ii_Concurrent = !state->readonly;
696+
indexinfo->ii_Concurrent = true;
708697

709698
/*
710699
* Don't wait for uncommitted tuple xact commit/abort when index is a
@@ -728,14 +717,11 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
728717
state->heaptuplespresent, RelationGetRelationName(heaprel),
729718
100.0 * bloom_prop_bits_set(state->filter))));
730719

731-
if (snapshot != SnapshotAny)
732-
UnregisterSnapshot(snapshot);
733-
734720
bloom_free(state->filter);
735721
}
736722

737723
/* Be tidy: */
738-
if (snapshot == SnapshotAny && state->snapshot != InvalidSnapshot)
724+
if (state->snapshot != InvalidSnapshot)
739725
UnregisterSnapshot(state->snapshot);
740726
MemoryContextDelete(state->targetcontext);
741727
}

doc/src/sgml/amcheck.sgml

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

0 commit comments

Comments
 (0)