Compare commits

..

1 Commits

Author SHA1 Message Date
Andrew Kane
a3c1eab27a Use pairing heap [skip ci] 2023-04-29 12:02:54 -07:00
15 changed files with 110 additions and 106 deletions

View File

@@ -1,7 +1,6 @@
## 0.4.2 (2023-05-13) ## 0.4.2 (unreleased)
- Added notice when index created with little data - Added notice when index created with little data
- Fixed dimensions check for some direct function calls
- Fixed installation error with Postgres 12.0-12.2 - Fixed installation error with Postgres 12.0-12.2
## 0.4.1 (2023-03-21) ## 0.4.1 (2023-03-21)

View File

@@ -2,7 +2,7 @@
"name": "vector", "name": "vector",
"abstract": "Open-source vector similarity search for Postgres", "abstract": "Open-source vector similarity search for Postgres",
"description": "Supports L2 distance, inner product, and cosine distance", "description": "Supports L2 distance, inner product, and cosine distance",
"version": "0.4.2", "version": "0.4.1",
"maintainer": [ "maintainer": [
"Andrew Kane <andrew@ankane.org>" "Andrew Kane <andrew@ankane.org>"
], ],
@@ -20,7 +20,7 @@
"vector": { "vector": {
"file": "sql/vector.sql", "file": "sql/vector.sql",
"docfile": "README.md", "docfile": "README.md",
"version": "0.4.2", "version": "0.4.1",
"abstract": "Open-source vector similarity search for Postgres" "abstract": "Open-source vector similarity search for Postgres"
} }
}, },

View File

