mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-26 05:42:43 +08:00
Compare commits
1 Commits
hnsw-build
...
parallel-i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6058beeda |
8
.github/workflows/build.yml
vendored
8
.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@v5
|
- uses: actions/checkout@v4
|
||||||
- uses: ankane/setup-postgres@v1
|
- uses: ankane/setup-postgres@v1
|
||||||
with:
|
with:
|
||||||
postgres-version: ${{ matrix.postgres }}
|
postgres-version: ${{ matrix.postgres }}
|
||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
- postgres: 14
|
- postgres: 14
|
||||||
os: macos-15-intel
|
os: macos-15-intel
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v4
|
||||||
- uses: ankane/setup-postgres@v1
|
- uses: ankane/setup-postgres@v1
|
||||||
with:
|
with:
|
||||||
postgres-version: ${{ matrix.postgres }}
|
postgres-version: ${{ matrix.postgres }}
|
||||||
@@ -92,7 +92,7 @@ jobs:
|
|||||||
- postgres: 14
|
- postgres: 14
|
||||||
os: windows-2022
|
os: windows-2022
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v4
|
||||||
- uses: ankane/setup-postgres@v1
|
- uses: ankane/setup-postgres@v1
|
||||||
with:
|
with:
|
||||||
postgres-version: ${{ matrix.postgres }}
|
postgres-version: ${{ matrix.postgres }}
|
||||||
@@ -133,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@v5
|
- uses: actions/checkout@v4
|
||||||
- uses: ankane/setup-postgres-valgrind@v1
|
- uses: ankane/setup-postgres-valgrind@v1
|
||||||
with:
|
with:
|
||||||
postgres-version: 17
|
postgres-version: 17
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
## 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
|
||||||
|
|||||||
@@ -1267,7 +1267,6 @@ 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,69 +131,6 @@ 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
|
||||||
*/
|
*/
|
||||||
@@ -227,24 +164,61 @@ 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;
|
||||||
|
|
||||||
WriteElement(base, element, etup, ntup, buildstate, maxSize, &buf, &page, index, forkNum);
|
/* Zero memory for each element */
|
||||||
|
MemSet(etup, 0, HNSW_TUPLE_ALLOC_SIZE);
|
||||||
|
|
||||||
for (int lc = element->level; lc >= 0; lc--)
|
/* 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)
|
||||||
{
|
{
|
||||||
HnswNeighborArray *neighbors = HnswGetNeighbors(base, element, lc);
|
element->neighborPage = element->blkno;
|
||||||
|
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,7 +253,6 @@ 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);
|
||||||
|
|
||||||
|
|||||||
@@ -143,6 +143,10 @@ ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
|||||||
*indexSelectivity = costs.indexSelectivity;
|
*indexSelectivity = costs.indexSelectivity;
|
||||||
*indexCorrelation = costs.indexCorrelation;
|
*indexCorrelation = costs.indexCorrelation;
|
||||||
*indexPages = costs.numIndexPages;
|
*indexPages = costs.numIndexPages;
|
||||||
|
|
||||||
|
elog(INFO, "ivfflatcostestimate = %f", costs.indexTotalCost);
|
||||||
|
/* Cost estimates for parallel workers applied outside of amcostestimate */
|
||||||
|
elog(INFO, "parallel_workers = %d, parallel aware = %d", path->path.parallel_workers, path->path.parallel_aware);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -170,6 +174,25 @@ ivfflatvalidate(Oid opclassoid)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Size
|
||||||
|
ivfflatestimateparallelscan()
|
||||||
|
{
|
||||||
|
elog(INFO, "ivfflatestimateparallelscan");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ivfflatinitparallelscan(void *target)
|
||||||
|
{
|
||||||
|
elog(INFO, "ivfflatinitparallelscan");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ivfflatparallelrescan(IndexScanDesc scan)
|
||||||
|
{
|
||||||
|
elog(INFO, "ivfflatparallelrescan");
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Define index handler
|
* Define index handler
|
||||||
*
|
*
|
||||||
@@ -200,7 +223,7 @@ ivfflathandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amstorage = false;
|
amroutine->amstorage = false;
|
||||||
amroutine->amclusterable = false;
|
amroutine->amclusterable = false;
|
||||||
amroutine->ampredlocks = false;
|
amroutine->ampredlocks = false;
|
||||||
amroutine->amcanparallel = false;
|
amroutine->amcanparallel = true;
|
||||||
#if PG_VERSION_NUM >= 170000
|
#if PG_VERSION_NUM >= 170000
|
||||||
amroutine->amcanbuildparallel = true;
|
amroutine->amcanbuildparallel = true;
|
||||||
#endif
|
#endif
|
||||||
@@ -242,9 +265,9 @@ ivfflathandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amrestrpos = NULL;
|
amroutine->amrestrpos = NULL;
|
||||||
|
|
||||||
/* Interface functions to support parallel index scans */
|
/* Interface functions to support parallel index scans */
|
||||||
amroutine->amestimateparallelscan = NULL;
|
amroutine->amestimateparallelscan = ivfflatestimateparallelscan;
|
||||||
amroutine->aminitparallelscan = NULL;
|
amroutine->aminitparallelscan = ivfflatinitparallelscan;
|
||||||
amroutine->amparallelrescan = NULL;
|
amroutine->amparallelrescan = ivfflatparallelrescan;
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 180000
|
#if PG_VERSION_NUM >= 180000
|
||||||
amroutine->amtranslatestrategy = NULL;
|
amroutine->amtranslatestrategy = NULL;
|
||||||
|
|||||||
@@ -119,6 +119,9 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
|||||||
|
|
||||||
tuplesort_reset(so->sortstate);
|
tuplesort_reset(so->sortstate);
|
||||||
|
|
||||||
|
if (scan->parallel_scan != NULL)
|
||||||
|
elog(INFO, "parallel scan");
|
||||||
|
|
||||||
/* Search closest probes lists */
|
/* Search closest probes lists */
|
||||||
while (so->listIndex < so->maxProbes && (++batchProbes) <= so->probes)
|
while (so->listIndex < so->maxProbes && (++batchProbes) <= so->probes)
|
||||||
{
|
{
|
||||||
|
|||||||
16
test/sql/ivfflat_parallel.sql
Normal file
16
test/sql/ivfflat_parallel.sql
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
-- SET force_parallel_mode = on;
|
||||||
|
SET parallel_setup_cost = 10;
|
||||||
|
SET parallel_tuple_cost = 0.000001;
|
||||||
|
SET min_parallel_table_scan_size = 1;
|
||||||
|
SET min_parallel_index_scan_size = 1;
|
||||||
|
|
||||||
|
CREATE TABLE t (id integer, val vector(3));
|
||||||
|
ALTER TABLE t ALTER COLUMN val SET STORAGE PLAIN;
|
||||||
|
INSERT INTO t (id, val) SELECT n, ARRAY[random(), random(), random()] FROM generate_series(1,1000000) n;
|
||||||
|
CREATE INDEX ON t USING ivfflat (val) WITH (lists = 10);
|
||||||
|
SET ivfflat.probes = 4;
|
||||||
|
|
||||||
|
EXPLAIN SELECT * FROM t ORDER BY val <-> '[0.5,0.5,0.5]' LIMIT 5;
|
||||||
|
SELECT * FROM t ORDER BY val <-> '[0.5,0.5,0.5]' LIMIT 5;
|
||||||
|
|
||||||
|
DROP TABLE t;
|
||||||
Reference in New Issue
Block a user