mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-22 12:07:34 +08:00
Compare commits
5 Commits
ivfflat-in
...
hnsw-index
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
915a630d00 | ||
|
|
20c24e2d18 | ||
|
|
466ec1ac46 | ||
|
|
ff2bdfb3b4 | ||
|
|
0a3669a680 |
@@ -1,7 +1,3 @@
|
|||||||
## 0.5.1 (unreleased)
|
|
||||||
|
|
||||||
- Improved performance of index scans for IVFFlat after updates and deletes
|
|
||||||
|
|
||||||
## 0.5.0 (2023-08-28)
|
## 0.5.0 (2023-08-28)
|
||||||
|
|
||||||
- Added HNSW index type
|
- Added HNSW index type
|
||||||
|
|||||||
@@ -595,18 +595,12 @@ pgvector is available on [these providers](https://github.com/pgvector/pgvector/
|
|||||||
|
|
||||||
## Upgrading
|
## Upgrading
|
||||||
|
|
||||||
Install the latest version. Then in each database you want to upgrade, run:
|
Install the latest version and run:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
ALTER EXTENSION vector UPDATE;
|
ALTER EXTENSION vector UPDATE;
|
||||||
```
|
```
|
||||||
|
|
||||||
You can check the version in the current database with:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT extversion FROM pg_extension WHERE extname = 'vector';
|
|
||||||
```
|
|
||||||
|
|
||||||
## Upgrade Notes
|
## Upgrade Notes
|
||||||
|
|
||||||
### 0.4.0
|
### 0.4.0
|
||||||
|
|||||||
13
src/hnsw.c
13
src/hnsw.c
@@ -91,7 +91,7 @@ hnswcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
|||||||
MemSet(&costs, 0, sizeof(costs));
|
MemSet(&costs, 0, sizeof(costs));
|
||||||
|
|
||||||
index = index_open(path->indexinfo->indexoid, NoLock);
|
index = index_open(path->indexinfo->indexoid, NoLock);
|
||||||
HnswGetMetaPageInfo(index, &m, NULL);
|
m = HnswGetM(index);
|
||||||
index_close(index, NoLock);
|
index_close(index, NoLock);
|
||||||
|
|
||||||
/* Approximate entry level */
|
/* Approximate entry level */
|
||||||
@@ -155,6 +155,15 @@ hnswvalidate(Oid opclassoid)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Checks if index-only scan is supported
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
hnswcanreturn(Relation indexRelation, int attno)
|
||||||
|
{
|
||||||
|
return attno == 1 && !OidIsValid(index_getprocid(indexRelation, 1, HNSW_NORM_PROC));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Define index handler
|
* Define index handler
|
||||||
*
|
*
|
||||||
@@ -196,7 +205,7 @@ hnswhandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->aminsert = hnswinsert;
|
amroutine->aminsert = hnswinsert;
|
||||||
amroutine->ambulkdelete = hnswbulkdelete;
|
amroutine->ambulkdelete = hnswbulkdelete;
|
||||||
amroutine->amvacuumcleanup = hnswvacuumcleanup;
|
amroutine->amvacuumcleanup = hnswvacuumcleanup;
|
||||||
amroutine->amcanreturn = NULL;
|
amroutine->amcanreturn = hnswcanreturn;
|
||||||
amroutine->amcostestimate = hnswcostestimate;
|
amroutine->amcostestimate = hnswcostestimate;
|
||||||
amroutine->amoptions = hnswoptions;
|
amroutine->amoptions = hnswoptions;
|
||||||
amroutine->amproperty = NULL; /* TODO AMPROP_DISTANCE_ORDERABLE */
|
amroutine->amproperty = NULL; /* TODO AMPROP_DISTANCE_ORDERABLE */
|
||||||
|
|||||||
@@ -266,9 +266,8 @@ Buffer HnswNewBuffer(Relation index, ForkNumber forkNum);
|
|||||||
void HnswInitPage(Buffer buf, Page page);
|
void HnswInitPage(Buffer buf, Page page);
|
||||||
void HnswInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state);
|
void HnswInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state);
|
||||||
void HnswInit(void);
|
void HnswInit(void);
|
||||||
List *HnswSearchLayer(Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *procinfo, Oid collation, int m, bool inserting, HnswElement skipElement);
|
List *HnswSearchLayer(Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec, HnswElement skipElement);
|
||||||
HnswElement HnswGetEntryPoint(Relation index);
|
HnswElement HnswGetEntryPoint(Relation index);
|
||||||
void HnswGetMetaPageInfo(Relation index, int *m, HnswElement * entryPoint);
|
|
||||||
HnswElement HnswInitElement(ItemPointer tid, int m, double ml, int maxLevel);
|
HnswElement HnswInitElement(ItemPointer tid, int m, double ml, int maxLevel);
|
||||||
void HnswFreeElement(HnswElement element);
|
void HnswFreeElement(HnswElement element);
|
||||||
HnswElement HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno);
|
HnswElement HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno);
|
||||||
@@ -285,7 +284,7 @@ void HnswLoadElementFromTuple(HnswElement element, HnswElementTuple etup, bool
|
|||||||
void HnswLoadElement(HnswElement element, float *distance, Datum *q, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec);
|
void HnswLoadElement(HnswElement element, float *distance, Datum *q, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec);
|
||||||
void HnswSetElementTuple(HnswElementTuple etup, HnswElement element);
|
void HnswSetElementTuple(HnswElementTuple etup, HnswElement element);
|
||||||
void HnswUpdateConnection(HnswElement element, HnswCandidate * hc, int m, int lc, int *updateIdx, Relation index, FmgrInfo *procinfo, Oid collation);
|
void HnswUpdateConnection(HnswElement element, HnswCandidate * hc, int m, int lc, int *updateIdx, Relation index, FmgrInfo *procinfo, Oid collation);
|
||||||
void HnswLoadNeighbors(HnswElement element, Relation index, int m);
|
void HnswLoadNeighbors(HnswElement element, Relation index);
|
||||||
|
|
||||||
/* Index access methods */
|
/* Index access methods */
|
||||||
IndexBuildResult *hnswbuild(Relation heap, Relation index, IndexInfo *indexInfo);
|
IndexBuildResult *hnswbuild(Relation heap, Relation index, IndexInfo *indexInfo);
|
||||||
|
|||||||
@@ -329,7 +329,7 @@ HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswE
|
|||||||
|
|
||||||
/* Get latest neighbors since they may have changed */
|
/* Get latest neighbors since they may have changed */
|
||||||
/* Do not lock yet since selecting neighbors can take time */
|
/* Do not lock yet since selecting neighbors can take time */
|
||||||
HnswLoadNeighbors(hc->element, index, m);
|
HnswLoadNeighbors(hc->element, index);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Could improve performance for vacuuming by checking neighbors
|
* Could improve performance for vacuuming by checking neighbors
|
||||||
@@ -492,8 +492,9 @@ HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_ti
|
|||||||
FmgrInfo *normprocinfo;
|
FmgrInfo *normprocinfo;
|
||||||
HnswElement entryPoint;
|
HnswElement entryPoint;
|
||||||
HnswElement element;
|
HnswElement element;
|
||||||
int m;
|
int m = HnswGetM(index);
|
||||||
int efConstruction = HnswGetEfConstruction(index);
|
int efConstruction = HnswGetEfConstruction(index);
|
||||||
|
double ml = HnswGetMl(m);
|
||||||
FmgrInfo *procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC);
|
FmgrInfo *procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC);
|
||||||
Oid collation = index->rd_indcollation[0];
|
Oid collation = index->rd_indcollation[0];
|
||||||
HnswElement dup;
|
HnswElement dup;
|
||||||
@@ -510,6 +511,10 @@ HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_ti
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Create an element */
|
||||||
|
element = HnswInitElement(heap_tid, m, ml, HnswGetMaxLevel(m));
|
||||||
|
element->vec = DatumGetVector(value);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get a shared lock. This allows vacuum to ensure no in-flight inserts
|
* Get a shared lock. This allows vacuum to ensure no in-flight inserts
|
||||||
* before repairing graph. Use a page lock so it does not interfere with
|
* before repairing graph. Use a page lock so it does not interfere with
|
||||||
@@ -517,12 +522,8 @@ HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_ti
|
|||||||
*/
|
*/
|
||||||
LockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
LockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
||||||
|
|
||||||
/* Get m and entry point */
|
/* Get entry point */
|
||||||
HnswGetMetaPageInfo(index, &m, &entryPoint);
|
entryPoint = HnswGetEntryPoint(index);
|
||||||
|
|
||||||
/* Create an element */
|
|
||||||
element = HnswInitElement(heap_tid, m, HnswGetMl(m), HnswGetMaxLevel(m));
|
|
||||||
element->vec = DatumGetVector(value);
|
|
||||||
|
|
||||||
/* Prevent concurrent inserts when likely updating entry point */
|
/* Prevent concurrent inserts when likely updating entry point */
|
||||||
if (entryPoint == NULL || element->level > entryPoint->level)
|
if (entryPoint == NULL || element->level > entryPoint->level)
|
||||||
|
|||||||
@@ -17,26 +17,23 @@ GetScanItems(IndexScanDesc scan, Datum q)
|
|||||||
Relation index = scan->indexRelation;
|
Relation index = scan->indexRelation;
|
||||||
FmgrInfo *procinfo = so->procinfo;
|
FmgrInfo *procinfo = so->procinfo;
|
||||||
Oid collation = so->collation;
|
Oid collation = so->collation;
|
||||||
|
bool loadVec = scan->xs_want_itup;
|
||||||
List *ep;
|
List *ep;
|
||||||
List *w;
|
List *w;
|
||||||
int m;
|
HnswElement entryPoint = HnswGetEntryPoint(index);
|
||||||
HnswElement entryPoint;
|
|
||||||
|
|
||||||
/* Get m and entry point */
|
|
||||||
HnswGetMetaPageInfo(index, &m, &entryPoint);
|
|
||||||
|
|
||||||
if (entryPoint == NULL)
|
if (entryPoint == NULL)
|
||||||
return NIL;
|
return NIL;
|
||||||
|
|
||||||
ep = list_make1(HnswEntryCandidate(entryPoint, q, index, procinfo, collation, false));
|
ep = list_make1(HnswEntryCandidate(entryPoint, q, index, procinfo, collation, loadVec));
|
||||||
|
|
||||||
for (int lc = entryPoint->level; lc >= 1; lc--)
|
for (int lc = entryPoint->level; lc >= 1; lc--)
|
||||||
{
|
{
|
||||||
w = HnswSearchLayer(q, ep, 1, lc, index, procinfo, collation, m, false, NULL);
|
w = HnswSearchLayer(q, ep, 1, lc, index, procinfo, collation, loadVec, NULL);
|
||||||
ep = w;
|
ep = w;
|
||||||
}
|
}
|
||||||
|
|
||||||
return HnswSearchLayer(q, ep, hnsw_ef_search, 0, index, procinfo, collation, m, false, NULL);
|
return HnswSearchLayer(q, ep, hnsw_ef_search, 0, index, procinfo, collation, loadVec, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -62,33 +59,6 @@ GetDimensions(Relation index)
|
|||||||
return dimensions;
|
return dimensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Get scan value
|
|
||||||
*/
|
|
||||||
static Datum
|
|
||||||
GetScanValue(IndexScanDesc scan)
|
|
||||||
{
|
|
||||||
HnswScanOpaque so = (HnswScanOpaque) scan->opaque;
|
|
||||||
Datum value;
|
|
||||||
|
|
||||||
if (scan->orderByData->sk_flags & SK_ISNULL)
|
|
||||||
value = PointerGetDatum(InitVector(GetDimensions(scan->indexRelation)));
|
|
||||||
else
|
|
||||||
{
|
|
||||||
value = scan->orderByData->sk_argument;
|
|
||||||
|
|
||||||
/* Value should not be compressed or toasted */
|
|
||||||
Assert(!VARATT_IS_COMPRESSED(DatumGetPointer(value)));
|
|
||||||
Assert(!VARATT_IS_EXTENDED(DatumGetPointer(value)));
|
|
||||||
|
|
||||||
/* Fine if normalization fails */
|
|
||||||
if (so->normprocinfo != NULL)
|
|
||||||
HnswNormValue(so->normprocinfo, so->collation, &value, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prepare for an index scan
|
* Prepare for an index scan
|
||||||
*/
|
*/
|
||||||
@@ -114,6 +84,9 @@ hnswbeginscan(Relation index, int nkeys, int norderbys)
|
|||||||
|
|
||||||
scan->opaque = so;
|
scan->opaque = so;
|
||||||
|
|
||||||
|
/* OK to always set since cheap */
|
||||||
|
scan->xs_itupdesc = RelationGetDescr(index);
|
||||||
|
|
||||||
return scan;
|
return scan;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,8 +134,20 @@ hnswgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
if (scan->orderByData == NULL)
|
if (scan->orderByData == NULL)
|
||||||
elog(ERROR, "cannot scan hnsw index without order");
|
elog(ERROR, "cannot scan hnsw index without order");
|
||||||
|
|
||||||
/* Get scan value */
|
if (scan->orderByData->sk_flags & SK_ISNULL)
|
||||||
value = GetScanValue(scan);
|
value = PointerGetDatum(InitVector(GetDimensions(scan->indexRelation)));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = scan->orderByData->sk_argument;
|
||||||
|
|
||||||
|
/* Value should not be compressed or toasted */
|
||||||
|
Assert(!VARATT_IS_COMPRESSED(DatumGetPointer(value)));
|
||||||
|
Assert(!VARATT_IS_EXTENDED(DatumGetPointer(value)));
|
||||||
|
|
||||||
|
/* Fine if normalization fails */
|
||||||
|
if (so->normprocinfo != NULL)
|
||||||
|
HnswNormValue(so->normprocinfo, so->collation, &value, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get a shared lock. This allows vacuum to ensure no in-flight scans
|
* Get a shared lock. This allows vacuum to ensure no in-flight scans
|
||||||
@@ -181,7 +166,7 @@ hnswgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
while (list_length(so->w) > 0)
|
while (list_length(so->w) > 0)
|
||||||
{
|
{
|
||||||
HnswCandidate *hc = llast(so->w);
|
HnswCandidate *hc = llast(so->w);
|
||||||
ItemPointer heaptid;
|
ItemPointer tid;
|
||||||
BlockNumber indexblkno;
|
BlockNumber indexblkno;
|
||||||
|
|
||||||
/* Move to next element if no valid heap TIDs */
|
/* Move to next element if no valid heap TIDs */
|
||||||
@@ -191,17 +176,26 @@ hnswgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
heaptid = llast(hc->element->heaptids);
|
tid = llast(hc->element->heaptids);
|
||||||
indexblkno = hc->element->blkno;
|
indexblkno = hc->element->blkno;
|
||||||
|
|
||||||
hc->element->heaptids = list_delete_last(hc->element->heaptids);
|
hc->element->heaptids = list_delete_last(hc->element->heaptids);
|
||||||
|
|
||||||
|
if (scan->xs_want_itup)
|
||||||
|
{
|
||||||
|
Datum value = PointerGetDatum(hc->element->vec);
|
||||||
|
bool isnull = false;
|
||||||
|
|
||||||
|
scan->xs_itup = index_form_tuple(scan->xs_itupdesc, &value, &isnull);
|
||||||
|
scan->xs_itup->t_tid = *tid;
|
||||||
|
}
|
||||||
|
|
||||||
MemoryContextSwitchTo(oldCtx);
|
MemoryContextSwitchTo(oldCtx);
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
scan->xs_heaptid = *heaptid;
|
scan->xs_heaptid = *tid;
|
||||||
#else
|
#else
|
||||||
scan->xs_ctup.t_self = *heaptid;
|
scan->xs_ctup.t_self = *tid;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Unpin buffer */
|
/* Unpin buffer */
|
||||||
|
|||||||
@@ -210,43 +210,25 @@ HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the metapage info
|
* Get the entry point
|
||||||
*/
|
*/
|
||||||
void
|
HnswElement
|
||||||
HnswGetMetaPageInfo(Relation index, int *m, HnswElement * entryPoint)
|
HnswGetEntryPoint(Relation index)
|
||||||
{
|
{
|
||||||
Buffer buf;
|
Buffer buf;
|
||||||
Page page;
|
Page page;
|
||||||
HnswMetaPage metap;
|
HnswMetaPage metap;
|
||||||
|
HnswElement entryPoint = NULL;
|
||||||
|
|
||||||
buf = ReadBuffer(index, HNSW_METAPAGE_BLKNO);
|
buf = ReadBuffer(index, HNSW_METAPAGE_BLKNO);
|
||||||
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
||||||
page = BufferGetPage(buf);
|
page = BufferGetPage(buf);
|
||||||
metap = HnswPageGetMeta(page);
|
metap = HnswPageGetMeta(page);
|
||||||
|
|
||||||
if (m != NULL)
|
if (BlockNumberIsValid(metap->entryBlkno))
|
||||||
*m = metap->m;
|
entryPoint = HnswInitElementFromBlock(metap->entryBlkno, metap->entryOffno);
|
||||||
|
|
||||||
if (entryPoint != NULL)
|
|
||||||
{
|
|
||||||
if (BlockNumberIsValid(metap->entryBlkno))
|
|
||||||
*entryPoint = HnswInitElementFromBlock(metap->entryBlkno, metap->entryOffno);
|
|
||||||
else
|
|
||||||
*entryPoint = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get the entry point
|
|
||||||
*/
|
|
||||||
HnswElement
|
|
||||||
HnswGetEntryPoint(Relation index)
|
|
||||||
{
|
|
||||||
HnswElement entryPoint;
|
|
||||||
|
|
||||||
HnswGetMetaPageInfo(index, NULL, &entryPoint);
|
|
||||||
|
|
||||||
return entryPoint;
|
return entryPoint;
|
||||||
}
|
}
|
||||||
@@ -355,9 +337,10 @@ HnswSetNeighborTuple(HnswNeighborTuple ntup, HnswElement e, int m)
|
|||||||
* Load neighbors from page
|
* Load neighbors from page
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
LoadNeighborsFromPage(HnswElement element, Relation index, Page page, int m)
|
LoadNeighborsFromPage(HnswElement element, Relation index, Page page)
|
||||||
{
|
{
|
||||||
HnswNeighborTuple ntup = (HnswNeighborTuple) PageGetItem(page, PageGetItemId(page, element->neighborOffno));
|
HnswNeighborTuple ntup = (HnswNeighborTuple) PageGetItem(page, PageGetItemId(page, element->neighborOffno));
|
||||||
|
int m = HnswGetM(index);
|
||||||
int neighborCount = (element->level + 2) * m;
|
int neighborCount = (element->level + 2) * m;
|
||||||
|
|
||||||
Assert(HnswIsNeighborTuple(ntup));
|
Assert(HnswIsNeighborTuple(ntup));
|
||||||
@@ -398,7 +381,7 @@ LoadNeighborsFromPage(HnswElement element, Relation index, Page page, int m)
|
|||||||
* Load neighbors
|
* Load neighbors
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
HnswLoadNeighbors(HnswElement element, Relation index, int m)
|
HnswLoadNeighbors(HnswElement element, Relation index)
|
||||||
{
|
{
|
||||||
Buffer buf;
|
Buffer buf;
|
||||||
Page page;
|
Page page;
|
||||||
@@ -407,7 +390,7 @@ HnswLoadNeighbors(HnswElement element, Relation index, int m)
|
|||||||
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
||||||
page = BufferGetPage(buf);
|
page = BufferGetPage(buf);
|
||||||
|
|
||||||
LoadNeighborsFromPage(element, index, page, m);
|
LoadNeighborsFromPage(element, index, page);
|
||||||
|
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
}
|
}
|
||||||
@@ -560,7 +543,7 @@ AddToVisited(HTAB *v, HnswCandidate * hc, Relation index, bool *found)
|
|||||||
* Algorithm 2 from paper
|
* Algorithm 2 from paper
|
||||||
*/
|
*/
|
||||||
List *
|
List *
|
||||||
HnswSearchLayer(Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *procinfo, Oid collation, int m, bool inserting, HnswElement skipElement)
|
HnswSearchLayer(Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec, HnswElement skipElement)
|
||||||
{
|
{
|
||||||
ListCell *lc2;
|
ListCell *lc2;
|
||||||
|
|
||||||
@@ -615,7 +598,7 @@ HnswSearchLayer(Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *pro
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
if (c->element->neighbors == NULL)
|
if (c->element->neighbors == NULL)
|
||||||
HnswLoadNeighbors(c->element, index, m);
|
HnswLoadNeighbors(c->element, index);
|
||||||
|
|
||||||
/* Get the neighborhood at layer lc */
|
/* Get the neighborhood at layer lc */
|
||||||
neighborhood = &c->element->neighbors[lc];
|
neighborhood = &c->element->neighbors[lc];
|
||||||
@@ -636,7 +619,7 @@ HnswSearchLayer(Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *pro
|
|||||||
if (index == NULL)
|
if (index == NULL)
|
||||||
eDistance = GetCandidateDistance(e, q, procinfo, collation);
|
eDistance = GetCandidateDistance(e, q, procinfo, collation);
|
||||||
else
|
else
|
||||||
HnswLoadElement(e->element, &eDistance, &q, index, procinfo, collation, inserting);
|
HnswLoadElement(e->element, &eDistance, &q, index, procinfo, collation, loadVec);
|
||||||
|
|
||||||
Assert(!e->element->deleted);
|
Assert(!e->element->deleted);
|
||||||
|
|
||||||
@@ -973,7 +956,7 @@ HnswInsertElement(HnswElement element, HnswElement entryPoint, Relation index, F
|
|||||||
/* 1st phase: greedy search to insert level */
|
/* 1st phase: greedy search to insert level */
|
||||||
for (int lc = entryLevel; lc >= level + 1; lc--)
|
for (int lc = entryLevel; lc >= level + 1; lc--)
|
||||||
{
|
{
|
||||||
w = HnswSearchLayer(q, ep, 1, lc, index, procinfo, collation, m, true, skipElement);
|
w = HnswSearchLayer(q, ep, 1, lc, index, procinfo, collation, true, skipElement);
|
||||||
ep = w;
|
ep = w;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -991,7 +974,7 @@ HnswInsertElement(HnswElement element, HnswElement entryPoint, Relation index, F
|
|||||||
List *neighbors;
|
List *neighbors;
|
||||||
List *lw;
|
List *lw;
|
||||||
|
|
||||||
w = HnswSearchLayer(q, ep, efConstruction, lc, index, procinfo, collation, m, true, skipElement);
|
w = HnswSearchLayer(q, ep, efConstruction, lc, index, procinfo, collation, true, skipElement);
|
||||||
|
|
||||||
/* Elements being deleted or skipped can help with search */
|
/* Elements being deleted or skipped can help with search */
|
||||||
/* but should be removed before selecting neighbors */
|
/* but should be removed before selecting neighbors */
|
||||||
|
|||||||
@@ -582,6 +582,7 @@ InitVacuumState(HnswVacuumState * vacuumstate, IndexVacuumInfo *info, IndexBulkD
|
|||||||
vacuumstate->stats = stats;
|
vacuumstate->stats = stats;
|
||||||
vacuumstate->callback = callback;
|
vacuumstate->callback = callback;
|
||||||
vacuumstate->callback_state = callback_state;
|
vacuumstate->callback_state = callback_state;
|
||||||
|
vacuumstate->m = HnswGetM(index);
|
||||||
vacuumstate->efConstruction = HnswGetEfConstruction(index);
|
vacuumstate->efConstruction = HnswGetEfConstruction(index);
|
||||||
vacuumstate->bas = GetAccessStrategy(BAS_BULKREAD);
|
vacuumstate->bas = GetAccessStrategy(BAS_BULKREAD);
|
||||||
vacuumstate->procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC);
|
vacuumstate->procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC);
|
||||||
@@ -591,9 +592,6 @@ InitVacuumState(HnswVacuumState * vacuumstate, IndexVacuumInfo *info, IndexBulkD
|
|||||||
"Hnsw vacuum temporary context",
|
"Hnsw vacuum temporary context",
|
||||||
ALLOCSET_DEFAULT_SIZES);
|
ALLOCSET_DEFAULT_SIZES);
|
||||||
|
|
||||||
/* Get m from metapage */
|
|
||||||
HnswGetMetaPageInfo(index, &vacuumstate->m, NULL);
|
|
||||||
|
|
||||||
/* Create hash table */
|
/* Create hash table */
|
||||||
hash_ctl.keysize = sizeof(ItemPointerData);
|
hash_ctl.keysize = sizeof(ItemPointerData);
|
||||||
hash_ctl.entrysize = sizeof(ItemPointerData);
|
hash_ctl.entrysize = sizeof(ItemPointerData);
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
|||||||
int lists;
|
int lists;
|
||||||
double ratio;
|
double ratio;
|
||||||
double spc_seq_page_cost;
|
double spc_seq_page_cost;
|
||||||
Relation index;
|
Relation indexRel;
|
||||||
#if PG_VERSION_NUM < 120000
|
#if PG_VERSION_NUM < 120000
|
||||||
List *qinfos;
|
List *qinfos;
|
||||||
#endif
|
#endif
|
||||||
@@ -89,9 +89,9 @@ ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
|||||||
|
|
||||||
MemSet(&costs, 0, sizeof(costs));
|
MemSet(&costs, 0, sizeof(costs));
|
||||||
|
|
||||||
index = index_open(path->indexinfo->indexoid, NoLock);
|
indexRel = index_open(path->indexinfo->indexoid, NoLock);
|
||||||
IvfflatGetMetaPageInfo(index, &lists, NULL);
|
lists = IvfflatGetLists(indexRel);
|
||||||
index_close(index, NoLock);
|
index_close(indexRel, NoLock);
|
||||||
|
|
||||||
/* Get the ratio of lists that we need to visit */
|
/* Get the ratio of lists that we need to visit */
|
||||||
ratio = ((double) ivfflat_probes) / lists;
|
ratio = ((double) ivfflat_probes) / lists;
|
||||||
@@ -182,15 +182,6 @@ ivfflatvalidate(Oid opclassoid)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Checks if index-only scan is supported
|
|
||||||
*/
|
|
||||||
static bool
|
|
||||||
ivfflatcanreturn(Relation index, int attno)
|
|
||||||
{
|
|
||||||
return attno == 1 && IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC) == NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Define index handler
|
* Define index handler
|
||||||
*
|
*
|
||||||
@@ -232,7 +223,7 @@ ivfflathandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->aminsert = ivfflatinsert;
|
amroutine->aminsert = ivfflatinsert;
|
||||||
amroutine->ambulkdelete = ivfflatbulkdelete;
|
amroutine->ambulkdelete = ivfflatbulkdelete;
|
||||||
amroutine->amvacuumcleanup = ivfflatvacuumcleanup;
|
amroutine->amvacuumcleanup = ivfflatvacuumcleanup;
|
||||||
amroutine->amcanreturn = ivfflatcanreturn;
|
amroutine->amcanreturn = NULL; /* tuple not included in heapsort */
|
||||||
amroutine->amcostestimate = ivfflatcostestimate;
|
amroutine->amcostestimate = ivfflatcostestimate;
|
||||||
amroutine->amoptions = ivfflatoptions;
|
amroutine->amoptions = ivfflatoptions;
|
||||||
amroutine->amproperty = NULL; /* TODO AMPROP_DISTANCE_ORDERABLE */
|
amroutine->amproperty = NULL; /* TODO AMPROP_DISTANCE_ORDERABLE */
|
||||||
|
|||||||
@@ -244,11 +244,8 @@ typedef struct IvfflatScanList
|
|||||||
typedef struct IvfflatScanOpaqueData
|
typedef struct IvfflatScanOpaqueData
|
||||||
{
|
{
|
||||||
int probes;
|
int probes;
|
||||||
int dimensions;
|
|
||||||
bool first;
|
bool first;
|
||||||
Buffer buf;
|
Buffer buf;
|
||||||
ItemPointerData heaptid;
|
|
||||||
IndexTuple itup;
|
|
||||||
|
|
||||||
/* Sorting */
|
/* Sorting */
|
||||||
Tuplesortstate *sortstate;
|
Tuplesortstate *sortstate;
|
||||||
@@ -281,7 +278,6 @@ void IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers);
|
|||||||
FmgrInfo *IvfflatOptionalProcInfo(Relation rel, uint16 procnum);
|
FmgrInfo *IvfflatOptionalProcInfo(Relation rel, uint16 procnum);
|
||||||
bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result);
|
bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result);
|
||||||
int IvfflatGetLists(Relation index);
|
int IvfflatGetLists(Relation index);
|
||||||
void IvfflatGetMetaPageInfo(Relation index, int *lists, int *dimensions);
|
|
||||||
void IvfflatUpdateList(Relation index, ListInfo listInfo, BlockNumber insertPage, BlockNumber originalInsertPage, BlockNumber startPage, ForkNumber forkNum);
|
void IvfflatUpdateList(Relation index, ListInfo listInfo, BlockNumber insertPage, BlockNumber originalInsertPage, BlockNumber startPage, ForkNumber forkNum);
|
||||||
void IvfflatCommitBuffer(Buffer buf, GenericXLogState *state);
|
void IvfflatCommitBuffer(Buffer buf, GenericXLogState *state);
|
||||||
void IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum);
|
void IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum);
|
||||||
|
|||||||
172
src/ivfscan.c
172
src/ivfscan.c
@@ -31,36 +31,36 @@ CompareLists(const pairingheap_node *a, const pairingheap_node *b, void *arg)
|
|||||||
static void
|
static void
|
||||||
GetScanLists(IndexScanDesc scan, Datum value)
|
GetScanLists(IndexScanDesc scan, Datum value)
|
||||||
{
|
{
|
||||||
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
Buffer cbuf;
|
||||||
|
Page cpage;
|
||||||
|
IvfflatList list;
|
||||||
|
OffsetNumber offno;
|
||||||
|
OffsetNumber maxoffno;
|
||||||
BlockNumber nextblkno = IVFFLAT_HEAD_BLKNO;
|
BlockNumber nextblkno = IVFFLAT_HEAD_BLKNO;
|
||||||
int listCount = 0;
|
int listCount = 0;
|
||||||
|
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
||||||
|
double distance;
|
||||||
|
IvfflatScanList *scanlist;
|
||||||
double maxDistance = DBL_MAX;
|
double maxDistance = DBL_MAX;
|
||||||
|
|
||||||
/* Search all list pages */
|
/* Search all list pages */
|
||||||
while (BlockNumberIsValid(nextblkno))
|
while (BlockNumberIsValid(nextblkno))
|
||||||
{
|
{
|
||||||
Buffer cbuf;
|
|
||||||
Page cpage;
|
|
||||||
OffsetNumber maxoffno;
|
|
||||||
|
|
||||||
cbuf = ReadBuffer(scan->indexRelation, nextblkno);
|
cbuf = ReadBuffer(scan->indexRelation, nextblkno);
|
||||||
LockBuffer(cbuf, BUFFER_LOCK_SHARE);
|
LockBuffer(cbuf, BUFFER_LOCK_SHARE);
|
||||||
cpage = BufferGetPage(cbuf);
|
cpage = BufferGetPage(cbuf);
|
||||||
|
|
||||||
maxoffno = PageGetMaxOffsetNumber(cpage);
|
maxoffno = PageGetMaxOffsetNumber(cpage);
|
||||||
|
|
||||||
for (OffsetNumber offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
||||||
{
|
{
|
||||||
IvfflatList list = (IvfflatList) PageGetItem(cpage, PageGetItemId(cpage, offno));
|
list = (IvfflatList) PageGetItem(cpage, PageGetItemId(cpage, offno));
|
||||||
double distance;
|
|
||||||
|
|
||||||
/* Use procinfo from the index instead of scan key for performance */
|
/* Use procinfo from the index instead of scan key for performance */
|
||||||
distance = DatumGetFloat8(FunctionCall2Coll(so->procinfo, so->collation, PointerGetDatum(&list->center), value));
|
distance = DatumGetFloat8(FunctionCall2Coll(so->procinfo, so->collation, PointerGetDatum(&list->center), value));
|
||||||
|
|
||||||
if (listCount < so->probes)
|
if (listCount < so->probes)
|
||||||
{
|
{
|
||||||
IvfflatScanList *scanlist;
|
|
||||||
|
|
||||||
scanlist = &so->lists[listCount];
|
scanlist = &so->lists[listCount];
|
||||||
scanlist->startPage = list->startPage;
|
scanlist->startPage = list->startPage;
|
||||||
scanlist->distance = distance;
|
scanlist->distance = distance;
|
||||||
@@ -75,8 +75,6 @@ GetScanLists(IndexScanDesc scan, Datum value)
|
|||||||
}
|
}
|
||||||
else if (distance < maxDistance)
|
else if (distance < maxDistance)
|
||||||
{
|
{
|
||||||
IvfflatScanList *scanlist;
|
|
||||||
|
|
||||||
/* Remove */
|
/* Remove */
|
||||||
scanlist = (IvfflatScanList *) pairingheap_remove_first(so->listQueue);
|
scanlist = (IvfflatScanList *) pairingheap_remove_first(so->listQueue);
|
||||||
|
|
||||||
@@ -103,6 +101,14 @@ static void
|
|||||||
GetScanItems(IndexScanDesc scan, Datum value)
|
GetScanItems(IndexScanDesc scan, Datum value)
|
||||||
{
|
{
|
||||||
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
||||||
|
Buffer buf;
|
||||||
|
Page page;
|
||||||
|
IndexTuple itup;
|
||||||
|
BlockNumber searchPage;
|
||||||
|
OffsetNumber offno;
|
||||||
|
OffsetNumber maxoffno;
|
||||||
|
Datum datum;
|
||||||
|
bool isnull;
|
||||||
TupleDesc tupdesc = RelationGetDescr(scan->indexRelation);
|
TupleDesc tupdesc = RelationGetDescr(scan->indexRelation);
|
||||||
double tuples = 0;
|
double tuples = 0;
|
||||||
|
|
||||||
@@ -122,35 +128,20 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
|||||||
/* Search closest probes lists */
|
/* Search closest probes lists */
|
||||||
while (!pairingheap_is_empty(so->listQueue))
|
while (!pairingheap_is_empty(so->listQueue))
|
||||||
{
|
{
|
||||||
BlockNumber searchPage = ((IvfflatScanList *) pairingheap_remove_first(so->listQueue))->startPage;
|
searchPage = ((IvfflatScanList *) pairingheap_remove_first(so->listQueue))->startPage;
|
||||||
|
|
||||||
/* Search all entry pages for list */
|
/* Search all entry pages for list */
|
||||||
while (BlockNumberIsValid(searchPage))
|
while (BlockNumberIsValid(searchPage))
|
||||||
{
|
{
|
||||||
Buffer buf;
|
|
||||||
Page page;
|
|
||||||
OffsetNumber maxoffno;
|
|
||||||
|
|
||||||
buf = ReadBufferExtended(scan->indexRelation, MAIN_FORKNUM, searchPage, RBM_NORMAL, bas);
|
buf = ReadBufferExtended(scan->indexRelation, MAIN_FORKNUM, searchPage, RBM_NORMAL, bas);
|
||||||
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
||||||
page = BufferGetPage(buf);
|
page = BufferGetPage(buf);
|
||||||
maxoffno = PageGetMaxOffsetNumber(page);
|
maxoffno = PageGetMaxOffsetNumber(page);
|
||||||
|
|
||||||
for (OffsetNumber offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
||||||
{
|
{
|
||||||
IndexTuple itup;
|
itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offno));
|
||||||
Datum datum;
|
|
||||||
bool isnull;
|
|
||||||
ItemPointerData indextid;
|
|
||||||
ItemId itemid = PageGetItemId(page, offno);
|
|
||||||
|
|
||||||
/* Skip dead tuples */
|
|
||||||
if (scan->ignore_killed_tuples && ItemIdIsDead(itemid))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
itup = (IndexTuple) PageGetItem(page, itemid);
|
|
||||||
datum = index_getattr(itup, 1, tupdesc, &isnull);
|
datum = index_getattr(itup, 1, tupdesc, &isnull);
|
||||||
ItemPointerSet(&indextid, searchPage, offno);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add virtual tuple
|
* Add virtual tuple
|
||||||
@@ -163,7 +154,7 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
|||||||
slot->tts_isnull[0] = false;
|
slot->tts_isnull[0] = false;
|
||||||
slot->tts_values[1] = PointerGetDatum(&itup->t_tid);
|
slot->tts_values[1] = PointerGetDatum(&itup->t_tid);
|
||||||
slot->tts_isnull[1] = false;
|
slot->tts_isnull[1] = false;
|
||||||
slot->tts_values[2] = PointerGetDatum(&indextid);
|
slot->tts_values[2] = Int32GetDatum((int) searchPage);
|
||||||
slot->tts_isnull[2] = false;
|
slot->tts_isnull[2] = false;
|
||||||
ExecStoreVirtualTuple(slot);
|
ExecStoreVirtualTuple(slot);
|
||||||
|
|
||||||
@@ -190,80 +181,26 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Mark prior tuple as dead
|
* Get dimensions from metapage
|
||||||
*/
|
*/
|
||||||
static void
|
static int
|
||||||
MarkPriorTupleDead(IndexScanDesc scan)
|
GetDimensions(Relation index)
|
||||||
{
|
{
|
||||||
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
Buffer buf;
|
||||||
Buffer buf = so->buf;
|
|
||||||
Page page;
|
Page page;
|
||||||
OffsetNumber maxoffno;
|
IvfflatMetaPage metap;
|
||||||
|
int dimensions;
|
||||||
|
|
||||||
/* Safety check */
|
buf = ReadBuffer(index, IVFFLAT_METAPAGE_BLKNO);
|
||||||
if (!BufferIsValid(so->buf) || !ItemPointerIsValid(&so->heaptid))
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* Only a shared locked is needed for ItemIdMarkDead */
|
|
||||||
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
||||||
page = BufferGetPage(buf);
|
page = BufferGetPage(buf);
|
||||||
maxoffno = PageGetMaxOffsetNumber(page);
|
metap = IvfflatPageGetMeta(page);
|
||||||
|
|
||||||
for (OffsetNumber offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
dimensions = metap->dimensions;
|
||||||
{
|
|
||||||
ItemId itemid = PageGetItemId(page, offno);
|
|
||||||
IndexTuple itup = (IndexTuple) PageGetItem(page, itemid);
|
|
||||||
|
|
||||||
/*
|
UnlockReleaseBuffer(buf);
|
||||||
* Find tuple. Since buffer has been pinned, tuple cannot have been
|
|
||||||
* vacuumed (and heap TID reused).
|
|
||||||
*/
|
|
||||||
if (ItemPointerEquals(&itup->t_tid, &so->heaptid))
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Make sure tuple has not already been marked dead to avoid extra
|
|
||||||
* WAL if wal_log_hints or data checksums enabled
|
|
||||||
*/
|
|
||||||
if (!ItemIdIsDead(itemid))
|
|
||||||
{
|
|
||||||
ItemIdMarkDead(itemid);
|
|
||||||
MarkBufferDirtyHint(buf, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
return dimensions;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Unlock buffer */
|
|
||||||
LockBuffer(buf, BUFFER_LOCK_UNLOCK);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Set tuple for index-only scan
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
SetIndexTuple(IndexScanDesc scan, ItemPointer indextid)
|
|
||||||
{
|
|
||||||
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
|
||||||
Buffer buf = so->buf;
|
|
||||||
Page page;
|
|
||||||
OffsetNumber offno = ItemPointerGetOffsetNumber(indextid);
|
|
||||||
IndexTuple itup;
|
|
||||||
Size itupSize;
|
|
||||||
|
|
||||||
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
|
||||||
page = BufferGetPage(buf);
|
|
||||||
itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offno));
|
|
||||||
itupSize = IndexTupleSize(itup);
|
|
||||||
|
|
||||||
if (so->itup == NULL)
|
|
||||||
so->itup = palloc(BLCKSZ);
|
|
||||||
|
|
||||||
memcpy(so->itup, itup, itupSize);
|
|
||||||
|
|
||||||
scan->xs_itup = so->itup;
|
|
||||||
|
|
||||||
LockBuffer(buf, BUFFER_LOCK_UNLOCK);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -275,7 +212,6 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
|||||||
IndexScanDesc scan;
|
IndexScanDesc scan;
|
||||||
IvfflatScanOpaque so;
|
IvfflatScanOpaque so;
|
||||||
int lists;
|
int lists;
|
||||||
int dimensions;
|
|
||||||
AttrNumber attNums[] = {1};
|
AttrNumber attNums[] = {1};
|
||||||
Oid sortOperators[] = {Float8LessOperator};
|
Oid sortOperators[] = {Float8LessOperator};
|
||||||
Oid sortCollations[] = {InvalidOid};
|
Oid sortCollations[] = {InvalidOid};
|
||||||
@@ -283,9 +219,7 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
|||||||
int probes = ivfflat_probes;
|
int probes = ivfflat_probes;
|
||||||
|
|
||||||
scan = RelationGetIndexScan(index, nkeys, norderbys);
|
scan = RelationGetIndexScan(index, nkeys, norderbys);
|
||||||
|
lists = IvfflatGetLists(scan->indexRelation);
|
||||||
/* Get lists and dimensions from metapage */
|
|
||||||
IvfflatGetMetaPageInfo(index, &lists, &dimensions);
|
|
||||||
|
|
||||||
if (probes > lists)
|
if (probes > lists)
|
||||||
probes = lists;
|
probes = lists;
|
||||||
@@ -293,10 +227,7 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
|||||||
so = (IvfflatScanOpaque) palloc(offsetof(IvfflatScanOpaqueData, lists) + probes * sizeof(IvfflatScanList));
|
so = (IvfflatScanOpaque) palloc(offsetof(IvfflatScanOpaqueData, lists) + probes * sizeof(IvfflatScanList));
|
||||||
so->buf = InvalidBuffer;
|
so->buf = InvalidBuffer;
|
||||||
so->first = true;
|
so->first = true;
|
||||||
ItemPointerSetInvalid(&so->heaptid);
|
|
||||||
so->itup = NULL;
|
|
||||||
so->probes = probes;
|
so->probes = probes;
|
||||||
so->dimensions = dimensions;
|
|
||||||
|
|
||||||
/* Set support functions */
|
/* Set support functions */
|
||||||
so->procinfo = index_getprocinfo(index, 1, IVFFLAT_DISTANCE_PROC);
|
so->procinfo = index_getprocinfo(index, 1, IVFFLAT_DISTANCE_PROC);
|
||||||
@@ -310,8 +241,8 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
|||||||
so->tupdesc = CreateTemplateTupleDesc(3, false);
|
so->tupdesc = CreateTemplateTupleDesc(3, false);
|
||||||
#endif
|
#endif
|
||||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 1, "distance", FLOAT8OID, -1, 0);
|
TupleDescInitEntry(so->tupdesc, (AttrNumber) 1, "distance", FLOAT8OID, -1, 0);
|
||||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 2, "heaptid", TIDOID, -1, 0);
|
TupleDescInitEntry(so->tupdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0);
|
||||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 3, "indextid", TIDOID, -1, 0);
|
TupleDescInitEntry(so->tupdesc, (AttrNumber) 3, "indexblkno", INT4OID, -1, 0);
|
||||||
|
|
||||||
/* Prep sort */
|
/* Prep sort */
|
||||||
so->sortstate = tuplesort_begin_heap(so->tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, work_mem, NULL, false);
|
so->sortstate = tuplesort_begin_heap(so->tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, work_mem, NULL, false);
|
||||||
@@ -326,8 +257,6 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
|||||||
|
|
||||||
scan->opaque = so;
|
scan->opaque = so;
|
||||||
|
|
||||||
scan->xs_itupdesc = RelationGetDescr(index);
|
|
||||||
|
|
||||||
return scan;
|
return scan;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,7 +274,6 @@ ivfflatrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
so->first = true;
|
so->first = true;
|
||||||
ItemPointerSetInvalid(&so->heaptid);
|
|
||||||
pairingheap_reset(so->listQueue);
|
pairingheap_reset(so->listQueue);
|
||||||
|
|
||||||
if (keys && scan->numberOfKeys > 0)
|
if (keys && scan->numberOfKeys > 0)
|
||||||
@@ -381,7 +309,7 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
elog(ERROR, "cannot scan ivfflat index without order");
|
elog(ERROR, "cannot scan ivfflat index without order");
|
||||||
|
|
||||||
if (scan->orderByData->sk_flags & SK_ISNULL)
|
if (scan->orderByData->sk_flags & SK_ISNULL)
|
||||||
value = PointerGetDatum(InitVector(so->dimensions));
|
value = PointerGetDatum(InitVector(GetDimensions(scan->indexRelation)));
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
value = scan->orderByData->sk_argument;
|
value = scan->orderByData->sk_argument;
|
||||||
@@ -403,27 +331,18 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
if (value != scan->orderByData->sk_argument)
|
if (value != scan->orderByData->sk_argument)
|
||||||
pfree(DatumGetPointer(value));
|
pfree(DatumGetPointer(value));
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
/* Mark prior tuple as dead */
|
|
||||||
if (scan->kill_prior_tuple)
|
|
||||||
MarkPriorTupleDead(scan);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tuplesort_gettupleslot(so->sortstate, true, false, so->slot, NULL))
|
if (tuplesort_gettupleslot(so->sortstate, true, false, so->slot, NULL))
|
||||||
{
|
{
|
||||||
ItemPointer heaptid = (ItemPointer) DatumGetPointer(slot_getattr(so->slot, 2, &so->isnull));
|
ItemPointer tid = (ItemPointer) DatumGetPointer(slot_getattr(so->slot, 2, &so->isnull));
|
||||||
ItemPointer indextid = (ItemPointer) DatumGetPointer(slot_getattr(so->slot, 3, &so->isnull));
|
BlockNumber indexblkno = DatumGetInt32(slot_getattr(so->slot, 3, &so->isnull));
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
scan->xs_heaptid = *heaptid;
|
scan->xs_heaptid = *tid;
|
||||||
#else
|
#else
|
||||||
scan->xs_ctup.t_self = *heaptid;
|
scan->xs_ctup.t_self = *tid;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Keep track of info needed to mark tuple as dead */
|
|
||||||
so->heaptid = *heaptid;
|
|
||||||
|
|
||||||
/* Unpin buffer */
|
/* Unpin buffer */
|
||||||
if (BufferIsValid(so->buf))
|
if (BufferIsValid(so->buf))
|
||||||
ReleaseBuffer(so->buf);
|
ReleaseBuffer(so->buf);
|
||||||
@@ -434,11 +353,7 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
*
|
*
|
||||||
* https://www.postgresql.org/docs/current/index-locking.html
|
* https://www.postgresql.org/docs/current/index-locking.html
|
||||||
*/
|
*/
|
||||||
so->buf = ReadBuffer(scan->indexRelation, ItemPointerGetBlockNumber(indextid));
|
so->buf = ReadBuffer(scan->indexRelation, indexblkno);
|
||||||
|
|
||||||
/* Set tuple for index-only scan */
|
|
||||||
if (scan->xs_want_itup)
|
|
||||||
SetIndexTuple(scan, indextid);
|
|
||||||
|
|
||||||
scan->xs_recheckorderby = false;
|
scan->xs_recheckorderby = false;
|
||||||
return true;
|
return true;
|
||||||
@@ -459,9 +374,6 @@ ivfflatendscan(IndexScanDesc scan)
|
|||||||
if (BufferIsValid(so->buf))
|
if (BufferIsValid(so->buf))
|
||||||
ReleaseBuffer(so->buf);
|
ReleaseBuffer(so->buf);
|
||||||
|
|
||||||
if (so->itup != NULL)
|
|
||||||
pfree(so->itup);
|
|
||||||
|
|
||||||
pairingheap_free(so->listQueue);
|
pairingheap_free(so->listQueue);
|
||||||
tuplesort_end(so->sortstate);
|
tuplesort_end(so->sortstate);
|
||||||
|
|
||||||
|
|||||||
@@ -172,29 +172,6 @@ IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **st
|
|||||||
*buf = newbuf;
|
*buf = newbuf;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Get the metapage info
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
IvfflatGetMetaPageInfo(Relation index, int *lists, int *dimensions)
|
|
||||||
{
|
|
||||||
Buffer buf;
|
|
||||||
Page page;
|
|
||||||
IvfflatMetaPage metap;
|
|
||||||
|
|
||||||
buf = ReadBuffer(index, IVFFLAT_METAPAGE_BLKNO);
|
|
||||||
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
|
||||||
page = BufferGetPage(buf);
|
|
||||||
metap = IvfflatPageGetMeta(page);
|
|
||||||
|
|
||||||
*lists = metap->lists;
|
|
||||||
|
|
||||||
if (dimensions != NULL)
|
|
||||||
*dimensions = metap->dimensions;
|
|
||||||
|
|
||||||
UnlockReleaseBuffer(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Update the start or insert page of a list
|
* Update the start or insert page of a list
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
use PostgresNode;
|
|
||||||
use TestLib;
|
|
||||||
use Test::More;
|
|
||||||
|
|
||||||
my $dim = 3;
|
|
||||||
|
|
||||||
my $array_sql = join(",", ('random()') x $dim);
|
|
||||||
|
|
||||||
# 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 vector($dim));");
|
|
||||||
$node->safe_psql("postgres",
|
|
||||||
"INSERT INTO tst (v) SELECT ARRAY[$array_sql] FROM generate_series(1, 10000) i;"
|
|
||||||
);
|
|
||||||
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v vector_l2_ops);");
|
|
||||||
|
|
||||||
# Delete data
|
|
||||||
$node->safe_psql("postgres", "DELETE FROM tst WHERE i % 100 != 0;");
|
|
||||||
|
|
||||||
my $exp = $node->safe_psql("postgres", qq(
|
|
||||||
SET enable_indexscan = off;
|
|
||||||
SELECT i FROM tst ORDER BY v <-> '[0,0,0]';
|
|
||||||
));
|
|
||||||
|
|
||||||
# Run twice to make sure correct tuples marked as dead
|
|
||||||
for (1 .. 2)
|
|
||||||
{
|
|
||||||
my $res = $node->safe_psql("postgres", qq(
|
|
||||||
SET enable_seqscan = off;
|
|
||||||
SET ivfflat.probes = 100;
|
|
||||||
SELECT i FROM tst ORDER BY v <-> '[0,0,0]';
|
|
||||||
));
|
|
||||||
is($res, $exp);
|
|
||||||
}
|
|
||||||
|
|
||||||
done_testing();
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
comment = 'vector data type and ivfflat and hnsw access methods'
|
comment = 'vector data type and ivfflat access method'
|
||||||
default_version = '0.5.0'
|
default_version = '0.5.0'
|
||||||
module_pathname = '$libdir/vector'
|
module_pathname = '$libdir/vector'
|
||||||
relocatable = true
|
relocatable = true
|
||||||
|
|||||||
Reference in New Issue
Block a user