mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-23 20:40:55 +08:00
Compare commits
1 Commits
ivfflat-se
...
tuple-vers
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5db9581e74 |
16
src/hnsw.h
16
src/hnsw.h
@@ -129,6 +129,7 @@ struct HnswElementData
|
||||
uint8 heaptidsLength;
|
||||
uint8 level;
|
||||
uint8 deleted;
|
||||
uint8 version;
|
||||
uint32 hash;
|
||||
HnswNeighborsPtr neighbors;
|
||||
BlockNumber blkno;
|
||||
@@ -155,13 +156,12 @@ struct HnswNeighborArray
|
||||
HnswCandidate items[FLEXIBLE_ARRAY_MEMBER];
|
||||
};
|
||||
|
||||
typedef struct HnswSearchCandidate
|
||||
typedef struct HnswPairingHeapNode
|
||||
{
|
||||
HnswCandidate *inner;
|
||||
pairingheap_node c_node;
|
||||
pairingheap_node w_node;
|
||||
HnswElementPtr element;
|
||||
float distance;
|
||||
} HnswSearchCandidate;
|
||||
} HnswPairingHeapNode;
|
||||
|
||||
/* HNSW index options */
|
||||
typedef struct HnswOptions
|
||||
@@ -306,10 +306,10 @@ typedef struct HnswElementTupleData
|
||||
uint8 type;
|
||||
uint8 level;
|
||||
uint8 deleted;
|
||||
uint8 unused;
|
||||
uint8 version;
|
||||
ItemPointerData heaptids[HNSW_HEAPTIDS];
|
||||
ItemPointerData neighbortid;
|
||||
uint16 unused2;
|
||||
uint16 unused;
|
||||
Vector data;
|
||||
} HnswElementTupleData;
|
||||
|
||||
@@ -318,7 +318,7 @@ typedef HnswElementTupleData * HnswElementTuple;
|
||||
typedef struct HnswNeighborTupleData
|
||||
{
|
||||
uint8 type;
|
||||
uint8 unused;
|
||||
uint8 version;
|
||||
uint16 count;
|
||||
ItemPointerData indextids[FLEXIBLE_ARRAY_MEMBER];
|
||||
} HnswNeighborTupleData;
|
||||
@@ -382,7 +382,7 @@ 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 HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint, Relation index, FmgrInfo *procinfo, Oid collation, int m, int efConstruction, bool existing);
|
||||
HnswSearchCandidate *HnswEntryCandidate(char *base, HnswElement em, Datum q, Relation rel, FmgrInfo *procinfo, Oid collation, bool loadVec);
|
||||
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);
|
||||
|
||||
@@ -36,7 +36,7 @@ GetInsertPage(Relation index)
|
||||
* Check for a free offset
|
||||
*/
|
||||
static bool
|
||||
HnswFreeOffset(Relation index, Buffer buf, Page page, HnswElement element, Size etupSize, Size ntupSize, Buffer *nbuf, Page *npage, OffsetNumber *freeOffno, OffsetNumber *freeNeighborOffno, BlockNumber *newInsertPage)
|
||||
HnswFreeOffset(Relation index, Buffer buf, Page page, HnswElement element, Size etupSize, Size ntupSize, Buffer *nbuf, Page *npage, OffsetNumber *freeOffno, OffsetNumber *freeNeighborOffno, BlockNumber *newInsertPage, uint8 *tupleVersion)
|
||||
{
|
||||
OffsetNumber offno;
|
||||
OffsetNumber maxoffno = PageGetMaxOffsetNumber(page);
|
||||
@@ -98,6 +98,7 @@ HnswFreeOffset(Relation index, Buffer buf, Page page, HnswElement element, Size
|
||||
{
|
||||
*freeOffno = offno;
|
||||
*freeNeighborOffno = neighborOffno;
|
||||
*tupleVersion = etup->version;
|
||||
return true;
|
||||
}
|
||||
else if (*nbuf != buf)
|
||||
@@ -153,6 +154,7 @@ AddElementOnDisk(Relation index, HnswElement e, int m, BlockNumber insertPage, B
|
||||
OffsetNumber freeOffno = InvalidOffsetNumber;
|
||||
OffsetNumber freeNeighborOffno = InvalidOffsetNumber;
|
||||
BlockNumber newInsertPage = InvalidBlockNumber;
|
||||
uint8 tupleVersion;
|
||||
char *base = NULL;
|
||||
|
||||
/* Calculate sizes */
|
||||
@@ -202,7 +204,7 @@ AddElementOnDisk(Relation index, HnswElement e, int m, BlockNumber insertPage, B
|
||||
}
|
||||
|
||||
/* Next, try space from a deleted element */
|
||||
if (HnswFreeOffset(index, buf, page, e, etupSize, ntupSize, &nbuf, &npage, &freeOffno, &freeNeighborOffno, &newInsertPage))
|
||||
if (HnswFreeOffset(index, buf, page, e, etupSize, ntupSize, &nbuf, &npage, &freeOffno, &freeNeighborOffno, &newInsertPage, &tupleVersion))
|
||||
{
|
||||
if (nbuf != buf)
|
||||
{
|
||||
@@ -212,6 +214,10 @@ AddElementOnDisk(Relation index, HnswElement e, int m, BlockNumber insertPage, B
|
||||
npage = GenericXLogRegisterBuffer(state, nbuf, 0);
|
||||
}
|
||||
|
||||
/* Set tuple version */
|
||||
etup->version = tupleVersion;
|
||||
ntup->version = tupleVersion;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -161,14 +161,14 @@ hnswgettuple(IndexScanDesc scan, ScanDirection dir)
|
||||
so->first = false;
|
||||
|
||||
#if defined(HNSW_MEMORY)
|
||||
elog(INFO, "memory: %zu KB", MemoryContextMemAllocated(so->tmpCtx, false) / 1024);
|
||||
elog(INFO, "memory: %zu MB", MemoryContextMemAllocated(so->tmpCtx, false) / (1024 * 1024));
|
||||
#endif
|
||||
}
|
||||
|
||||
while (list_length(so->w) > 0)
|
||||
{
|
||||
char *base = NULL;
|
||||
HnswSearchCandidate *hc = llast(so->w);
|
||||
HnswCandidate *hc = llast(so->w);
|
||||
HnswElement element = HnswPtrAccess(base, hc->element);
|
||||
ItemPointer heaptid;
|
||||
|
||||
|
||||
101
src/hnswutils.c
101
src/hnswutils.c
@@ -253,6 +253,8 @@ HnswInitElement(char *base, ItemPointer heaptid, int m, double ml, int maxLevel,
|
||||
|
||||
element->level = level;
|
||||
element->deleted = 0;
|
||||
/* Start at one to make it easier to find issues */
|
||||
element->version = 1;
|
||||
|
||||
HnswInitNeighbors(base, element, m, allocator);
|
||||
|
||||
@@ -405,6 +407,7 @@ HnswSetElementTuple(char *base, HnswElementTuple etup, HnswElement element)
|
||||
etup->type = HNSW_ELEMENT_TUPLE_TYPE;
|
||||
etup->level = element->level;
|
||||
etup->deleted = 0;
|
||||
etup->version = element->version;
|
||||
for (int i = 0; i < HNSW_HEAPTIDS; i++)
|
||||
{
|
||||
if (i < element->heaptidsLength)
|
||||
@@ -447,6 +450,7 @@ HnswSetNeighborTuple(char *base, HnswNeighborTuple ntup, HnswElement e, int m)
|
||||
}
|
||||
|
||||
ntup->count = idx;
|
||||
ntup->version = e->version;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -520,6 +524,7 @@ HnswLoadElementFromTuple(HnswElement element, HnswElementTuple etup, bool loadHe
|
||||
{
|
||||
element->level = etup->level;
|
||||
element->deleted = etup->deleted;
|
||||
element->version = etup->version;
|
||||
element->neighborPage = ItemPointerGetBlockNumber(&etup->neighbortid);
|
||||
element->neighborOffno = ItemPointerGetOffsetNumber(&etup->neighbortid);
|
||||
element->heaptidsLength = 0;
|
||||
@@ -608,10 +613,10 @@ GetElementDistance(char *base, HnswElement element, Datum q, FmgrInfo *procinfo,
|
||||
/*
|
||||
* Create a candidate for the entry point
|
||||
*/
|
||||
HnswSearchCandidate *
|
||||
HnswCandidate *
|
||||
HnswEntryCandidate(char *base, HnswElement entryPoint, Datum q, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec)
|
||||
{
|
||||
HnswSearchCandidate *hc = palloc(sizeof(HnswSearchCandidate));
|
||||
HnswCandidate *hc = palloc(sizeof(HnswCandidate));
|
||||
|
||||
HnswPtrStore(base, hc->element, entryPoint);
|
||||
if (index == NULL)
|
||||
@@ -621,8 +626,8 @@ HnswEntryCandidate(char *base, HnswElement entryPoint, Datum q, Relation index,
|
||||
return hc;
|
||||
}
|
||||
|
||||
#define HnswGetSearchCandidate(membername, ptr) pairingheap_container(HnswSearchCandidate, membername, ptr)
|
||||
#define HnswGetSearchCandidateConst(membername, ptr) pairingheap_const_container(HnswSearchCandidate, membername, ptr)
|
||||
#define HnswGetPairingHeapCandidate(membername, ptr) (pairingheap_container(HnswPairingHeapNode, membername, ptr)->inner)
|
||||
#define HnswGetPairingHeapCandidateConst(membername, ptr) (pairingheap_const_container(HnswPairingHeapNode, membername, ptr)->inner)
|
||||
|
||||
/*
|
||||
* Compare candidate distances
|
||||
@@ -630,10 +635,10 @@ HnswEntryCandidate(char *base, HnswElement entryPoint, Datum q, Relation index,
|
||||
static int
|
||||
CompareNearestCandidates(const pairingheap_node *a, const pairingheap_node *b, void *arg)
|
||||
{
|
||||
if (HnswGetSearchCandidateConst(c_node, a)->distance < HnswGetSearchCandidateConst(c_node, b)->distance)
|
||||
if (HnswGetPairingHeapCandidateConst(c_node, a)->distance < HnswGetPairingHeapCandidateConst(c_node, b)->distance)
|
||||
return 1;
|
||||
|
||||
if (HnswGetSearchCandidateConst(c_node, a)->distance > HnswGetSearchCandidateConst(c_node, b)->distance)
|
||||
if (HnswGetPairingHeapCandidateConst(c_node, a)->distance > HnswGetPairingHeapCandidateConst(c_node, b)->distance)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
@@ -645,15 +650,27 @@ CompareNearestCandidates(const pairingheap_node *a, const pairingheap_node *b, v
|
||||
static int
|
||||
CompareFurthestCandidates(const pairingheap_node *a, const pairingheap_node *b, void *arg)
|
||||
{
|
||||
if (HnswGetSearchCandidateConst(w_node, a)->distance < HnswGetSearchCandidateConst(w_node, b)->distance)
|
||||
if (HnswGetPairingHeapCandidateConst(w_node, a)->distance < HnswGetPairingHeapCandidateConst(w_node, b)->distance)
|
||||
return -1;
|
||||
|
||||
if (HnswGetSearchCandidateConst(w_node, a)->distance > HnswGetSearchCandidateConst(w_node, b)->distance)
|
||||
if (HnswGetPairingHeapCandidateConst(w_node, a)->distance > HnswGetPairingHeapCandidateConst(w_node, b)->distance)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a pairing heap node for a candidate
|
||||
*/
|
||||
static HnswPairingHeapNode *
|
||||
CreatePairingHeapNode(HnswCandidate * c)
|
||||
{
|
||||
HnswPairingHeapNode *node = palloc(sizeof(HnswPairingHeapNode));
|
||||
|
||||
node->inner = c;
|
||||
return node;
|
||||
}
|
||||
|
||||
/*
|
||||
* Init visited
|
||||
*/
|
||||
@@ -754,20 +771,30 @@ HnswLoadUnvisitedFromDisk(HnswElement element, HnswUnvisited * unvisited, int *u
|
||||
int start;
|
||||
ItemPointerData indextids[HNSW_MAX_M * 2];
|
||||
|
||||
*unvisitedLength = 0;
|
||||
|
||||
buf = ReadBuffer(index, element->neighborPage);
|
||||
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
||||
page = BufferGetPage(buf);
|
||||
|
||||
ntup = (HnswNeighborTuple) PageGetItem(page, PageGetItemId(page, element->neighborOffno));
|
||||
start = (element->level - lc) * m;
|
||||
|
||||
/*
|
||||
* Ensure the neighbor tuple has not been deleted or replaced between
|
||||
* index scan iterations
|
||||
*/
|
||||
if (ntup->version != element->version)
|
||||
{
|
||||
UnlockReleaseBuffer(buf);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Copy to minimize lock time */
|
||||
start = (element->level - lc) * m;
|
||||
memcpy(&indextids, ntup->indextids + start, lm * sizeof(ItemPointerData));
|
||||
|
||||
UnlockReleaseBuffer(buf);
|
||||
|
||||
*unvisitedLength = 0;
|
||||
|
||||
for (int i = 0; i < lm; i++)
|
||||
{
|
||||
ItemPointer indextid = &indextids[i];
|
||||
@@ -813,13 +840,15 @@ HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, F
|
||||
/* Add entry points to v, C, and W */
|
||||
foreach(lc2, ep)
|
||||
{
|
||||
HnswSearchCandidate *hc = (HnswSearchCandidate *) lfirst(lc2);
|
||||
HnswCandidate *hc = (HnswCandidate *) lfirst(lc2);
|
||||
bool found;
|
||||
HnswPairingHeapNode *node;
|
||||
|
||||
AddToVisited(base, &v, hc->element, index, &found);
|
||||
|
||||
pairingheap_add(C, &hc->c_node);
|
||||
pairingheap_add(W, &hc->w_node);
|
||||
node = CreatePairingHeapNode(hc);
|
||||
pairingheap_add(C, &node->c_node);
|
||||
pairingheap_add(W, &node->w_node);
|
||||
|
||||
/*
|
||||
* Do not count elements being deleted towards ef when vacuuming. It
|
||||
@@ -832,8 +861,8 @@ HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, F
|
||||
|
||||
while (!pairingheap_is_empty(C))
|
||||
{
|
||||
HnswSearchCandidate *c = HnswGetSearchCandidate(c_node, pairingheap_remove_first(C));
|
||||
HnswSearchCandidate *f = HnswGetSearchCandidate(w_node, pairingheap_first(W));
|
||||
HnswCandidate *c = HnswGetPairingHeapCandidate(c_node, pairingheap_remove_first(C));
|
||||
HnswCandidate *f = HnswGetPairingHeapCandidate(w_node, pairingheap_first(W));
|
||||
HnswElement cElement;
|
||||
|
||||
if (c->distance > f->distance)
|
||||
@@ -849,16 +878,20 @@ HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, F
|
||||
for (int i = 0; i < unvisitedLength; i++)
|
||||
{
|
||||
HnswElement eElement;
|
||||
HnswSearchCandidate *e;
|
||||
HnswCandidate *e;
|
||||
HnswPairingHeapNode *node;
|
||||
float eDistance;
|
||||
bool alwaysAdd = wlen < ef;
|
||||
|
||||
f = HnswGetSearchCandidate(w_node, pairingheap_first(W));
|
||||
f = HnswGetPairingHeapCandidate(w_node, pairingheap_first(W));
|
||||
|
||||
if (index == NULL)
|
||||
{
|
||||
eElement = unvisited[i].element;
|
||||
eDistance = GetElementDistance(base, eElement, q, procinfo, collation);
|
||||
|
||||
if (!(eDistance < f->distance || alwaysAdd))
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -874,9 +907,6 @@ HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, F
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(eDistance < f->distance || alwaysAdd))
|
||||
continue;
|
||||
|
||||
Assert(!eElement->deleted);
|
||||
|
||||
/* Make robust to issues */
|
||||
@@ -884,11 +914,13 @@ HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, F
|
||||
continue;
|
||||
|
||||
/* Create a new candidate */
|
||||
e = palloc(sizeof(HnswSearchCandidate));
|
||||
e = palloc(sizeof(HnswCandidate));
|
||||
HnswPtrStore(base, e->element, eElement);
|
||||
e->distance = eDistance;
|
||||
pairingheap_add(C, &e->c_node);
|
||||
pairingheap_add(W, &e->w_node);
|
||||
|
||||
node = CreatePairingHeapNode(e);
|
||||
pairingheap_add(C, &node->c_node);
|
||||
pairingheap_add(W, &node->w_node);
|
||||
|
||||
/*
|
||||
* Do not count elements being deleted towards ef when vacuuming.
|
||||
@@ -909,7 +941,7 @@ HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, F
|
||||
/* Add each element of W to w */
|
||||
while (!pairingheap_is_empty(W))
|
||||
{
|
||||
HnswSearchCandidate *hc = HnswGetSearchCandidate(w_node, pairingheap_remove_first(W));
|
||||
HnswCandidate *hc = HnswGetPairingHeapCandidate(w_node, pairingheap_remove_first(W));
|
||||
|
||||
w = lappend(w, hc);
|
||||
}
|
||||
@@ -1290,27 +1322,16 @@ HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint
|
||||
{
|
||||
int lm = HnswGetLayerM(m, lc);
|
||||
List *neighbors;
|
||||
List *lw = NIL;
|
||||
ListCell *lc2;
|
||||
List *lw;
|
||||
|
||||
w = HnswSearchLayer(base, q, ep, efConstruction, lc, index, procinfo, collation, m, true, skipElement);
|
||||
|
||||
/* Convert search candidates to candidates */
|
||||
foreach(lc2, w)
|
||||
{
|
||||
HnswSearchCandidate *sc = lfirst(lc2);
|
||||
HnswCandidate *hc = palloc(sizeof(HnswCandidate));
|
||||
|
||||
hc->element = sc->element;
|
||||
hc->distance = sc->distance;
|
||||
|
||||
lw = lappend(lw, hc);
|
||||
}
|
||||
|
||||
/* Elements being deleted or skipped can help with search */
|
||||
/* but should be removed before selecting neighbors */
|
||||
if (index != NULL)
|
||||
lw = RemoveElements(base, lw, skipElement);
|
||||
lw = RemoveElements(base, w, skipElement);
|
||||
else
|
||||
lw = w;
|
||||
|
||||
/*
|
||||
* Candidates are sorted, but not deterministically. Could set
|
||||
|
||||
@@ -527,6 +527,11 @@ MarkDeleted(HnswVacuumState * vacuumstate)
|
||||
for (int i = 0; i < ntup->count; i++)
|
||||
ItemPointerSetInvalid(&ntup->indextids[i]);
|
||||
|
||||
/* Increment version */
|
||||
/* This is used to avoid incorrect reads for iterative scans */
|
||||
etup->version++;
|
||||
ntup->version = etup->version;
|
||||
|
||||
/*
|
||||
* We modified the tuples in place, no need to call
|
||||
* PageIndexTupleOverwrite
|
||||
|
||||
@@ -57,39 +57,6 @@ ivfflatbuildphasename(int64 phasenum)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Estimate the number of probes needed to get requested tuples
|
||||
*/
|
||||
static int
|
||||
EstimateProbes(PlannerInfo *root, IndexPath *path, int lists)
|
||||
{
|
||||
double selectivity = 1;
|
||||
ListCell *lc;
|
||||
double tuplesPerList;
|
||||
|
||||
/* Cannot estimate without limit */
|
||||
/* limit_tuples includes offset */
|
||||
if (root->limit_tuples < 0)
|
||||
return 0;
|
||||
|
||||
/* Get the selectivity of non-index conditions */
|
||||
foreach(lc, path->indexinfo->indrestrictinfo)
|
||||
{
|
||||
RestrictInfo *rinfo = lfirst(lc);
|
||||
|
||||
/* Skip DEFAULT_INEQ_SEL since it may be a distance filter */
|
||||
if (rinfo->norm_selec >= 0 && rinfo->norm_selec <= 1 && rinfo->norm_selec != (Selectivity) DEFAULT_INEQ_SEL)
|
||||
selectivity *= rinfo->norm_selec;
|
||||
}
|
||||
|
||||
tuplesPerList = path->indexinfo->tuples * selectivity / (double) lists;
|
||||
if (tuplesPerList == 0)
|
||||
return lists;
|
||||
|
||||
/* No need to cap at this point */
|
||||
return root->limit_tuples / tuplesPerList;
|
||||
}
|
||||
|
||||
/*
|
||||
* Estimate the cost of an index scan
|
||||
*/
|
||||
@@ -101,7 +68,6 @@ ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
||||
{
|
||||
GenericCosts costs;
|
||||
int lists;
|
||||
int probes;
|
||||
double ratio;
|
||||
double spc_seq_page_cost;
|
||||
Relation index;
|
||||
@@ -123,10 +89,8 @@ ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
||||
IvfflatGetMetaPageInfo(index, &lists, NULL);
|
||||
index_close(index, NoLock);
|
||||
|
||||
probes = Max(ivfflat_probes, EstimateProbes(root, path, lists));
|
||||
|
||||
/* Get the ratio of lists that we need to visit */
|
||||
ratio = ((double) probes) / lists;
|
||||
ratio = ((double) ivfflat_probes) / lists;
|
||||
if (ratio > 1.0)
|
||||
ratio = 1.0;
|
||||
|
||||
|
||||
@@ -253,9 +253,8 @@ typedef struct IvfflatScanOpaqueData
|
||||
/* Sorting */
|
||||
Tuplesortstate *sortstate;
|
||||
TupleDesc tupdesc;
|
||||
TupleTableSlot *vslot;
|
||||
TupleTableSlot *mslot;
|
||||
BufferAccessStrategy bas;
|
||||
TupleTableSlot *slot;
|
||||
bool isnull;
|
||||
|
||||
/* Support functions */
|
||||
FmgrInfo *procinfo;
|
||||
|
||||
@@ -15,19 +15,16 @@
|
||||
#include "utils/memutils.h"
|
||||
#endif
|
||||
|
||||
#define GetScanList(ptr) pairingheap_container(IvfflatScanList, ph_node, ptr)
|
||||
#define GetScanListConst(ptr) pairingheap_const_container(IvfflatScanList, ph_node, ptr)
|
||||
|
||||
/*
|
||||
* Compare list distances
|
||||
*/
|
||||
static int
|
||||
CompareLists(const pairingheap_node *a, const pairingheap_node *b, void *arg)
|
||||
{
|
||||
if (GetScanListConst(a)->distance > GetScanListConst(b)->distance)
|
||||
if (((const IvfflatScanList *) a)->distance > ((const IvfflatScanList *) b)->distance)
|
||||
return 1;
|
||||
|
||||
if (GetScanListConst(a)->distance < GetScanListConst(b)->distance)
|
||||
if (((const IvfflatScanList *) a)->distance < ((const IvfflatScanList *) b)->distance)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
@@ -79,14 +76,14 @@ GetScanLists(IndexScanDesc scan, Datum value)
|
||||
|
||||
/* Calculate max distance */
|
||||
if (listCount == so->probes)
|
||||
maxDistance = GetScanList(pairingheap_first(so->listQueue))->distance;
|
||||
maxDistance = ((IvfflatScanList *) pairingheap_first(so->listQueue))->distance;
|
||||
}
|
||||
else if (distance < maxDistance)
|
||||
{
|
||||
IvfflatScanList *scanlist;
|
||||
|
||||
/* Remove */
|
||||
scanlist = GetScanList(pairingheap_remove_first(so->listQueue));
|
||||
scanlist = (IvfflatScanList *) pairingheap_remove_first(so->listQueue);
|
||||
|
||||
/* Reuse */
|
||||
scanlist->startPage = list->startPage;
|
||||
@@ -94,7 +91,7 @@ GetScanLists(IndexScanDesc scan, Datum value)
|
||||
pairingheap_add(so->listQueue, &scanlist->ph_node);
|
||||
|
||||
/* Update max distance */
|
||||
maxDistance = GetScanList(pairingheap_first(so->listQueue))->distance;
|
||||
maxDistance = ((IvfflatScanList *) pairingheap_first(so->listQueue))->distance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,12 +110,19 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
||||
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
||||
TupleDesc tupdesc = RelationGetDescr(scan->indexRelation);
|
||||
double tuples = 0;
|
||||
TupleTableSlot *slot = so->vslot;
|
||||
TupleTableSlot *slot = MakeSingleTupleTableSlot(so->tupdesc, &TTSOpsVirtual);
|
||||
|
||||
/*
|
||||
* Reuse same set of shared buffers for scan
|
||||
*
|
||||
* See postgres/src/backend/storage/buffer/README for description
|
||||
*/
|
||||
BufferAccessStrategy bas = GetAccessStrategy(BAS_BULKREAD);
|
||||
|
||||
/* Search closest probes lists */
|
||||
while (!pairingheap_is_empty(so->listQueue))
|
||||
{
|
||||
BlockNumber searchPage = GetScanList(pairingheap_remove_first(so->listQueue))->startPage;
|
||||
BlockNumber searchPage = ((IvfflatScanList *) pairingheap_remove_first(so->listQueue))->startPage;
|
||||
|
||||
/* Search all entry pages for list */
|
||||
while (BlockNumberIsValid(searchPage))
|
||||
@@ -127,7 +131,7 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
||||
Page page;
|
||||
OffsetNumber maxoffno;
|
||||
|
||||
buf = ReadBufferExtended(scan->indexRelation, MAIN_FORKNUM, searchPage, RBM_NORMAL, so->bas);
|
||||
buf = ReadBufferExtended(scan->indexRelation, MAIN_FORKNUM, searchPage, RBM_NORMAL, bas);
|
||||
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
||||
page = BufferGetPage(buf);
|
||||
maxoffno = PageGetMaxOffsetNumber(page);
|
||||
@@ -166,6 +170,8 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
||||
}
|
||||
}
|
||||
|
||||
FreeAccessStrategy(bas);
|
||||
|
||||
if (tuples < 100)
|
||||
ereport(DEBUG1,
|
||||
(errmsg("index scan found few tuples"),
|
||||
@@ -268,16 +274,7 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
||||
/* Prep sort */
|
||||
so->sortstate = InitScanSortState(so->tupdesc);
|
||||
|
||||
/* Need separate slots for puttuple and gettuple */
|
||||
so->vslot = MakeSingleTupleTableSlot(so->tupdesc, &TTSOpsVirtual);
|
||||
so->mslot = MakeSingleTupleTableSlot(so->tupdesc, &TTSOpsMinimalTuple);
|
||||
|
||||
/*
|
||||
* Reuse same set of shared buffers for scan
|
||||
*
|
||||
* See postgres/src/backend/storage/buffer/README for description
|
||||
*/
|
||||
so->bas = GetAccessStrategy(BAS_BULKREAD);
|
||||
so->slot = MakeSingleTupleTableSlot(so->tupdesc, &TTSOpsMinimalTuple);
|
||||
|
||||
so->listQueue = pairingheap_allocate(CompareLists, scan);
|
||||
|
||||
@@ -351,10 +348,9 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
|
||||
pfree(DatumGetPointer(value));
|
||||
}
|
||||
|
||||
if (tuplesort_gettupleslot(so->sortstate, true, false, so->mslot, NULL))
|
||||
if (tuplesort_gettupleslot(so->sortstate, true, false, so->slot, NULL))
|
||||
{
|
||||
bool isnull;
|
||||
ItemPointer heaptid = (ItemPointer) DatumGetPointer(slot_getattr(so->mslot, 2, &isnull));
|
||||
ItemPointer heaptid = (ItemPointer) DatumGetPointer(slot_getattr(so->slot, 2, &so->isnull));
|
||||
|
||||
scan->xs_heaptid = *heaptid;
|
||||
scan->xs_recheck = false;
|
||||
@@ -375,10 +371,6 @@ ivfflatendscan(IndexScanDesc scan)
|
||||
|
||||
pairingheap_free(so->listQueue);
|
||||
tuplesort_end(so->sortstate);
|
||||
FreeAccessStrategy(so->bas);
|
||||
FreeTupleDesc(so->tupdesc);
|
||||
|
||||
/* TODO Free vslot and mslot without freeing TupleDesc */
|
||||
|
||||
pfree(so);
|
||||
scan->opaque = NULL;
|
||||
|
||||
@@ -5,7 +5,7 @@ use PostgreSQL::Test::Utils;
|
||||
use Test::More;
|
||||
|
||||
my $dim = 3;
|
||||
my $nc = 100;
|
||||
my $nc = 50;
|
||||
my $limit = 20;
|
||||
|
||||
my $array_sql = join(",", ('random()') x $dim);
|
||||
@@ -102,13 +102,15 @@ $node->safe_psql("postgres", "CREATE INDEX attribute_idx ON tst (c);");
|
||||
$explain = $node->safe_psql("postgres", qq(
|
||||
EXPLAIN ANALYZE SELECT i FROM tst WHERE c = $c ORDER BY v <-> '$query' LIMIT $limit;
|
||||
));
|
||||
like($explain, qr/Index Scan on attribute_idx/);
|
||||
# TODO Use attribute index
|
||||
like($explain, qr/Index Scan using idx/);
|
||||
|
||||
# Test partial index
|
||||
$node->safe_psql("postgres", "CREATE INDEX partial_idx ON tst USING ivfflat (v vector_l2_ops) WITH (lists = 5) WHERE (c = $c);");
|
||||
$explain = $node->safe_psql("postgres", qq(
|
||||
EXPLAIN ANALYZE SELECT i FROM tst WHERE c = $c ORDER BY v <-> '$query' LIMIT $limit;
|
||||
));
|
||||
like($explain, qr/Index Scan using partial_idx/);
|
||||
# TODO Use partial index
|
||||
like($explain, qr/Index Scan using idx/);
|
||||
|
||||
done_testing();
|
||||
|
||||
Reference in New Issue
Block a user