Compare commits

..

3 Commits

Author SHA1 Message Date
Andrew Kane
3f75886bb2 Switched to runtime check [skip ci] 2024-01-28 22:08:53 -08:00
Andrew Kane
ebf7de0c16 Added comment [skip ci] 2024-01-28 21:35:53 -08:00
Andrew Kane
142a32af96 Update neighbor tuples in-place for HNSW vacuum 2024-01-28 21:27:17 -08:00
32 changed files with 224 additions and 557 deletions

View File

@@ -28,7 +28,7 @@ jobs:
dev-files: true
- run: make
env:
PG_CFLAGS: -DUSE_ASSERT_CHECKING -Wall -Wextra -Werror -Wno-unused-parameter -Wno-sign-compare
PG_CFLAGS: -Wall -Wextra -Werror -Wno-unused-parameter -Wno-sign-compare
- run: |
export PG_CONFIG=`which pg_config`
sudo --preserve-env=PG_CONFIG make install
@@ -49,7 +49,7 @@ jobs:
postgres-version: 14
- run: make
env:
PG_CFLAGS: -DUSE_ASSERT_CHECKING -Wall -Wextra -Werror -Wno-unused-parameter
PG_CFLAGS: -Wall -Wextra -Werror -Wno-unused-parameter
- run: make install
- run: make installcheck
- if: ${{ failure() }}
@@ -60,9 +60,7 @@ jobs:
wget -q https://github.com/postgres/postgres/archive/refs/tags/REL_14_10.tar.gz
tar xf REL_14_10.tar.gz
- run: make prove_installcheck PROVE_FLAGS="-I ./postgres-REL_14_10/src/test/perl" PERL5LIB="/Users/runner/perl5/lib/perl5"
- run: make clean && /usr/local/opt/llvm@15/bin/scan-build --status-bugs make
env:
PG_CFLAGS: -DUSE_ASSERT_CHECKING
- run: make clean && /usr/local/opt/llvm@15/bin/scan-build --status-bugs make PG_CFLAGS="-DUSE_ASSERT_CHECKING"
windows:
runs-on: windows-latest
if: ${{ !startsWith(github.ref_name, 'mac') }}
@@ -73,7 +71,6 @@ jobs:
postgres-version: 14
- run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" && ^
cd %TEMP% && ^
nmake /NOLOGO /F Makefile.win && ^
nmake /NOLOGO /F Makefile.win install && ^
nmake /NOLOGO /F Makefile.win installcheck && ^
@@ -100,15 +97,4 @@ jobs:
sudo -u postgres make installcheck
sudo -u postgres make prove_installcheck
env:
PG_CFLAGS: -DUSE_ASSERT_CHECKING -Wall -Wextra -Werror -Wno-unused-parameter -Wno-sign-compare
valgrind:
if: ${{ !startsWith(github.ref_name, 'mac') && !startsWith(github.ref_name, 'windows') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ankane/setup-postgres-valgrind@v1
with:
postgres-version: 16
- run: make
- run: sudo --preserve-env=PG_CONFIG make install
- run: make installcheck
PG_CFLAGS: -Wall -Wextra -Werror -Wno-unused-parameter -Wno-sign-compare

View File

@@ -1,21 +1,11 @@
## 0.6.2 (2024-03-18)
## 0.6.0 (unreleased)
- Reduced lock contention with parallel HNSW index builds
If upgrading with Postgres 12 or Docker, see [these notes](https://github.com/pgvector/pgvector#060-unreleased).
## 0.6.1 (2024-03-04)
- Fixed error with `ANALYZE` and vectors with different dimensions
- Fixed segmentation fault with `shared_preload_libraries`
- Fixed vector subtraction being marked as commutative
## 0.6.0 (2024-01-29)
If upgrading with Postgres 12 or Docker, see [these notes](https://github.com/pgvector/pgvector#060).
- Added support for parallel index builds for HNSW
- Added validation for GUC parameters
- Changed storage for vector from `extended` to `external`
- Improved performance of HNSW
- Added support for parallel index builds for HNSW
- Added validation for GUC parameters
- Reduced memory usage for HNSW index builds
- Reduced WAL generation for HNSW index builds
- Fixed error with logical replication

View File

@@ -1,4 +1,4 @@
ARG PG_MAJOR=16
ARG PG_MAJOR=15
FROM postgres:$PG_MAJOR
ARG PG_MAJOR

View File

@@ -1,4 +1,4 @@
Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
Portions Copyright (c) 1994, The Regents of the University of California

View File

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

View File

@@ -1,5 +1,5 @@
EXTENSION = vector
EXTVERSION = 0.6.2
EXTVERSION = 0.5.1
MODULE_big = vector
DATA = $(wildcard sql/*--*.sql)
@@ -71,9 +71,11 @@ PG_MAJOR ?= 16
.PHONY: docker
docker:
docker build --pull --no-cache --build-arg PG_MAJOR=$(PG_MAJOR) -t pgvector/pgvector:pg$(PG_MAJOR) -t pgvector/pgvector:$(EXTVERSION)-pg$(PG_MAJOR) .
docker build --pull --no-cache --build-arg PG_MAJOR=$(PG_MAJOR) -t pgvector/pgvector:pg$(PG_MAJOR) .
docker build --build-arg PG_MAJOR=$(PG_MAJOR) -t pgvector/pgvector:$(EXTVERSION)-pg$(PG_MAJOR) .
.PHONY: docker-release
docker-release:
docker buildx build --push --pull --no-cache --platform linux/amd64,linux/arm64 --build-arg PG_MAJOR=$(PG_MAJOR) -t pgvector/pgvector:pg$(PG_MAJOR) -t pgvector/pgvector:$(EXTVERSION)-pg$(PG_MAJOR) .
docker buildx build --push --pull --no-cache --platform linux/amd64,linux/arm64 --build-arg PG_MAJOR=$(PG_MAJOR) -t pgvector/pgvector:pg$(PG_MAJOR) .
docker buildx build --push --platform linux/amd64,linux/arm64 --build-arg PG_MAJOR=$(PG_MAJOR) -t pgvector/pgvector:$(EXTVERSION)-pg$(PG_MAJOR) .

View File

@@ -1,5 +1,5 @@
EXTENSION = vector
EXTVERSION = 0.6.2
EXTVERSION = 0.5.1
OBJS = 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\vector.obj
HEADERS = src\vector.h

208
README.md
View File

@@ -16,19 +16,19 @@ Plus [ACID](https://en.wikipedia.org/wiki/ACID) compliance, point-in-time recove
### Linux and Mac
Compile and install the extension (supports Postgres 12+)
Compile and install the extension (supports Postgres 11+)
```sh
cd /tmp
git clone --branch v0.6.2 https://github.com/pgvector/pgvector.git
git clone --branch v0.5.1 https://github.com/pgvector/pgvector.git
cd pgvector
make
make install # may need sudo
```
See the [installation notes](#installation-notes---linux-and-mac) if you run into issues
See the [installation notes](#installation-notes) if you run into issues
You can also install it with [Docker](#docker), [Homebrew](#homebrew), [PGXN](#pgxn), [APT](#apt), [Yum](#yum), [pkg](#pkg), or [conda-forge](#conda-forge), and it comes preinstalled with [Postgres.app](#postgresapp) and many [hosted providers](#hosted-postgres). There are also instructions for [GitHub Actions](https://github.com/pgvector/setup-pgvector).
You can also install it with [Docker](#docker), [Homebrew](#homebrew), [PGXN](#pgxn), [APT](#apt), [Yum](#yum), or [conda-forge](#conda-forge), and it comes preinstalled with [Postgres.app](#postgresapp) and many [hosted providers](#hosted-postgres). There are also instructions for [GitHub Actions](https://github.com/pgvector/setup-pgvector).
### Windows
@@ -44,15 +44,12 @@ Then use `nmake` to build:
```cmd
set "PGROOT=C:\Program Files\PostgreSQL\16"
cd %TEMP%
git clone --branch v0.6.2 https://github.com/pgvector/pgvector.git
git clone --branch v0.5.1 https://github.com/pgvector/pgvector.git
cd pgvector
nmake /F Makefile.win
nmake /F Makefile.win install
```
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).
## Getting Started
@@ -105,12 +102,6 @@ Insert vectors
INSERT INTO items (embedding) VALUES ('[1,2,3]'), ('[4,5,6]');
```
Or load vectors in bulk using `COPY` ([example](https://github.com/pgvector/pgvector-python/blob/master/examples/bulk_loading.py))
```sql
COPY items (embedding) FROM STDIN WITH (FORMAT BINARY);
```
Upsert vectors
```sql
@@ -273,9 +264,7 @@ HINT: Increase maintenance_work_mem to speed up builds.
Note: Do not set `maintenance_work_mem` so high that it exhausts the memory on the server
Like other index types, its 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)
Starting with 0.6.0 [unreleased], you can also speed up index creation by increasing the number of parallel workers (2 by default)
```sql
SET max_parallel_maintenance_workers = 7; -- plus leader
@@ -413,51 +402,13 @@ You can use [Reciprocal Rank Fusion](https://github.com/pgvector/pgvector-python
## Performance
### Tuning
Use a tool like [PgTune](https://pgtune.leopard.in.ua/) to set initial values for Postgres server parameters. For instance, `shared_buffers` should typically be 25% of the servers memory. You can find the config file with:
```sql
SHOW config_file;
```
And check individual settings with:
```sql
SHOW shared_buffers;
```
Be sure to restart Postgres for changes to take effect.
### Loading
Use `COPY` for bulk loading data ([example](https://github.com/pgvector/pgvector-python/blob/master/examples/bulk_loading.py)).
```sql
COPY items (embedding) FROM STDIN WITH (FORMAT BINARY);
```
Add any indexes *after* loading the initial data for best performance.
### Indexing
See index build time for [HNSW](#index-build-time) and [IVFFlat](#index-build-time-1).
In production environments, create indexes concurrently to avoid blocking writes.
```sql
CREATE INDEX CONCURRENTLY ...
```
### Querying
Use `EXPLAIN ANALYZE` to debug performance.
```sql
EXPLAIN ANALYZE SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 5;
```
#### Exact Search
### Exact Search
To speed up queries without an index, increase `max_parallel_workers_per_gather`.
@@ -471,7 +422,7 @@ If vectors are normalized to length 1 (like [OpenAI embeddings](https://platform
SELECT * FROM items ORDER BY embedding <#> '[3,1,2]' LIMIT 5;
```
#### Approximate Search
### Approximate Search
To speed up queries with an IVFFlat index, increase the number of inverted lists (at the expense of recall).
@@ -479,50 +430,6 @@ To speed up queries with an IVFFlat index, increase the number of inverted lists
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 1000);
```
### Vacuuming
Vacuuming can take a while for HNSW indexes. Speed it up by reindexing first.
```sql
REINDEX INDEX CONCURRENTLY index_name;
VACUUM table_name;
```
## Monitoring
Monitor performance with [pg_stat_statements](https://www.postgresql.org/docs/current/pgstatstatements.html) (be sure to add it to `shared_preload_libraries`).
```sql
CREATE EXTENSION pg_stat_statements;
```
Get the most time-consuming queries with:
```sql
SELECT query, calls, ROUND((total_plan_time + total_exec_time) / calls) AS avg_time_ms,
ROUND((total_plan_time + total_exec_time) / 60000) AS total_time_min
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.
```sql
BEGIN;
SET LOCAL enable_indexscan = off; -- use exact search
SELECT ...
COMMIT;
```
## Scaling
Scale pgvector the same way you scale Postgres.
Scale vertically by increasing memory, CPU, and storage on a single instance. Use existing tools to [tune parameters](#tuning) and [monitor performance](#monitoring).
Scale horizontally with [replicas](https://www.postgresql.org/docs/current/hot-standby.html), or use [Citus](https://github.com/citusdata/citus) or another approach for sharding ([example](https://github.com/pgvector/pgvector-python/blob/master/examples/citus.py)).
## Languages
Use pgvector from any language with a Postgres client. You can even generate and store vectors in one language and query them in another.
@@ -616,18 +523,6 @@ and query with:
SELECT * FROM items ORDER BY embedding::vector(3) <-> '[3,1,2]' LIMIT 5;
```
#### Are binary vectors supported?
You can store binary vectors and perform exact nearest neighbor search by Hamming distance in Postgres without an extension ([example](https://github.com/pgvector/pgvector-python/blob/master/examples/hash_image_search.py)).
```tsql
CREATE TABLE items (id bigserial PRIMARY KEY, embedding bit(3));
INSERT INTO items (embedding) VALUES (B'000'), (B'111');
SELECT * FROM items ORDER BY bit_count(embedding # B'101') LIMIT 5;
```
Indexing is not currently supported.
#### Do indexes need to fit into memory?
No, but like other index types, youll likely see better performance if they do. You can get the size of an index with:
@@ -640,17 +535,7 @@ SELECT pg_size_pretty(pg_relation_size('index_name'));
#### Why isnt a query using an index?
The query needs to have an `ORDER BY` and `LIMIT`, and the `ORDER BY` must be the result of a distance operator, not an expression.
```sql
-- index
ORDER BY embedding <=> '[3,1,2]' LIMIT 5;
-- no index
ORDER BY 1 - (embedding <=> '[3,1,2]') DESC LIMIT 5;
```
You can encourage the planner to use an index for a query with:
The cost estimation in pgvector < 0.4.3 does not always work well with the planner. You can encourage the planner to use an index for a query with:
```sql
BEGIN;
@@ -679,12 +564,6 @@ or choose to store vectors inline:
ALTER TABLE items ALTER COLUMN embedding SET STORAGE PLAIN;
```
#### 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.
Also, note that `NULL` vectors are not indexed (as well as zero vectors for cosine distance).
#### Why are there less results for a query after adding an IVFFlat index?
The index was likely created with too little data for the number of lists. Drop the index until the table has more data.
@@ -693,15 +572,11 @@ The index was likely created with too little data for the number of lists. Drop
DROP INDEX index_name;
```
Results can also be limited by the number of probes (`ivfflat.probes`).
Also, note that `NULL` vectors are not indexed (as well as zero vectors for cosine distance).
## Reference
### Vector Type
Each vector takes `4 * dimensions + 8` bytes of storage. Each element is a single-precision floating-point number (like the `real` type in Postgres), and all elements must be finite (no `NaN`, `Infinity` or `-Infinity`). Vectors can have up to 16,000 dimensions.
Each vector takes `4 * dimensions + 8` bytes of storage. Each element is a single precision floating-point number (like the `real` type in Postgres), and all elements must be finite (no `NaN`, `Infinity` or `-Infinity`). Vectors can have up to 16,000 dimensions.
### Vector Operators
@@ -725,14 +600,14 @@ l1_distance(vector, vector) → double precision | taxicab distance | 0.5.0
vector_dims(vector) → integer | number of dimensions |
vector_norm(vector) → double precision | Euclidean norm |
### Vector Aggregate Functions
### Aggregate Functions
Function | Description | Added
--- | --- | ---
avg(vector) → vector | average |
sum(vector) → vector | sum | 0.5.0
## Installation Notes - Linux and Mac
## Installation Notes
### Postgres Location
@@ -772,44 +647,24 @@ Note: Replace `16` with your Postgres server version
If compilation fails and the output includes `warning: no such sysroot directory` on Mac, reinstall Xcode Command Line Tools.
### Portability
By default, pgvector compiles with `-march=native` on some platforms for best performance. However, this can lead to `Illegal instruction` errors if trying to run the compiled extension on a different machine.
To compile for portability, use:
```sh
make OPTFLAGS=""
```
## Installation Notes - Windows
### Missing Header
If compilation fails with `Cannot open include file: 'postgres.h': No such file or directory`, make sure `PGROOT` is correct.
### Permissions
If installation fails with `Access is denied`, re-run the installation instructions as an administrator.
## Additional Installation Methods
### Docker
Get the [Docker image](https://hub.docker.com/r/pgvector/pgvector) with:
Get the [Docker image](https://hub.docker.com/r/ankane/pgvector) with:
```sh
docker pull pgvector/pgvector:pg16
docker pull ankane/pgvector
```
This adds pgvector to the [Postgres image](https://hub.docker.com/_/postgres) (replace `16` with your Postgres server version, and run it the same way).
This adds pgvector to the [Postgres image](https://hub.docker.com/_/postgres) (run it the same way).
You can also build the image manually:
```sh
git clone --branch v0.6.2 https://github.com/pgvector/pgvector.git
git clone --branch v0.5.1 https://github.com/pgvector/pgvector.git
cd pgvector
docker build --build-arg PG_MAJOR=16 -t myuser/pgvector .
docker build --build-arg PG_MAJOR=15 -t myuser/pgvector .
```
### Homebrew
@@ -852,21 +707,6 @@ sudo dnf install pgvector_16
Note: Replace `16` with your Postgres server version
### pkg
Install the FreeBSD package with:
```sh
pkg install postgresql15-pg_vector
```
or the port with:
```sh
cd /usr/ports/databases/pgvector
make install
```
### conda-forge
With Conda Postgres, install from [conda-forge](https://anaconda.org/conda-forge/pgvector) with:
@@ -901,7 +741,7 @@ SELECT extversion FROM pg_extension WHERE extname = 'vector';
## Upgrade Notes
### 0.6.0
### 0.6.0 [unreleased]
#### Postgres 12
@@ -976,12 +816,6 @@ make installcheck REGRESS=functions # regression test
make prove_installcheck PROVE_TESTS=test/t/001_ivfflat_wal.pl # TAP test
```
To enable assertions:
```sh
make clean && PG_CFLAGS="-DUSE_ASSERT_CHECKING" make && make install
```
To enable benchmarking:
```sh
@@ -994,6 +828,12 @@ To show memory usage:
make clean && PG_CFLAGS="-DHNSW_MEMORY -DIVFFLAT_MEMORY" make && make install
```
To enable assertions:
```sh
make clean && PG_CFLAGS="-DUSE_ASSERT_CHECKING" make && make install
```
To get k-means metrics:
```sh

View File

@@ -1,16 +0,0 @@
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "ALTER EXTENSION vector UPDATE TO '0.6.1'" to load this file. \quit
DROP OPERATOR - (vector, vector);
CREATE OPERATOR - (
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_sub
);
ALTER OPERATOR <= (vector, vector) SET (
RESTRICT = scalarlesel, JOIN = scalarlejoinsel
);
ALTER OPERATOR >= (vector, vector) SET (
RESTRICT = scalargesel, JOIN = scalargejoinsel
);

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.6.2'" to load this file. \quit

View File

@@ -1,7 +1,7 @@
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "CREATE EXTENSION vector" to load this file. \quit
-- vector type
-- type
CREATE TYPE vector;
@@ -29,7 +29,7 @@ CREATE TYPE vector (
STORAGE = external
);
-- vector functions
-- functions
CREATE FUNCTION l2_distance(vector, vector) RETURNS float8
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
@@ -58,7 +58,7 @@ CREATE FUNCTION vector_sub(vector, vector) RETURNS vector
CREATE FUNCTION vector_mul(vector, vector) RETURNS vector
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
-- vector private functions
-- private functions
CREATE FUNCTION vector_lt(vector, vector) RETURNS bool
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
@@ -99,7 +99,7 @@ CREATE FUNCTION vector_avg(double precision[]) RETURNS vector
CREATE FUNCTION vector_combine(double precision[], double precision[]) RETURNS double precision[]
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
-- vector aggregates
-- aggregates
CREATE AGGREGATE avg(vector) (
SFUNC = vector_accum,
@@ -117,7 +117,7 @@ CREATE AGGREGATE sum(vector) (
PARALLEL = SAFE
);
-- vector cast functions
-- cast functions
CREATE FUNCTION vector(vector, integer, boolean) RETURNS vector
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
@@ -137,7 +137,7 @@ CREATE FUNCTION array_to_vector(numeric[], integer, boolean) RETURNS vector
CREATE FUNCTION vector_to_float4(vector, integer, boolean) RETURNS real[]
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
-- vector casts
-- casts
CREATE CAST (vector AS vector)
WITH FUNCTION vector(vector, integer, boolean) AS IMPLICIT;
@@ -157,7 +157,7 @@ CREATE CAST (double precision[] AS vector)
CREATE CAST (numeric[] AS vector)
WITH FUNCTION array_to_vector(numeric[], integer, boolean) AS ASSIGNMENT;
-- vector operators
-- operators
CREATE OPERATOR <-> (
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = l2_distance,
@@ -180,7 +180,8 @@ CREATE OPERATOR + (
);
CREATE OPERATOR - (
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_sub
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_sub,
COMMUTATOR = -
);
CREATE OPERATOR * (
@@ -194,10 +195,11 @@ CREATE OPERATOR < (
RESTRICT = scalarltsel, JOIN = scalarltjoinsel
);
-- should use scalarlesel and scalarlejoinsel, but not supported in Postgres < 11
CREATE OPERATOR <= (
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_le,
COMMUTATOR = >= , NEGATOR = > ,
RESTRICT = scalarlesel, JOIN = scalarlejoinsel
RESTRICT = scalarltsel, JOIN = scalarltjoinsel
);
CREATE OPERATOR = (
@@ -212,10 +214,11 @@ CREATE OPERATOR <> (
RESTRICT = eqsel, JOIN = eqjoinsel
);
-- should use scalargesel and scalargejoinsel, but not supported in Postgres < 11
CREATE OPERATOR >= (
LEFTARG = vector, RIGHTARG = vector, PROCEDURE = vector_ge,
COMMUTATOR = <= , NEGATOR = < ,
RESTRICT = scalargesel, JOIN = scalargejoinsel
RESTRICT = scalargtsel, JOIN = scalargtjoinsel
);
CREATE OPERATOR > (
@@ -240,7 +243,7 @@ CREATE ACCESS METHOD hnsw TYPE INDEX HANDLER hnswhandler;
COMMENT ON ACCESS METHOD hnsw IS 'hnsw index access method';
-- vector opclasses
-- opclasses
CREATE OPERATOR CLASS vector_ops
DEFAULT FOR TYPE vector USING btree AS

View File

@@ -8,7 +8,6 @@
#include "commands/progress.h"
#include "commands/vacuum.h"
#include "hnsw.h"
#include "miscadmin.h"
#include "utils/guc.h"
#include "utils/selfuncs.h"
@@ -29,7 +28,7 @@ static relopt_kind hnsw_relopt_kind;
* this grows bigger, we should use a shmem_request_hook and
* RequestAddinShmemSpace() to pre-reserve space for this.
*/
void
static void
HnswInitLockTranche(void)
{
int *tranche_ids;
@@ -54,8 +53,7 @@ HnswInitLockTranche(void)
void
HnswInit(void)
{
if (!process_shared_preload_libraries_in_progress)
HnswInitLockTranche();
HnswInitLockTranche();
hnsw_relopt_kind = add_reloption_kind();
add_int_reloption(hnsw_relopt_kind, "m", "Max number of connections",

View File

@@ -55,17 +55,11 @@
#define HNSW_UPDATE_ENTRY_GREATER 1
#define HNSW_UPDATE_ENTRY_ALWAYS 2
typedef enum HnswType
{
HNSW_TYPE_VECTOR
} HnswType;
/* Build phases */
/* PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE is 1 */
#define PROGRESS_HNSW_PHASE_LOAD 2
#define HNSW_MAX_SIZE (BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(HnswPageOpaqueData)) - sizeof(ItemIdData))
#define HNSW_TUPLE_ALLOC_SIZE BLCKSZ
#define HNSW_ELEMENT_TUPLE_SIZE(size) MAXALIGN(offsetof(HnswElementTupleData, data) + (size))
#define HNSW_NEIGHBOR_TUPLE_SIZE(level, m) MAXALIGN(offsetof(HnswNeighborTupleData, indextids) + ((level) + 2) * (m) * sizeof(ItemPointerData))
@@ -85,7 +79,7 @@ typedef enum HnswType
#if PG_VERSION_NUM < 130000
#define list_delete_last(list) list_truncate(list, list_length(list) - 1)
#define list_sort(list, cmp) ((list) = list_qsort(list, cmp))
#define list_sort(list, cmp) list_qsort(list, cmp)
#endif
#define HnswIsElementTuple(tup) ((tup)->type == HNSW_ELEMENT_TUPLE_TYPE)
@@ -134,7 +128,7 @@ HnswPtrDeclare(HnswNeighborArray, HnswNeighborArrayRelptr, HnswNeighborArrayPtr)
HnswPtrDeclare(HnswNeighborArrayPtr, HnswNeighborsRelptr, HnswNeighborsPtr);
HnswPtrDeclare(char, DatumRelptr, DatumPtr);
struct HnswElementData
typedef struct HnswElementData
{
HnswElementPtr next;
ItemPointerData heaptids[HNSW_HEAPTIDS];
@@ -149,7 +143,7 @@ struct HnswElementData
BlockNumber neighborPage;
DatumPtr value;
LWLock lock;
};
} HnswElementData;
typedef HnswElementData * HnswElement;
@@ -160,12 +154,12 @@ typedef struct HnswCandidate
bool closer;
} HnswCandidate;
struct HnswNeighborArray
typedef struct HnswNeighborArray
{
int length;
bool closerSet;
HnswCandidate items[FLEXIBLE_ARRAY_MEMBER];
};
} HnswNeighborArray;
typedef struct HnswPairingHeapNode
{
@@ -190,7 +184,6 @@ typedef struct HnswGraph
/* Entry state */
LWLock entryLock;
LWLock entryWaitLock;
HnswElementPtr entryPoint;
/* Allocations state */
@@ -247,7 +240,6 @@ typedef struct HnswBuildState
Relation index;
IndexInfo *indexInfo;
ForkNumber forkNum;
HnswType type;
/* Settings */
int dimensions;
@@ -268,6 +260,7 @@ typedef struct HnswBuildState
HnswGraph *graph;
double ml;
int maxLevel;
Vector *normvec;
/* Memory */
MemoryContext graphCtx;
@@ -361,7 +354,6 @@ typedef struct HnswVacuumState
/* Variables */
struct tidhash_hash *deleted;
BufferAccessStrategy bas;
HnswNeighborTuple ntup;
HnswElementData highestPoint;
/* Memory */
@@ -372,8 +364,7 @@ typedef struct HnswVacuumState
int HnswGetM(Relation index);
int HnswGetEfConstruction(Relation index);
FmgrInfo *HnswOptionalProcInfo(Relation index, uint16 procnum);
HnswType HnswGetType(Relation index);
bool HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, HnswType type);
bool HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result);
Buffer HnswNewBuffer(Relation index, ForkNumber forkNum);
void HnswInitPage(Buffer buf, Page page);
void HnswInit(void);
@@ -396,7 +387,6 @@ void HnswLoadElement(HnswElement element, float *distance, Datum *q, Relation i
void HnswSetElementTuple(char *base, HnswElementTuple etup, HnswElement element);
void HnswUpdateConnection(char *base, HnswElement element, HnswCandidate * hc, int lm, int lc, int *updateIdx, Relation index, FmgrInfo *procinfo, Oid collation);
void HnswLoadNeighbors(HnswElement element, Relation index, int m);
void HnswInitLockTranche(void);
PGDLLEXPORT void HnswParallelBuildMain(dsm_segment *seg, shm_toc *toc);
/* Index access methods */

View File

@@ -148,6 +148,7 @@ CreateGraphPages(HnswBuildState * buildstate)
{
Relation index = buildstate->index;
ForkNumber forkNum = buildstate->forkNum;
Size etupAllocSize;
Size maxSize;
HnswElementTuple etup;
HnswNeighborTuple ntup;
@@ -159,11 +160,12 @@ CreateGraphPages(HnswBuildState * buildstate)
char *base = buildstate->hnswarea;
/* Calculate sizes */
etupAllocSize = BLCKSZ;
maxSize = HNSW_MAX_SIZE;
/* Allocate once */
etup = palloc0(HNSW_TUPLE_ALLOC_SIZE);
ntup = palloc0(HNSW_TUPLE_ALLOC_SIZE);
etup = palloc0(etupAllocSize);
ntup = palloc0(BLCKSZ);
/* Prepare first page */
buf = HnswNewBuffer(index, forkNum);
@@ -176,13 +178,13 @@ CreateGraphPages(HnswBuildState * buildstate)
Size etupSize;
Size ntupSize;
Size combinedSize;
Pointer valuePtr = HnswPtrAccess(base, element->value);
void *valuePtr = HnswPtrAccess(base, element->value);
/* Update iterator */
iter = element->next;
/* Zero memory for each element */
MemSet(etup, 0, HNSW_TUPLE_ALLOC_SIZE);
MemSet(etup, 0, etupAllocSize);
/* Calculate sizes */
etupSize = HNSW_ELEMENT_TUPLE_SIZE(VARSIZE_ANY(valuePtr));
@@ -190,7 +192,7 @@ CreateGraphPages(HnswBuildState * buildstate)
combinedSize = etupSize + ntupSize + sizeof(ItemIdData);
/* Initial size check */
if (etupSize > HNSW_TUPLE_ALLOC_SIZE)
if (etupSize > etupAllocSize)
elog(ERROR, "index tuple too large");
HnswSetElementTuple(base, etup, element);
@@ -252,43 +254,38 @@ WriteNeighborTuples(HnswBuildState * buildstate)
int m = buildstate->m;
HnswElementPtr iter = buildstate->graph->head;
char *base = buildstate->hnswarea;
HnswNeighborTuple ntup;
/* Allocate once */
ntup = palloc0(HNSW_TUPLE_ALLOC_SIZE);
while (!HnswPtrIsNull(base, iter))
{
HnswElement element = HnswPtrAccess(base, iter);
HnswElement e = HnswPtrAccess(base, iter);
Buffer buf;
Page page;
Size ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(element->level, m);
ItemId itemid;
HnswNeighborTuple ntup;
/* Update iterator */
iter = element->next;
/* Zero memory for each element */
MemSet(ntup, 0, HNSW_TUPLE_ALLOC_SIZE);
iter = e->next;
/* Can take a while, so ensure we can interrupt */
/* Needs to be called when no buffer locks are held */
CHECK_FOR_INTERRUPTS();
buf = ReadBufferExtended(index, forkNum, element->neighborPage, RBM_NORMAL, NULL);
buf = ReadBufferExtended(index, forkNum, e->neighborPage, RBM_NORMAL, NULL);
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
page = BufferGetPage(buf);
itemid = PageGetItemId(page, e->neighborOffno);
ntup = (HnswNeighborTuple) PageGetItem(page, itemid);
HnswSetNeighborTuple(base, ntup, element, m);
/* Check expected size */
Assert(ItemIdGetLength(itemid) == HNSW_NEIGHBOR_TUPLE_SIZE(e->level, m));
if (!PageIndexTupleOverwrite(page, element->neighborOffno, (Item) ntup, ntupSize))
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
/* Update page in-place */
HnswSetNeighborTuple(base, ntup, e, m);
/* Commit */
MarkBufferDirty(buf);
UnlockReleaseBuffer(buf);
}
pfree(ntup);
}
/*
@@ -431,15 +428,10 @@ InsertTupleInMemory(HnswBuildState * buildstate, HnswElement element)
HnswGraph *graph = buildstate->graph;
HnswElement entryPoint;
LWLock *entryLock = &graph->entryLock;
LWLock *entryWaitLock = &graph->entryWaitLock;
int efConstruction = buildstate->efConstruction;
int m = buildstate->m;
char *base = buildstate->hnswarea;
/* Wait if another process needs exclusive lock on entry lock */
LWLockAcquire(entryWaitLock, LW_EXCLUSIVE);
LWLockRelease(entryWaitLock);
/* Get entry point */
LWLockAcquire(entryLock, LW_SHARED);
entryPoint = HnswPtrAccess(base, graph->entryPoint);
@@ -450,10 +442,8 @@ InsertTupleInMemory(HnswBuildState * buildstate, HnswElement element)
/* Release shared lock */
LWLockRelease(entryLock);
/* Tell other processes to wait and get exclusive lock */
LWLockAcquire(entryWaitLock, LW_EXCLUSIVE);
/* Get exclusive lock */
LWLockAcquire(entryLock, LW_EXCLUSIVE);
LWLockRelease(entryWaitLock);
/* Get latest entry point after lock is acquired */
entryPoint = HnswPtrAccess(base, graph->entryPoint);
@@ -489,7 +479,7 @@ InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heaptid, Hn
/* Normalize if needed */
if (buildstate->normprocinfo != NULL)
{
if (!HnswNormValue(buildstate->normprocinfo, buildstate->collation, &value, buildstate->type))
if (!HnswNormValue(buildstate->normprocinfo, buildstate->collation, &value, buildstate->normvec))
return false;
}
@@ -608,9 +598,6 @@ BuildCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
static void
InitGraph(HnswGraph * graph, char *base, long memoryTotal)
{
/* Initialize the lock tranche if needed */
HnswInitLockTranche();
HnswPtrStore(base, graph->head, (HnswElement) NULL);
HnswPtrStore(base, graph->entryPoint, (HnswElement) NULL);
graph->memoryUsed = 0;
@@ -619,7 +606,6 @@ InitGraph(HnswGraph * graph, char *base, long memoryTotal)
graph->indtuples = 0;
SpinLockInit(&graph->lock);
LWLockInitialize(&graph->entryLock, hnsw_lock_tranche_id);
LWLockInitialize(&graph->entryWaitLock, hnsw_lock_tranche_id);
LWLockInitialize(&graph->allocatorLock, hnsw_lock_tranche_id);
LWLockInitialize(&graph->flushLock, hnsw_lock_tranche_id);
}
@@ -671,13 +657,10 @@ HnswSharedMemoryAlloc(Size size, void *state)
static void
InitBuildState(HnswBuildState * buildstate, Relation heap, Relation index, IndexInfo *indexInfo, ForkNumber forkNum)
{
int maxDimensions = HNSW_MAX_DIM;
buildstate->heap = heap;
buildstate->index = index;
buildstate->indexInfo = indexInfo;
buildstate->forkNum = forkNum;
buildstate->type = HnswGetType(index);
buildstate->m = HnswGetM(index);
buildstate->efConstruction = HnswGetEfConstruction(index);
@@ -687,8 +670,8 @@ InitBuildState(HnswBuildState * buildstate, Relation heap, Relation index, Index
if (buildstate->dimensions < 0)
elog(ERROR, "column does not have dimensions");
if (buildstate->dimensions > maxDimensions)
elog(ERROR, "column cannot have more than %d dimensions for hnsw index", maxDimensions);
if (buildstate->dimensions > HNSW_MAX_DIM)
elog(ERROR, "column cannot have more than %d dimensions for hnsw index", HNSW_MAX_DIM);
if (buildstate->efConstruction < 2 * buildstate->m)
elog(ERROR, "ef_construction must be greater than or equal to 2 * m");
@@ -706,6 +689,9 @@ InitBuildState(HnswBuildState * buildstate, Relation heap, Relation index, Index
buildstate->ml = HnswGetMl(buildstate->m);
buildstate->maxLevel = HnswGetMaxLevel(buildstate->m);
/* Reuse for each tuple */
buildstate->normvec = InitVector(buildstate->dimensions);
buildstate->graphCtx = GenerationContextCreate(CurrentMemoryContext,
"Hnsw build graph context",
#if PG_VERSION_NUM >= 150000
@@ -729,6 +715,7 @@ InitBuildState(HnswBuildState * buildstate, Relation heap, Relation index, Index
static void
FreeBuildState(HnswBuildState * buildstate)
{
pfree(buildstate->normvec);
MemoryContextDelete(buildstate->graphCtx);
MemoryContextDelete(buildstate->tmpCtx);
}
@@ -984,14 +971,6 @@ HnswBeginParallel(HnswBuildState * buildstate, bool isconcurrent, int request)
/* Report less than allocated so never fails */
InitGraph(&hnswshared->graphData, hnswarea, esthnswarea - 1024 * 1024);
/*
* Avoid base address for relptr for Postgres < 14.5
* https://github.com/postgres/postgres/commit/7201cd18627afc64850537806da7f22150d1a83b
*/
#if PG_VERSION_NUM < 140005
hnswshared->graphData.memoryUsed += MAXALIGN(1);
#endif
shm_toc_insert(pcxt->toc, PARALLEL_KEY_HNSW_SHARED, hnswshared);
shm_toc_insert(pcxt->toc, PARALLEL_KEY_HNSW_AREA, hnswarea);

View File

@@ -355,7 +355,9 @@ HnswUpdateNeighborsOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, Hns
Buffer buf;
Page page;
GenericXLogState *state;
ItemId itemid;
HnswNeighborTuple ntup;
Size ntupSize;
int idx = -1;
int startIdx;
HnswElement neighborElement = HnswPtrAccess(base, hc->element);
@@ -394,7 +396,9 @@ HnswUpdateNeighborsOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, Hns
}
/* Get tuple */
ntup = (HnswNeighborTuple) PageGetItem(page, PageGetItemId(page, offno));
itemid = PageGetItemId(page, offno);
ntup = (HnswNeighborTuple) PageGetItem(page, itemid);
ntupSize = ItemIdGetLength(itemid);
/* Calculate index for update */
startIdx = (neighborElement->level - lc) * m;
@@ -423,9 +427,13 @@ HnswUpdateNeighborsOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, Hns
{
ItemPointer indextid = &ntup->indextids[idx];
/* Update neighbor on the buffer */
/* Update neighbor */
ItemPointerSet(indextid, e->blkno, e->offno);
/* Overwrite tuple */
if (!PageIndexTupleOverwrite(page, offno, (Item) ntup, ntupSize))
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
/* Commit */
if (building)
MarkBufferDirty(buf);
@@ -449,7 +457,9 @@ AddDuplicateOnDisk(Relation index, HnswElement element, HnswElement dup, bool bu
Buffer buf;
Page page;
GenericXLogState *state;
ItemId itemid;
HnswElementTuple etup;
Size etupSize;
int i;
/* Read page */
@@ -467,7 +477,9 @@ AddDuplicateOnDisk(Relation index, HnswElement element, HnswElement dup, bool bu
}
/* Find space */
etup = (HnswElementTuple) PageGetItem(page, PageGetItemId(page, dup->offno));
itemid = PageGetItemId(page, dup->offno);
etup = (HnswElementTuple) PageGetItem(page, itemid);
etupSize = ItemIdGetLength(itemid);
for (i = 0; i < HNSW_HEAPTIDS; i++)
{
if (!ItemPointerIsValid(&etup->heaptids[i]))
@@ -483,9 +495,13 @@ AddDuplicateOnDisk(Relation index, HnswElement element, HnswElement dup, bool bu
return false;
}
/* Add heap TID, modifying the tuple on the page directly */
/* Add heap TID */
etup->heaptids[i] = element->heaptids[0];
/* Overwrite tuple */
if (!PageIndexTupleOverwrite(page, dup->offno, (Item) etup, etupSize))
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
/* Commit */
if (building)
MarkBufferDirty(buf);
@@ -622,7 +638,7 @@ HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_ti
normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC);
if (normprocinfo != NULL)
{
if (!HnswNormValue(normprocinfo, collation, &value, HnswGetType(index)))
if (!HnswNormValue(normprocinfo, collation, &value, NULL))
return;
}

View File

@@ -40,6 +40,29 @@ GetScanItems(IndexScanDesc scan, Datum q)
return HnswSearchLayer(base, q, ep, hnsw_ef_search, 0, index, procinfo, collation, m, false, NULL);
}
/*
* Get dimensions from metapage
*/
static int
GetDimensions(Relation index)
{
Buffer buf;
Page page;
HnswMetaPage metap;
int dimensions;
buf = ReadBuffer(index, HNSW_METAPAGE_BLKNO);
LockBuffer(buf, BUFFER_LOCK_SHARE);
page = BufferGetPage(buf);
metap = HnswPageGetMeta(page);
dimensions = metap->dimensions;
UnlockReleaseBuffer(buf);
return dimensions;
}
/*
* Get scan value
*/
@@ -50,7 +73,7 @@ GetScanValue(IndexScanDesc scan)
Datum value;
if (scan->orderByData->sk_flags & SK_ISNULL)
value = PointerGetDatum(NULL);
value = PointerGetDatum(InitVector(GetDimensions(scan->indexRelation)));
else
{
value = scan->orderByData->sk_argument;
@@ -61,7 +84,7 @@ GetScanValue(IndexScanDesc scan)
/* Fine if normalization fails */
if (so->normprocinfo != NULL)
HnswNormValue(so->normprocinfo, so->collation, &value, HnswGetType(scan->indexRelation));
HnswNormValue(so->normprocinfo, so->collation, &value, NULL);
}
return value;

View File

@@ -149,15 +149,6 @@ HnswOptionalProcInfo(Relation index, uint16 procnum)
return index_getprocinfo(index, 1, procnum);
}
/*
* Get vector type
*/
HnswType
HnswGetType(Relation index)
{
return HNSW_TYPE_VECTOR;
}
/*
* Divide by the norm
*
@@ -167,25 +158,21 @@ HnswGetType(Relation index)
* if it's different than the original value
*/
bool
HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, HnswType type)
HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result)
{
double norm = DatumGetFloat8(FunctionCall1Coll(procinfo, collation, *value));
if (norm > 0)
{
/* TODO Remove vector-specific code */
if (type == HNSW_TYPE_VECTOR)
{
Vector *v = DatumGetVector(*value);
Vector *result = InitVector(v->dim);
Vector *v = DatumGetVector(*value);
for (int i = 0; i < v->dim; i++)
result->x[i] = v->x[i] / norm;
if (result == NULL)
result = InitVector(v->dim);
*value = PointerGetDatum(result);
}
else
elog(ERROR, "Unsupported type");
for (int i = 0; i < v->dim; i++)
result->x[i] = v->x[i] / norm;
*value = PointerGetDatum(result);
return true;
}
@@ -588,12 +575,7 @@ HnswLoadElement(HnswElement element, float *distance, Datum *q, Relation index,
/* Calculate distance */
if (distance != NULL)
{
if (DatumGetPointer(*q) == NULL)
*distance = 0;
else
*distance = (float) DatumGetFloat8(FunctionCall2Coll(procinfo, collation, *q, PointerGetDatum(&etup->data)));
}
*distance = (float) DatumGetFloat8(FunctionCall2Coll(procinfo, collation, *q, PointerGetDatum(&etup->data)));
UnlockReleaseBuffer(buf);
}
@@ -878,15 +860,12 @@ HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, F
static int
#if PG_VERSION_NUM >= 130000
CompareCandidateDistances(const ListCell *a, const ListCell *b)
{
HnswCandidate *hca = lfirst(a);
HnswCandidate *hcb = lfirst(b);
#else
CompareCandidateDistances(const void *a, const void *b)
{
HnswCandidate *hca = lfirst(*(ListCell **) a);
HnswCandidate *hcb = lfirst(*(ListCell **) b);
#endif
{
HnswCandidate *hca = lfirst((ListCell *) a);
HnswCandidate *hcb = lfirst((ListCell *) b);
if (hca->distance < hcb->distance)
return 1;
@@ -909,15 +888,12 @@ CompareCandidateDistances(const void *a, const void *b)
static int
#if PG_VERSION_NUM >= 130000
CompareCandidateDistancesOffset(const ListCell *a, const ListCell *b)
{
HnswCandidate *hca = lfirst(a);
HnswCandidate *hcb = lfirst(b);
#else
CompareCandidateDistancesOffset(const void *a, const void *b)
{
HnswCandidate *hca = lfirst(*(ListCell **) a);
HnswCandidate *hcb = lfirst(*(ListCell **) b);
#endif
{
HnswCandidate *hca = lfirst((ListCell *) a);
HnswCandidate *hcb = lfirst((ListCell *) b);
if (hca->distance < hcb->distance)
return 1;
@@ -976,9 +952,7 @@ SelectNeighbors(char *base, List *c, int lm, int lc, FmgrInfo *procinfo, Oid col
{
List *r = NIL;
List *w = list_copy(c);
HnswCandidate **wd;
int wdlen = 0;
int wdoff = 0;
pairingheap *wd;
HnswNeighborArray *neighbors = HnswGetNeighbors(base, e2, lc);
bool mustCalculate = !neighbors->closerSet;
List *added = NIL;
@@ -987,7 +961,7 @@ SelectNeighbors(char *base, List *c, int lm, int lc, FmgrInfo *procinfo, Oid col
if (list_length(w) <= lm)
return w;
wd = palloc(sizeof(HnswCandidate *) * list_length(w));
wd = pairingheap_allocate(CompareNearestCandidates, NULL);
/* Ensure order of candidates is deterministic for closer caching */
if (sortCandidates)
@@ -1053,21 +1027,21 @@ SelectNeighbors(char *base, List *c, int lm, int lc, FmgrInfo *procinfo, Oid col
if (e->closer)
r = lappend(r, e);
else
wd[wdlen++] = e;
pairingheap_add(wd, &(CreatePairingHeapNode(e)->ph_node));
}
/* Cached value can only be used in future if sorted deterministically */
neighbors->closerSet = sortCandidates;
/* Keep pruned connections */
while (wdoff < wdlen && list_length(r) < lm)
r = lappend(r, wd[wdoff++]);
while (!pairingheap_is_empty(wd) && list_length(r) < lm)
r = lappend(r, ((HnswPairingHeapNode *) pairingheap_remove_first(wd))->inner);
/* Return pruned for update connections */
if (pruned != NULL)
{
if (wdoff < wdlen)
*pruned = wd[wdoff];
if (!pairingheap_is_empty(wd))
*pruned = ((HnswPairingHeapNode *) pairingheap_first(wd))->inner;
else
*pruned = linitial(w);
}

View File

@@ -60,7 +60,8 @@ RemoveHeapTids(HnswVacuumState * vacuumstate)
/* Iterate over nodes */
for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
{
HnswElementTuple etup = (HnswElementTuple) PageGetItem(page, PageGetItemId(page, offno));
ItemId itemid = PageGetItemId(page, offno);
HnswElementTuple etup = (HnswElementTuple) PageGetItem(page, itemid);
int idx = 0;
bool itemUpdated = false;
@@ -91,10 +92,15 @@ RemoveHeapTids(HnswVacuumState * vacuumstate)
if (itemUpdated)
{
Size etupSize = ItemIdGetLength(itemid);
/* Mark rest as invalid */
for (int i = idx; i < HNSW_HEAPTIDS; i++)
ItemPointerSetInvalid(&etup->heaptids[i]);
if (!PageIndexTupleOverwrite(page, offno, (Item) etup, etupSize))
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
updated = true;
}
}
@@ -192,8 +198,8 @@ RepairGraphElement(HnswVacuumState * vacuumstate, HnswElement element, HnswEleme
FmgrInfo *procinfo = vacuumstate->procinfo;
Oid collation = vacuumstate->collation;
BufferAccessStrategy bas = vacuumstate->bas;
HnswNeighborTuple ntup = vacuumstate->ntup;
Size ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(element->level, m);
ItemId itemid;
HnswNeighborTuple ntup;
char *base = NULL;
/* Skip if element is entry point */
@@ -207,23 +213,23 @@ RepairGraphElement(HnswVacuumState * vacuumstate, HnswElement element, HnswEleme
/* Find neighbors for element, skipping itself */
HnswFindElementNeighbors(base, element, entryPoint, index, procinfo, collation, m, efConstruction, true);
/* Zero memory for each element */
MemSet(ntup, 0, HNSW_TUPLE_ALLOC_SIZE);
/* Update neighbor tuple */
/* Do this before getting page to minimize locking */
HnswSetNeighborTuple(base, ntup, element, m);
/* Get neighbor page */
buf = ReadBufferExtended(index, MAIN_FORKNUM, element->neighborPage, RBM_NORMAL, bas);
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
state = GenericXLogStart(index);
page = GenericXLogRegisterBuffer(state, buf, 0);
/* Overwrite tuple */
if (!PageIndexTupleOverwrite(page, element->neighborOffno, (Item) ntup, ntupSize))
/* Get neighbor tuple */
itemid = PageGetItemId(page, element->neighborOffno);
ntup = (HnswNeighborTuple) PageGetItem(page, itemid);
/* Check expected size */
if (ItemIdGetLength(itemid) != HNSW_NEIGHBOR_TUPLE_SIZE(element->level, m))
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
/* Update page in-place */
HnswSetNeighborTuple(base, ntup, element, m);
/* Commit */
GenericXLogFinish(state);
UnlockReleaseBuffer(buf);
@@ -476,8 +482,11 @@ MarkDeleted(HnswVacuumState * vacuumstate)
/* Update element and neighbors together */
for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
{
HnswElementTuple etup = (HnswElementTuple) PageGetItem(page, PageGetItemId(page, offno));
ItemId itemid = PageGetItemId(page, offno);
HnswElementTuple etup = (HnswElementTuple) PageGetItem(page, itemid);
HnswNeighborTuple ntup;
Size etupSize;
Size ntupSize;
Buffer nbuf;
Page npage;
BlockNumber neighborPage;
@@ -501,6 +510,10 @@ MarkDeleted(HnswVacuumState * vacuumstate)
if (ItemPointerIsValid(&etup->heaptids[0]))
continue;
/* Calculate sizes */
etupSize = ItemIdGetLength(itemid);
ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(etup->level, vacuumstate->m);
/* Get neighbor page */
neighborPage = ItemPointerGetBlockNumber(&etup->neighbortid);
neighborOffno = ItemPointerGetOffsetNumber(&etup->neighbortid);
@@ -527,10 +540,13 @@ MarkDeleted(HnswVacuumState * vacuumstate)
for (int i = 0; i < ntup->count; i++)
ItemPointerSetInvalid(&ntup->indextids[i]);
/*
* We modified the tuples in place, no need to call
* PageIndexTupleOverwrite
*/
/* Overwrite element tuple */
if (!PageIndexTupleOverwrite(page, offno, (Item) etup, etupSize))
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
/* Overwrite neighbor tuple */
if (!PageIndexTupleOverwrite(npage, neighborOffno, (Item) ntup, ntupSize))
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
/* Commit */
GenericXLogFinish(state);
@@ -575,7 +591,6 @@ InitVacuumState(HnswVacuumState * vacuumstate, IndexVacuumInfo *info, IndexBulkD
vacuumstate->bas = GetAccessStrategy(BAS_BULKREAD);
vacuumstate->procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC);
vacuumstate->collation = index->rd_indcollation[0];
vacuumstate->ntup = palloc0(HNSW_TUPLE_ALLOC_SIZE);
vacuumstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
"Hnsw vacuum temporary context",
ALLOCSET_DEFAULT_SIZES);
@@ -595,7 +610,6 @@ FreeVacuumState(HnswVacuumState * vacuumstate)
{
tidhash_destroy(vacuumstate->deleted);
FreeAccessStrategy(vacuumstate->bas);
pfree(vacuumstate->ntup);
MemoryContextDelete(vacuumstate->tmpCtx);
}

View File

@@ -57,7 +57,7 @@ AddSample(Datum *values, IvfflatBuildState * buildstate)
*/
if (buildstate->kmeansnormprocinfo != NULL)
{
if (!IvfflatNormValue(buildstate->kmeansnormprocinfo, buildstate->collation, &value))
if (!IvfflatNormValue(buildstate->kmeansnormprocinfo, buildstate->collation, &value, buildstate->normvec))
return;
}
@@ -105,7 +105,7 @@ SampleCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
/* Add sample */
AddSample(values, buildstate);
AddSample(values, state);
/* Reset memory context */
MemoryContextSwitchTo(oldCtx);
@@ -153,7 +153,7 @@ AddTupleToSort(Relation index, ItemPointer tid, Datum *values, IvfflatBuildState
/* Normalize if needed */
if (buildstate->normprocinfo != NULL)
{
if (!IvfflatNormValue(buildstate->normprocinfo, buildstate->collation, &value))
if (!IvfflatNormValue(buildstate->normprocinfo, buildstate->collation, &value, buildstate->normvec))
return;
}
@@ -356,6 +356,9 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
buildstate->centers = VectorArrayInit(buildstate->lists, buildstate->dimensions);
buildstate->listInfo = palloc(sizeof(ListInfo) * buildstate->lists);
/* Reuse for each tuple */
buildstate->normvec = InitVector(buildstate->dimensions);
buildstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
"Ivfflat build temporary context",
ALLOCSET_DEFAULT_SIZES);
@@ -377,6 +380,7 @@ FreeBuildState(IvfflatBuildState * buildstate)
{
VectorArrayFree(buildstate->centers);
pfree(buildstate->listInfo);
pfree(buildstate->normvec);
#ifdef IVFFLAT_KMEANS_DEBUG
pfree(buildstate->listSums);

View File

@@ -172,6 +172,7 @@ typedef struct IvfflatBuildState
VectorArray samples;
VectorArray centers;
ListInfo *listInfo;
Vector *normvec;
#ifdef IVFFLAT_KMEANS_DEBUG
double inertia;
@@ -266,7 +267,7 @@ void VectorArrayFree(VectorArray arr);
void PrintVectorArray(char *msg, VectorArray arr);
void IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers);
FmgrInfo *IvfflatOptionalProcInfo(Relation index, uint16 procnum);
bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value);
bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result);
int IvfflatGetLists(Relation index);
void IvfflatGetMetaPageInfo(Relation index, int *lists, int *dimensions);
void IvfflatUpdateList(Relation index, ListInfo listInfo, BlockNumber insertPage, BlockNumber originalInsertPage, BlockNumber startPage, ForkNumber forkNum);

View File

@@ -85,7 +85,7 @@ InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, R
normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC);
if (normprocinfo != NULL)
{
if (!IvfflatNormValue(normprocinfo, index->rd_indcollation[0], &value))
if (!IvfflatNormValue(normprocinfo, index->rd_indcollation[0], &value, NULL))
return;
}

View File

@@ -293,7 +293,7 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
/* Fine if normalization fails */
if (so->normprocinfo != NULL)
IvfflatNormValue(so->normprocinfo, so->collation, &value);
IvfflatNormValue(so->normprocinfo, so->collation, &value, NULL);
}
IvfflatBench("GetScanLists", GetScanLists(scan, value));

View File

@@ -75,14 +75,16 @@ IvfflatOptionalProcInfo(Relation index, uint16 procnum)
* if it's different than the original value
*/
bool
IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value)
IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result)
{
double norm = DatumGetFloat8(FunctionCall1Coll(procinfo, collation, *value));
if (norm > 0)
{
Vector *v = DatumGetVector(*value);
Vector *result = InitVector(v->dim);
if (result == NULL)
result = InitVector(v->dim);
for (int i = 0; i < v->dim; i++)
result->x[i] = v->x[i] / norm;

View File

@@ -197,8 +197,6 @@ vector_in(PG_FUNCTION_ARGS)
while (pt != NULL && *stringEnd != ']')
{
float val;
if (dim == VECTOR_MAX_DIM)
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
@@ -214,23 +212,15 @@ vector_in(PG_FUNCTION_ARGS)
errmsg("invalid input syntax for type vector: \"%s\"", lit)));
/* Use strtof like float4in to avoid a double-rounding problem */
errno = 0;
val = strtof(pt, &stringEnd);
x[dim] = strtof(pt, &stringEnd);
CheckElement(x[dim]);
dim++;
if (stringEnd == pt)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type vector: \"%s\"", lit)));
/* Check for range error like float4in */
if (errno == ERANGE && (val == 0 || isinf(val)))
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"%s\" is out of range for type vector", pt)));
CheckElement(val);
x[dim++] = val;
while (vector_isspace(*stringEnd))
stringEnd++;
@@ -876,10 +866,9 @@ vector_mul(PG_FUNCTION_ARGS)
int
vector_cmp_internal(Vector * a, Vector * b)
{
int dim = Min(a->dim, b->dim);
CheckDims(a, b);
/* Check values before dimensions to be consistent with Postgres arrays */
for (int i = 0; i < dim; i++)
for (int i = 0; i < a->dim; i++)
{
if (a->x[i] < b->x[i])
return -1;
@@ -887,13 +876,6 @@ vector_cmp_internal(Vector * a, Vector * b)
if (a->x[i] > b->x[i])
return 1;
}
if (a->dim < b->dim)
return -1;
if (a->dim > b->dim)
return 1;
return 0;
}
@@ -907,9 +889,6 @@ vector_lt(PG_FUNCTION_ARGS)
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
/* TODO Remove in 0.7.0 */
CheckDims(a, b);
PG_RETURN_BOOL(vector_cmp_internal(a, b) < 0);
}
@@ -923,9 +902,6 @@ vector_le(PG_FUNCTION_ARGS)
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
/* TODO Remove in 0.7.0 */
CheckDims(a, b);
PG_RETURN_BOOL(vector_cmp_internal(a, b) <= 0);
}
@@ -939,9 +915,6 @@ vector_eq(PG_FUNCTION_ARGS)
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
/* TODO Remove in 0.7.0 */
CheckDims(a, b);
PG_RETURN_BOOL(vector_cmp_internal(a, b) == 0);
}
@@ -955,9 +928,6 @@ vector_ne(PG_FUNCTION_ARGS)
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
/* TODO Remove in 0.7.0 */
CheckDims(a, b);
PG_RETURN_BOOL(vector_cmp_internal(a, b) != 0);
}
@@ -971,9 +941,6 @@ vector_ge(PG_FUNCTION_ARGS)
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
/* TODO Remove in 0.7.0 */
CheckDims(a, b);
PG_RETURN_BOOL(vector_cmp_internal(a, b) >= 0);
}
@@ -987,9 +954,6 @@ vector_gt(PG_FUNCTION_ARGS)
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
/* TODO Remove in 0.7.0 */
CheckDims(a, b);
PG_RETURN_BOOL(vector_cmp_internal(a, b) > 0);
}

View File

@@ -24,56 +24,6 @@ SELECT '[1e37]'::vector * '[1e37]';
ERROR: value out of range: overflow
SELECT '[1e-37]'::vector * '[1e-37]';
ERROR: value out of range: underflow
SELECT '[1,2,3]'::vector = '[1,2,3]';
?column?
----------
t
(1 row)
SELECT '[1,2,3]'::vector = '[1,2]';
ERROR: different vector dimensions 3 and 2
SELECT vector_cmp('[1,2,3]', '[1,2,3]');
vector_cmp
------------
0
(1 row)
SELECT vector_cmp('[1,2,3]', '[0,0,0]');
vector_cmp
------------
1
(1 row)
SELECT vector_cmp('[0,0,0]', '[1,2,3]');
vector_cmp
------------
-1
(1 row)
SELECT vector_cmp('[1,2]', '[1,2,3]');
vector_cmp
------------
-1
(1 row)
SELECT vector_cmp('[1,2,3]', '[1,2]');
vector_cmp
------------
1
(1 row)
SELECT vector_cmp('[1,2]', '[2,3,4]');
vector_cmp
------------
-1
(1 row)
SELECT vector_cmp('[2,3]', '[1,2,3]');
vector_cmp
------------
1
(1 row)
SELECT vector_dims('[1,2,3]');
vector_dims
-------------

View File

@@ -12,11 +12,14 @@ SELECT * FROM t ORDER BY val <-> '[3,3,3]';
[0,0,0]
(4 rows)
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <-> (SELECT NULL::vector)) t2;
count
-------
4
(1 row)
SELECT * FROM t ORDER BY val <-> (SELECT NULL::vector);
val
---------
[0,0,0]
[1,1,1]
[1,2,3]
[1,2,4]
(4 rows)
SELECT COUNT(*) FROM t;
count

View File

@@ -63,21 +63,9 @@ SELECT '[1.5e-38,-1.5e-38]'::vector;
(1 row)
SELECT '[4e38,1]'::vector;
ERROR: "4e38" is out of range for type vector
ERROR: infinite value not allowed in vector
LINE 1: SELECT '[4e38,1]'::vector;
^
SELECT '[-4e38,1]'::vector;
ERROR: "-4e38" is out of range for type vector
LINE 1: SELECT '[-4e38,1]'::vector;
^
SELECT '[1e-46,1]'::vector;
ERROR: "1e-46" is out of range for type vector
LINE 1: SELECT '[1e-46,1]'::vector;
^
SELECT '[-1e-46,1]'::vector;
ERROR: "-1e-46" is out of range for type vector
LINE 1: SELECT '[-1e-46,1]'::vector;
^
SELECT '[1,2,3'::vector;
ERROR: malformed vector literal: "[1,2,3"
LINE 1: SELECT '[1,2,3'::vector;
@@ -128,30 +116,8 @@ SELECT '[1, ,3]'::vector;
ERROR: invalid input syntax for type vector: "[1, ,3]"
LINE 1: SELECT '[1, ,3]'::vector;
^
SELECT '[1,2,3]'::vector(3);
vector
---------
[1,2,3]
(1 row)
SELECT '[1,2,3]'::vector(2);
ERROR: expected 2 dimensions, not 3
SELECT '[1,2,3]'::vector(3, 2);
ERROR: invalid type modifier
LINE 1: SELECT '[1,2,3]'::vector(3, 2);
^
SELECT '[1,2,3]'::vector('a');
ERROR: invalid input syntax for type integer: "a"
LINE 1: SELECT '[1,2,3]'::vector('a');
^
SELECT '[1,2,3]'::vector(0);
ERROR: dimensions for type vector must be at least 1
LINE 1: SELECT '[1,2,3]'::vector(0);
^
SELECT '[1,2,3]'::vector(16001);
ERROR: dimensions for type vector cannot exceed 16000
LINE 1: SELECT '[1,2,3]'::vector(16001);
^
SELECT unnest('{"[1,2,3]", "[4,5,6]"}'::vector[]);
unnest
---------

View File

@@ -6,17 +6,6 @@ SELECT '[1,2,3]'::vector * '[4,5,6]';
SELECT '[1e37]'::vector * '[1e37]';
SELECT '[1e-37]'::vector * '[1e-37]';
SELECT '[1,2,3]'::vector = '[1,2,3]';
SELECT '[1,2,3]'::vector = '[1,2]';
SELECT vector_cmp('[1,2,3]', '[1,2,3]');
SELECT vector_cmp('[1,2,3]', '[0,0,0]');
SELECT vector_cmp('[0,0,0]', '[1,2,3]');
SELECT vector_cmp('[1,2]', '[1,2,3]');
SELECT vector_cmp('[1,2,3]', '[1,2]');
SELECT vector_cmp('[1,2]', '[2,3,4]');
SELECT vector_cmp('[2,3]', '[1,2,3]');
SELECT vector_dims('[1,2,3]');
SELECT round(vector_norm('[1,1]')::numeric, 5);

View File

@@ -7,7 +7,7 @@ CREATE INDEX ON t USING hnsw (val vector_l2_ops);
INSERT INTO t (val) VALUES ('[1,2,4]');
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <-> (SELECT NULL::vector)) t2;
SELECT * FROM t ORDER BY val <-> (SELECT NULL::vector);
SELECT COUNT(*) FROM t;
TRUNCATE t;

