v8-0004-BRIN-Backfill-empty-ranges.patch

text/x-patch

Filename: v8-0004-BRIN-Backfill-empty-ranges.patch
Type: text/x-patch
Part: 3
Message: Re: Parallel CREATE INDEX for BRIN indexes

Patch

Format: format-patch
Series: patch v8-0004
Subject: BRIN: Backfill empty ranges
File+
src/backend/access/brin/brin.c 58 38
From 27675f10b328fc6d80331dfa99a1b8e3309a90ff Mon Sep 17 00:00:00 2001
From: Matthias van de Meent <boekewurm+postgres@gmail.com>
Date: Thu, 30 Nov 2023 18:12:16 +0100
Subject: [PATCH v8 4/6] 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 | 96 ++++++++++++++++++++--------------
 1 file changed, 58 insertions(+), 38 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bc519825f2e..00b437d9c1f 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -221,6 +221,8 @@ static void brin_vacuum_scan(Relation idxrel, BufferAccessStrategy strategy);
 static bool add_values_to_range(Relation idxRel, BrinDesc *bdesc,
 								BrinMemTuple *dtup, const Datum *values, const bool *nulls);
 static bool check_null_keys(BrinValues *bval, ScanKey *nullkeys, int nnullkeys);
+static BrinTuple *brin_init_empty_tuple(BrinBuildState *state, BlockNumber blkno,
+										BrinTuple *emptyTuple, Size *emptySize);
 
 /* parallel index builds */
 static void _brin_begin_parallel(BrinBuildState *buildstate, Relation heap, Relation index,
@@ -2619,17 +2621,7 @@ _brin_end_parallel(BrinLeader *brinleader, BrinBuildState *state)
 			prevblkno += state->bs_pagesPerRange;
 
 			/* Did we already build the empty range? If not, do it now. */
-			if (emptyTuple == NULL)
-			{
-				BrinMemTuple *dtuple = brin_new_memtuple(state->bs_bdesc);
-
-				emptyTuple = brin_form_tuple(state->bs_bdesc, prevblkno, dtuple, &emptySize);
-			}
-			else
-			{
-				/* we already have am "empty range" tuple, just set the block */
-				emptyTuple->bt_blkno = prevblkno;
-			}
+			emptyTuple = brin_init_empty_tuple(state, prevblkno, emptyTuple, &emptySize);
 
 			brin_doinsert(state->bs_irel, state->bs_pagesPerRange, state->bs_rmAccess,
 						  &state->bs_currentInsertBuf,
@@ -2641,32 +2633,7 @@ _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)
-	{
-		/* the missing range */
-		prevblkno += state->bs_pagesPerRange;
-
-		/* Did we already build the empty range? If not, do it now. */
-		if (emptyTuple == NULL)
-		{
-			BrinMemTuple *dtuple = brin_new_memtuple(state->bs_bdesc);
-
-			emptyTuple = brin_form_tuple(state->bs_bdesc, prevblkno, dtuple, &emptySize);
-		}
-		else
-		{
-			/* we already have am "empty range" tuple, just set the block */
-			emptyTuple->bt_blkno = prevblkno;
-		}
-
-		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. */
+	/* Fill the BRIN tuple for the last page range with data. */
 	if (prevblkno != InvalidBlockNumber)
 	{
 		BrinTuple  *tmp;
@@ -2682,7 +2649,32 @@ _brin_end_parallel(BrinLeader *brinleader, BrinBuildState *state)
 	}
 
 	/*
-	 * Switch back to the originam memory context, and destroy the one we
+	 * 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. */
+		emptyTuple = brin_init_empty_tuple(state, prevblkno, emptyTuple, &emptySize);
+
+		/* Insert the missing range */
+		brin_doinsert(state->bs_irel, state->bs_pagesPerRange, state->bs_rmAccess,
+					  &state->bs_currentInsertBuf,
+					  emptyTuple->bt_blkno, emptyTuple, emptySize);
+
+		/* ... and update to the next range's block number */
+		prevblkno += state->bs_pagesPerRange;
+	}
+
+	/*
+	 * Switch back to the original memory context, and destroy the one we
 	 * created to isolate the union_tuple calls.
 	 */
 	MemoryContextSwitchTo(oldCxt);
@@ -2897,3 +2889,31 @@ _brin_parallel_build_main(dsm_segment *seg, shm_toc *toc)
 	index_close(indexRel, indexLockmode);
 	table_close(heapRel, heapLockmode);
 }
+
+/*
+ * brin_init_empty_tuple
+ *		Maybe initialize a BRIN tuple representing empty range.
+ *
+ * If emptyTuple is NULL, initializes new tuple representing empty range at
+ * block blkno. Otherwise the tuple is reused, and only the bt_blkno field
+ * is updated.
+ */
+static BrinTuple *
+brin_init_empty_tuple(BrinBuildState *state, BlockNumber blkno,
+					  BrinTuple *emptyTuple, Size *emptySize)
+{
+	/* Did we already build the empty range? If not, do it now. */
+	if (emptyTuple == NULL)
+	{
+		BrinMemTuple *dtuple = brin_new_memtuple(state->bs_bdesc);
+
+		emptyTuple = brin_form_tuple(state->bs_bdesc, blkno, dtuple, emptySize);
+	}
+	else
+	{
+		/* we already have an "empty range" tuple, just set the block */
+		emptyTuple->bt_blkno = blkno;
+	}
+
+	return emptyTuple;
+}
-- 
2.41.0