From 8c5a4bfb6cf9601b01f8b2cab2e986c33e49e758 Mon Sep 17 00:00:00 2001 From: Andrew Kane Date: Fri, 19 Jul 2024 13:54:36 -0700 Subject: [PATCH] Fixed failed to add index item error with sparsevec - fixes #625 --- CHANGELOG.md | 1 + src/hnswinsert.c | 28 ++++++++++++---- test/t/038_hnsw_sparsevec_vacuum_insert.pl | 38 ++++++++++++++++++++++ 3 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 test/t/038_hnsw_sparsevec_vacuum_insert.pl diff --git a/CHANGELOG.md b/CHANGELOG.md index 80826c8..44ef23f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## 0.7.3 (unreleased) +- Fixed `failed to add index item` error with `sparsevec` - Fixed compilation error with FreeBSD ARM - Fixed compilation warning with MSVC and Postgres 16 diff --git a/src/hnswinsert.c b/src/hnswinsert.c index 0023024..04a10f9 100644 --- a/src/hnswinsert.c +++ b/src/hnswinsert.c @@ -36,14 +36,15 @@ GetInsertPage(Relation index) * Check for a free offset */ static bool -HnswFreeOffset(Relation index, Buffer buf, Page page, HnswElement element, Size ntupSize, Buffer *nbuf, Page *npage, OffsetNumber *freeOffno, OffsetNumber *freeNeighborOffno, BlockNumber *newInsertPage) +HnswFreeOffset(Relation index, Buffer buf, Page page, HnswElement element, Size etupSize, Size ntupSize, Buffer *nbuf, Page *npage, OffsetNumber *freeOffno, OffsetNumber *freeNeighborOffno, BlockNumber *newInsertPage) { OffsetNumber offno; OffsetNumber maxoffno = PageGetMaxOffsetNumber(page); for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno)) { - HnswElementTuple etup = (HnswElementTuple) PageGetItem(page, PageGetItemId(page, offno)); + ItemId eitemid = PageGetItemId(page, offno); + HnswElementTuple etup = (HnswElementTuple) PageGetItem(page, eitemid); /* Skip neighbor tuples */ if (!HnswIsElementTuple(etup)) @@ -54,7 +55,9 @@ HnswFreeOffset(Relation index, Buffer buf, Page page, HnswElement element, Size BlockNumber elementPage = BufferGetBlockNumber(buf); BlockNumber neighborPage = ItemPointerGetBlockNumber(&etup->neighbortid); OffsetNumber neighborOffno = ItemPointerGetOffsetNumber(&etup->neighbortid); - ItemId itemid; + ItemId nitemid; + Size pageFree; + Size npageFree; if (!BlockNumberIsValid(*newInsertPage)) *newInsertPage = elementPage; @@ -73,10 +76,21 @@ HnswFreeOffset(Relation index, Buffer buf, Page page, HnswElement element, Size *npage = BufferGetPage(*nbuf); } - itemid = PageGetItemId(*npage, neighborOffno); + nitemid = PageGetItemId(*npage, neighborOffno); - /* Check for space on neighbor tuple page */ - if (PageGetFreeSpace(*npage) + ItemIdGetLength(itemid) - sizeof(ItemIdData) >= ntupSize) + /* + * Calculate free space individually since tuples are overwritten + * individually (in separate calls to PageIndexTupleOverwrite) + */ + pageFree = ItemIdGetLength(eitemid) + PageGetExactFreeSpace(page); + npageFree = ItemIdGetLength(nitemid); + if (neighborPage != elementPage) + npageFree += PageGetExactFreeSpace(*npage); + else if (pageFree >= etupSize) + npageFree += pageFree - etupSize; + + /* Check for space */ + if (pageFree >= etupSize && npageFree >= ntupSize) { *freeOffno = offno; *freeNeighborOffno = neighborOffno; @@ -184,7 +198,7 @@ AddElementOnDisk(Relation index, HnswElement e, int m, BlockNumber insertPage, B } /* Next, try space from a deleted element */ - if (HnswFreeOffset(index, buf, page, e, ntupSize, &nbuf, &npage, &freeOffno, &freeNeighborOffno, &newInsertPage)) + if (HnswFreeOffset(index, buf, page, e, etupSize, ntupSize, &nbuf, &npage, &freeOffno, &freeNeighborOffno, &newInsertPage)) { if (nbuf != buf) { diff --git a/test/t/038_hnsw_sparsevec_vacuum_insert.pl b/test/t/038_hnsw_sparsevec_vacuum_insert.pl new file mode 100644 index 0000000..28e71c7 --- /dev/null +++ b/test/t/038_hnsw_sparsevec_vacuum_insert.pl @@ -0,0 +1,38 @@ +use strict; +use warnings; +use PostgresNode; +use TestLib; +use Test::More; + +# Initialize node +my $node = get_new_node('node'); +$node->init; +$node->start; + +# Create table and index +$node->safe_psql("postgres", "CREATE EXTENSION vector;"); +$node->safe_psql("postgres", "CREATE TABLE tst (i serial, v sparsevec(100000));"); +$node->safe_psql("postgres", "CREATE INDEX ON tst USING hnsw (v sparsevec_l2_ops);"); + +for (1 .. 3) { + for (1 .. 100) { + my @elements; + my %indices; + for (1 .. int(rand() * 100)) { + my $index = int(rand() * (100000 - 1)) + 1; + if (!exists($indices{$index})) { + my $value = rand(); + push(@elements, "$index:$value"); + $indices{$index} = 1; + } + } + my $embedding = "{" . join(",", @elements) . "}/100000"; + $node->safe_psql("postgres", "INSERT INTO tst (v) VALUES ('$embedding');"); + } + + $node->safe_psql("postgres", "DELETE FROM tst WHERE i % 2 = 0;"); + $node->safe_psql("postgres", "VACUUM tst;"); + is(1, 1); +} + +done_testing();