View File

@@ -11,9 +11,6 @@ SELECT '[1.5e38,-1.5e38]'::vector;
SELECT '[1.5e+38,-1.5e+38]'::vector;
SELECT '[1.5e-38,-1.5e-38]'::vector;
SELECT '[4e38,1]'::vector;
SELECT '[-4e38,1]'::vector;
SELECT '[1e-46,1]'::vector;
SELECT '[-1e-46,1]'::vector;
SELECT '[1,2,3'::vector;
SELECT '[1,2,3]9'::vector;
SELECT '1,2,3'::vector;
@@ -25,13 +22,7 @@ SELECT '[1,]'::vector;
SELECT '[1a]'::vector;
SELECT '[1,,3]'::vector;
SELECT '[1, ,3]'::vector;
SELECT '[1,2,3]'::vector(3);
SELECT '[1,2,3]'::vector(2);
SELECT '[1,2,3]'::vector(3, 2);
SELECT '[1,2,3]'::vector('a');
SELECT '[1,2,3]'::vector(0);
SELECT '[1,2,3]'::vector(16001);
SELECT unnest('{"[1,2,3]", "[4,5,6]"}'::vector[]);
SELECT '{"[1,2,3]"}'::vector(2)[];

View File

@@ -86,7 +86,7 @@ foreach (@queries)
push(@expected, $res);
}
test_recall(0.19, $limit, "before vacuum");
test_recall(0.20, $limit, "before vacuum");
test_recall(0.95, 100, "before vacuum");
# TODO Test concurrent inserts with vacuum

View File

@@ -1,4 +1,4 @@
comment = 'vector data type and ivfflat and hnsw access methods'
default_version = '0.6.2'
default_version = '0.5.1'
module_pathname = '$libdir/vector'
relocatable = true