mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-22 20:15:46 +08:00
Compare commits
1 Commits
pairinghea
...
bas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb72a6c063 |
@@ -1,7 +1,6 @@
|
||||
## 0.4.2 (unreleased)
|
||||
|
||||
- Added notice when index created with little data
|
||||
- Fixed installation error with Postgres 12.0-12.2
|
||||
|
||||
## 0.4.1 (2023-03-21)
|
||||
|
||||
|
||||
21
README.md
21
README.md
@@ -2,11 +2,7 @@
|
||||
|
||||
Open-source vector similarity search for Postgres
|
||||
|
||||
Supports
|
||||
|
||||
- exact and approximate nearest neighbor search
|
||||
- L2 distance, inner product, and cosine distance
|
||||
- any [language](#languages) with a Postgres client
|
||||
Supports exact and approximate nearest neighbor search for L2 distance, inner product, and cosine distance
|
||||
|
||||
[](https://github.com/pgvector/pgvector/actions)
|
||||
|
||||
@@ -40,7 +36,7 @@ Create a vector column with 3 dimensions
|
||||
CREATE TABLE items (id bigserial PRIMARY KEY, embedding vector(3));
|
||||
```
|
||||
|
||||
Insert vectors
|
||||
Insert values
|
||||
|
||||
```sql
|
||||
INSERT INTO items (embedding) VALUES ('[1,2,3]'), ('[4,5,6]');
|
||||
@@ -128,7 +124,7 @@ SELECT embedding <-> '[3,1,2]' AS distance FROM items;
|
||||
For inner product, multiply by -1 (since `<#>` returns the negative inner product)
|
||||
|
||||
```sql
|
||||
SELECT (embedding <#> '[3,1,2]') * -1 AS inner_product FROM items;
|
||||
SELECT -1 * (embedding <#> '[3,1,2]') AS inner_product FROM items;
|
||||
```
|
||||
|
||||
For cosine similarity, use 1 - cosine distance
|
||||
@@ -137,7 +133,7 @@ For cosine similarity, use 1 - cosine distance
|
||||
SELECT 1 - (embedding <=> '[3,1,2]') AS cosine_similarity FROM items;
|
||||
```
|
||||
|
||||
#### Aggregates
|
||||
#### Averaging
|
||||
|
||||
Average vectors
|
||||
|
||||
@@ -160,8 +156,8 @@ You can add an index to use approximate nearest neighbor search, which trades so
|
||||
Three keys to achieving good recall are:
|
||||
|
||||
1. Create the index *after* the table has some data
|
||||
2. Choose an appropriate number of lists - a good place to start is `rows / 1000` for up to 1M rows and `sqrt(rows)` for over 1M rows
|
||||
3. When querying, specify an appropriate number of [probes](#query-options) (higher is better for recall, lower is better for speed) - a good place to start is `lists / 10` for up to 1M rows and `sqrt(lists)` for over 1M rows
|
||||
2. Choose an appropriate number of lists (a good place to start is `rows / 1000` for up to 1M rows and `sqrt(rows)` for over 1M rows)
|
||||
3. When querying, specify an appropriate number of [probes](#query-options) (higher is better for recall, lower is better for speed)
|
||||
|
||||
Add an index for each distance function you want to use.
|
||||
|
||||
@@ -279,10 +275,8 @@ Language | Libraries / Examples
|
||||
--- | ---
|
||||
C++ | [pgvector-cpp](https://github.com/pgvector/pgvector-cpp)
|
||||
C# | [pgvector-dotnet](https://github.com/pgvector/pgvector-dotnet)
|
||||
Crystal | [pgvector-crystal](https://github.com/pgvector/pgvector-crystal)
|
||||
Elixir | [pgvector-elixir](https://github.com/pgvector/pgvector-elixir)
|
||||
Go | [pgvector-go](https://github.com/pgvector/pgvector-go)
|
||||
Haskell | [pgvector-haskell](https://github.com/pgvector/pgvector-haskell)
|
||||
Java, Scala | [pgvector-java](https://github.com/pgvector/pgvector-java)
|
||||
Julia | [pgvector-julia](https://github.com/pgvector/pgvector-julia)
|
||||
Lua | [pgvector-lua](https://github.com/pgvector/pgvector-lua)
|
||||
@@ -293,7 +287,6 @@ Python | [pgvector-python](https://github.com/pgvector/pgvector-python)
|
||||
R | [pgvector-r](https://github.com/pgvector/pgvector-r)
|
||||
Ruby | [pgvector-ruby](https://github.com/pgvector/pgvector-ruby), [Neighbor](https://github.com/ankane/neighbor)
|
||||
Rust | [pgvector-rust](https://github.com/pgvector/pgvector-rust)
|
||||
Swift | [pgvector-swift](https://github.com/pgvector/pgvector-swift)
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
@@ -448,7 +441,7 @@ To request a new extension on other providers:
|
||||
- Google Cloud SQL - vote or comment on [this page](https://issuetracker.google.com/issues/265172065)
|
||||
- Azure Database - vote or comment on [this page](https://feedback.azure.com/d365community/idea/7b423322-6189-ed11-a81b-000d3ae49307)
|
||||
- DigitalOcean Managed Databases - vote or comment on [this page](https://ideas.digitalocean.com/app-framework-services/p/pgvector-extension-for-postgresql)
|
||||
- Heroku Postgres - vote or comment on [this page](https://github.com/heroku/roadmap/issues/156)
|
||||
- Render - vote or comment on [this page](https://feedback.render.com/features/p/add-pgvector-extension-to-postgresql)
|
||||
|
||||
## Upgrading
|
||||
|
||||
|
||||
@@ -187,14 +187,6 @@ typedef struct IvfflatScanList
|
||||
double distance;
|
||||
} IvfflatScanList;
|
||||
|
||||
typedef struct IvfflatScanItem
|
||||
{
|
||||
pairingheap_node ph_node;
|
||||
BlockNumber searchPage;
|
||||
double distance;
|
||||
ItemPointerData tid;
|
||||
} IvfflatScanItem;
|
||||
|
||||
typedef struct IvfflatScanOpaqueData
|
||||
{
|
||||
int probes;
|
||||
@@ -212,13 +204,6 @@ typedef struct IvfflatScanOpaqueData
|
||||
FmgrInfo *normprocinfo;
|
||||
Oid collation;
|
||||
|
||||
/* Items */
|
||||
int maxItems;
|
||||
int itemCount;
|
||||
pairingheap *itemQueue;
|
||||
IvfflatScanItem *items;
|
||||
IvfflatScanItem **sortedItems;
|
||||
|
||||
/* Lists */
|
||||
pairingheap *listQueue;
|
||||
IvfflatScanList lists[FLEXIBLE_ARRAY_MEMBER]; /* must come last */
|
||||
|
||||
141
src/ivfscan.c
141
src/ivfscan.c
@@ -26,21 +26,6 @@ CompareLists(const pairingheap_node *a, const pairingheap_node *b, void *arg)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Compare item distances
|
||||
*/
|
||||
static int
|
||||
CompareItems(const pairingheap_node *a, const pairingheap_node *b, void *arg)
|
||||
{
|
||||
if (((const IvfflatScanItem *) a)->distance > ((const IvfflatScanItem *) b)->distance)
|
||||
return 1;
|
||||
|
||||
if (((const IvfflatScanItem *) a)->distance < ((const IvfflatScanItem *) b)->distance)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get lists and sort by distance
|
||||
*/
|
||||
@@ -126,17 +111,20 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
||||
Datum datum;
|
||||
bool isnull;
|
||||
TupleDesc tupdesc = RelationGetDescr(scan->indexRelation);
|
||||
int i;
|
||||
double distance;
|
||||
IvfflatScanItem *scanitem;
|
||||
double maxDistance = DBL_MAX;
|
||||
double tuples = 0;
|
||||
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
TupleTableSlot *slot = MakeSingleTupleTableSlot(so->tupdesc, &TTSOpsVirtual);
|
||||
#else
|
||||
TupleTableSlot *slot = MakeSingleTupleTableSlot(so->tupdesc);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Reuse same set of shared buffers for scan
|
||||
*
|
||||
* See postgres/src/backend/storage/buffer/README for description
|
||||
*/
|
||||
BufferAccessStrategy bas = GetAccessStrategy(BAS_BULKREAD);
|
||||
BufferAccessStrategy bas = GetAccessStrategy(BAS_NORMAL);
|
||||
|
||||
/* Search closest probes lists */
|
||||
while (!pairingheap_is_empty(so->listQueue))
|
||||
@@ -155,40 +143,25 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
||||
{
|
||||
itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offno));
|
||||
datum = index_getattr(itup, 1, tupdesc, &isnull);
|
||||
distance = DatumGetFloat8(FunctionCall2Coll(so->procinfo, so->collation, datum, value));
|
||||
|
||||
if (so->itemCount < so->maxItems)
|
||||
{
|
||||
scanitem = &so->items[so->itemCount];
|
||||
scanitem->searchPage = searchPage;
|
||||
scanitem->tid = itup->t_tid;
|
||||
scanitem->distance = distance;
|
||||
so->itemCount++;
|
||||
/*
|
||||
* Add virtual tuple
|
||||
*
|
||||
* Use procinfo from the index instead of scan key for
|
||||
* performance
|
||||
*/
|
||||
ExecClearTuple(slot);
|
||||
slot->tts_values[0] = FunctionCall2Coll(so->procinfo, so->collation, datum, value);
|
||||
slot->tts_isnull[0] = false;
|
||||
slot->tts_values[1] = PointerGetDatum(&itup->t_tid);
|
||||
slot->tts_isnull[1] = false;
|
||||
slot->tts_values[2] = Int32GetDatum((int) searchPage);
|
||||
slot->tts_isnull[2] = false;
|
||||
ExecStoreVirtualTuple(slot);
|
||||
|
||||
/* Add to heap */
|
||||
pairingheap_add(so->itemQueue, &scanitem->ph_node);
|
||||
tuplesort_puttupleslot(so->sortstate, slot);
|
||||
|
||||
/* Calculate max distance */
|
||||
if (so->itemCount == so->maxItems)
|
||||
{
|
||||
maxDistance = ((IvfflatScanItem *) pairingheap_first(so->itemQueue))->distance;
|
||||
scanitem = &so->items[so->itemCount];
|
||||
}
|
||||
}
|
||||
else if (distance < maxDistance)
|
||||
{
|
||||
/* Reuse */
|
||||
scanitem->searchPage = searchPage;
|
||||
scanitem->tid = itup->t_tid;
|
||||
scanitem->distance = distance;
|
||||
pairingheap_add(so->itemQueue, &scanitem->ph_node);
|
||||
|
||||
/* Remove */
|
||||
scanitem = (IvfflatScanItem *) pairingheap_remove_first(so->itemQueue);
|
||||
|
||||
/* Update max distance */
|
||||
maxDistance = ((IvfflatScanItem *) pairingheap_first(so->itemQueue))->distance;
|
||||
}
|
||||
tuples++;
|
||||
}
|
||||
|
||||
searchPage = IvfflatPageGetOpaque(page)->nextblkno;
|
||||
@@ -197,10 +170,14 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < so->itemCount; i++)
|
||||
so->sortedItems[i] = (IvfflatScanItem *) pairingheap_remove_first(so->itemQueue);
|
||||
/* TODO Scan more lists */
|
||||
if (tuples < 100)
|
||||
ereport(DEBUG1,
|
||||
(errmsg("index scan found few tuples"),
|
||||
errdetail("index may have been created without data or lists is too high"),
|
||||
errhint("recreate the index and possibly decrease lists")));
|
||||
|
||||
Assert(pairingheap_is_empty(so->itemQueue));
|
||||
tuplesort_performsort(so->sortstate);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -212,6 +189,10 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
||||
IndexScanDesc scan;
|
||||
IvfflatScanOpaque so;
|
||||
int lists;
|
||||
AttrNumber attNums[] = {1};
|
||||
Oid sortOperators[] = {Float8LessOperator};
|
||||
Oid sortCollations[] = {InvalidOid};
|
||||
bool nullsFirstFlags[] = {false};
|
||||
int probes = ivfflat_probes;
|
||||
|
||||
scan = RelationGetIndexScan(index, nkeys, norderbys);
|
||||
@@ -230,13 +211,26 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
||||
so->normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC);
|
||||
so->collation = index->rd_indcollation[0];
|
||||
|
||||
so->listQueue = pairingheap_allocate(CompareLists, scan);
|
||||
/* Create tuple description for sorting */
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
so->tupdesc = CreateTemplateTupleDesc(3);
|
||||
#else
|
||||
so->tupdesc = CreateTemplateTupleDesc(3, false);
|
||||
#endif
|
||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 1, "distance", FLOAT8OID, -1, 0);
|
||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0);
|
||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 3, "indexblkno", INT4OID, -1, 0);
|
||||
|
||||
so->maxItems = 10;
|
||||
so->itemCount = 0;
|
||||
so->itemQueue = pairingheap_allocate(CompareItems, scan);
|
||||
so->items = palloc(sizeof(IvfflatScanItem) * (so->maxItems + 1));
|
||||
so->sortedItems = palloc(sizeof(IvfflatScanItem *) * so->maxItems);
|
||||
/* Prep sort */
|
||||
so->sortstate = tuplesort_begin_heap(so->tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, work_mem, NULL, false);
|
||||
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
so->slot = MakeSingleTupleTableSlot(so->tupdesc, &TTSOpsMinimalTuple);
|
||||
#else
|
||||
so->slot = MakeSingleTupleTableSlot(so->tupdesc);
|
||||
#endif
|
||||
|
||||
so->listQueue = pairingheap_allocate(CompareLists, scan);
|
||||
|
||||
scan->opaque = so;
|
||||
|
||||
@@ -251,10 +245,13 @@ ivfflatrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int
|
||||
{
|
||||
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
||||
|
||||
#if PG_VERSION_NUM >= 130000
|
||||
if (!so->first)
|
||||
tuplesort_reset(so->sortstate);
|
||||
#endif
|
||||
|
||||
so->first = true;
|
||||
pairingheap_reset(so->listQueue);
|
||||
pairingheap_reset(so->itemQueue);
|
||||
so->itemCount = 0;
|
||||
|
||||
if (keys && scan->numberOfKeys > 0)
|
||||
memmove(scan->keyData, keys, scan->numberOfKeys * sizeof(ScanKeyData));
|
||||
@@ -314,18 +311,15 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
|
||||
pfree(DatumGetPointer(value));
|
||||
}
|
||||
|
||||
if (so->itemCount > 0)
|
||||
if (tuplesort_gettupleslot(so->sortstate, true, false, so->slot, NULL))
|
||||
{
|
||||
IvfflatScanItem *scanitem;
|
||||
|
||||
so->itemCount--;
|
||||
|
||||
scanitem = so->sortedItems[so->itemCount];
|
||||
ItemPointer tid = (ItemPointer) DatumGetPointer(slot_getattr(so->slot, 2, &so->isnull));
|
||||
BlockNumber indexblkno = DatumGetInt32(slot_getattr(so->slot, 3, &so->isnull));
|
||||
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
scan->xs_heaptid = scanitem->tid;
|
||||
scan->xs_heaptid = *tid;
|
||||
#else
|
||||
scan->xs_ctup.t_self = scanitem->tid;
|
||||
scan->xs_ctup.t_self = *tid;
|
||||
#endif
|
||||
|
||||
if (BufferIsValid(so->buf))
|
||||
@@ -337,7 +331,7 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
|
||||
*
|
||||
* https://www.postgresql.org/docs/current/index-locking.html
|
||||
*/
|
||||
so->buf = ReadBuffer(scan->indexRelation, scanitem->searchPage);
|
||||
so->buf = ReadBuffer(scan->indexRelation, indexblkno);
|
||||
|
||||
scan->xs_recheckorderby = false;
|
||||
return true;
|
||||
@@ -359,10 +353,7 @@ ivfflatendscan(IndexScanDesc scan)
|
||||
ReleaseBuffer(so->buf);
|
||||
|
||||
pairingheap_free(so->listQueue);
|
||||
|
||||
pairingheap_free(so->itemQueue);
|
||||
pfree(so->items);
|
||||
pfree(so->sortedItems);
|
||||
tuplesort_end(so->sortstate);
|
||||
|
||||
pfree(so);
|
||||
scan->opaque = NULL;
|
||||
|
||||
@@ -100,7 +100,7 @@ CheckStateArray(ArrayType *statearray, const char *caller)
|
||||
return (float8 *) ARR_DATA_PTR(statearray);
|
||||
}
|
||||
|
||||
#if PG_VERSION_NUM < 120003
|
||||
#if PG_VERSION_NUM < 120000
|
||||
static pg_noinline void
|
||||
float_overflow_error(void)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user