mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-28 06:42:43 +08:00
Compare commits
6 Commits
v0.8.1
...
hnsw-build
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52184bfa10 | ||
|
|
e7899b1250 | ||
|
|
9e29dff78c | ||
|
|
13c0dbf530 | ||
|
|
c205a12107 | ||
|
|
db3755a58f |
30
.github/workflows/build.yml
vendored
30
.github/workflows/build.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
- postgres: 13
|
- postgres: 13
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: ankane/setup-postgres@v1
|
- uses: ankane/setup-postgres@v1
|
||||||
with:
|
with:
|
||||||
postgres-version: ${{ matrix.postgres }}
|
postgres-version: ${{ matrix.postgres }}
|
||||||
@@ -49,11 +49,11 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- postgres: 17
|
- postgres: 17
|
||||||
os: macos-15
|
os: macos-26
|
||||||
- postgres: 14
|
- postgres: 14
|
||||||
os: macos-13
|
os: macos-15-intel
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: ankane/setup-postgres@v1
|
- uses: ankane/setup-postgres@v1
|
||||||
with:
|
with:
|
||||||
postgres-version: ${{ matrix.postgres }}
|
postgres-version: ${{ matrix.postgres }}
|
||||||
@@ -72,27 +72,35 @@ jobs:
|
|||||||
tar xf $TAG.tar.gz
|
tar xf $TAG.tar.gz
|
||||||
mv postgres-$TAG postgres
|
mv postgres-$TAG postgres
|
||||||
env:
|
env:
|
||||||
TAG: ${{ matrix.postgres == 17 && 'REL_17_2' || 'REL_14_15' }}
|
TAG: ${{ matrix.postgres == 17 && 'REL_17_6' || 'REL_14_19' }}
|
||||||
- run: make prove_installcheck PROVE_FLAGS="-I ./postgres/src/test/perl -I ./test/perl"
|
- run: make prove_installcheck PROVE_FLAGS="-I ./postgres/src/test/perl -I ./test/perl"
|
||||||
env:
|
env:
|
||||||
PERL5LIB: /Users/runner/perl5/lib/perl5
|
PERL5LIB: /Users/runner/perl5/lib/perl5
|
||||||
- run: make clean && $(brew --prefix llvm@$LLVM_VERSION)/bin/scan-build --status-bugs make
|
- run: make clean && $(brew --prefix llvm@$LLVM_VERSION)/bin/scan-build --status-bugs make
|
||||||
env:
|
env:
|
||||||
LLVM_VERSION: ${{ matrix.os == 'macos-15' && 18 || 15 }}
|
LLVM_VERSION: ${{ matrix.os == 'macos-26' && 20 || 18 }}
|
||||||
PG_CFLAGS: -DUSE_ASSERT_CHECKING
|
PG_CFLAGS: -DUSE_ASSERT_CHECKING
|
||||||
windows:
|
windows:
|
||||||
runs-on: windows-latest
|
runs-on: ${{ matrix.os }}
|
||||||
if: ${{ !startsWith(github.ref_name, 'mac') }}
|
if: ${{ !startsWith(github.ref_name, 'mac') }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- postgres: 17
|
||||||
|
os: windows-2025
|
||||||
|
- postgres: 14
|
||||||
|
os: windows-2022
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: ankane/setup-postgres@v1
|
- uses: ankane/setup-postgres@v1
|
||||||
with:
|
with:
|
||||||
postgres-version: 14
|
postgres-version: ${{ matrix.postgres }}
|
||||||
- run: |
|
- run: |
|
||||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" && ^
|
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" && ^
|
||||||
nmake /NOLOGO /F Makefile.win && ^
|
nmake /NOLOGO /F Makefile.win && ^
|
||||||
nmake /NOLOGO /F Makefile.win install && ^
|
nmake /NOLOGO /F Makefile.win install && ^
|
||||||
nmake /NOLOGO /F Makefile.win installcheck && ^
|
nmake /NOLOGO /F Makefile.win installcheck ${{ matrix.postgres != 17 && 'PG_REGRESS=$(PGROOT)\bin\pg_regress' || '' }} && ^
|
||||||
nmake /NOLOGO /F Makefile.win clean && ^
|
nmake /NOLOGO /F Makefile.win clean && ^
|
||||||
nmake /NOLOGO /F Makefile.win uninstall
|
nmake /NOLOGO /F Makefile.win uninstall
|
||||||
shell: cmd
|
shell: cmd
|
||||||
@@ -125,7 +133,7 @@ jobs:
|
|||||||
if: ${{ !startsWith(github.ref_name, 'mac') && !startsWith(github.ref_name, 'windows') }}
|
if: ${{ !startsWith(github.ref_name, 'mac') && !startsWith(github.ref_name, 'windows') }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: ankane/setup-postgres-valgrind@v1
|
- uses: ankane/setup-postgres-valgrind@v1
|
||||||
with:
|
with:
|
||||||
postgres-version: 17
|
postgres-version: 17
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
## 0.8.2 (unreleased)
|
||||||
|
|
||||||
|
- Improved performance of HNSW index scans from disk
|
||||||
|
|
||||||
## 0.8.1 (2025-09-04)
|
## 0.8.1 (2025-09-04)
|
||||||
|
|
||||||
- Added support for Postgres 18 rc1
|
- Added support for Postgres 18 rc1
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ LIBDIR = $(PGROOT)\lib
|
|||||||
PKGLIBDIR = $(PGROOT)\lib
|
PKGLIBDIR = $(PGROOT)\lib
|
||||||
SHAREDIR = $(PGROOT)\share
|
SHAREDIR = $(PGROOT)\share
|
||||||
|
|
||||||
|
# Use $(PGROOT)\bin\pg_regress for Postgres < 17
|
||||||
|
PG_REGRESS = $(LIBDIR)\pgxs\src\test\regress\pg_regress
|
||||||
|
|
||||||
CFLAGS = /nologo /I"$(INCLUDEDIR_SERVER)\port\win32_msvc" /I"$(INCLUDEDIR_SERVER)\port\win32" /I"$(INCLUDEDIR_SERVER)" /I"$(INCLUDEDIR)"
|
CFLAGS = /nologo /I"$(INCLUDEDIR_SERVER)\port\win32_msvc" /I"$(INCLUDEDIR_SERVER)\port\win32" /I"$(INCLUDEDIR_SERVER)" /I"$(INCLUDEDIR)"
|
||||||
|
|
||||||
CFLAGS = $(CFLAGS) $(PG_CFLAGS)
|
CFLAGS = $(CFLAGS) $(PG_CFLAGS)
|
||||||
@@ -58,7 +61,7 @@ install: all
|
|||||||
for %f in ($(HEADERS)) do copy %f "$(INCLUDEDIR_SERVER)\extension\$(EXTENSION)"
|
for %f in ($(HEADERS)) do copy %f "$(INCLUDEDIR_SERVER)\extension\$(EXTENSION)"
|
||||||
|
|
||||||
installcheck:
|
installcheck:
|
||||||
"$(BINDIR)\pg_regress" --bindir="$(BINDIR)" $(REGRESS_OPTS) $(REGRESS)
|
"$(PG_REGRESS)" --bindir="$(BINDIR)" $(REGRESS_OPTS) $(REGRESS)
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
del /f "$(PKGLIBDIR)\$(SHLIB)"
|
del /f "$(PKGLIBDIR)\$(SHLIB)"
|
||||||
|
|||||||
@@ -1267,6 +1267,7 @@ Thanks to:
|
|||||||
- [k-means++: The Advantage of Careful Seeding](https://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf)
|
- [k-means++: The Advantage of Careful Seeding](https://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf)
|
||||||
- [Concept Decompositions for Large Sparse Text Data using Clustering](https://www.cs.utexas.edu/users/inderjit/public_papers/concept_mlj.pdf)
|
- [Concept Decompositions for Large Sparse Text Data using Clustering](https://www.cs.utexas.edu/users/inderjit/public_papers/concept_mlj.pdf)
|
||||||
- [Efficient and Robust Approximate Nearest Neighbor Search using Hierarchical Navigable Small World Graphs](https://arxiv.org/ftp/arxiv/papers/1603/1603.09320.pdf)
|
- [Efficient and Robust Approximate Nearest Neighbor Search using Hierarchical Navigable Small World Graphs](https://arxiv.org/ftp/arxiv/papers/1603/1603.09320.pdf)
|
||||||
|
- [Turbocharging Vector Databases using Modern SSDs](https://www.vldb.org/pvldb/volumes/18/paper/Turbocharging%20Vector%20Databases%20using%20Modern%20SSDs)
|
||||||
|
|
||||||
## History
|
## History
|
||||||
|
|
||||||
|
|||||||
122
src/hnswbuild.c
122
src/hnswbuild.c
@@ -131,6 +131,69 @@ HnswBuildAppendPage(Relation index, Buffer *buf, Page *page, ForkNumber forkNum)
|
|||||||
HnswInitPage(*buf, *page);
|
HnswInitPage(*buf, *page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
WriteElement(char *base, HnswElement element, HnswElementTuple etup, HnswNeighborTuple ntup, HnswBuildState * buildstate, Size maxSize, Buffer *buf, Page *page, Relation index, ForkNumber forkNum)
|
||||||
|
{
|
||||||
|
Size etupSize;
|
||||||
|
Size ntupSize;
|
||||||
|
Size combinedSize;
|
||||||
|
Pointer valuePtr;
|
||||||
|
|
||||||
|
/* Skip if already added */
|
||||||
|
if (BlockNumberIsValid(element->neighborPage))
|
||||||
|
return;
|
||||||
|
|
||||||
|
valuePtr = HnswPtrAccess(base, element->value);
|
||||||
|
|
||||||
|
/* Zero memory for each element */
|
||||||
|
MemSet(etup, 0, HNSW_TUPLE_ALLOC_SIZE);
|
||||||
|
|
||||||
|
/* Calculate sizes */
|
||||||
|
etupSize = HNSW_ELEMENT_TUPLE_SIZE(VARSIZE_ANY(valuePtr));
|
||||||
|
ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(element->level, buildstate->m);
|
||||||
|
combinedSize = etupSize + ntupSize + sizeof(ItemIdData);
|
||||||
|
|
||||||
|
/* Initial size check */
|
||||||
|
if (etupSize > HNSW_TUPLE_ALLOC_SIZE)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
||||||
|
errmsg("index tuple too large")));
|
||||||
|
|
||||||
|
HnswSetElementTuple(base, etup, element);
|
||||||
|
|
||||||
|
/* Keep element and neighbors on the same page if possible */
|
||||||
|
if (PageGetFreeSpace(*page) < etupSize || (combinedSize <= maxSize && PageGetFreeSpace(*page) < combinedSize))
|
||||||
|
HnswBuildAppendPage(index, buf, page, forkNum);
|
||||||
|
|
||||||
|
/* Calculate offsets */
|
||||||
|
element->blkno = BufferGetBlockNumber(*buf);
|
||||||
|
element->offno = OffsetNumberNext(PageGetMaxOffsetNumber(*page));
|
||||||
|
if (combinedSize <= maxSize)
|
||||||
|
{
|
||||||
|
element->neighborPage = element->blkno;
|
||||||
|
element->neighborOffno = OffsetNumberNext(element->offno);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
element->neighborPage = element->blkno + 1;
|
||||||
|
element->neighborOffno = FirstOffsetNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemPointerSet(&etup->neighbortid, element->neighborPage, element->neighborOffno);
|
||||||
|
|
||||||
|
/* Add element */
|
||||||
|
if (PageAddItem(*page, (Item) etup, etupSize, InvalidOffsetNumber, false, false) != element->offno)
|
||||||
|
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||||
|
|
||||||
|
/* Add new page if needed */
|
||||||
|
if (PageGetFreeSpace(*page) < ntupSize)
|
||||||
|
HnswBuildAppendPage(index, buf, page, forkNum);
|
||||||
|
|
||||||
|
/* Add placeholder for neighbors */
|
||||||
|
if (PageAddItem(*page, (Item) ntup, ntupSize, InvalidOffsetNumber, false, false) != element->neighborOffno)
|
||||||
|
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create graph pages
|
* Create graph pages
|
||||||
*/
|
*/
|
||||||
@@ -164,61 +227,24 @@ CreateGraphPages(HnswBuildState * buildstate)
|
|||||||
while (!HnswPtrIsNull(base, iter))
|
while (!HnswPtrIsNull(base, iter))
|
||||||
{
|
{
|
||||||
HnswElement element = HnswPtrAccess(base, iter);
|
HnswElement element = HnswPtrAccess(base, iter);
|
||||||
Size etupSize;
|
|
||||||
Size ntupSize;
|
|
||||||
Size combinedSize;
|
|
||||||
Pointer valuePtr = HnswPtrAccess(base, element->value);
|
|
||||||
|
|
||||||
/* Update iterator */
|
/* Update iterator */
|
||||||
iter = element->next;
|
iter = element->next;
|
||||||
|
|
||||||
/* Zero memory for each element */
|
WriteElement(base, element, etup, ntup, buildstate, maxSize, &buf, &page, index, forkNum);
|
||||||
MemSet(etup, 0, HNSW_TUPLE_ALLOC_SIZE);
|
|
||||||
|
|
||||||
/* Calculate sizes */
|
for (int lc = element->level; lc >= 0; lc--)
|
||||||
etupSize = HNSW_ELEMENT_TUPLE_SIZE(VARSIZE_ANY(valuePtr));
|
|
||||||
ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(element->level, buildstate->m);
|
|
||||||
combinedSize = etupSize + ntupSize + sizeof(ItemIdData);
|
|
||||||
|
|
||||||
/* Initial size check */
|
|
||||||
if (etupSize > HNSW_TUPLE_ALLOC_SIZE)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
|
||||||
errmsg("index tuple too large")));
|
|
||||||
|
|
||||||
HnswSetElementTuple(base, etup, element);
|
|
||||||
|
|
||||||
/* Keep element and neighbors on the same page if possible */
|
|
||||||
if (PageGetFreeSpace(page) < etupSize || (combinedSize <= maxSize && PageGetFreeSpace(page) < combinedSize))
|
|
||||||
HnswBuildAppendPage(index, &buf, &page, forkNum);
|
|
||||||
|
|
||||||
/* Calculate offsets */
|
|
||||||
element->blkno = BufferGetBlockNumber(buf);
|
|
||||||
element->offno = OffsetNumberNext(PageGetMaxOffsetNumber(page));
|
|
||||||
if (combinedSize <= maxSize)
|
|
||||||
{
|
{
|
||||||
element->neighborPage = element->blkno;
|
HnswNeighborArray *neighbors = HnswGetNeighbors(base, element, lc);
|
||||||
element->neighborOffno = OffsetNumberNext(element->offno);
|
|
||||||
|
for (int i = 0; i < neighbors->length; i++)
|
||||||
|
{
|
||||||
|
HnswCandidate *hc = &neighbors->items[i];
|
||||||
|
HnswElement hce = HnswPtrAccess(base, hc->element);
|
||||||
|
|
||||||
|
WriteElement(base, hce, etup, ntup, buildstate, maxSize, &buf, &page, index, forkNum);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
element->neighborPage = element->blkno + 1;
|
|
||||||
element->neighborOffno = FirstOffsetNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemPointerSet(&etup->neighbortid, element->neighborPage, element->neighborOffno);
|
|
||||||
|
|
||||||
/* Add element */
|
|
||||||
if (PageAddItem(page, (Item) etup, etupSize, InvalidOffsetNumber, false, false) != element->offno)
|
|
||||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
|
||||||
|
|
||||||
/* Add new page if needed */
|
|
||||||
if (PageGetFreeSpace(page) < ntupSize)
|
|
||||||
HnswBuildAppendPage(index, &buf, &page, forkNum);
|
|
||||||
|
|
||||||
/* Add placeholder for neighbors */
|
|
||||||
if (PageAddItem(page, (Item) ntup, ntupSize, InvalidOffsetNumber, false, false) != element->neighborOffno)
|
|
||||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
insertPage = BufferGetBlockNumber(buf);
|
insertPage = BufferGetBlockNumber(buf);
|
||||||
|
|||||||
@@ -253,6 +253,7 @@ HnswInitElement(char *base, ItemPointer heaptid, int m, double ml, int maxLevel,
|
|||||||
element->deleted = 0;
|
element->deleted = 0;
|
||||||
/* Start at one to make it easier to find issues */
|
/* Start at one to make it easier to find issues */
|
||||||
element->version = 1;
|
element->version = 1;
|
||||||
|
element->neighborPage = InvalidBlockNumber;
|
||||||
|
|
||||||
HnswInitNeighbors(base, element, m, allocator);
|
HnswInitNeighbors(base, element, m, allocator);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user