mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-22 20:15:46 +08:00
Compare commits
21 Commits
hnsw-fast-
...
hnsw-fast-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13d3bae99b | ||
|
|
3b9c4c55ee | ||
|
|
06e2052e40 | ||
|
|
891072df68 | ||
|
|
e9a96d341f | ||
|
|
a45ca414f4 | ||
|
|
b4f3cc3e13 | ||
|
|
c01cf8a315 | ||
|
|
3ecb9a3cb2 | ||
|
|
a069e18fe4 | ||
|
|
5174a23094 | ||
|
|
16d7de79f6 | ||
|
|
e54ec4d637 | ||
|
|
cc641002d3 | ||
|
|
2f9b1e2893 | ||
|
|
a3e4fbf6aa | ||
|
|
09a4ec29a0 | ||
|
|
ca3b4cd029 | ||
|
|
d96e486274 | ||
|
|
88213186a5 | ||
|
|
7dd9534894 |
14
src/hnsw.h
14
src/hnsw.h
@@ -114,8 +114,6 @@
|
||||
|
||||
/* Variables */
|
||||
extern int hnsw_ef_search;
|
||||
|
||||
/* This is initialized when the module is loaded */
|
||||
extern int hnsw_lock_tranche_id;
|
||||
|
||||
typedef struct HnswElementData HnswElementData;
|
||||
@@ -200,20 +198,12 @@ typedef struct HnswGraph
|
||||
bool flushed;
|
||||
} HnswGraph;
|
||||
|
||||
typedef struct HnswSpool
|
||||
{
|
||||
Relation heap;
|
||||
Relation index;
|
||||
} HnswSpool;
|
||||
|
||||
typedef struct HnswShared
|
||||
{
|
||||
/* Immutable state */
|
||||
Oid heaprelid;
|
||||
Oid indexrelid;
|
||||
bool isconcurrent;
|
||||
int scantuplesortstates;
|
||||
dsa_pointer dsaptr;
|
||||
|
||||
/* Worker progress */
|
||||
ConditionVariable workersdonecv;
|
||||
@@ -393,14 +383,14 @@ void HnswGetMetaPageInfo(Relation index, int *m, HnswElement * entryPoint);
|
||||
void *HnswAlloc(HnswAllocator * allocator, Size size);
|
||||
HnswElement HnswInitElement(char *base, ItemPointer tid, int m, double ml, int maxLevel, HnswAllocator * alloc);
|
||||
HnswElement HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno);
|
||||
void HnswInsertElement(char *base, HnswElement element, HnswElement entryPoint, Relation index, FmgrInfo *procinfo, Oid collation, int m, int efConstruction, bool existing);
|
||||
void HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint, Relation index, FmgrInfo *procinfo, Oid collation, int m, int efConstruction, bool existing);
|
||||
HnswCandidate *HnswEntryCandidate(char *base, HnswElement em, Datum q, Relation rel, FmgrInfo *procinfo, Oid collation, bool loadVec);
|
||||
void HnswUpdateMetaPage(Relation index, int updateEntry, HnswElement entryPoint, BlockNumber insertPage, ForkNumber forkNum, bool building);
|
||||
void HnswSetNeighborTuple(char *base, HnswNeighborTuple ntup, HnswElement e, int m);
|
||||
void HnswAddHeapTid(HnswElement element, ItemPointer heaptid);
|
||||
void HnswInitNeighbors(char *base, HnswElement element, int m, HnswAllocator * alloc);
|
||||
bool HnswInsertTupleOnDisk(Relation index, Datum value, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel, bool building);
|
||||
void HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting, bool building);
|
||||
void HnswUpdateNeighborsOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting, bool building);
|
||||
void HnswLoadElementFromTuple(HnswElement element, HnswElementTuple etup, bool loadHeaptids, bool loadVec);
|
||||
void HnswLoadElement(HnswElement element, float *distance, Datum *q, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec);
|
||||
void HnswSetElementTuple(char *base, HnswElementTuple etup, HnswElement element);
|
||||
|
||||
236
src/hnswbuild.c
236
src/hnswbuild.c
@@ -1,3 +1,39 @@
|
||||
/*
|
||||
* The HNSW build happens in two phases:
|
||||
*
|
||||
* 1. In-memory phase
|
||||
*
|
||||
* In this first phase, the graph is held completely in memory. When the graph
|
||||
* is fully built, or we run out of memory reserved for the build (determined
|
||||
* by maintenance_work_mem), we materialize the graph to disk (see
|
||||
* FlushPages()), and switch to the on-disk phase.
|
||||
*
|
||||
* In a parallel build, a large contiguous chunk of shared memory is allocated
|
||||
* to hold the graph. Each worker process has its own HnswBuildState struct in
|
||||
* private memory, which contains information that doesn't change throughout
|
||||
* the build, and pointers to the shared structs in shared memory. The shared
|
||||
* memory area is mapped to a different address in each worker process, and
|
||||
* 'HnswBuildState.hnswarea' points to the beginning of the shared area in the
|
||||
* worker process's address space. All pointers used in the graph are
|
||||
* "relative pointers", stored as an offset from 'hnswarea'.
|
||||
*
|
||||
* Each element is protected by an LWLock. It must be held when reading or
|
||||
* modifying the element's neighbors or 'heaptids'.
|
||||
*
|
||||
* In a non-parallel build, the graph is held in backend-private memory. All
|
||||
* the elements are allocated in a dedicated memory context, 'graphCtx', and
|
||||
* the pointers used in the graph are regular pointers.
|
||||
*
|
||||
* 2. On-disk phase
|
||||
*
|
||||
* In the on-disk phase, the index is built by inserting each vector to the
|
||||
* index one by one, just like on INSERT. The only difference is that we don't
|
||||
* WAL-log the individual inserts. If the graph fit completely in memory and
|
||||
* was fully built in the in-memory phase, the on-disk phase is skipped.
|
||||
*
|
||||
* After we have finished building the graph, we perform one more scan through
|
||||
* the index and write all the pages to the WAL.
|
||||
*/
|
||||
#include "postgres.h"
|
||||
|
||||
#include <math.h>
|
||||
@@ -295,7 +331,7 @@ FlushPages(HnswBuildState * buildstate)
|
||||
* Add a heap TID to an existing element
|
||||
*/
|
||||
static bool
|
||||
HnswAddDuplicateInMemory(HnswElement element, HnswElement dup)
|
||||
AddDuplicateInMemory(HnswElement element, HnswElement dup)
|
||||
{
|
||||
LWLockAcquire(&dup->lock, LW_EXCLUSIVE);
|
||||
|
||||
@@ -316,15 +352,15 @@ HnswAddDuplicateInMemory(HnswElement element, HnswElement dup)
|
||||
* Find duplicate element
|
||||
*/
|
||||
static bool
|
||||
HnswFindDuplicateInMemory(char *base, HnswElement element)
|
||||
FindDuplicateInMemory(char *base, HnswElement element)
|
||||
{
|
||||
HnswNeighborArray *neighbors = HnswGetNeighbors(base, element, 0);
|
||||
Datum value = HnswGetValue(base, element);
|
||||
|
||||
for (int i = 0; i < neighbors->length; i++)
|
||||
{
|
||||
HnswCandidate *neighbor = &neighbors->items[i];
|
||||
HnswElement neighborElement = HnswPtrAccess(base, neighbor->element);
|
||||
Datum value = HnswGetValue(base, element);
|
||||
Datum neighborValue = HnswGetValue(base, neighborElement);
|
||||
|
||||
/* Exit early since ordered by distance */
|
||||
@@ -332,7 +368,7 @@ HnswFindDuplicateInMemory(char *base, HnswElement element)
|
||||
return false;
|
||||
|
||||
/* Check for space */
|
||||
if (HnswAddDuplicateInMemory(element, neighborElement))
|
||||
if (AddDuplicateInMemory(element, neighborElement))
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -340,10 +376,10 @@ HnswFindDuplicateInMemory(char *base, HnswElement element)
|
||||
}
|
||||
|
||||
/*
|
||||
* Add to element and neighbor pages
|
||||
* Add to element list
|
||||
*/
|
||||
static void
|
||||
WriteNewElementPagesInMemory(char *base, HnswGraph * graph, HnswElement element)
|
||||
AddElementInMemory(char *base, HnswGraph * graph, HnswElement element)
|
||||
{
|
||||
SpinLockAcquire(&graph->lock);
|
||||
element->next = graph->head;
|
||||
@@ -355,7 +391,7 @@ WriteNewElementPagesInMemory(char *base, HnswGraph * graph, HnswElement element)
|
||||
* Update neighbors
|
||||
*/
|
||||
static void
|
||||
HnswUpdateNeighborPagesInMemory(char *base, FmgrInfo *procinfo, Oid collation, HnswElement e, int m)
|
||||
UpdateNeighborsInMemory(char *base, FmgrInfo *procinfo, Oid collation, HnswElement e, int m)
|
||||
{
|
||||
for (int lc = e->level; lc >= 0; lc--)
|
||||
{
|
||||
@@ -379,45 +415,82 @@ HnswUpdateNeighborPagesInMemory(char *base, FmgrInfo *procinfo, Oid collation, H
|
||||
}
|
||||
|
||||
/*
|
||||
* Write changes in memory
|
||||
* Update graph in memory
|
||||
*/
|
||||
static void
|
||||
WriteElementInMemory(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement element, int m, int efConstruction, HnswElement entryPoint, HnswBuildState * buildstate, HnswGraph * graph, bool updateEntryPoint)
|
||||
UpdateGraphInMemory(FmgrInfo *procinfo, Oid collation, HnswElement element, int m, int efConstruction, HnswElement entryPoint, HnswBuildState * buildstate)
|
||||
{
|
||||
HnswGraph *graph = buildstate->graph;
|
||||
char *base = buildstate->hnswarea;
|
||||
|
||||
/* Try to add to existing page */
|
||||
if (HnswFindDuplicateInMemory(base, element))
|
||||
/* Look for duplicate */
|
||||
if (FindDuplicateInMemory(base, element))
|
||||
return;
|
||||
|
||||
/* Write element and neighbor tuples */
|
||||
WriteNewElementPagesInMemory(base, graph, element);
|
||||
/* Add element */
|
||||
AddElementInMemory(base, graph, element);
|
||||
|
||||
/* Update neighbors */
|
||||
HnswUpdateNeighborPagesInMemory(base, procinfo, collation, element, m);
|
||||
UpdateNeighborsInMemory(base, procinfo, collation, element, m);
|
||||
|
||||
/* Update entry point if needed (already have lock) */
|
||||
if (updateEntryPoint)
|
||||
if (entryPoint == NULL || element->level > entryPoint->level)
|
||||
HnswPtrStore(base, graph->entryPoint, element);
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert tuple in memory
|
||||
*/
|
||||
static void
|
||||
InsertTupleInMemory(HnswBuildState * buildstate, HnswElement element)
|
||||
{
|
||||
FmgrInfo *procinfo = buildstate->procinfo;
|
||||
Oid collation = buildstate->collation;
|
||||
HnswGraph *graph = buildstate->graph;
|
||||
HnswElement entryPoint;
|
||||
LWLock *entryLock = &graph->entryLock;
|
||||
int efConstruction = buildstate->efConstruction;
|
||||
int m = buildstate->m;
|
||||
char *base = buildstate->hnswarea;
|
||||
|
||||
/* Get entry point */
|
||||
LWLockAcquire(entryLock, LW_SHARED);
|
||||
entryPoint = HnswPtrAccess(base, graph->entryPoint);
|
||||
|
||||
/* Prevent concurrent inserts when likely updating entry point */
|
||||
if (entryPoint == NULL || element->level > entryPoint->level)
|
||||
{
|
||||
/* Release shared lock */
|
||||
LWLockRelease(entryLock);
|
||||
|
||||
/* Get exclusive lock */
|
||||
LWLockAcquire(entryLock, LW_EXCLUSIVE);
|
||||
|
||||
/* Get latest entry point after lock is acquired */
|
||||
entryPoint = HnswPtrAccess(base, graph->entryPoint);
|
||||
}
|
||||
|
||||
/* Find neighbors for element */
|
||||
HnswFindElementNeighbors(base, element, entryPoint, NULL, procinfo, collation, m, efConstruction, false);
|
||||
|
||||
/* Update graph in memory */
|
||||
UpdateGraphInMemory(procinfo, collation, element, m, efConstruction, entryPoint, buildstate);
|
||||
|
||||
/* Release entry lock */
|
||||
LWLockRelease(entryLock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert tuple
|
||||
*/
|
||||
static bool
|
||||
InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heaptid, HnswBuildState * buildstate)
|
||||
{
|
||||
FmgrInfo *procinfo = buildstate->procinfo;
|
||||
Oid collation = buildstate->collation;
|
||||
HnswGraph *graph = buildstate->graph;
|
||||
HnswElement entryPoint;
|
||||
int efConstruction = buildstate->efConstruction;
|
||||
int m = buildstate->m;
|
||||
HnswElement element;
|
||||
HnswAllocator *allocator = &buildstate->allocator;
|
||||
Size valueSize;
|
||||
Pointer valuePtr;
|
||||
bool updateEntryPoint;
|
||||
LWLock *flushLock = &graph->flushLock;
|
||||
char *base = buildstate->hnswarea;
|
||||
|
||||
@@ -427,7 +500,7 @@ InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heaptid, Hn
|
||||
/* Normalize if needed */
|
||||
if (buildstate->normprocinfo != NULL)
|
||||
{
|
||||
if (!HnswNormValue(buildstate->normprocinfo, collation, &value, buildstate->normvec))
|
||||
if (!HnswNormValue(buildstate->normprocinfo, buildstate->collation, &value, buildstate->normvec))
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -437,6 +510,7 @@ InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heaptid, Hn
|
||||
/* Ensure graph not flushed when inserting */
|
||||
LWLockAcquire(flushLock, LW_SHARED);
|
||||
|
||||
/* Are we in the on-disk phase? */
|
||||
if (graph->flushed)
|
||||
{
|
||||
LWLockRelease(flushLock);
|
||||
@@ -444,10 +518,16 @@ InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heaptid, Hn
|
||||
return HnswInsertTupleOnDisk(index, value, values, isnull, heaptid, buildstate->heap, true);
|
||||
}
|
||||
|
||||
/* Get lock for allocator */
|
||||
/*
|
||||
* In a parallel build, the HnswElement is allocated from the shared
|
||||
* memory area, so we need to coordinate with other processes.
|
||||
*/
|
||||
LWLockAcquire(&graph->allocatorLock, LW_EXCLUSIVE);
|
||||
|
||||
/* Flush pages if needed */
|
||||
/*
|
||||
* Check that we have enough memory available for the new element now that
|
||||
* we have the allocator lock, and flush pages if needed.
|
||||
*/
|
||||
if (graph->memoryUsed >= graph->memoryTotal)
|
||||
{
|
||||
LWLockRelease(&graph->allocatorLock);
|
||||
@@ -470,38 +550,26 @@ InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heaptid, Hn
|
||||
return HnswInsertTupleOnDisk(index, value, values, isnull, heaptid, buildstate->heap, true);
|
||||
}
|
||||
|
||||
/* Create an element */
|
||||
/* Ok, we can proceed to allocate the element */
|
||||
element = HnswInitElement(base, heaptid, buildstate->m, buildstate->ml, buildstate->maxLevel, allocator);
|
||||
valuePtr = HnswAlloc(allocator, valueSize);
|
||||
|
||||
/* Release allocator lock */
|
||||
/*
|
||||
* We have now allocated the space needed for the element, so we don't
|
||||
* need the allocator lock anymore. Release it and initialize the rest of
|
||||
* the element.
|
||||
*/
|
||||
LWLockRelease(&graph->allocatorLock);
|
||||
|
||||
/* Copy datum */
|
||||
/* Copy the datum */
|
||||
memcpy(valuePtr, DatumGetPointer(value), valueSize);
|
||||
HnswPtrStore(base, element->value, valuePtr);
|
||||
|
||||
/* Create element lock */
|
||||
/* Create a lock for the element */
|
||||
LWLockInitialize(&element->lock, hnsw_lock_tranche_id);
|
||||
|
||||
/* Get entry point */
|
||||
LWLockAcquire(&graph->entryLock, LW_EXCLUSIVE);
|
||||
entryPoint = HnswPtrAccess(base, graph->entryPoint);
|
||||
updateEntryPoint = entryPoint == NULL || element->level > entryPoint->level;
|
||||
|
||||
/* Release lock if not updating entry point */
|
||||
if (!updateEntryPoint)
|
||||
LWLockRelease(&graph->entryLock);
|
||||
|
||||
/* Insert element in graph */
|
||||
HnswInsertElement(base, element, entryPoint, NULL, procinfo, collation, m, efConstruction, false);
|
||||
|
||||
/* Write to memory */
|
||||
WriteElementInMemory(index, procinfo, collation, element, m, efConstruction, entryPoint, buildstate, graph, updateEntryPoint);
|
||||
|
||||
/* Release lock if needed */
|
||||
if (updateEntryPoint)
|
||||
LWLockRelease(&graph->entryLock);
|
||||
/* Insert tuple */
|
||||
InsertTupleInMemory(buildstate, element);
|
||||
|
||||
/* Release flush lock */
|
||||
LWLockRelease(flushLock);
|
||||
@@ -710,7 +778,7 @@ ParallelHeapScan(HnswBuildState * buildstate)
|
||||
* Perform a worker's portion of a parallel insert
|
||||
*/
|
||||
static void
|
||||
HnswParallelScanAndInsert(HnswSpool * hnswspool, HnswShared * hnswshared, char *hnswarea, bool progress)
|
||||
HnswParallelScanAndInsert(Relation heapRel, Relation indexRel, HnswShared * hnswshared, char *hnswarea, bool progress)
|
||||
{
|
||||
HnswBuildState buildstate;
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
@@ -722,21 +790,21 @@ HnswParallelScanAndInsert(HnswSpool * hnswspool, HnswShared * hnswshared, char *
|
||||
IndexInfo *indexInfo;
|
||||
|
||||
/* Join parallel scan */
|
||||
indexInfo = BuildIndexInfo(hnswspool->index);
|
||||
indexInfo = BuildIndexInfo(indexRel);
|
||||
indexInfo->ii_Concurrent = hnswshared->isconcurrent;
|
||||
InitBuildState(&buildstate, hnswspool->heap, hnswspool->index, indexInfo, MAIN_FORKNUM);
|
||||
InitBuildState(&buildstate, heapRel, indexRel, indexInfo, MAIN_FORKNUM);
|
||||
buildstate.graph = &hnswshared->graphData;
|
||||
buildstate.hnswarea = hnswarea;
|
||||
InitAllocator(&buildstate.allocator, &HnswSharedMemoryAlloc, &buildstate);
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
scan = table_beginscan_parallel(hnswspool->heap,
|
||||
scan = table_beginscan_parallel(heapRel,
|
||||
ParallelTableScanFromHnswShared(hnswshared));
|
||||
reltuples = table_index_build_scan(hnswspool->heap, hnswspool->index, indexInfo,
|
||||
reltuples = table_index_build_scan(heapRel, indexRel, indexInfo,
|
||||
true, progress, BuildCallback,
|
||||
(void *) &buildstate, scan);
|
||||
#else
|
||||
scan = heap_beginscan_parallel(hnswspool->heap, &hnswshared->heapdesc);
|
||||
reltuples = IndexBuildHeapScan(hnswspool->heap, hnswspool->index, indexInfo,
|
||||
scan = heap_beginscan_parallel(heapRel, &hnswshared->heapdesc);
|
||||
reltuples = IndexBuildHeapScan(heapRel, indexRel, indexInfo,
|
||||
true, BuildCallback,
|
||||
(void *) &buildstate, scan);
|
||||
#endif
|
||||
@@ -766,10 +834,7 @@ void
|
||||
HnswParallelBuildMain(dsm_segment *seg, shm_toc *toc)
|
||||
{
|
||||
char *sharedquery;
|
||||
HnswSpool *hnswspool;
|
||||
HnswShared *hnswshared;
|
||||
dsa_area *dsa;
|
||||
void *dsaspace;
|
||||
char *hnswarea;
|
||||
Relation heapRel;
|
||||
Relation indexRel;
|
||||
@@ -806,17 +871,10 @@ HnswParallelBuildMain(dsm_segment *seg, shm_toc *toc)
|
||||
#endif
|
||||
indexRel = index_open(hnswshared->indexrelid, indexLockmode);
|
||||
|
||||
/* Initialize worker's own spool */
|
||||
hnswspool = (HnswSpool *) palloc0(sizeof(HnswSpool));
|
||||
hnswspool->heap = heapRel;
|
||||
hnswspool->index = indexRel;
|
||||
|
||||
dsaspace = shm_toc_lookup(toc, PARALLEL_KEY_HNSW_AREA, false);
|
||||
dsa = dsa_attach_in_place(dsaspace, seg);
|
||||
hnswarea = dsa_get_address(dsa, hnswshared->dsaptr);
|
||||
hnswarea = shm_toc_lookup(toc, PARALLEL_KEY_HNSW_AREA, false);
|
||||
|
||||
/* Perform inserts */
|
||||
HnswParallelScanAndInsert(hnswspool, hnswshared, hnswarea, false);
|
||||
HnswParallelScanAndInsert(heapRel, indexRel, hnswshared, hnswarea, false);
|
||||
|
||||
/* Close relations within worker */
|
||||
index_close(indexRel, indexLockmode);
|
||||
@@ -871,15 +929,9 @@ static void
|
||||
HnswLeaderParticipateAsWorker(HnswBuildState * buildstate)
|
||||
{
|
||||
HnswLeader *hnswleader = buildstate->hnswleader;
|
||||
HnswSpool *leaderworker;
|
||||
|
||||
/* Allocate memory and initialize private spool */
|
||||
leaderworker = (HnswSpool *) palloc0(sizeof(HnswSpool));
|
||||
leaderworker->heap = buildstate->heap;
|
||||
leaderworker->index = buildstate->index;
|
||||
|
||||
/* Perform work common to all participants */
|
||||
HnswParallelScanAndInsert(leaderworker, hnswleader->hnswshared, hnswleader->hnswarea, true);
|
||||
HnswParallelScanAndInsert(buildstate->heap, buildstate->index, hnswleader->hnswshared, hnswleader->hnswarea, true);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -889,14 +941,11 @@ static void
|
||||
HnswBeginParallel(HnswBuildState * buildstate, bool isconcurrent, int request)
|
||||
{
|
||||
ParallelContext *pcxt;
|
||||
int scantuplesortstates;
|
||||
Snapshot snapshot;
|
||||
Size esthnswshared;
|
||||
Size esthnswarea;
|
||||
Size estdsaspace;
|
||||
Size estother;
|
||||
HnswShared *hnswshared;
|
||||
void *dsaspace;
|
||||
dsa_area *dsa;
|
||||
char *hnswarea;
|
||||
HnswLeader *hnswleader = (HnswLeader *) palloc0(sizeof(HnswLeader));
|
||||
bool leaderparticipates = true;
|
||||
@@ -915,8 +964,6 @@ HnswBeginParallel(HnswBuildState * buildstate, bool isconcurrent, int request)
|
||||
pcxt = CreateParallelContext("vector", "HnswParallelBuildMain", request, true);
|
||||
#endif
|
||||
|
||||
scantuplesortstates = leaderparticipates ? request + 1 : request;
|
||||
|
||||
/* Get snapshot for table scan */
|
||||
if (!isconcurrent)
|
||||
snapshot = SnapshotAny;
|
||||
@@ -927,9 +974,15 @@ HnswBeginParallel(HnswBuildState * buildstate, bool isconcurrent, int request)
|
||||
esthnswshared = ParallelEstimateShared(buildstate->heap, snapshot);
|
||||
shm_toc_estimate_chunk(&pcxt->estimator, esthnswshared);
|
||||
|
||||
/* Start with a minimal DSA so InitializeParallelDSM does not fail */
|
||||
estdsaspace = dsa_minimum_size();
|
||||
shm_toc_estimate_chunk(&pcxt->estimator, estdsaspace);
|
||||
/* Leave space for other objects in shared memory */
|
||||
/* Docker has a default limit of 64 MB for shm_size */
|
||||
/* which happens to be the default value of maintenance_work_mem */
|
||||
esthnswarea = maintenance_work_mem * 1024L;
|
||||
estother = 2 * 1024 * 1024;
|
||||
if (esthnswarea > estother)
|
||||
esthnswarea -= estother;
|
||||
|
||||
shm_toc_estimate_chunk(&pcxt->estimator, esthnswarea);
|
||||
shm_toc_estimate_keys(&pcxt->estimator, 2);
|
||||
|
||||
/* Finally, estimate PARALLEL_KEY_QUERY_TEXT space */
|
||||
@@ -961,7 +1014,6 @@ HnswBeginParallel(HnswBuildState * buildstate, bool isconcurrent, int request)
|
||||
hnswshared->heaprelid = RelationGetRelid(buildstate->heap);
|
||||
hnswshared->indexrelid = RelationGetRelid(buildstate->index);
|
||||
hnswshared->isconcurrent = isconcurrent;
|
||||
hnswshared->scantuplesortstates = scantuplesortstates;
|
||||
ConditionVariableInit(&hnswshared->workersdonecv);
|
||||
SpinLockInit(&hnswshared->mutex);
|
||||
/* Initialize mutable state */
|
||||
@@ -975,28 +1027,12 @@ HnswBeginParallel(HnswBuildState * buildstate, bool isconcurrent, int request)
|
||||
heap_parallelscan_initialize(&hnswshared->heapdesc, buildstate->heap, snapshot);
|
||||
#endif
|
||||
|
||||
dsaspace = shm_toc_allocate(pcxt->toc, estdsaspace);
|
||||
dsa = dsa_create_in_place(dsaspace, estdsaspace, hnsw_lock_tranche_id, pcxt->seg);
|
||||
|
||||
esthnswarea = maintenance_work_mem * 1024L;
|
||||
hnswshared->dsaptr = dsa_allocate_extended(dsa, esthnswarea, DSA_ALLOC_HUGE | DSA_ALLOC_NO_OOM);
|
||||
|
||||
/* If not enough shared memory, back out (do serial build) */
|
||||
if (!DsaPointerIsValid(hnswshared->dsaptr))
|
||||
{
|
||||
if (IsMVCCSnapshot(snapshot))
|
||||
UnregisterSnapshot(snapshot);
|
||||
DestroyParallelContext(pcxt);
|
||||
ExitParallelMode();
|
||||
return;
|
||||
}
|
||||
|
||||
hnswarea = dsa_get_address(dsa, hnswshared->dsaptr);
|
||||
hnswarea = (char *) shm_toc_allocate(pcxt->toc, esthnswarea);
|
||||
/* Report less than allocated so never fails */
|
||||
InitGraph(&hnswshared->graphData, hnswarea, esthnswarea - 1024 * 1024);
|
||||
|
||||
shm_toc_insert(pcxt->toc, PARALLEL_KEY_HNSW_SHARED, hnswshared);
|
||||
shm_toc_insert(pcxt->toc, PARALLEL_KEY_HNSW_AREA, dsaspace);
|
||||
shm_toc_insert(pcxt->toc, PARALLEL_KEY_HNSW_AREA, hnswarea);
|
||||
|
||||
/* Store query string for workers */
|
||||
if (debug_query_string)
|
||||
|
||||
@@ -116,7 +116,7 @@ HnswInsertAppendPage(Relation index, Buffer *nbuf, Page *npage, GenericXLogState
|
||||
* Add to element and neighbor pages
|
||||
*/
|
||||
static void
|
||||
WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPage, BlockNumber *updatedInsertPage, bool building)
|
||||
AddElementOnDisk(Relation index, HnswElement e, int m, BlockNumber insertPage, BlockNumber *updatedInsertPage, bool building)
|
||||
{
|
||||
Buffer buf;
|
||||
Page page;
|
||||
@@ -339,7 +339,7 @@ ConnectionExists(HnswElement e, HnswNeighborTuple ntup, int startIdx, int lm)
|
||||
* Update neighbors
|
||||
*/
|
||||
void
|
||||
HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting, bool building)
|
||||
HnswUpdateNeighborsOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting, bool building)
|
||||
{
|
||||
char *base = NULL;
|
||||
|
||||
@@ -451,7 +451,7 @@ HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswE
|
||||
* Add a heap TID to an existing element
|
||||
*/
|
||||
static bool
|
||||
HnswAddDuplicate(Relation index, HnswElement element, HnswElement dup, bool building)
|
||||
AddDuplicateOnDisk(Relation index, HnswElement element, HnswElement dup, bool building)
|
||||
{
|
||||
Buffer buf;
|
||||
Page page;
|
||||
@@ -515,23 +515,23 @@ HnswAddDuplicate(Relation index, HnswElement element, HnswElement dup, bool buil
|
||||
* Find duplicate element
|
||||
*/
|
||||
static bool
|
||||
HnswFindDuplicate(Relation index, HnswElement element, bool building)
|
||||
FindDuplicateOnDisk(Relation index, HnswElement element, bool building)
|
||||
{
|
||||
char *base = NULL;
|
||||
HnswNeighborArray *neighbors = HnswGetNeighbors(base, element, 0);
|
||||
Datum value = HnswGetValue(base, element);
|
||||
|
||||
for (int i = 0; i < neighbors->length; i++)
|
||||
{
|
||||
HnswCandidate *neighbor = &neighbors->items[i];
|
||||
HnswElement neighborElement = HnswPtrAccess(base, neighbor->element);
|
||||
Datum value = HnswGetValue(base, element);
|
||||
Datum neighborValue = HnswGetValue(base, neighborElement);
|
||||
|
||||
/* Exit early since ordered by distance */
|
||||
if (!datumIsEqual(value, neighborValue, false, -1))
|
||||
return false;
|
||||
|
||||
if (HnswAddDuplicate(index, element, neighborElement, building))
|
||||
if (AddDuplicateOnDisk(index, element, neighborElement, building))
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -539,26 +539,26 @@ HnswFindDuplicate(Relation index, HnswElement element, bool building)
|
||||
}
|
||||
|
||||
/*
|
||||
* Write changes to disk
|
||||
* Update graph on disk
|
||||
*/
|
||||
static void
|
||||
WriteElement(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement element, int m, int efConstruction, HnswElement entryPoint, bool building)
|
||||
UpdateGraphOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement element, int m, int efConstruction, HnswElement entryPoint, bool building)
|
||||
{
|
||||
BlockNumber newInsertPage = InvalidBlockNumber;
|
||||
|
||||
/* Look for duplicate */
|
||||
if (HnswFindDuplicate(index, element, building))
|
||||
if (FindDuplicateOnDisk(index, element, building))
|
||||
return;
|
||||
|
||||
/* Write element and neighbor tuples */
|
||||
WriteNewElementPages(index, element, m, GetInsertPage(index), &newInsertPage, building);
|
||||
/* Add element */
|
||||
AddElementOnDisk(index, element, m, GetInsertPage(index), &newInsertPage, building);
|
||||
|
||||
/* Update insert page if needed */
|
||||
if (BlockNumberIsValid(newInsertPage))
|
||||
HnswUpdateMetaPage(index, 0, NULL, newInsertPage, MAIN_FORKNUM, building);
|
||||
|
||||
/* Update neighbors */
|
||||
HnswUpdateNeighborPages(index, procinfo, collation, element, m, false, building);
|
||||
HnswUpdateNeighborsOnDisk(index, procinfo, collation, element, m, false, building);
|
||||
|
||||
/* Update entry point if needed */
|
||||
if (entryPoint == NULL || element->level > entryPoint->level)
|
||||
@@ -608,11 +608,11 @@ HnswInsertTupleOnDisk(Relation index, Datum value, Datum *values, bool *isnull,
|
||||
entryPoint = HnswGetEntryPoint(index);
|
||||
}
|
||||
|
||||
/* Insert element in graph */
|
||||
HnswInsertElement(base, element, entryPoint, index, procinfo, collation, m, efConstruction, false);
|
||||
/* Find neighbors for element */
|
||||
HnswFindElementNeighbors(base, element, entryPoint, index, procinfo, collation, m, efConstruction, false);
|
||||
|
||||
/* Write to disk */
|
||||
WriteElement(index, procinfo, collation, element, m, efConstruction, entryPoint, building);
|
||||
/* Update graph on disk */
|
||||
UpdateGraphOnDisk(index, procinfo, collation, element, m, efConstruction, entryPoint, building);
|
||||
|
||||
/* Release lock */
|
||||
UnlockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
||||
|
||||
@@ -656,11 +656,25 @@ CreatePairingHeapNode(HnswCandidate * c)
|
||||
return node;
|
||||
}
|
||||
|
||||
/*
|
||||
* Init visited
|
||||
*/
|
||||
static inline void
|
||||
InitVisited(char *base, visited_hash * v, Relation index, int ef, int m)
|
||||
{
|
||||
if (index != NULL)
|
||||
v->tids = tidhash_create(CurrentMemoryContext, ef * m * 2, NULL);
|
||||
else if (base != NULL)
|
||||
v->offsets = offsethash_create(CurrentMemoryContext, ef * m * 2, NULL);
|
||||
else
|
||||
v->pointers = pointerhash_create(CurrentMemoryContext, ef * m * 2, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add to visited
|
||||
*/
|
||||
static inline void
|
||||
AddToVisited(char *base, visited_hash v, HnswCandidate * hc, Relation index, bool *found)
|
||||
AddToVisited(char *base, visited_hash * v, HnswCandidate * hc, Relation index, bool *found)
|
||||
{
|
||||
if (index != NULL)
|
||||
{
|
||||
@@ -668,16 +682,16 @@ AddToVisited(char *base, visited_hash v, HnswCandidate * hc, Relation index, boo
|
||||
ItemPointerData indextid;
|
||||
|
||||
ItemPointerSet(&indextid, element->blkno, element->offno);
|
||||
tidhash_insert(v.tids, indextid, found);
|
||||
tidhash_insert(v->tids, indextid, found);
|
||||
}
|
||||
else if (base != NULL)
|
||||
{
|
||||
#if PG_VERSION_NUM >= 130000
|
||||
HnswElement element = HnswPtrAccess(base, hc->element);
|
||||
|
||||
offsethash_insert_hash(v.offsets, HnswPtrOffset(hc->element), element->hash, found);
|
||||
offsethash_insert_hash(v->offsets, HnswPtrOffset(hc->element), element->hash, found);
|
||||
#else
|
||||
offsethash_insert(v.offsets, HnswPtrOffset(hc->element), found);
|
||||
offsethash_insert(v->offsets, HnswPtrOffset(hc->element), found);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
@@ -685,9 +699,9 @@ AddToVisited(char *base, visited_hash v, HnswCandidate * hc, Relation index, boo
|
||||
#if PG_VERSION_NUM >= 130000
|
||||
HnswElement element = HnswPtrAccess(base, hc->element);
|
||||
|
||||
pointerhash_insert_hash(v.pointers, (uintptr_t) HnswPtrPointer(hc->element), element->hash, found);
|
||||
pointerhash_insert_hash(v->pointers, (uintptr_t) HnswPtrPointer(hc->element), element->hash, found);
|
||||
#else
|
||||
pointerhash_insert(v.pointers, (uintptr_t) HnswPtrPointer(hc->element), found);
|
||||
pointerhash_insert(v->pointers, (uintptr_t) HnswPtrPointer(hc->element), found);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -725,13 +739,7 @@ HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, F
|
||||
HnswNeighborArray *neighborhoodData = NULL;
|
||||
Size neighborhoodSize;
|
||||
|
||||
/* Create hash table */
|
||||
if (index != NULL)
|
||||
v.tids = tidhash_create(CurrentMemoryContext, ef * m * 2, NULL);
|
||||
else if (base != NULL)
|
||||
v.offsets = offsethash_create(CurrentMemoryContext, ef * m * 2, NULL);
|
||||
else
|
||||
v.pointers = pointerhash_create(CurrentMemoryContext, ef * m * 2, NULL);
|
||||
InitVisited(base, &v, index, ef, m);
|
||||
|
||||
/* Create local memory for neighborhood if needed */
|
||||
if (index == NULL)
|
||||
@@ -746,7 +754,7 @@ HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, F
|
||||
HnswCandidate *hc = (HnswCandidate *) lfirst(lc2);
|
||||
bool found;
|
||||
|
||||
AddToVisited(base, v, hc, index, &found);
|
||||
AddToVisited(base, &v, hc, index, &found);
|
||||
|
||||
pairingheap_add(C, &(CreatePairingHeapNode(hc)->ph_node));
|
||||
pairingheap_add(W, &(CreatePairingHeapNode(hc)->ph_node));
|
||||
@@ -781,7 +789,7 @@ HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, F
|
||||
/* Copy neighborhood to local memory if needed */
|
||||
if (index == NULL)
|
||||
{
|
||||
LWLockAcquire(&cElement->lock, LW_EXCLUSIVE);
|
||||
LWLockAcquire(&cElement->lock, LW_SHARED);
|
||||
memcpy(neighborhoodData, neighborhood, neighborhoodSize);
|
||||
LWLockRelease(&cElement->lock);
|
||||
neighborhood = neighborhoodData;
|
||||
@@ -792,7 +800,7 @@ HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, F
|
||||
HnswCandidate *e = &neighborhood->items[i];
|
||||
bool visited;
|
||||
|
||||
AddToVisited(base, v, e, index, &visited);
|
||||
AddToVisited(base, &v, e, index, &visited);
|
||||
|
||||
if (!visited)
|
||||
{
|
||||
@@ -963,7 +971,6 @@ CheckElementCloser(char *base, HnswCandidate * e, List *r, int lc, FmgrInfo *pro
|
||||
{
|
||||
HnswCandidate *ri = lfirst(lc2);
|
||||
HnswElement riElement = HnswPtrAccess(base, ri->element);
|
||||
|
||||
float distance = HnswGetDistance(base, eElement, riElement, lc, procinfo, collation);
|
||||
|
||||
if (distance <= e->distance)
|
||||
@@ -1216,7 +1223,7 @@ PrecomputeHash(char *base, HnswElement element)
|
||||
* Algorithm 1 from paper
|
||||
*/
|
||||
void
|
||||
HnswInsertElement(char *base, HnswElement element, HnswElement entryPoint, Relation index, FmgrInfo *procinfo, Oid collation, int m, int efConstruction, bool existing)
|
||||
HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint, Relation index, FmgrInfo *procinfo, Oid collation, int m, int efConstruction, bool existing)
|
||||
{
|
||||
List *ep;
|
||||
List *w;
|
||||
|
||||
@@ -199,21 +199,22 @@ RepairGraphElement(HnswVacuumState * vacuumstate, HnswElement element, HnswEleme
|
||||
BufferAccessStrategy bas = vacuumstate->bas;
|
||||
HnswNeighborTuple ntup = vacuumstate->ntup;
|
||||
Size ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(element->level, m);
|
||||
char *base = NULL;
|
||||
|
||||
/* Skip if element is entry point */
|
||||
if (entryPoint != NULL && element->blkno == entryPoint->blkno && element->offno == entryPoint->offno)
|
||||
return;
|
||||
|
||||
/* Init fields */
|
||||
HnswInitNeighbors(NULL, element, m, NULL);
|
||||
HnswInitNeighbors(base, element, m, NULL);
|
||||
element->heaptidsLength = 0;
|
||||
|
||||
/* Add element to graph, skipping itself */
|
||||
HnswInsertElement(NULL, element, entryPoint, index, procinfo, collation, m, efConstruction, true);
|
||||
/* Find neighbors for element, skipping itself */
|
||||
HnswFindElementNeighbors(base, element, entryPoint, index, procinfo, collation, m, efConstruction, true);
|
||||
|
||||
/* Update neighbor tuple */
|
||||
/* Do this before getting page to minimize locking */
|
||||
HnswSetNeighborTuple(NULL, ntup, element, m);
|
||||
HnswSetNeighborTuple(base, ntup, element, m);
|
||||
|
||||
/* Get neighbor page */
|
||||
buf = ReadBufferExtended(index, MAIN_FORKNUM, element->neighborPage, RBM_NORMAL, bas);
|
||||
@@ -230,7 +231,7 @@ RepairGraphElement(HnswVacuumState * vacuumstate, HnswElement element, HnswEleme
|
||||
UnlockReleaseBuffer(buf);
|
||||
|
||||
/* Update neighbors */
|
||||
HnswUpdateNeighborPages(index, procinfo, collation, element, m, true, false);
|
||||
HnswUpdateNeighborsOnDisk(index, procinfo, collation, element, m, true, false);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user