mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-22 12:07:34 +08:00
Compare commits
58 Commits
guc-explai
...
hnsw-prefe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e5e0cf7b5 | ||
|
|
bbe7135ae4 | ||
|
|
7b58352336 | ||
|
|
83d410eae9 | ||
|
|
ebbfe8dba0 | ||
|
|
e575866297 | ||
|
|
35f4f7fc80 | ||
|
|
32e95a8598 | ||
|
|
a03dc5b7d0 | ||
|
|
d365aac370 | ||
|
|
05182479a2 | ||
|
|
cc0958dec5 | ||
|
|
4af2b06dc5 | ||
|
|
880dc4d6b9 | ||
|
|
fef635c9e5 | ||
|
|
78ed8f1157 | ||
|
|
f11e4d7b20 | ||
|
|
aafdf4167d | ||
|
|
656b059258 | ||
|
|
7cf9980696 | ||
|
|
2fe560dc58 | ||
|
|
b46beada1a | ||
|
|
0a42bc7aa5 | ||
|
|
f5df32c41d | ||
|
|
2c53c30415 | ||
|
|
b4bc010459 | ||
|
|
7b4ff9b59f | ||
|
|
cfdcbd75d1 | ||
|
|
5136983f35 | ||
|
|
4ab4b89980 | ||
|
|
85f0e3ccf6 | ||
|
|
28e797cb5a | ||
|
|
1263d753be | ||
|
|
5bc7937715 | ||
|
|
e7e899e9af | ||
|
|
2627c5ff77 | ||
|
|
34b3cfdc43 | ||
|
|
cd218aae5a | ||
|
|
ba9367f86c | ||
|
|
9c20550a41 | ||
|
|
e3e74fe94e | ||
|
|
96a5a44632 | ||
|
|
67e1392a83 | ||
|
|
e530a1a026 | ||
|
|
6170e2645b | ||
|
|
e6bae175f1 | ||
|
|
52b777e04a | ||
|
|
307271214f | ||
|
|
6e9f74ddce | ||
|
|
258215ad97 | ||
|
|
fb87b6da91 | ||
|
|
a2a0b377f0 | ||
|
|
2f770307b8 | ||
|
|
c04e16ff5b | ||
|
|
bd4d272f26 | ||
|
|
8bb797cc2f | ||
|
|
fe6ec03dac | ||
|
|
c1161f8889 |
19
.github/workflows/build.yml
vendored
19
.github/workflows/build.yml
vendored
@@ -13,13 +13,13 @@ jobs:
|
|||||||
- postgres: 17
|
- postgres: 17
|
||||||
os: ubuntu-24.04
|
os: ubuntu-24.04
|
||||||
- postgres: 16
|
- postgres: 16
|
||||||
os: ubuntu-22.04
|
os: ubuntu-24.04-arm
|
||||||
- postgres: 15
|
- postgres: 15
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
- postgres: 14
|
- postgres: 14
|
||||||
os: ubuntu-20.04
|
os: ubuntu-22.04-arm
|
||||||
- postgres: 13
|
- postgres: 13
|
||||||
os: ubuntu-20.04
|
os: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: ankane/setup-postgres@v1
|
- uses: ankane/setup-postgres@v1
|
||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
dev-files: true
|
dev-files: true
|
||||||
- run: make
|
- run: make
|
||||||
env:
|
env:
|
||||||
PG_CFLAGS: -DUSE_ASSERT_CHECKING -Wall -Wextra -Werror -Wno-unused-parameter -Wno-sign-compare
|
PG_CFLAGS: ${{ matrix.postgres == 18 && '-Wno-missing-field-initializers' || '' }} -DUSE_ASSERT_CHECKING -Wall -Wextra -Werror -Wno-unused-parameter -Wno-sign-compare
|
||||||
- run: |
|
- run: |
|
||||||
export PG_CONFIG=`which pg_config`
|
export PG_CONFIG=`which pg_config`
|
||||||
sudo --preserve-env=PG_CONFIG make install
|
sudo --preserve-env=PG_CONFIG make install
|
||||||
@@ -46,8 +46,8 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- postgres: 16
|
- postgres: 17
|
||||||
os: macos-14
|
os: macos-15
|
||||||
- postgres: 14
|
- postgres: 14
|
||||||
os: macos-13
|
os: macos-13
|
||||||
steps:
|
steps:
|
||||||
@@ -70,12 +70,13 @@ jobs:
|
|||||||
tar xf $TAG.tar.gz
|
tar xf $TAG.tar.gz
|
||||||
mv postgres-$TAG postgres
|
mv postgres-$TAG postgres
|
||||||
env:
|
env:
|
||||||
TAG: ${{ matrix.postgres == 16 && 'REL_16_2' || 'REL_14_11' }}
|
TAG: ${{ matrix.postgres == 17 && 'REL_17_2' || 'REL_14_15' }}
|
||||||
- run: make prove_installcheck PROVE_FLAGS="-I ./postgres/src/test/perl -I ./test/perl"
|
- run: make prove_installcheck PROVE_FLAGS="-I ./postgres/src/test/perl -I ./test/perl"
|
||||||
env:
|
env:
|
||||||
PERL5LIB: /Users/runner/perl5/lib/perl5
|
PERL5LIB: /Users/runner/perl5/lib/perl5
|
||||||
- run: make clean && $(brew --prefix llvm@15)/bin/scan-build --status-bugs make
|
- run: make clean && $(brew --prefix llvm@$LLVM_VERSION)/bin/scan-build --status-bugs make
|
||||||
env:
|
env:
|
||||||
|
LLVM_VERSION: ${{ matrix.os == 'macos-15' && 18 || 15 }}
|
||||||
PG_CFLAGS: -DUSE_ASSERT_CHECKING
|
PG_CFLAGS: -DUSE_ASSERT_CHECKING
|
||||||
windows:
|
windows:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
@@ -125,7 +126,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: ankane/setup-postgres-valgrind@v1
|
- uses: ankane/setup-postgres-valgrind@v1
|
||||||
with:
|
with:
|
||||||
postgres-version: 16
|
postgres-version: 17
|
||||||
check-ub: yes
|
check-ub: yes
|
||||||
- run: make OPTFLAGS=""
|
- run: make OPTFLAGS=""
|
||||||
- run: sudo --preserve-env=PG_CONFIG make install
|
- run: sudo --preserve-env=PG_CONFIG make install
|
||||||
|
|||||||
@@ -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
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
|||||||
Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
|
Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
|
||||||
|
|
||||||
Portions Copyright (c) 1994, The Regents of the University of California
|
Portions Copyright (c) 1994, The Regents of the University of California
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
184
README.md
184
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,14 +46,12 @@ 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
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: Postgres 17 is not supported yet due to an upstream issue
|
|
||||||
|
|
||||||
See the [installation notes](#installation-notes---windows) if you run into issues
|
See the [installation notes](#installation-notes---windows) if you run into issues
|
||||||
|
|
||||||
You can also install it with [Docker](#docker) or [conda-forge](#conda-forge).
|
You can also install it with [Docker](#docker) or [conda-forge](#conda-forge).
|
||||||
@@ -84,7 +82,7 @@ Get the nearest neighbors by L2 distance
|
|||||||
SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 5;
|
SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 5;
|
||||||
```
|
```
|
||||||
|
|
||||||
Also supports inner product (`<#>`), cosine distance (`<=>`), and L1 distance (`<+>`, added in 0.7.0)
|
Also supports inner product (`<#>`), cosine distance (`<=>`), and L1 distance (`<+>`)
|
||||||
|
|
||||||
Note: `<#>` returns the negative inner product since Postgres only supports `ASC` order index scans on operators
|
Note: `<#>` returns the negative inner product since Postgres only supports `ASC` order index scans on operators
|
||||||
|
|
||||||
@@ -148,9 +146,9 @@ Supported distance functions are:
|
|||||||
- `<->` - L2 distance
|
- `<->` - L2 distance
|
||||||
- `<#>` - (negative) inner product
|
- `<#>` - (negative) inner product
|
||||||
- `<=>` - cosine distance
|
- `<=>` - cosine distance
|
||||||
- `<+>` - L1 distance (added in 0.7.0)
|
- `<+>` - L1 distance
|
||||||
- `<~>` - Hamming distance (binary vectors, added in 0.7.0)
|
- `<~>` - Hamming distance (binary vectors)
|
||||||
- `<%>` - Jaccard distance (binary vectors, added in 0.7.0)
|
- `<%>` - Jaccard distance (binary vectors)
|
||||||
|
|
||||||
Get the nearest neighbors to a row
|
Get the nearest neighbors to a row
|
||||||
|
|
||||||
@@ -237,19 +235,19 @@ Cosine distance
|
|||||||
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);
|
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);
|
||||||
```
|
```
|
||||||
|
|
||||||
L1 distance - added in 0.7.0
|
L1 distance
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE INDEX ON items USING hnsw (embedding vector_l1_ops);
|
CREATE INDEX ON items USING hnsw (embedding vector_l1_ops);
|
||||||
```
|
```
|
||||||
|
|
||||||
Hamming distance - added in 0.7.0
|
Hamming distance
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE INDEX ON items USING hnsw (embedding bit_hamming_ops);
|
CREATE INDEX ON items USING hnsw (embedding bit_hamming_ops);
|
||||||
```
|
```
|
||||||
|
|
||||||
Jaccard distance - added in 0.7.0
|
Jaccard distance
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE INDEX ON items USING hnsw (embedding bit_jaccard_ops);
|
CREATE INDEX ON items USING hnsw (embedding bit_jaccard_ops);
|
||||||
@@ -258,9 +256,9 @@ CREATE INDEX ON items USING hnsw (embedding bit_jaccard_ops);
|
|||||||
Supported types are:
|
Supported types are:
|
||||||
|
|
||||||
- `vector` - up to 2,000 dimensions
|
- `vector` - up to 2,000 dimensions
|
||||||
- `halfvec` - up to 4,000 dimensions (added in 0.7.0)
|
- `halfvec` - up to 4,000 dimensions
|
||||||
- `bit` - up to 64,000 dimensions (added in 0.7.0)
|
- `bit` - up to 64,000 dimensions
|
||||||
- `sparsevec` - up to 1,000 non-zero elements (added in 0.7.0)
|
- `sparsevec` - up to 1,000 non-zero elements
|
||||||
|
|
||||||
### Index Options
|
### Index Options
|
||||||
|
|
||||||
@@ -314,17 +312,19 @@ Note: Do not set `maintenance_work_mem` so high that it exhausts the memory on t
|
|||||||
|
|
||||||
Like other index types, it’s faster to create an index after loading your initial data
|
Like other index types, it’s faster to create an index after loading your initial data
|
||||||
|
|
||||||
Starting with 0.6.0, you can also speed up index creation by increasing the number of parallel workers (2 by default)
|
You can also speed up index creation by increasing the number of parallel workers (2 by default)
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SET max_parallel_maintenance_workers = 7; -- plus leader
|
SET max_parallel_maintenance_workers = 7; -- plus leader
|
||||||
```
|
```
|
||||||
|
|
||||||
For a large number of workers, you may also need to increase `max_parallel_workers` (8 by default)
|
For a large number of workers, you may need to increase `max_parallel_workers` (8 by default)
|
||||||
|
|
||||||
|
The [index options](#index-options) also have a significant impact on build time (use the defaults unless seeing low recall)
|
||||||
|
|
||||||
### 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;
|
||||||
@@ -367,7 +367,7 @@ Cosine distance
|
|||||||
CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
|
CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
|
||||||
```
|
```
|
||||||
|
|
||||||
Hamming distance - added in 0.7.0
|
Hamming distance
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE INDEX ON items USING ivfflat (embedding bit_hamming_ops) WITH (lists = 100);
|
CREATE INDEX ON items USING ivfflat (embedding bit_hamming_ops) WITH (lists = 100);
|
||||||
@@ -376,8 +376,8 @@ CREATE INDEX ON items USING ivfflat (embedding bit_hamming_ops) WITH (lists = 10
|
|||||||
Supported types are:
|
Supported types are:
|
||||||
|
|
||||||
- `vector` - up to 2,000 dimensions
|
- `vector` - up to 2,000 dimensions
|
||||||
- `halfvec` - up to 4,000 dimensions (added in 0.7.0)
|
- `halfvec` - up to 4,000 dimensions
|
||||||
- `bit` - up to 64,000 dimensions (added in 0.7.0)
|
- `bit` - up to 64,000 dimensions
|
||||||
|
|
||||||
### Query Options
|
### Query Options
|
||||||
|
|
||||||
@@ -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,9 @@ CREATE TABLE items (embedding vector(3), category_id int) PARTITION BY LIST(cate
|
|||||||
|
|
||||||
## Iterative Index Scans
|
## Iterative Index Scans
|
||||||
|
|
||||||
*Unreleased*
|
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`).
|
||||||
|
|
||||||
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.
|
Iterative scans can use strict or relaxed ordering.
|
||||||
|
|
||||||
There are two modes for iterative scans: strict and relaxed.
|
|
||||||
|
|
||||||
Strict ensures results are in the exact order by distance
|
Strict ensures results are in the exact order by distance
|
||||||
|
|
||||||
@@ -493,7 +515,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 +533,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
|
||||||
|
|
||||||
@@ -536,8 +547,6 @@ Note: If this is lower than `ivfflat.probes`, `ivfflat.probes` will be used
|
|||||||
|
|
||||||
## Half-Precision Vectors
|
## Half-Precision Vectors
|
||||||
|
|
||||||
*Added in 0.7.0*
|
|
||||||
|
|
||||||
Use the `halfvec` type to store half-precision vectors
|
Use the `halfvec` type to store half-precision vectors
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
@@ -546,8 +555,6 @@ CREATE TABLE items (id bigserial PRIMARY KEY, embedding halfvec(3));
|
|||||||
|
|
||||||
## Half-Precision Indexing
|
## Half-Precision Indexing
|
||||||
|
|
||||||
*Added in 0.7.0*
|
|
||||||
|
|
||||||
Index vectors at half precision for smaller indexes
|
Index vectors at half precision for smaller indexes
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
@@ -569,24 +576,16 @@ CREATE TABLE items (id bigserial PRIMARY KEY, embedding bit(3));
|
|||||||
INSERT INTO items (embedding) VALUES ('000'), ('111');
|
INSERT INTO items (embedding) VALUES ('000'), ('111');
|
||||||
```
|
```
|
||||||
|
|
||||||
Get the nearest neighbors by Hamming distance (added in 0.7.0)
|
Get the nearest neighbors by Hamming distance
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT * FROM items ORDER BY embedding <~> '101' LIMIT 5;
|
SELECT * FROM items ORDER BY embedding <~> '101' LIMIT 5;
|
||||||
```
|
```
|
||||||
|
|
||||||
Or (before 0.7.0)
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT * FROM items ORDER BY bit_count(embedding # '101') LIMIT 5;
|
|
||||||
```
|
|
||||||
|
|
||||||
Also supports Jaccard distance (`<%>`)
|
Also supports Jaccard distance (`<%>`)
|
||||||
|
|
||||||
## Binary Quantization
|
## Binary Quantization
|
||||||
|
|
||||||
*Added in 0.7.0*
|
|
||||||
|
|
||||||
Use expression indexing for binary quantization
|
Use expression indexing for binary quantization
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
@@ -609,8 +608,6 @@ SELECT * FROM (
|
|||||||
|
|
||||||
## Sparse Vectors
|
## Sparse Vectors
|
||||||
|
|
||||||
*Added in 0.7.0*
|
|
||||||
|
|
||||||
Use the `sparsevec` type to store sparse vectors
|
Use the `sparsevec` type to store sparse vectors
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
@@ -644,8 +641,6 @@ You can use [Reciprocal Rank Fusion](https://github.com/pgvector/pgvector-python
|
|||||||
|
|
||||||
## Indexing Subvectors
|
## Indexing Subvectors
|
||||||
|
|
||||||
*Added in 0.7.0*
|
|
||||||
|
|
||||||
Use expression indexing to index subvectors
|
Use expression indexing to index subvectors
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
@@ -759,8 +754,6 @@ SELECT query, calls, ROUND((total_plan_time + total_exec_time) / calls) AS avg_t
|
|||||||
FROM pg_stat_statements ORDER BY total_plan_time + total_exec_time DESC LIMIT 20;
|
FROM pg_stat_statements ORDER BY total_plan_time + total_exec_time DESC LIMIT 20;
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: Replace `total_plan_time + total_exec_time` with `total_time` for Postgres < 13
|
|
||||||
|
|
||||||
Monitor recall by comparing results from approximate search with exact search.
|
Monitor recall by comparing results from approximate search with exact search.
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
@@ -788,8 +781,12 @@ C | [pgvector-c](https://github.com/pgvector/pgvector-c)
|
|||||||
C++ | [pgvector-cpp](https://github.com/pgvector/pgvector-cpp)
|
C++ | [pgvector-cpp](https://github.com/pgvector/pgvector-cpp)
|
||||||
C#, F#, Visual Basic | [pgvector-dotnet](https://github.com/pgvector/pgvector-dotnet)
|
C#, F#, Visual Basic | [pgvector-dotnet](https://github.com/pgvector/pgvector-dotnet)
|
||||||
Crystal | [pgvector-crystal](https://github.com/pgvector/pgvector-crystal)
|
Crystal | [pgvector-crystal](https://github.com/pgvector/pgvector-crystal)
|
||||||
|
D | [pgvector-d](https://github.com/pgvector/pgvector-d)
|
||||||
Dart | [pgvector-dart](https://github.com/pgvector/pgvector-dart)
|
Dart | [pgvector-dart](https://github.com/pgvector/pgvector-dart)
|
||||||
Elixir | [pgvector-elixir](https://github.com/pgvector/pgvector-elixir)
|
Elixir | [pgvector-elixir](https://github.com/pgvector/pgvector-elixir)
|
||||||
|
Erlang | [pgvector-erlang](https://github.com/pgvector/pgvector-erlang)
|
||||||
|
Fortran | [pgvector-fortran](https://github.com/pgvector/pgvector-fortran)
|
||||||
|
Gleam | [pgvector-gleam](https://github.com/pgvector/pgvector-gleam)
|
||||||
Go | [pgvector-go](https://github.com/pgvector/pgvector-go)
|
Go | [pgvector-go](https://github.com/pgvector/pgvector-go)
|
||||||
Haskell | [pgvector-haskell](https://github.com/pgvector/pgvector-haskell)
|
Haskell | [pgvector-haskell](https://github.com/pgvector/pgvector-haskell)
|
||||||
Java, Kotlin, Groovy, Scala | [pgvector-java](https://github.com/pgvector/pgvector-java)
|
Java, Kotlin, Groovy, Scala | [pgvector-java](https://github.com/pgvector/pgvector-java)
|
||||||
@@ -803,6 +800,7 @@ Perl | [pgvector-perl](https://github.com/pgvector/pgvector-perl)
|
|||||||
PHP | [pgvector-php](https://github.com/pgvector/pgvector-php)
|
PHP | [pgvector-php](https://github.com/pgvector/pgvector-php)
|
||||||
Python | [pgvector-python](https://github.com/pgvector/pgvector-python)
|
Python | [pgvector-python](https://github.com/pgvector/pgvector-python)
|
||||||
R | [pgvector-r](https://github.com/pgvector/pgvector-r)
|
R | [pgvector-r](https://github.com/pgvector/pgvector-r)
|
||||||
|
Raku | [pgvector-raku](https://github.com/pgvector/pgvector-raku)
|
||||||
Ruby | [pgvector-ruby](https://github.com/pgvector/pgvector-ruby), [Neighbor](https://github.com/ankane/neighbor)
|
Ruby | [pgvector-ruby](https://github.com/pgvector/pgvector-ruby), [Neighbor](https://github.com/ankane/neighbor)
|
||||||
Rust | [pgvector-rust](https://github.com/pgvector/pgvector-rust)
|
Rust | [pgvector-rust](https://github.com/pgvector/pgvector-rust)
|
||||||
Swift | [pgvector-swift](https://github.com/pgvector/pgvector-swift)
|
Swift | [pgvector-swift](https://github.com/pgvector/pgvector-swift)
|
||||||
@@ -824,7 +822,7 @@ You can use [half-precision indexing](#half-precision-indexing) to index up to 4
|
|||||||
|
|
||||||
#### Can I store vectors with different dimensions in the same column?
|
#### Can I store vectors with different dimensions in the same column?
|
||||||
|
|
||||||
You can use `vector` as the type (instead of `vector(3)`).
|
You can use `vector` as the type (instead of `vector(n)`).
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE TABLE embeddings (model_id bigint, item_id bigint, embedding vector, PRIMARY KEY (model_id, item_id));
|
CREATE TABLE embeddings (model_id bigint, item_id bigint, embedding vector, PRIMARY KEY (model_id, item_id));
|
||||||
@@ -924,7 +922,7 @@ ALTER TABLE items ALTER COLUMN embedding SET STORAGE PLAIN;
|
|||||||
|
|
||||||
#### Why are there less results for a query after adding an HNSW index?
|
#### Why are there less results for a query after adding an HNSW index?
|
||||||
|
|
||||||
Results are limited by the size of the dynamic candidate list (`hnsw.ef_search`). There may be even less results due to dead tuples or filtering conditions in the query. We recommend setting `hnsw.ef_search` to at least twice the `LIMIT` of the query. If you need more than 500 results, use an IVFFlat index instead.
|
Results are limited by the size of the dynamic candidate list (`hnsw.ef_search`), which is 40 by default. There may be even less results due to dead tuples or filtering conditions in the query. Enabling [iterative index scans](#iterative-index-scans) can help address this.
|
||||||
|
|
||||||
Also, note that `NULL` vectors are not indexed (as well as zero vectors for cosine distance).
|
Also, note that `NULL` vectors are not indexed (as well as zero vectors for cosine distance).
|
||||||
|
|
||||||
@@ -936,7 +934,7 @@ The index was likely created with too little data for the number of lists. Drop
|
|||||||
DROP INDEX index_name;
|
DROP INDEX index_name;
|
||||||
```
|
```
|
||||||
|
|
||||||
Results can also be limited by the number of probes (`ivfflat.probes`).
|
Results can also be limited by the number of probes (`ivfflat.probes`). Enabling [iterative index scans](#iterative-index-scans) can address this.
|
||||||
|
|
||||||
Also, note that `NULL` vectors are not indexed (as well as zero vectors for cosine distance).
|
Also, note that `NULL` vectors are not indexed (as well as zero vectors for cosine distance).
|
||||||
|
|
||||||
@@ -1103,7 +1101,13 @@ Note: Replace `17` with your Postgres server version
|
|||||||
|
|
||||||
### Missing SDK
|
### Missing SDK
|
||||||
|
|
||||||
If compilation fails and the output includes `warning: no such sysroot directory` on Mac, reinstall Xcode Command Line Tools.
|
If compilation fails and the output includes `warning: no such sysroot directory` on Mac, your Postgres installation points to a path that no longer exists.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pg_config --cppflags
|
||||||
|
```
|
||||||
|
|
||||||
|
Reinstall Postgres to fix this.
|
||||||
|
|
||||||
### Portability
|
### Portability
|
||||||
|
|
||||||
@@ -1121,6 +1125,14 @@ make OPTFLAGS=""
|
|||||||
|
|
||||||
If compilation fails with `Cannot open include file: 'postgres.h': No such file or directory`, make sure `PGROOT` is correct.
|
If compilation fails with `Cannot open include file: 'postgres.h': No such file or directory`, make sure `PGROOT` is correct.
|
||||||
|
|
||||||
|
### Mismatched Architecture
|
||||||
|
|
||||||
|
If compilation fails with `error C2196: case value '4' already used`, make sure `vcvars64.bat` was called. Then run `nmake /F Makefile.win clean` and re-run the installation instructions.
|
||||||
|
|
||||||
|
### Missing Symbol
|
||||||
|
|
||||||
|
If linking fails with `unresolved external symbol float_to_shortest_decimal_bufn` with Postgres 17.0-17.2, upgrade to Postgres 17.3+.
|
||||||
|
|
||||||
### Permissions
|
### Permissions
|
||||||
|
|
||||||
If installation fails with `Access is denied`, re-run the installation instructions as an administrator.
|
If installation fails with `Access is denied`, re-run the installation instructions as an administrator.
|
||||||
@@ -1140,11 +1152,17 @@ 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 .
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you increase `maintenance_work_mem`, make sure `--shm-size` is at least that size to avoid an error with parallel HNSW index builds.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker run --shm-size=1g ...
|
||||||
|
```
|
||||||
|
|
||||||
### Homebrew
|
### Homebrew
|
||||||
|
|
||||||
With Homebrew Postgres, you can use:
|
With Homebrew Postgres, you can use:
|
||||||
@@ -1190,7 +1208,7 @@ Note: Replace `17` with your Postgres server version
|
|||||||
Install the FreeBSD package with:
|
Install the FreeBSD package with:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pkg install postgresql15-pgvector
|
pkg install postgresql16-pgvector
|
||||||
```
|
```
|
||||||
|
|
||||||
or the port with:
|
or the port with:
|
||||||
@@ -1232,36 +1250,6 @@ You can check the version in the current database with:
|
|||||||
SELECT extversion FROM pg_extension WHERE extname = 'vector';
|
SELECT extversion FROM pg_extension WHERE extname = 'vector';
|
||||||
```
|
```
|
||||||
|
|
||||||
## Upgrade Notes
|
|
||||||
|
|
||||||
### 0.6.0
|
|
||||||
|
|
||||||
#### Postgres 12
|
|
||||||
|
|
||||||
If upgrading with Postgres 12, remove this line from `sql/vector--0.5.1--0.6.0.sql`:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
ALTER TYPE vector SET (STORAGE = external);
|
|
||||||
```
|
|
||||||
|
|
||||||
Then run `make install` and `ALTER EXTENSION vector UPDATE;`.
|
|
||||||
|
|
||||||
#### Docker
|
|
||||||
|
|
||||||
The Docker image is now published in the `pgvector` org, and there are tags for each supported version of Postgres (rather than a `latest` tag).
|
|
||||||
|
|
||||||
```sh
|
|
||||||
docker pull pgvector/pgvector:pg16
|
|
||||||
# or
|
|
||||||
docker pull pgvector/pgvector:0.6.0-pg16
|
|
||||||
```
|
|
||||||
|
|
||||||
Also, if you’ve increased `maintenance_work_mem`, make sure `--shm-size` is at least that size to avoid an error with parallel HNSW index builds.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
docker run --shm-size=1g ...
|
|
||||||
```
|
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
|
||||||
Thanks to:
|
Thanks to:
|
||||||
|
|||||||
21
src/hnsw.c
21
src/hnsw.c
@@ -77,21 +77,21 @@ HnswInit(void)
|
|||||||
|
|
||||||
DefineCustomIntVariable("hnsw.ef_search", "Sets the size of the dynamic candidate list for search",
|
DefineCustomIntVariable("hnsw.ef_search", "Sets the size of the dynamic candidate list for search",
|
||||||
"Valid range is 1..1000.", &hnsw_ef_search,
|
"Valid range is 1..1000.", &hnsw_ef_search,
|
||||||
HNSW_DEFAULT_EF_SEARCH, HNSW_MIN_EF_SEARCH, HNSW_MAX_EF_SEARCH, PGC_USERSET, GUC_EXPLAIN, NULL, NULL, NULL);
|
HNSW_DEFAULT_EF_SEARCH, HNSW_MIN_EF_SEARCH, HNSW_MAX_EF_SEARCH, PGC_USERSET, 0, NULL, NULL, NULL);
|
||||||
|
|
||||||
DefineCustomEnumVariable("hnsw.iterative_scan", "Sets the mode for iterative scans",
|
DefineCustomEnumVariable("hnsw.iterative_scan", "Sets the mode for iterative scans",
|
||||||
NULL, &hnsw_iterative_scan,
|
NULL, &hnsw_iterative_scan,
|
||||||
HNSW_ITERATIVE_SCAN_OFF, hnsw_iterative_scan_options, PGC_USERSET, GUC_EXPLAIN, NULL, NULL, NULL);
|
HNSW_ITERATIVE_SCAN_OFF, hnsw_iterative_scan_options, PGC_USERSET, 0, NULL, NULL, NULL);
|
||||||
|
|
||||||
/* This is approximate and does not affect the initial scan */
|
/* This is approximate and does not affect the initial scan */
|
||||||
DefineCustomIntVariable("hnsw.max_scan_tuples", "Sets the max number of tuples to visit for iterative scans",
|
DefineCustomIntVariable("hnsw.max_scan_tuples", "Sets the max number of tuples to visit for iterative scans",
|
||||||
NULL, &hnsw_max_scan_tuples,
|
NULL, &hnsw_max_scan_tuples,
|
||||||
20000, 1, INT_MAX, PGC_USERSET, GUC_EXPLAIN, NULL, NULL, NULL);
|
20000, 1, INT_MAX, PGC_USERSET, 0, NULL, NULL, NULL);
|
||||||
|
|
||||||
/* Same range as hash_mem_multiplier */
|
/* Same range as hash_mem_multiplier */
|
||||||
DefineCustomRealVariable("hnsw.scan_mem_multiplier", "Sets the multiple of work_mem to use for iterative scans",
|
DefineCustomRealVariable("hnsw.scan_mem_multiplier", "Sets the multiple of work_mem to use for iterative scans",
|
||||||
NULL, &hnsw_scan_mem_multiplier,
|
NULL, &hnsw_scan_mem_multiplier,
|
||||||
1, 1, 1000, PGC_USERSET, GUC_EXPLAIN, NULL, NULL, NULL);
|
1, 1, 1000, PGC_USERSET, 0, NULL, NULL, NULL);
|
||||||
|
|
||||||
MarkGUCPrefixReserved("hnsw");
|
MarkGUCPrefixReserved("hnsw");
|
||||||
}
|
}
|
||||||
@@ -259,6 +259,11 @@ hnswhandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amoptsprocnum = 0;
|
amroutine->amoptsprocnum = 0;
|
||||||
amroutine->amcanorder = false;
|
amroutine->amcanorder = false;
|
||||||
amroutine->amcanorderbyop = true;
|
amroutine->amcanorderbyop = true;
|
||||||
|
#if PG_VERSION_NUM >= 180000
|
||||||
|
amroutine->amcanhash = false;
|
||||||
|
amroutine->amconsistentequality = false;
|
||||||
|
amroutine->amconsistentordering = false;
|
||||||
|
#endif
|
||||||
amroutine->amcanbackward = false; /* can change direction mid-scan */
|
amroutine->amcanbackward = false; /* can change direction mid-scan */
|
||||||
amroutine->amcanunique = false;
|
amroutine->amcanunique = false;
|
||||||
amroutine->amcanmulticol = false;
|
amroutine->amcanmulticol = false;
|
||||||
@@ -291,6 +296,9 @@ hnswhandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amvacuumcleanup = hnswvacuumcleanup;
|
amroutine->amvacuumcleanup = hnswvacuumcleanup;
|
||||||
amroutine->amcanreturn = NULL;
|
amroutine->amcanreturn = NULL;
|
||||||
amroutine->amcostestimate = hnswcostestimate;
|
amroutine->amcostestimate = hnswcostestimate;
|
||||||
|
#if PG_VERSION_NUM >= 180000
|
||||||
|
amroutine->amgettreeheight = NULL;
|
||||||
|
#endif
|
||||||
amroutine->amoptions = hnswoptions;
|
amroutine->amoptions = hnswoptions;
|
||||||
amroutine->amproperty = NULL; /* TODO AMPROP_DISTANCE_ORDERABLE */
|
amroutine->amproperty = NULL; /* TODO AMPROP_DISTANCE_ORDERABLE */
|
||||||
amroutine->ambuildphasename = hnswbuildphasename;
|
amroutine->ambuildphasename = hnswbuildphasename;
|
||||||
@@ -311,5 +319,10 @@ hnswhandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->aminitparallelscan = NULL;
|
amroutine->aminitparallelscan = NULL;
|
||||||
amroutine->amparallelrescan = NULL;
|
amroutine->amparallelrescan = NULL;
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= 180000
|
||||||
|
amroutine->amtranslatestrategy = NULL;
|
||||||
|
amroutine->amtranslatecmptype = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
PG_RETURN_POINTER(amroutine);
|
PG_RETURN_POINTER(amroutine);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ typedef struct HnswNeighborArray HnswNeighborArray;
|
|||||||
|
|
||||||
#define HnswPtrDeclare(type, relptrtype, ptrtype) \
|
#define HnswPtrDeclare(type, relptrtype, ptrtype) \
|
||||||
relptr_declare(type, relptrtype); \
|
relptr_declare(type, relptrtype); \
|
||||||
typedef union { type *ptr; relptrtype relptr; } ptrtype;
|
typedef union { type *ptr; relptrtype relptr; } ptrtype
|
||||||
|
|
||||||
/* Pointers that can be absolute or relative */
|
/* Pointers that can be absolute or relative */
|
||||||
/* Use char for DatumPtr so works with Pointer */
|
/* Use char for DatumPtr so works with Pointer */
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -805,6 +805,12 @@ HnswLoadUnvisitedFromDisk(HnswElement element, HnswUnvisited * unvisited, int *u
|
|||||||
if (!found)
|
if (!found)
|
||||||
unvisited[(*unvisitedLength)++].indextid = *indextid;
|
unvisited[(*unvisitedLength)++].indextid = *indextid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_PREFETCH
|
||||||
|
/* TODO limit by get_tablespace_io_concurrency */
|
||||||
|
for (int i = 0; i < *unvisitedLength; i++)
|
||||||
|
PrefetchBuffer(index, MAIN_FORKNUM, ItemPointerGetBlockNumber(&unvisited[i].indextid));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -1393,7 +1399,7 @@ hnsw_halfvec_support(PG_FUNCTION_ARGS)
|
|||||||
};
|
};
|
||||||
|
|
||||||
PG_RETURN_POINTER(&typeInfo);
|
PG_RETURN_POINTER(&typeInfo);
|
||||||
};
|
}
|
||||||
|
|
||||||
FUNCTION_PREFIX PG_FUNCTION_INFO_V1(hnsw_bit_support);
|
FUNCTION_PREFIX PG_FUNCTION_INFO_V1(hnsw_bit_support);
|
||||||
Datum
|
Datum
|
||||||
@@ -1406,7 +1412,7 @@ hnsw_bit_support(PG_FUNCTION_ARGS)
|
|||||||
};
|
};
|
||||||
|
|
||||||
PG_RETURN_POINTER(&typeInfo);
|
PG_RETURN_POINTER(&typeInfo);
|
||||||
};
|
}
|
||||||
|
|
||||||
FUNCTION_PREFIX PG_FUNCTION_INFO_V1(hnsw_sparsevec_support);
|
FUNCTION_PREFIX PG_FUNCTION_INFO_V1(hnsw_sparsevec_support);
|
||||||
Datum
|
Datum
|
||||||
@@ -1419,4 +1425,4 @@ hnsw_sparsevec_support(PG_FUNCTION_ARGS)
|
|||||||
};
|
};
|
||||||
|
|
||||||
PG_RETURN_POINTER(&typeInfo);
|
PG_RETURN_POINTER(&typeInfo);
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
#include "storage/lmgr.h"
|
#include "storage/lmgr.h"
|
||||||
#include "utils/memutils.h"
|
#include "utils/memutils.h"
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= 180000
|
||||||
|
#define vacuum_delay_point() vacuum_delay_point(false)
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check if deleted list contains an index TID
|
* Check if deleted list contains an index TID
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -360,7 +360,7 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
|
|||||||
buildstate->sortdesc = CreateTemplateTupleDesc(3);
|
buildstate->sortdesc = CreateTemplateTupleDesc(3);
|
||||||
TupleDescInitEntry(buildstate->sortdesc, (AttrNumber) 1, "list", INT4OID, -1, 0);
|
TupleDescInitEntry(buildstate->sortdesc, (AttrNumber) 1, "list", INT4OID, -1, 0);
|
||||||
TupleDescInitEntry(buildstate->sortdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0);
|
TupleDescInitEntry(buildstate->sortdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0);
|
||||||
TupleDescInitEntry(buildstate->sortdesc, (AttrNumber) 3, "vector", buildstate->tupdesc->attrs[0].atttypid, -1, 0);
|
TupleDescInitEntry(buildstate->sortdesc, (AttrNumber) 3, "vector", TupleDescAttr(buildstate->tupdesc, 0)->atttypid, -1, 0);
|
||||||
|
|
||||||
buildstate->slot = MakeSingleTupleTableSlot(buildstate->sortdesc, &TTSOpsVirtual);
|
buildstate->slot = MakeSingleTupleTableSlot(buildstate->sortdesc, &TTSOpsVirtual);
|
||||||
|
|
||||||
|
|||||||
@@ -39,16 +39,16 @@ IvfflatInit(void)
|
|||||||
|
|
||||||
DefineCustomIntVariable("ivfflat.probes", "Sets the number of probes",
|
DefineCustomIntVariable("ivfflat.probes", "Sets the number of probes",
|
||||||
"Valid range is 1..lists.", &ivfflat_probes,
|
"Valid range is 1..lists.", &ivfflat_probes,
|
||||||
IVFFLAT_DEFAULT_PROBES, IVFFLAT_MIN_LISTS, IVFFLAT_MAX_LISTS, PGC_USERSET, GUC_EXPLAIN, NULL, NULL, NULL);
|
IVFFLAT_DEFAULT_PROBES, IVFFLAT_MIN_LISTS, IVFFLAT_MAX_LISTS, PGC_USERSET, 0, NULL, NULL, NULL);
|
||||||
|
|
||||||
DefineCustomEnumVariable("ivfflat.iterative_scan", "Sets the mode for iterative scans",
|
DefineCustomEnumVariable("ivfflat.iterative_scan", "Sets the mode for iterative scans",
|
||||||
NULL, &ivfflat_iterative_scan,
|
NULL, &ivfflat_iterative_scan,
|
||||||
IVFFLAT_ITERATIVE_SCAN_OFF, ivfflat_iterative_scan_options, PGC_USERSET, GUC_EXPLAIN, NULL, NULL, NULL);
|
IVFFLAT_ITERATIVE_SCAN_OFF, ivfflat_iterative_scan_options, PGC_USERSET, 0, NULL, NULL, NULL);
|
||||||
|
|
||||||
/* If this is less than probes, probes is used */
|
/* If this is less than probes, probes is used */
|
||||||
DefineCustomIntVariable("ivfflat.max_probes", "Sets the max number of probes for iterative scans",
|
DefineCustomIntVariable("ivfflat.max_probes", "Sets the max number of probes for iterative scans",
|
||||||
NULL, &ivfflat_max_probes,
|
NULL, &ivfflat_max_probes,
|
||||||
IVFFLAT_MAX_LISTS, IVFFLAT_MIN_LISTS, IVFFLAT_MAX_LISTS, PGC_USERSET, GUC_EXPLAIN, NULL, NULL, NULL);
|
IVFFLAT_MAX_LISTS, IVFFLAT_MIN_LISTS, IVFFLAT_MAX_LISTS, PGC_USERSET, 0, NULL, NULL, NULL);
|
||||||
|
|
||||||
MarkGUCPrefixReserved("ivfflat");
|
MarkGUCPrefixReserved("ivfflat");
|
||||||
}
|
}
|
||||||
@@ -186,6 +186,11 @@ ivfflathandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amoptsprocnum = 0;
|
amroutine->amoptsprocnum = 0;
|
||||||
amroutine->amcanorder = false;
|
amroutine->amcanorder = false;
|
||||||
amroutine->amcanorderbyop = true;
|
amroutine->amcanorderbyop = true;
|
||||||
|
#if PG_VERSION_NUM >= 180000
|
||||||
|
amroutine->amcanhash = false;
|
||||||
|
amroutine->amconsistentequality = false;
|
||||||
|
amroutine->amconsistentordering = false;
|
||||||
|
#endif
|
||||||
amroutine->amcanbackward = false; /* can change direction mid-scan */
|
amroutine->amcanbackward = false; /* can change direction mid-scan */
|
||||||
amroutine->amcanunique = false;
|
amroutine->amcanunique = false;
|
||||||
amroutine->amcanmulticol = false;
|
amroutine->amcanmulticol = false;
|
||||||
@@ -218,6 +223,9 @@ ivfflathandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amvacuumcleanup = ivfflatvacuumcleanup;
|
amroutine->amvacuumcleanup = ivfflatvacuumcleanup;
|
||||||
amroutine->amcanreturn = NULL; /* tuple not included in heapsort */
|
amroutine->amcanreturn = NULL; /* tuple not included in heapsort */
|
||||||
amroutine->amcostestimate = ivfflatcostestimate;
|
amroutine->amcostestimate = ivfflatcostestimate;
|
||||||
|
#if PG_VERSION_NUM >= 180000
|
||||||
|
amroutine->amgettreeheight = NULL;
|
||||||
|
#endif
|
||||||
amroutine->amoptions = ivfflatoptions;
|
amroutine->amoptions = ivfflatoptions;
|
||||||
amroutine->amproperty = NULL; /* TODO AMPROP_DISTANCE_ORDERABLE */
|
amroutine->amproperty = NULL; /* TODO AMPROP_DISTANCE_ORDERABLE */
|
||||||
amroutine->ambuildphasename = ivfflatbuildphasename;
|
amroutine->ambuildphasename = ivfflatbuildphasename;
|
||||||
@@ -238,5 +246,10 @@ ivfflathandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->aminitparallelscan = NULL;
|
amroutine->aminitparallelscan = NULL;
|
||||||
amroutine->amparallelrescan = NULL;
|
amroutine->amparallelrescan = NULL;
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= 180000
|
||||||
|
amroutine->amtranslatestrategy = NULL;
|
||||||
|
amroutine->amtranslatecmptype = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
PG_RETURN_POINTER(amroutine);
|
PG_RETURN_POINTER(amroutine);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -355,7 +355,7 @@ ivfflat_halfvec_support(PG_FUNCTION_ARGS)
|
|||||||
};
|
};
|
||||||
|
|
||||||
PG_RETURN_POINTER(&typeInfo);
|
PG_RETURN_POINTER(&typeInfo);
|
||||||
};
|
}
|
||||||
|
|
||||||
FUNCTION_PREFIX PG_FUNCTION_INFO_V1(ivfflat_bit_support);
|
FUNCTION_PREFIX PG_FUNCTION_INFO_V1(ivfflat_bit_support);
|
||||||
Datum
|
Datum
|
||||||
@@ -370,4 +370,4 @@ ivfflat_bit_support(PG_FUNCTION_ARGS)
|
|||||||
};
|
};
|
||||||
|
|
||||||
PG_RETURN_POINTER(&typeInfo);
|
PG_RETURN_POINTER(&typeInfo);
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= 180000
|
||||||
|
#define vacuum_delay_point() vacuum_delay_point(false)
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Bulk delete tuples from the index
|
* Bulk delete tuples from the index
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -123,6 +123,12 @@ SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
|||||||
[0,0,0]
|
[0,0,0]
|
||||||
(3 rows)
|
(3 rows)
|
||||||
|
|
||||||
|
TRUNCATE t;
|
||||||
|
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||||
|
val
|
||||||
|
-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
RESET hnsw.iterative_scan;
|
RESET hnsw.iterative_scan;
|
||||||
RESET hnsw.ef_search;
|
RESET hnsw.ef_search;
|
||||||
DROP TABLE t;
|
DROP TABLE t;
|
||||||
@@ -190,4 +196,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;
|
||||||
|
|||||||
@@ -110,6 +110,15 @@ SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
|||||||
[1,1,1]
|
[1,1,1]
|
||||||
(2 rows)
|
(2 rows)
|
||||||
|
|
||||||
|
TRUNCATE t;
|
||||||
|
NOTICE: ivfflat index created with little data
|
||||||
|
DETAIL: This will cause low recall.
|
||||||
|
HINT: Drop the index until the table has more data.
|
||||||
|
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||||
|
val
|
||||||
|
-----
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
RESET ivfflat.iterative_scan;
|
RESET ivfflat.iterative_scan;
|
||||||
RESET ivfflat.max_probes;
|
RESET ivfflat.max_probes;
|
||||||
DROP TABLE t;
|
DROP TABLE t;
|
||||||
|
|||||||
@@ -70,6 +70,9 @@ SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
|||||||
SET hnsw.iterative_scan = relaxed_order;
|
SET hnsw.iterative_scan = relaxed_order;
|
||||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||||
|
|
||||||
|
TRUNCATE t;
|
||||||
|
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||||
|
|
||||||
RESET hnsw.iterative_scan;
|
RESET hnsw.iterative_scan;
|
||||||
RESET hnsw.ef_search;
|
RESET hnsw.ef_search;
|
||||||
DROP TABLE t;
|
DROP TABLE t;
|
||||||
@@ -109,5 +112,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;
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
|||||||
SET ivfflat.max_probes = 2;
|
SET ivfflat.max_probes = 2;
|
||||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||||
|
|
||||||
|
TRUNCATE t;
|
||||||
|
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
||||||
|
|
||||||
RESET ivfflat.iterative_scan;
|
RESET ivfflat.iterative_scan;
|
||||||
RESET ivfflat.max_probes;
|
RESET ivfflat.max_probes;
|
||||||
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