diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 360b26e6fc470de15bba62ea942afe7b9de03c8f..fc9f964bf78887ea08439e60acb548d805dd1848 100644 --- a/src/backend/access/brin/brin.c +++ b/src/backend/access/brin/brin.c @@ -696,7 +696,7 @@ brinbuildempty(PG_FUNCTION_ARGS) * * XXX we could mark item tuples as "dirty" (when a minimum or maximum heap * tuple is deleted), meaning the need to re-run summarization on the affected - * range. Need to an extra flag in brintuples for that. + * range. Would need to add an extra flag in brintuples for that. */ Datum brinbulkdelete(PG_FUNCTION_ARGS) @@ -951,9 +951,13 @@ summarize_range(IndexInfo *indexInfo, BrinBuildState *state, Relation heapRel, * Execute the partial heap scan covering the heap blocks in the specified * page range, summarizing the heap tuples in it. This scan stops just * short of brinbuildCallback creating the new index entry. + * + * Note that it is critical we use the "any visible" mode of + * IndexBuildHeapRangeScan here: otherwise, we would miss tuples inserted + * by transactions that are still in progress, among other corner cases. */ state->bs_currRangeStart = heapBlk; - IndexBuildHeapRangeScan(heapRel, state->bs_irel, indexInfo, false, + IndexBuildHeapRangeScan(heapRel, state->bs_irel, indexInfo, false, true, heapBlk, state->bs_pagesPerRange, brinbuildCallback, (void *) state); @@ -1058,36 +1062,6 @@ brinsummarize(Relation index, Relation heapRel, double *numSummarized, state = initialize_brin_buildstate(index, revmap, pagesPerRange); indexInfo = BuildIndexInfo(index); - - /* - * We only have ShareUpdateExclusiveLock on the table, and - * therefore other sessions may insert tuples into the range - * we're going to scan. This is okay, because we take - * additional precautions to avoid losing the additional - * tuples; see comments in summarize_range. Set the - * concurrent flag, which causes IndexBuildHeapRangeScan to - * use a snapshot other than SnapshotAny, and silences - * warnings emitted there. - */ - indexInfo->ii_Concurrent = true; - - /* - * If using transaction-snapshot mode, it would be possible - * for another transaction to insert a tuple that's not - * visible to our snapshot if we have already acquired one, - * when in snapshot-isolation mode; therefore, disallow this - * from running in such a transaction unless a snapshot hasn't - * been acquired yet. - * - * This code is called by VACUUM and - * brin_summarize_new_values. Have the error message mention - * the latter because VACUUM cannot run in a transaction and - * thus cannot cause this issue. - */ - if (IsolationUsesXactSnapshot() && FirstSnapshotSet) - ereport(ERROR, - (errcode(ERRCODE_INVALID_TRANSACTION_STATE), - errmsg("brin_summarize_new_values() cannot run in a transaction that has already obtained a snapshot"))); } summarize_range(indexInfo, state, heapRel, heapBlk); @@ -1111,7 +1085,10 @@ brinsummarize(Relation index, Relation heapRel, double *numSummarized, /* free resources */ brinRevmapTerminate(revmap); if (state) + { terminate_brin_buildstate(state); + pfree(indexInfo); + } } /* diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 69f35c933097920c3a0da919e8a9ef7ff6563fec..e59b163173c611f0605ed8e255debcfec0225f5b 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -2161,6 +2161,7 @@ IndexBuildHeapScan(Relation heapRelation, { return IndexBuildHeapRangeScan(heapRelation, indexRelation, indexInfo, allow_sync, + false, 0, InvalidBlockNumber, callback, callback_state); } @@ -2170,12 +2171,17 @@ IndexBuildHeapScan(Relation heapRelation, * number of blocks are scanned. Scan to end-of-rel can be signalled by * passing InvalidBlockNumber as numblocks. Note that restricting the range * to scan cannot be done when requesting syncscan. + * + * When "anyvisible" mode is requested, all tuples visible to any transaction + * are considered, including those inserted or deleted by transactions that are + * still in progress. */ double IndexBuildHeapRangeScan(Relation heapRelation, Relation indexRelation, IndexInfo *indexInfo, bool allow_sync, + bool anyvisible, BlockNumber start_blockno, BlockNumber numblocks, IndexBuildCallback callback, @@ -2209,6 +2215,12 @@ IndexBuildHeapRangeScan(Relation heapRelation, checking_uniqueness = (indexInfo->ii_Unique || indexInfo->ii_ExclusionOps != NULL); + /* + * "Any visible" mode is not compatible with uniqueness checks; make sure + * only one of those is requested. + */ + Assert(!(anyvisible && checking_uniqueness)); + /* * Need an EState for evaluation of index expressions and partial-index * predicates. Also a slot to hold the current tuple. @@ -2236,6 +2248,9 @@ IndexBuildHeapRangeScan(Relation heapRelation, { snapshot = RegisterSnapshot(GetTransactionSnapshot()); OldestXmin = InvalidTransactionId; /* not used */ + + /* "any visible" mode is not compatible with this */ + Assert(!anyvisible); } else { @@ -2363,6 +2378,17 @@ IndexBuildHeapRangeScan(Relation heapRelation, break; case HEAPTUPLE_INSERT_IN_PROGRESS: + /* + * In "anyvisible" mode, this tuple is visible and we don't + * need any further checks. + */ + if (anyvisible) + { + indexIt = true; + tupleIsAlive = true; + break; + } + /* * Since caller should hold ShareLock or better, normally * the only way to see this is if it was inserted earlier @@ -2409,8 +2435,16 @@ IndexBuildHeapRangeScan(Relation heapRelation, /* * As with INSERT_IN_PROGRESS case, this is unexpected - * unless it's our own deletion or a system catalog. + * unless it's our own deletion or a system catalog; + * but in anyvisible mode, this tuple is visible. */ + if (anyvisible) + { + indexIt = true; + tupleIsAlive = false; + break; + } + xwait = HeapTupleHeaderGetUpdateXid(heapTuple->t_data); if (!TransactionIdIsCurrentTransactionId(xwait)) { diff --git a/src/include/access/brin.h b/src/include/access/brin.h index a04a1320b10f48af5975e9488a1c0a8a8415bf57..e72fb2dcfca2a499a493e34c16d1246afada48f1 100644 --- a/src/include/access/brin.h +++ b/src/include/access/brin.h @@ -22,7 +22,6 @@ extern Datum brinbuild(PG_FUNCTION_ARGS); extern Datum brinbuildempty(PG_FUNCTION_ARGS); extern Datum brininsert(PG_FUNCTION_ARGS); extern Datum brinbeginscan(PG_FUNCTION_ARGS); -extern Datum bringettuple(PG_FUNCTION_ARGS); extern Datum bringetbitmap(PG_FUNCTION_ARGS); extern Datum brinrescan(PG_FUNCTION_ARGS); extern Datum brinendscan(PG_FUNCTION_ARGS); @@ -30,7 +29,6 @@ extern Datum brinmarkpos(PG_FUNCTION_ARGS); extern Datum brinrestrpos(PG_FUNCTION_ARGS); extern Datum brinbulkdelete(PG_FUNCTION_ARGS); extern Datum brinvacuumcleanup(PG_FUNCTION_ARGS); -extern Datum brincanreturn(PG_FUNCTION_ARGS); extern Datum brincostestimate(PG_FUNCTION_ARGS); extern Datum brinoptions(PG_FUNCTION_ARGS); diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h index 8b3b28d954c6d2de9c080a00f1f585f5f0159279..a29174b69057be201c0639c3be7b4c33bb19f343 100644 --- a/src/include/catalog/index.h +++ b/src/include/catalog/index.h @@ -105,6 +105,7 @@ extern double IndexBuildHeapRangeScan(Relation heapRelation, Relation indexRelation, IndexInfo *indexInfo, bool allow_sync, + bool anyvisible, BlockNumber start_blockno, BlockNumber end_blockno, IndexBuildCallback callback, diff --git a/src/test/isolation/expected/brin-1.out b/src/test/isolation/expected/brin-1.out new file mode 100644 index 0000000000000000000000000000000000000000..ddb90f4dba1389d58e53154b198aa12c023b8700 --- /dev/null +++ b/src/test/isolation/expected/brin-1.out @@ -0,0 +1,39 @@ +Parsed test spec with 2 sessions + +starting permutation: s2check s1b s2b s1i s2summ s1c s2c s2check +step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); +itemoffset blknum attnum allnulls hasnulls placeholder value + +1 0 1 f f f {1 .. 1} +step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ; +step s2b: BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; +?column? + +1 +step s1i: INSERT INTO brin_iso VALUES (1000); +step s2summ: SELECT brin_summarize_new_values('brinidx'::regclass); +brin_summarize_new_values + +1 +step s1c: COMMIT; +step s2c: COMMIT; +step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); +itemoffset blknum attnum allnulls hasnulls placeholder value + +1 0 1 f f f {1 .. 1} +2 1 1 f f f {1 .. 1000} + +starting permutation: s2check s1b s1i s2vacuum s1c s2check +step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); +itemoffset blknum attnum allnulls hasnulls placeholder value + +1 0 1 f f f {1 .. 1} +step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ; +step s1i: INSERT INTO brin_iso VALUES (1000); +step s2vacuum: VACUUM brin_iso; +step s1c: COMMIT; +step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); +itemoffset blknum attnum allnulls hasnulls placeholder value + +1 0 1 f f f {1 .. 1} +2 1 1 f f f {1 .. 1000} diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule index c0ed637cd2485b7d59aba2608e5b832e1072d1c5..5852c6e51c2ed72fdc270ceddabcc95a63ed9804 100644 --- a/src/test/isolation/isolation_schedule +++ b/src/test/isolation/isolation_schedule @@ -36,6 +36,7 @@ test: skip-locked test: skip-locked-2 test: skip-locked-3 test: skip-locked-4 +test: brin-1 test: drop-index-concurrently-1 test: alter-table-1 test: alter-table-2 diff --git a/src/test/isolation/specs/brin-1.spec b/src/test/isolation/specs/brin-1.spec new file mode 100644 index 0000000000000000000000000000000000000000..19ac18a2e88dc440e63caba08e4ecf2039cc80e6 --- /dev/null +++ b/src/test/isolation/specs/brin-1.spec @@ -0,0 +1,44 @@ +# This test verifies that values inserted in transactions still in progress +# are considered during concurrent range summarization (either using the +# brin_summarize_new_values function or regular VACUUM). + +setup +{ + CREATE TABLE brin_iso ( + value int + ) WITH (fillfactor=10); + CREATE INDEX brinidx ON brin_iso USING brin (value) WITH (pages_per_range=1); + -- this fills the first page + DO $$ + DECLARE curtid tid; + BEGIN + LOOP + INSERT INTO brin_iso VALUES (1) RETURNING ctid INTO curtid; + EXIT WHEN curtid > tid '(1, 0)'; + END LOOP; + END; + $$; + CREATE EXTENSION IF NOT EXISTS pageinspect; +} + +teardown +{ + DROP TABLE brin_iso; +} + +session "s1" +step "s1b" { BEGIN ISOLATION LEVEL REPEATABLE READ; } +step "s1i" { INSERT INTO brin_iso VALUES (1000); } +step "s1c" { COMMIT; } + +session "s2" +step "s2b" { BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT 1; } +step "s2summ" { SELECT brin_summarize_new_values('brinidx'::regclass); } +step "s2c" { COMMIT; } + +step "s2vacuum" { VACUUM brin_iso; } + +step "s2check" { SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); } + +permutation "s2check" "s1b" "s2b" "s1i" "s2summ" "s1c" "s2c" "s2check" +permutation "s2check" "s1b" "s1i" "s2vacuum" "s1c" "s2check"