From 24bd672deb4a6fa14abfc8583b500d1cbc332032 Mon Sep 17 00:00:00 2001 From: John Naylor Date: Fri, 5 Apr 2024 17:04:12 +0700 Subject: [PATCH v83 1/3] store offsets in the header --- src/backend/access/common/tidstore.c | 52 +++++++++++++++++++ .../test_tidstore/expected/test_tidstore.out | 14 +++++ .../test_tidstore/sql/test_tidstore.sql | 5 ++ 3 files changed, 71 insertions(+) diff --git a/src/backend/access/common/tidstore.c b/src/backend/access/common/tidstore.c index e1a7e82469..4eb5d46951 100644 --- a/src/backend/access/common/tidstore.c +++ b/src/backend/access/common/tidstore.c @@ -34,6 +34,9 @@ /* number of active words for a page: */ #define WORDS_PER_PAGE(n) ((n) / BITS_PER_BITMAPWORD + 1) +/* number of offsets we can store in the header of a BlocktableEntry */ +#define NUM_FULL_OFFSETS ((sizeof(uintptr_t) - sizeof(uint16)) / sizeof(OffsetNumber)) + /* * This is named similarly to PagetableEntry in tidbitmap.c * because the two have a similar function. @@ -41,6 +44,10 @@ typedef struct BlocktableEntry { uint16 nwords; + + /* We can store a small number of offsets here to avoid wasting space with a sparse bitmap. */ + OffsetNumber full_offsets[NUM_FULL_OFFSETS]; + bitmapword words[FLEXIBLE_ARRAY_MEMBER]; } BlocktableEntry; #define MaxBlocktableEntrySize \ @@ -316,6 +323,25 @@ TidStoreSetBlockOffsets(TidStore *ts, BlockNumber blkno, OffsetNumber *offsets, for (int i = 1; i < num_offsets; i++) Assert(offsets[i] > offsets[i - 1]); + memset(page, 0, offsetof(BlocktableEntry, words)); + + if (num_offsets <= NUM_FULL_OFFSETS) + { + for (int i = 0; i < num_offsets; i++) + { + OffsetNumber off = offsets[i]; + + /* safety check to ensure we don't overrun bit array bounds */ + if (!OffsetNumberIsValid(off)) + elog(ERROR, "tuple offset out of range: %u", off); + + page->full_offsets[i] = off; + } + + page->nwords = 0; + } + else + { for (wordnum = 0, next_word_threshold = BITS_PER_BITMAPWORD; wordnum <= WORDNUM(offsets[num_offsets - 1]); wordnum++, next_word_threshold += BITS_PER_BITMAPWORD) @@ -343,6 +369,7 @@ TidStoreSetBlockOffsets(TidStore *ts, BlockNumber blkno, OffsetNumber *offsets, page->nwords = wordnum; Assert(page->nwords == WORDS_PER_PAGE(offsets[num_offsets - 1])); +} if (TidStoreIsShared(ts)) shared_ts_set(ts->tree.shared, blkno, page); @@ -369,6 +396,18 @@ TidStoreIsMember(TidStore *ts, ItemPointer tid) if (page == NULL) return false; + if (page->nwords == 0) + { + /* we have offsets in the header */ + for (int i = 0; i < NUM_FULL_OFFSETS; i++) + { + if (page->full_offsets[i] == off) + return true; + } + return false; + } + else + { wordnum = WORDNUM(off); bitnum = BITNUM(off); @@ -378,6 +417,7 @@ TidStoreIsMember(TidStore *ts, ItemPointer tid) return (page->words[wordnum] & ((bitmapword) 1 << bitnum)) != 0; } +} /* * Prepare to iterate through a TidStore. @@ -496,6 +536,17 @@ tidstore_iter_extract_tids(TidStoreIter *iter, BlockNumber blkno, result->num_offsets = 0; result->blkno = blkno; + if (page->nwords == 0) + { + /* we have offsets in the header */ + for (int i = 0; i < NUM_FULL_OFFSETS; i++) + { + if (page->full_offsets[i] != InvalidOffsetNumber) + result->offsets[result->num_offsets++] = page->full_offsets[i]; + } + } + else + { for (wordnum = 0; wordnum < page->nwords; wordnum++) { bitmapword w = page->words[wordnum]; @@ -518,3 +569,4 @@ tidstore_iter_extract_tids(TidStoreIter *iter, BlockNumber blkno, } } } +} diff --git a/src/test/modules/test_tidstore/expected/test_tidstore.out b/src/test/modules/test_tidstore/expected/test_tidstore.out index 0ae2f970da..c40779859b 100644 --- a/src/test/modules/test_tidstore/expected/test_tidstore.out +++ b/src/test/modules/test_tidstore/expected/test_tidstore.out @@ -36,6 +36,20 @@ SELECT do_set_block_offsets(blk, array_agg(off)::int2[]) (VALUES (0), (1), (:maxblkno / 2), (:maxblkno - 1), (:maxblkno)) AS blocks(blk), (VALUES (1), (2), (:maxoffset / 2), (:maxoffset - 1), (:maxoffset)) AS offsets(off) GROUP BY blk; +-- Test offsets embedded in the bitmap header. +SELECT do_set_block_offsets(501, array[greatest((random() * :maxoffset)::int, 1)]::int2[]); + do_set_block_offsets +---------------------- + 501 +(1 row) + +SELECT do_set_block_offsets(502, array_agg(DISTINCT greatest((random() * :maxoffset)::int, 1))::int2[]) + FROM generate_series(1, 3); + do_set_block_offsets +---------------------- + 502 +(1 row) + -- Add enough TIDs to cause the store to appear "full", compared -- to the allocated memory it started out with. This is easier -- with memory contexts in local memory. diff --git a/src/test/modules/test_tidstore/sql/test_tidstore.sql b/src/test/modules/test_tidstore/sql/test_tidstore.sql index e5edfbb264..1f4e4a807e 100644 --- a/src/test/modules/test_tidstore/sql/test_tidstore.sql +++ b/src/test/modules/test_tidstore/sql/test_tidstore.sql @@ -28,6 +28,11 @@ SELECT do_set_block_offsets(blk, array_agg(off)::int2[]) (VALUES (1), (2), (:maxoffset / 2), (:maxoffset - 1), (:maxoffset)) AS offsets(off) GROUP BY blk; +-- Test offsets embedded in the bitmap header. +SELECT do_set_block_offsets(501, array[greatest((random() * :maxoffset)::int, 1)]::int2[]); +SELECT do_set_block_offsets(502, array_agg(DISTINCT greatest((random() * :maxoffset)::int, 1))::int2[]) + FROM generate_series(1, 3); + -- Add enough TIDs to cause the store to appear "full", compared -- to the allocated memory it started out with. This is easier -- with memory contexts in local memory. -- 2.44.0