@@ -1,5 +1,5 @@
EXTENSION = vector EXTENSION = vector
EXTVERSION = 0.4.2 EXTVERSION = 0.4.1
MODULE_big = vector MODULE_big = vector
DATA = $(wildcard sql/*--*.sql) DATA = $(wildcard sql/*--*.sql)
@@ -63,9 +63,3 @@ dist:
docker: docker:
docker build --pull --no-cache --platform linux/amd64 -t ankane/pgvector:latest . docker build --pull --no-cache --platform linux/amd64 -t ankane/pgvector:latest .
.PHONY: docker-release
docker-release:
docker buildx build --push --pull --no-cache --platform linux/amd64,linux/arm64 -t ankane/pgvector:latest .
docker buildx build --push --platform linux/amd64,linux/arm64 -t ankane/pgvector:v$(EXTVERSION) .

View File

@@ -1,5 +1,5 @@
EXTENSION = vector EXTENSION = vector
EXTVERSION = 0.4.2 EXTVERSION = 0.4.1
OBJS = src\ivfbuild.obj src\ivfflat.obj src\ivfinsert.obj src\ivfkmeans.obj src\ivfscan.obj src\ivfutils.obj src\ivfvacuum.obj src\vector.obj OBJS = src\ivfbuild.obj src\ivfflat.obj src\ivfinsert.obj src\ivfkmeans.obj src\ivfscan.obj src\ivfutils.obj src\ivfvacuum.obj src\vector.obj

View File

@@ -16,7 +16,7 @@ Compile and install the extension (supports Postgres 11+)
```sh ```sh
cd /tmp cd /tmp
git clone --branch v0.4.2 https://github.com/pgvector/pgvector.git git clone --branch v0.4.1 https://github.com/pgvector/pgvector.git
cd pgvector cd pgvector
make make
make install # may need sudo make install # may need sudo
@@ -307,11 +307,10 @@ Yes, pgvector uses the write-ahead log (WAL), which allows for replication and p
#### What if I want to index vectors with more than 2,000 dimensions? #### What if I want to index vectors with more than 2,000 dimensions?
Youll need to use [dimensionality reduction](https://en.wikipedia.org/wiki/Dimensionality_reduction) at the moment. Two things you can try are:
#### Why am I seeing less results after adding an index? 1. use dimensionality reduction
2. compile Postgres with a larger block size (`./configure --with-blocksize=32`) and edit the limit in `src/ivfflat.h`
The index was likely created with too little data for the number of lists. Drop the index until the table has more data.
## Reference ## Reference
@@ -355,11 +354,7 @@ If your machine has multiple Postgres installations, specify the path to [pg_con
export PG_CONFIG=/Applications/Postgres.app/Contents/Versions/latest/bin/pg_config export PG_CONFIG=/Applications/Postgres.app/Contents/Versions/latest/bin/pg_config
``` ```
Then re-run the installation instructions (run `make clean` before `make` if needed). If `sudo` is needed for `make install`, use: Then re-run the installation instructions (run `make clean` before `make` if needed)
```sh
sudo --preserve-env=PG_CONFIG make install
```
### Missing Header ### Missing Header
@@ -379,7 +374,7 @@ Support for Windows is currently experimental. Use `nmake` to build:
```cmd ```cmd
set "PGROOT=C:\Program Files\PostgreSQL\15" set "PGROOT=C:\Program Files\PostgreSQL\15"
git clone --branch v0.4.2 https://github.com/pgvector/pgvector.git git clone --branch v0.4.1 https://github.com/pgvector/pgvector.git
cd pgvector cd pgvector
nmake /F Makefile.win nmake /F Makefile.win
nmake /F Makefile.win install nmake /F Makefile.win install
@@ -400,7 +395,7 @@ This adds pgvector to the [Postgres image](https://hub.docker.com/_/postgres) (r
You can also build the image manually: You can also build the image manually:
```sh ```sh
git clone --branch v0.4.2 https://github.com/pgvector/pgvector.git git clone --branch v0.4.1 https://github.com/pgvector/pgvector.git
cd pgvector cd pgvector
docker build -t pgvector . docker build -t pgvector .
``` ```
@@ -413,8 +408,6 @@ With Homebrew Postgres, you can use:
brew install pgvector brew install pgvector
``` ```
Note: This only adds it to the `postgresql@14` formula
### PGXN ### PGXN
Install from the [PostgreSQL Extension Network](https://pgxn.org/dist/vector) with: Install from the [PostgreSQL Extension Network](https://pgxn.org/dist/vector) with:
@@ -451,9 +444,10 @@ pgvector is available on [these providers](https://github.com/pgvector/pgvector/
To request a new extension on other providers: To request a new extension on other providers:
- Amazon RDS - follow the instructions on [this page](https://aws.amazon.com/rds/postgresql/faqs/)
- Google Cloud SQL - vote or comment on [this page](https://issuetracker.google.com/issues/265172065) - 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) - 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/managed-database/p/pgvector-extension-for-postgresql) - 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) - Heroku Postgres - vote or comment on [this page](https://github.com/heroku/roadmap/issues/156)
## Upgrading ## Upgrading

View File

@@ -1,2 +0,0 @@
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "ALTER EXTENSION vector UPDATE TO '0.4.2'" to load this file. \quit

View File

@@ -438,8 +438,8 @@ ComputeCenters(IvfflatBuildState * buildstate)
{ {
ereport(NOTICE, ereport(NOTICE,
(errmsg("ivfflat index created with little data"), (errmsg("ivfflat index created with little data"),
errdetail("This will cause low recall."), errdetail("this will cause poor recall"),
errhint("Drop the index until the table has more data."))); errhint("drop the index until the table has more data")));
} }
} }

View File

@@ -187,6 +187,14 @@ typedef struct IvfflatScanList
double distance; double distance;
} IvfflatScanList; } IvfflatScanList;
typedef struct IvfflatScanItem
{
pairingheap_node ph_node;
BlockNumber searchPage;
double distance;
ItemPointerData tid;
} IvfflatScanItem;
typedef struct IvfflatScanOpaqueData typedef struct IvfflatScanOpaqueData
{ {
int probes; int probes;
@@ -204,6 +212,13 @@ typedef struct IvfflatScanOpaqueData
FmgrInfo *normprocinfo; FmgrInfo *normprocinfo;
Oid collation; Oid collation;
/* Items */
int maxItems;
int itemCount;
pairingheap *itemQueue;
IvfflatScanItem *items;
IvfflatScanItem **sortedItems;
/* Lists */ /* Lists */
pairingheap *listQueue; pairingheap *listQueue;
IvfflatScanList lists[FLEXIBLE_ARRAY_MEMBER]; /* must come last */ IvfflatScanList lists[FLEXIBLE_ARRAY_MEMBER]; /* must come last */

View File

@@ -26,6 +26,21 @@ CompareLists(const pairingheap_node *a, const pairingheap_node *b, void *arg)
return 0; 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 * Get lists and sort by distance
*/ */
@@ -111,13 +126,10 @@ GetScanItems(IndexScanDesc scan, Datum value)
Datum datum; Datum datum;
bool isnull; bool isnull;
TupleDesc tupdesc = RelationGetDescr(scan->indexRelation); TupleDesc tupdesc = RelationGetDescr(scan->indexRelation);
double tuples = 0; int i;
double distance;
#if PG_VERSION_NUM >= 120000 IvfflatScanItem *scanitem;
TupleTableSlot *slot = MakeSingleTupleTableSlot(so->tupdesc, &TTSOpsVirtual); double maxDistance = DBL_MAX;
#else
TupleTableSlot *slot = MakeSingleTupleTableSlot(so->tupdesc);
#endif
/* /*
* Reuse same set of shared buffers for scan * Reuse same set of shared buffers for scan
@@ -143,25 +155,40 @@ GetScanItems(IndexScanDesc scan, Datum value)
{ {
itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offno)); itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offno));
datum = index_getattr(itup, 1, tupdesc, &isnull); datum = index_getattr(itup, 1, tupdesc, &isnull);
distance = DatumGetFloat8(FunctionCall2Coll(so->procinfo, so->collation, datum, value));
/* if (so->itemCount < so->maxItems)
* Add virtual tuple {
* scanitem = &so->items[so->itemCount];
* Use procinfo from the index instead of scan key for scanitem->searchPage = searchPage;
* performance scanitem->tid = itup->t_tid;
*/ scanitem->distance = distance;
ExecClearTuple(slot); so->itemCount++;
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);
tuplesort_puttupleslot(so->sortstate, slot); /* Add to heap */
pairingheap_add(so->itemQueue, &scanitem->ph_node);
tuples++; /* 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;
}
} }
searchPage = IvfflatPageGetOpaque(page)->nextblkno; searchPage = IvfflatPageGetOpaque(page)->nextblkno;
@@ -170,14 +197,10 @@ GetScanItems(IndexScanDesc scan, Datum value)
} }
} }
/* TODO Scan more lists */ for (i = 0; i < so->itemCount; i++)
if (tuples < 100) so->sortedItems[i] = (IvfflatScanItem *) pairingheap_remove_first(so->itemQueue);
ereport(DEBUG1,
(errmsg("index scan found few tuples"),
errdetail("Index may have been created with little data."),
errhint("Recreate the index and possibly decrease lists.")));
tuplesort_performsort(so->sortstate); Assert(pairingheap_is_empty(so->itemQueue));
} }
/* /*
@@ -189,10 +212,6 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
IndexScanDesc scan; IndexScanDesc scan;
IvfflatScanOpaque so; IvfflatScanOpaque so;
int lists; int lists;
AttrNumber attNums[] = {1};
Oid sortOperators[] = {Float8LessOperator};
Oid sortCollations[] = {InvalidOid};
bool nullsFirstFlags[] = {false};
int probes = ivfflat_probes; int probes = ivfflat_probes;
scan = RelationGetIndexScan(index, nkeys, norderbys); scan = RelationGetIndexScan(index, nkeys, norderbys);
@@ -211,27 +230,14 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
so->normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC); so->normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC);
so->collation = index->rd_indcollation[0]; so->collation = index->rd_indcollation[0];
/* 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);
/* 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); so->listQueue = pairingheap_allocate(CompareLists, scan);
so->maxItems = 1024;
so->itemCount = 0;
so->itemQueue = pairingheap_allocate(CompareItems, scan);
so->items = palloc(sizeof(IvfflatScanItem) * (so->maxItems + 1));
so->sortedItems = palloc(sizeof(IvfflatScanItem *) * so->maxItems);
scan->opaque = so; scan->opaque = so;
return scan; return scan;
@@ -245,13 +251,10 @@ ivfflatrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int
{ {
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque; IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
#if PG_VERSION_NUM >= 130000
if (!so->first)
tuplesort_reset(so->sortstate);
#endif
so->first = true; so->first = true;
pairingheap_reset(so->listQueue); pairingheap_reset(so->listQueue);
pairingheap_reset(so->itemQueue);
so->itemCount = 0;
if (keys && scan->numberOfKeys > 0) if (keys && scan->numberOfKeys > 0)
memmove(scan->keyData, keys, scan->numberOfKeys * sizeof(ScanKeyData)); memmove(scan->keyData, keys, scan->numberOfKeys * sizeof(ScanKeyData));
@@ -311,15 +314,18 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
pfree(DatumGetPointer(value)); pfree(DatumGetPointer(value));
} }
if (tuplesort_gettupleslot(so->sortstate, true, false, so->slot, NULL)) if (so->itemCount > 0)
{ {
ItemPointer tid = (ItemPointer) DatumGetPointer(slot_getattr(so->slot, 2, &so->isnull)); IvfflatScanItem *scanitem;
BlockNumber indexblkno = DatumGetInt32(slot_getattr(so->slot, 3, &so->isnull));
so->itemCount--;
scanitem = so->sortedItems[so->itemCount];
#if PG_VERSION_NUM >= 120000 #if PG_VERSION_NUM >= 120000
scan->xs_heaptid = *tid; scan->xs_heaptid = scanitem->tid;
#else #else
scan->xs_ctup.t_self = *tid; scan->xs_ctup.t_self = scanitem->tid;
#endif #endif
if (BufferIsValid(so->buf)) if (BufferIsValid(so->buf))
@@ -331,7 +337,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, indexblkno); so->buf = ReadBuffer(scan->indexRelation, scanitem->searchPage);
scan->xs_recheckorderby = false; scan->xs_recheckorderby = false;
return true; return true;
@@ -353,7 +359,10 @@ ivfflatendscan(IndexScanDesc scan)
ReleaseBuffer(so->buf); ReleaseBuffer(so->buf);
pairingheap_free(so->listQueue); pairingheap_free(so->listQueue);
tuplesort_end(so->sortstate);
pairingheap_free(so->itemQueue);
pfree(so->items);
pfree(so->sortedItems);
pfree(so); pfree(so);
scan->opaque = NULL; scan->opaque = NULL;

View File

@@ -396,8 +396,10 @@ array_to_vector(PG_FUNCTION_ARGS)
get_typlenbyvalalign(ARR_ELEMTYPE(array), &typlen, &typbyval, &typalign); get_typlenbyvalalign(ARR_ELEMTYPE(array), &typlen, &typbyval, &typalign);
deconstruct_array(array, ARR_ELEMTYPE(array), typlen, typbyval, typalign, &elemsp, &nullsp, &nelemsp); deconstruct_array(array, ARR_ELEMTYPE(array), typlen, typbyval, typalign, &elemsp, &nullsp, &nelemsp);
CheckDim(nelemsp); if (typmod == -1)
CheckExpectedDim(typmod, nelemsp); CheckDim(nelemsp);
else
CheckExpectedDim(typmod, nelemsp);
result = InitVector(nelemsp); result = InitVector(nelemsp);
for (i = 0; i < nelemsp; i++) for (i = 0; i < nelemsp; i++)
@@ -950,7 +952,6 @@ vector_avg(PG_FUNCTION_ARGS)
/* Create vector */ /* Create vector */
dim = STATE_DIMS(statearray); dim = STATE_DIMS(statearray);
CheckDim(dim);
result = InitVector(dim); result = InitVector(dim);
for (int i = 0; i < dim; i++) for (int i = 0; i < dim; i++)
{ {

View File

@@ -46,8 +46,6 @@ SELECT '[1,2,3]'::vector::real[];
SELECT array_agg(n)::vector FROM generate_series(1, 16001) n; SELECT array_agg(n)::vector FROM generate_series(1, 16001) n;
ERROR: vector cannot have more than 16000 dimensions ERROR: vector cannot have more than 16000 dimensions
SELECT array_to_vector(array_agg(n), 16001, false) FROM generate_series(1, 16001) n;
ERROR: vector cannot have more than 16000 dimensions
-- ensure no error -- ensure no error
SELECT ARRAY[1,2,3] = ARRAY[1,2,3]; SELECT ARRAY[1,2,3] = ARRAY[1,2,3];
?column? ?column?

View File

@@ -102,5 +102,3 @@ SELECT avg(v) FROM unnest(ARRAY[]::vector[]) v;
SELECT avg(v) FROM unnest(ARRAY['[1,2]'::vector, '[3]']) v; SELECT avg(v) FROM unnest(ARRAY['[1,2]'::vector, '[3]']) v;
ERROR: expected 2 dimensions, not 1 ERROR: expected 2 dimensions, not 1
SELECT vector_avg(array_agg(n)) FROM generate_series(1, 16002) n;
ERROR: vector cannot have more than 16000 dimensions

View File

@@ -10,7 +10,6 @@ SELECT '{-Infinity}'::real[]::vector;
SELECT '{}'::real[]::vector; SELECT '{}'::real[]::vector;
SELECT '[1,2,3]'::vector::real[]; SELECT '[1,2,3]'::vector::real[];
SELECT array_agg(n)::vector FROM generate_series(1, 16001) n; SELECT array_agg(n)::vector FROM generate_series(1, 16001) n;
SELECT array_to_vector(array_agg(n), 16001, false) FROM generate_series(1, 16001) n;
-- ensure no error -- ensure no error
SELECT ARRAY[1,2,3] = ARRAY[1,2,3]; SELECT ARRAY[1,2,3] = ARRAY[1,2,3];

View File

@@ -24,4 +24,3 @@ SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]']) v;
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]', NULL]) v; SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]', NULL]) v;
SELECT avg(v) FROM unnest(ARRAY[]::vector[]) v; SELECT avg(v) FROM unnest(ARRAY[]::vector[]) v;
SELECT avg(v) FROM unnest(ARRAY['[1,2]'::vector, '[3]']) v; SELECT avg(v) FROM unnest(ARRAY['[1,2]'::vector, '[3]']) v;
SELECT vector_avg(array_agg(n)) FROM generate_series(1, 16002) n;

View File

@@ -1,4 +1,4 @@
comment = 'vector data type and ivfflat access method' comment = 'vector data type and ivfflat access method'
default_version = '0.4.2' default_version = '0.4.1'
module_pathname = '$libdir/vector' module_pathname = '$libdir/vector'
relocatable = true relocatable = true