mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-23 04:20:56 +08:00
Compare commits
23 Commits
guc-explai
...
v0.8.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2627c5ff77 | ||
|
|
34b3cfdc43 | ||
|
|
cd218aae5a | ||
|
|
ba9367f86c | ||
|
|
9c20550a41 | ||
|
|
e3e74fe94e | ||
|
|
96a5a44632 | ||
|
|
67e1392a83 | ||
|
|
e530a1a026 | ||
|
|
6170e2645b | ||
|
|
e6bae175f1 | ||
|
|
52b777e04a | ||
|
|
307271214f | ||
|
|
6e9f74ddce | ||
|
|
258215ad97 | ||
|
|
fb87b6da91 | ||
|
|
a2a0b377f0 | ||
|
|
2f770307b8 | ||
|
|
c04e16ff5b | ||
|
|
bd4d272f26 | ||
|
|
8bb797cc2f | ||
|
|
fe6ec03dac | ||
|
|
c1161f8889 |
@@ -1,10 +1,10 @@
|
|||||||
## 0.8.0 (unreleased)
|
## 0.8.0 (2024-10-30)
|
||||||
|
|
||||||
- Added support for iterative index scans
|
- Added support for iterative index scans
|
||||||
- Added casts for arrays to `sparsevec`
|
- Added casts for arrays to `sparsevec`
|
||||||
- Improved cost estimation
|
- Improved cost estimation for better index selection when filtering
|
||||||
|
- Improved performance of HNSW index scans
|
||||||
- Improved performance of HNSW inserts and on-disk index builds
|
- Improved performance of HNSW inserts and on-disk index builds
|
||||||
- Reduced memory usage for HNSW index scans
|
|
||||||
- Dropped support for Postgres 12
|
- Dropped support for Postgres 12
|
||||||
|
|
||||||
## 0.7.4 (2024-08-05)
|
## 0.7.4 (2024-08-05)
|
||||||
|
|||||||
@@ -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.7.4",
|
"version": "0.8.0",
|
||||||
"maintainer": [
|
"maintainer": [
|
||||||
"Andrew Kane <andrew@ankane.org>"
|
"Andrew Kane <andrew@ankane.org>"
|
||||||
],
|
],
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"prereqs": {
|
"prereqs": {
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"requires": {
|
"requires": {
|
||||||
"PostgreSQL": "12.0.0"
|
"PostgreSQL": "13.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"vector": {
|
"vector": {
|
||||||
"file": "sql/vector.sql",
|
"file": "sql/vector.sql",
|
||||||
"docfile": "README.md",
|
"docfile": "README.md",
|
||||||
"version": "0.7.4",
|
"version": "0.8.0",
|
||||||
"abstract": "Open-source vector similarity search for Postgres"
|
"abstract": "Open-source vector similarity search for Postgres"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -1,5 +1,5 @@
|
|||||||
EXTENSION = vector
|
EXTENSION = vector
|
||||||
EXTVERSION = 0.7.4
|
EXTVERSION = 0.8.0
|
||||||
|
|
||||||
MODULE_big = vector
|
MODULE_big = vector
|
||||||
DATA = $(wildcard sql/*--*--*.sql)
|
DATA = $(wildcard sql/*--*--*.sql)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
EXTENSION = vector
|
EXTENSION = vector
|
||||||
EXTVERSION = 0.7.4
|
EXTVERSION = 0.8.0
|
||||||
|
|
||||||
DATA_built = sql\$(EXTENSION)--$(EXTVERSION).sql
|
DATA_built = sql\$(EXTENSION)--$(EXTVERSION).sql
|
||||||
OBJS = src\bitutils.obj src\bitvec.obj src\halfutils.obj src\halfvec.obj src\hnsw.obj src\hnswbuild.obj src\hnswinsert.obj src\hnswscan.obj src\hnswutils.obj src\hnswvacuum.obj src\ivfbuild.obj src\ivfflat.obj src\ivfinsert.obj src\ivfkmeans.obj src\ivfscan.obj src\ivfutils.obj src\ivfvacuum.obj src\sparsevec.obj src\vector.obj
|
OBJS = src\bitutils.obj src\bitvec.obj src\halfutils.obj src\halfvec.obj src\hnsw.obj src\hnswbuild.obj src\hnswinsert.obj src\hnswscan.obj src\hnswutils.obj src\hnswvacuum.obj src\ivfbuild.obj src\ivfflat.obj src\ivfinsert.obj src\ivfkmeans.obj src\ivfscan.obj src\ivfutils.obj src\ivfvacuum.obj src\sparsevec.obj src\vector.obj
|
||||||
|
|||||||
65
README.md
65
README.md
@@ -17,11 +17,11 @@ Plus [ACID](https://en.wikipedia.org/wiki/ACID) compliance, point-in-time recove
|
|||||||
|
|
||||||
### Linux and Mac
|
### Linux and Mac
|
||||||
|
|
||||||
Compile and install the extension (supports Postgres 12+)
|
Compile and install the extension (supports Postgres 13+)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd /tmp
|
cd /tmp
|
||||||
git clone --branch v0.7.4 https://github.com/pgvector/pgvector.git
|
git clone --branch v0.8.0 https://github.com/pgvector/pgvector.git
|
||||||
cd pgvector
|
cd pgvector
|
||||||
make
|
make
|
||||||
make install # may need sudo
|
make install # may need sudo
|
||||||
@@ -46,7 +46,7 @@ Then use `nmake` to build:
|
|||||||
```cmd
|
```cmd
|
||||||
set "PGROOT=C:\Program Files\PostgreSQL\16"
|
set "PGROOT=C:\Program Files\PostgreSQL\16"
|
||||||
cd %TEMP%
|
cd %TEMP%
|
||||||
git clone --branch v0.7.4 https://github.com/pgvector/pgvector.git
|
git clone --branch v0.8.0 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
|
||||||
@@ -324,7 +324,7 @@ For a large number of workers, you may also need to increase `max_parallel_worke
|
|||||||
|
|
||||||
### Indexing Progress
|
### Indexing Progress
|
||||||
|
|
||||||
Check [indexing progress](https://www.postgresql.org/docs/current/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING) with Postgres 12+
|
Check [indexing progress](https://www.postgresql.org/docs/current/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING)
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;
|
SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;
|
||||||
@@ -410,7 +410,7 @@ For a large number of workers, you may also need to increase `max_parallel_worke
|
|||||||
|
|
||||||
### Indexing Progress
|
### Indexing Progress
|
||||||
|
|
||||||
Check [indexing progress](https://www.postgresql.org/docs/current/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING) with Postgres 12+
|
Check [indexing progress](https://www.postgresql.org/docs/current/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING)
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT phase, round(100.0 * tuples_done / nullif(tuples_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;
|
SELECT phase, round(100.0 * tuples_done / nullif(tuples_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;
|
||||||
@@ -427,25 +427,49 @@ Note: `%` is only populated during the `loading tuples` phase
|
|||||||
|
|
||||||
## Filtering
|
## Filtering
|
||||||
|
|
||||||
There are a few ways to index nearest neighbor queries with a `WHERE` clause
|
There are a few ways to index nearest neighbor queries with a `WHERE` clause.
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT * FROM items WHERE category_id = 123 ORDER BY embedding <-> '[3,1,2]' LIMIT 5;
|
SELECT * FROM items WHERE category_id = 123 ORDER BY embedding <-> '[3,1,2]' LIMIT 5;
|
||||||
```
|
```
|
||||||
|
|
||||||
Create an index on one [or more](https://www.postgresql.org/docs/current/indexes-multicolumn.html) of the `WHERE` columns for exact search
|
A good place to start is creating an index on the filter column. This can provide fast, exact nearest neighbor search in many cases. Postgres has a number of [index types](https://www.postgresql.org/docs/current/indexes-types.html) for this: B-tree (default), hash, GiST, SP-GiST, GIN, and BRIN.
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE INDEX ON items (category_id);
|
CREATE INDEX ON items (category_id);
|
||||||
```
|
```
|
||||||
|
|
||||||
Or a [partial index](https://www.postgresql.org/docs/current/indexes-partial.html) on the vector column for approximate search
|
For multiple columns, consider a [multicolumn index](https://www.postgresql.org/docs/current/indexes-multicolumn.html).
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE INDEX ON items (location_id, category_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
Exact indexes work well for conditions that match a low percentage of rows. Otherwise, [approximate indexes](#indexing) can work better.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops);
|
||||||
|
```
|
||||||
|
|
||||||
|
With approximate indexes, filtering is applied *after* the index is scanned. If a condition matches 10% of rows, with HNSW and the default `hnsw.ef_search` of 40, only 4 rows will match on average. For more rows, increase `hnsw.ef_search`.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SET hnsw.ef_search = 200;
|
||||||
|
```
|
||||||
|
|
||||||
|
Starting with 0.8.0, you can enable [iterative index scans](#iterative-index-scans), which will automatically scan more of the index when needed.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SET hnsw.iterative_scan = strict_order;
|
||||||
|
```
|
||||||
|
|
||||||
|
If filtering by only a few distinct values, consider [partial indexing](https://www.postgresql.org/docs/current/indexes-partial.html).
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops) WHERE (category_id = 123);
|
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops) WHERE (category_id = 123);
|
||||||
```
|
```
|
||||||
|
|
||||||
Use [partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html) for approximate search on many different values of the `WHERE` columns
|
If filtering by many different values, consider [partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html).
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE TABLE items (embedding vector(3), category_id int) PARTITION BY LIST(category_id);
|
CREATE TABLE items (embedding vector(3), category_id int) PARTITION BY LIST(category_id);
|
||||||
@@ -453,11 +477,11 @@ CREATE TABLE items (embedding vector(3), category_id int) PARTITION BY LIST(cate
|
|||||||
|
|
||||||
## Iterative Index Scans
|
## Iterative Index Scans
|
||||||
|
|
||||||
*Unreleased*
|
*Added in 0.8.0*
|
||||||
|
|
||||||
With approximate indexes, queries with filtering can return less results (due to post-filtering). Starting with 0.8.0, you can enable iterative index scans. If too few results from the initial scan match the filters, the scan will resume until enough results are found (or it reaches `hnsw.max_scan_tuples` or `ivfflat.max_probes`). This can significantly improve recall.
|
With approximate indexes, queries with filtering can return less results since filtering is applied *after* the index is scanned. Starting with 0.8.0, you can enable iterative index scans, which will automatically scan more of the index until enough results are found (or it reaches `hnsw.max_scan_tuples` or `ivfflat.max_probes`).
|
||||||
|
|
||||||
There are two modes for iterative scans: strict and relaxed.
|
Iterative scans can use strict or relaxed ordering.
|
||||||
|
|
||||||
Strict ensures results are in the exact order by distance
|
Strict ensures results are in the exact order by distance
|
||||||
|
|
||||||
@@ -493,7 +517,7 @@ Note: Place any other filters inside the CTE
|
|||||||
|
|
||||||
### Iterative Scan Options
|
### Iterative Scan Options
|
||||||
|
|
||||||
Since scanning a large portion of an approximate index is expensive, there are options to control when a scan ends
|
Since scanning a large portion of an approximate index is expensive, there are options to control when a scan ends.
|
||||||
|
|
||||||
#### HNSW
|
#### HNSW
|
||||||
|
|
||||||
@@ -511,18 +535,7 @@ Specify the max amount of memory to use, as a multiple of `work_mem` (1 by defau
|
|||||||
SET hnsw.scan_mem_multiplier = 2;
|
SET hnsw.scan_mem_multiplier = 2;
|
||||||
```
|
```
|
||||||
|
|
||||||
You can see when increasing this is needed by enabling debug messages
|
Note: Try increasing this if increasing `hnsw.max_scan_tuples` does not improve recall
|
||||||
|
|
||||||
```sql
|
|
||||||
SET client_min_messages = debug1;
|
|
||||||
```
|
|
||||||
|
|
||||||
which will show when a scan reaches the memory limit
|
|
||||||
|
|
||||||
```text
|
|
||||||
DEBUG: hnsw index scan reached memory limit after 20000 tuples
|
|
||||||
HINT: Increase hnsw.scan_mem_multiplier to scan more tuples.
|
|
||||||
```
|
|
||||||
|
|
||||||
#### IVFFlat
|
#### IVFFlat
|
||||||
|
|
||||||
@@ -1140,7 +1153,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.7.4 https://github.com/pgvector/pgvector.git
|
git clone --branch v0.8.0 https://github.com/pgvector/pgvector.git
|
||||||
cd pgvector
|
cd pgvector
|
||||||
docker build --pull --build-arg PG_MAJOR=17 -t myuser/pgvector .
|
docker build --pull --build-arg PG_MAJOR=17 -t myuser/pgvector .
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -240,8 +240,8 @@ hnswgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
if (so->discarded == NULL)
|
if (so->discarded == NULL)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* Reached max number of tuples */
|
/* Reached max number of tuples or memory limit */
|
||||||
if (so->tuples >= hnsw_max_scan_tuples)
|
if (so->tuples >= hnsw_max_scan_tuples || MemoryContextMemAllocated(so->tmpCtx, false) > so->maxMemory)
|
||||||
{
|
{
|
||||||
if (pairingheap_is_empty(so->discarded))
|
if (pairingheap_is_empty(so->discarded))
|
||||||
break;
|
break;
|
||||||
@@ -249,21 +249,6 @@ hnswgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
/* Return remaining tuples */
|
/* Return remaining tuples */
|
||||||
so->w = lappend(so->w, HnswGetSearchCandidate(w_node, pairingheap_remove_first(so->discarded)));
|
so->w = lappend(so->w, HnswGetSearchCandidate(w_node, pairingheap_remove_first(so->discarded)));
|
||||||
}
|
}
|
||||||
/* Prevent scans from consuming too much memory */
|
|
||||||
else if (MemoryContextMemAllocated(so->tmpCtx, false) > so->maxMemory)
|
|
||||||
{
|
|
||||||
if (pairingheap_is_empty(so->discarded))
|
|
||||||
{
|
|
||||||
ereport(DEBUG1,
|
|
||||||
(errmsg("hnsw index scan reached memory limit after " INT64_FORMAT " tuples", so->tuples),
|
|
||||||
errhint("Increase hnsw.scan_mem_multiplier to scan more tuples.")));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Return remaining tuples */
|
|
||||||
so->w = lappend(so->w, HnswGetSearchCandidate(w_node, pairingheap_remove_first(so->discarded)));
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -114,7 +114,6 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
|||||||
{
|
{
|
||||||
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
||||||
TupleDesc tupdesc = RelationGetDescr(scan->indexRelation);
|
TupleDesc tupdesc = RelationGetDescr(scan->indexRelation);
|
||||||
double tuples = 0;
|
|
||||||
TupleTableSlot *slot = so->vslot;
|
TupleTableSlot *slot = so->vslot;
|
||||||
int batchProbes = 0;
|
int batchProbes = 0;
|
||||||
|
|
||||||
@@ -161,8 +160,6 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
|||||||
ExecStoreVirtualTuple(slot);
|
ExecStoreVirtualTuple(slot);
|
||||||
|
|
||||||
tuplesort_puttupleslot(so->sortstate, slot);
|
tuplesort_puttupleslot(so->sortstate, slot);
|
||||||
|
|
||||||
tuples++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
searchPage = IvfflatPageGetOpaque(page)->nextblkno;
|
searchPage = IvfflatPageGetOpaque(page)->nextblkno;
|
||||||
@@ -171,12 +168,6 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tuples < 100 && ivfflat_iterative_scan == IVFFLAT_ITERATIVE_SCAN_OFF)
|
|
||||||
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);
|
tuplesort_performsort(so->sortstate);
|
||||||
|
|
||||||
#if defined(IVFFLAT_MEMORY)
|
#if defined(IVFFLAT_MEMORY)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
|
#include "common/shortest_dec.h"
|
||||||
#include "common/string.h"
|
#include "common/string.h"
|
||||||
#include "fmgr.h"
|
#include "fmgr.h"
|
||||||
#include "halfutils.h"
|
#include "halfutils.h"
|
||||||
@@ -12,17 +13,10 @@
|
|||||||
#include "sparsevec.h"
|
#include "sparsevec.h"
|
||||||
#include "utils/array.h"
|
#include "utils/array.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
|
#include "utils/float.h"
|
||||||
#include "utils/lsyscache.h"
|
#include "utils/lsyscache.h"
|
||||||
#include "vector.h"
|
#include "vector.h"
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
#include "common/shortest_dec.h"
|
|
||||||
#include "utils/float.h"
|
|
||||||
#else
|
|
||||||
#include <float.h>
|
|
||||||
#include "utils/builtins.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef struct SparseInputElement
|
typedef struct SparseInputElement
|
||||||
{
|
{
|
||||||
int32 index;
|
int32 index;
|
||||||
|
|||||||
@@ -190,4 +190,6 @@ SHOW hnsw.scan_mem_multiplier;
|
|||||||
|
|
||||||
SET hnsw.scan_mem_multiplier = 0;
|
SET hnsw.scan_mem_multiplier = 0;
|
||||||
ERROR: 0 is outside the valid range for parameter "hnsw.scan_mem_multiplier" (1 .. 1000)
|
ERROR: 0 is outside the valid range for parameter "hnsw.scan_mem_multiplier" (1 .. 1000)
|
||||||
|
SET hnsw.scan_mem_multiplier = 1001;
|
||||||
|
ERROR: 1001 is outside the valid range for parameter "hnsw.scan_mem_multiplier" (1 .. 1000)
|
||||||
DROP TABLE t;
|
DROP TABLE t;
|
||||||
|
|||||||
@@ -109,5 +109,6 @@ SET hnsw.max_scan_tuples = 0;
|
|||||||
SHOW hnsw.scan_mem_multiplier;
|
SHOW hnsw.scan_mem_multiplier;
|
||||||
|
|
||||||
SET hnsw.scan_mem_multiplier = 0;
|
SET hnsw.scan_mem_multiplier = 0;
|
||||||
|
SET hnsw.scan_mem_multiplier = 1001;
|
||||||
|
|
||||||
DROP TABLE t;
|
DROP TABLE t;
|
||||||
|
|||||||
@@ -56,13 +56,4 @@ foreach ((30000, 50000, 70000))
|
|||||||
cmp_ok($avg, '<', $expected + 2);
|
cmp_ok($avg, '<', $expected + 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
my ($ret, $stdout, $stderr) = $node->psql("postgres", qq(
|
|
||||||
SET enable_seqscan = off;
|
|
||||||
SET hnsw.iterative_scan = relaxed_order;
|
|
||||||
SET client_min_messages = debug1;
|
|
||||||
SET work_mem = '1MB';
|
|
||||||
SELECT COUNT(*) FROM (SELECT v FROM tst WHERE i % 10000 = 0 ORDER BY v <-> (SELECT v FROM tst LIMIT 1) LIMIT 11) t;
|
|
||||||
));
|
|
||||||
like($stderr, qr/hnsw index scan reached memory limit after \d+ tuples/);
|
|
||||||
|
|
||||||
done_testing();
|
done_testing();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
comment = 'vector data type and ivfflat and hnsw access methods'
|
comment = 'vector data type and ivfflat and hnsw access methods'
|
||||||
default_version = '0.7.4'
|
default_version = '0.8.0'
|
||||||
module_pathname = '$libdir/vector'
|
module_pathname = '$libdir/vector'
|
||||||
relocatable = true
|
relocatable = true
|
||||||
|
|||||||
Reference in New Issue
Block a user