From 922460a1d56c0365770bfb08bdd8e2b291164362 Mon Sep 17 00:00:00 2001 From: Matthias van de Meent Date: Thu, 30 Nov 2023 18:12:16 +0100 Subject: [PATCH v7 3/3] BRIN: Backfill empty ranges If we don't, then an index with WHERE (practically_nonnull IS NULL) would be a full table scan due to missing entries in the ranges table. The issue is fixed for both normal and parallel index creation. --- src/backend/access/brin/brin.c | 69 ++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 251350fde2..9d2179e6d7 100644 --- a/src/backend/access/brin/brin.c +++ b/src/backend/access/brin/brin.c @@ -165,6 +165,7 @@ typedef struct BrinBuildState double bs_numtuples; double bs_reltuples; Buffer bs_currentInsertBuf; + BlockNumber bs_tablePages; BlockNumber bs_pagesPerRange; BlockNumber bs_currRangeStart; BrinRevmap *bs_rmAccess; @@ -1134,6 +1135,7 @@ brinbuild(Relation heap, Relation index, IndexInfo *indexInfo) state->bs_spool = (BrinSpool *) palloc0(sizeof(BrinSpool)); state->bs_spool->heap = heap; state->bs_spool->index = index; + state->bs_tablePages = RelationGetNumberOfBlocks(heap); /* * Attempt to launch parallel worker scan when required @@ -1208,6 +1210,23 @@ brinbuild(Relation heap, Relation index, IndexInfo *indexInfo) /* process the final batch */ form_and_insert_tuple(state); + state->bs_currRangeStart += state->bs_pagesPerRange; + /* + * Backfill the final ranges with empty data. + * + * This saves us from doing what amounts to full table scans when the + * index is built on stupid index quals like WHERE (nonnull_column IS + * NULL). + */ + while (state->bs_currRangeStart + state->bs_pagesPerRange - 1 < state->bs_tablePages) + { + brin_memtuple_initialize(state->bs_dtuple, state->bs_bdesc); + + form_and_insert_tuple(state); + + state->bs_currRangeStart += state->bs_pagesPerRange; + } + /* track the number of relation tuples */ state->bs_reltuples = reltuples; } @@ -2625,13 +2644,34 @@ _brin_end_parallel(BrinLeader *brinleader, BrinBuildState *state) tuplesort_end(spool->sortstate); - /* Fill empty ranges at the end, for all ranges missing in the tuplesort. */ - prevblkno = (prevblkno == InvalidBlockNumber) ? 0 : prevblkno; - while (prevblkno + state->bs_pagesPerRange < memtuple->bt_blkno) + /* Fill the BRIN tuple for the last page range with data. */ + if (prevblkno != InvalidBlockNumber) { - /* the missing range */ + BrinTuple *tmp; + Size len; + + tmp = brin_form_tuple(state->bs_bdesc, memtuple->bt_blkno, + memtuple, &len); + + brin_doinsert(state->bs_irel, state->bs_pagesPerRange, state->bs_rmAccess, + &state->bs_currentInsertBuf, tmp->bt_blkno, tmp, len); + + pfree(tmp); + } + + /* + * Fill empty ranges at the end, for all ranges missing in the tuplesort. + * + * Starting from here, prevblkno is the to-be-inserted range's start block + * number. Note that we don't fill in the relation's last page range. + */ + if (prevblkno == InvalidBlockNumber) + prevblkno = 0; + else prevblkno += state->bs_pagesPerRange; + while (prevblkno + state->bs_pagesPerRange < state->bs_tablePages) + { /* Did we already build the empty range? If not, do it now. */ if (emptyTuple == NULL) { @@ -2641,32 +2681,21 @@ _brin_end_parallel(BrinLeader *brinleader, BrinBuildState *state) } else { - /* we already have am "empty range" tuple, just set the block */ + /* we already have an "empty range" tuple, just set the block */ emptyTuple->bt_blkno = prevblkno; } + /* Insert the missing range */ brin_doinsert(state->bs_irel, state->bs_pagesPerRange, state->bs_rmAccess, &state->bs_currentInsertBuf, emptyTuple->bt_blkno, emptyTuple, emptySize); - } - /* Fill the BRIN tuple for the last page range. */ - if (prevblkno != InvalidBlockNumber) - { - BrinTuple *tmp; - Size len; - - tmp = brin_form_tuple(state->bs_bdesc, memtuple->bt_blkno, - memtuple, &len); - - brin_doinsert(state->bs_irel, state->bs_pagesPerRange, state->bs_rmAccess, - &state->bs_currentInsertBuf, tmp->bt_blkno, tmp, len); - - pfree(tmp); + /* ... and update to the next range's block number */ + prevblkno += state->bs_pagesPerRange; } /* - * Switch back to the originam memory context, and destroy the one we + * Switch back to the original memory context, and destroy the one we * created to isolate the union_tuple calls. */ MemoryContextSwitchTo(oldCxt); -- 2.40.1