Teach TID store to skip bitmap for small numbers of offsets

The header portion of BlocktableEntry has enough padding space for
an array of 3 offsets (1 on 32-bit platforms). Use this space instead
of having a sparse bitmap array. This will take up a constant amount
of space no matter what the offsets are.

Reviewed (in an earlier version) by Masahiko Sawada

Discussion: https://postgr.es/m/CANWCAZYw+_KAaUNruhJfE=h6WgtBKeDG32St8vBJBEY82bGVRQ@mail.gmail.com
Discussion: https://postgr.es/m/CAD21AoBci3Hujzijubomo1tdwH3XtQ9F89cTNQ4bsQijOmqnEw@mail.gmail.com
This commit is contained in:
John Naylor 2024-04-08 18:38:11 +07:00
parent dd1f6b0c17
commit f35bd9bf35
3 changed files with 130 additions and 37 deletions

View File

@ -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(bitmapword) - sizeof(uint16)) / sizeof(OffsetNumber))
/*
* This is named similarly to PagetableEntry in tidbitmap.c
* because the two have a similar function.
@ -41,6 +44,13 @@
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 \
@ -331,6 +341,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)
@ -358,6 +387,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);
@ -384,6 +414,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);
@ -392,6 +434,7 @@ TidStoreIsMember(TidStore *ts, ItemPointer tid)
return false;
return (page->words[wordnum] & ((bitmapword) 1 << bitnum)) != 0;
}
}
/*
@ -511,6 +554,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];
@ -532,4 +586,5 @@ tidstore_iter_extract_tids(TidStoreIter *iter, BlockNumber blkno,
w >>= 1;
}
}
}
}

View File

@ -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.
@ -73,6 +87,20 @@ SELECT test_create(true);
(1 row)
-- 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)
-- Random TIDs test. The offset numbers are randomized and must be
-- unique and ordered.
INSERT INTO hideblocks (blockno)

View File

@ -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.
@ -49,6 +54,11 @@ SELECT test_destroy();
-- because unused static functions would raise warnings there.
SELECT test_create(true);
-- 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);
-- Random TIDs test. The offset numbers are randomized and must be
-- unique and ordered.
INSERT INTO hideblocks (blockno)