mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-22 03:57:34 +08:00
Compare commits
20 Commits
v0.8.4
...
hnsw-strea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c01e76f2fa | ||
|
|
ab57217f48 | ||
|
|
5f6e031ccc | ||
|
|
1a1221f905 | ||
|
|
40c3e402c7 | ||
|
|
058248fdcc | ||
|
|
73c5145b77 | ||
|
|
ec4a23fe49 | ||
|
|
38207f5640 | ||
|
|
4e35c6abe3 | ||
|
|
11e4d040d9 | ||
|
|
b2fa625255 | ||
|
|
a8e699c927 | ||
|
|
91541fece6 | ||
|
|
f3de487da2 | ||
|
|
721d4b7e3f | ||
|
|
28066d8fe4 | ||
|
|
495041e43b | ||
|
|
52c385c03a | ||
|
|
80cbd32dab |
8
.dockerignore
Normal file
8
.dockerignore
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/.git/
|
||||||
|
/dist/
|
||||||
|
/results/
|
||||||
|
/tmp_check/
|
||||||
|
/sql/vector--?.?.?.sql
|
||||||
|
regression.*
|
||||||
|
*.o
|
||||||
|
*.so
|
||||||
53
.github/workflows/build.yml
vendored
53
.github/workflows/build.yml
vendored
@@ -8,29 +8,27 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- postgres: 19
|
# - postgres: 18
|
||||||
os: ubuntu-24.04
|
# os: ubuntu-24.04
|
||||||
- postgres: 18
|
|
||||||
os: ubuntu-24.04
|
|
||||||
- postgres: 17
|
- postgres: 17
|
||||||
os: ubuntu-24.04
|
os: ubuntu-24.04
|
||||||
- postgres: 16
|
- postgres: 16
|
||||||
os: ubuntu-24.04-arm
|
os: ubuntu-22.04
|
||||||
- postgres: 15
|
- postgres: 15
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
- postgres: 14
|
- postgres: 14
|
||||||
os: ubuntu-22.04-arm
|
os: ubuntu-20.04
|
||||||
- postgres: 13
|
- postgres: 13
|
||||||
os: ubuntu-22.04
|
os: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
- uses: ankane/setup-postgres@v1
|
- uses: ankane/setup-postgres@v1
|
||||||
with:
|
with:
|
||||||
postgres-version: ${{ matrix.postgres }}
|
postgres-version: ${{ matrix.postgres }}
|
||||||
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 ${{ matrix.postgres >= 18 && '-Wno-missing-field-initializers' || '' }}
|
PG_CFLAGS: -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
|
||||||
@@ -48,18 +46,18 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- postgres: 18
|
- postgres: 16
|
||||||
os: macos-26
|
os: macos-14
|
||||||
- postgres: 14
|
- postgres: 14
|
||||||
os: macos-15-intel
|
os: macos-12
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
- uses: ankane/setup-postgres@v1
|
- uses: ankane/setup-postgres@v1
|
||||||
with:
|
with:
|
||||||
postgres-version: ${{ matrix.postgres }}
|
postgres-version: ${{ matrix.postgres }}
|
||||||
- run: make
|
- run: make
|
||||||
env:
|
env:
|
||||||
PG_CFLAGS: -DUSE_ASSERT_CHECKING -Wall -Wextra -Werror -Wno-unused-parameter -Wno-unknown-warning-option ${{ matrix.postgres >= 18 && '-Wno-missing-field-initializers' || '' }}
|
PG_CFLAGS: -DUSE_ASSERT_CHECKING -Wall -Wextra -Werror -Wno-unused-parameter
|
||||||
- run: make install
|
- run: make install
|
||||||
- run: make installcheck
|
- run: make installcheck
|
||||||
- if: ${{ failure() }}
|
- if: ${{ failure() }}
|
||||||
@@ -72,35 +70,26 @@ jobs:
|
|||||||
tar xf $TAG.tar.gz
|
tar xf $TAG.tar.gz
|
||||||
mv postgres-$TAG postgres
|
mv postgres-$TAG postgres
|
||||||
env:
|
env:
|
||||||
TAG: ${{ matrix.postgres == 18 && 'REL_18_2' || 'REL_14_21' }}
|
TAG: ${{ matrix.postgres == 16 && 'REL_16_2' || 'REL_14_11' }}
|
||||||
- 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@$LLVM_VERSION)/bin/scan-build --status-bugs make
|
- run: make clean && $(brew --prefix llvm@15)/bin/scan-build --status-bugs make
|
||||||
env:
|
env:
|
||||||
LLVM_VERSION: ${{ matrix.os == 'macos-26' && 20 || 18 }}
|
|
||||||
PG_CFLAGS: -DUSE_ASSERT_CHECKING
|
PG_CFLAGS: -DUSE_ASSERT_CHECKING
|
||||||
windows:
|
windows:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: windows-latest
|
||||||
if: ${{ !startsWith(github.ref_name, 'mac') }}
|
if: ${{ !startsWith(github.ref_name, 'mac') }}
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- postgres: 17
|
|
||||||
os: windows-2025
|
|
||||||
- postgres: 14
|
|
||||||
os: windows-2022
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
- uses: ankane/setup-postgres@v1
|
- uses: ankane/setup-postgres@v1
|
||||||
with:
|
with:
|
||||||
postgres-version: ${{ matrix.postgres }}
|
postgres-version: 14
|
||||||
- run: |
|
- run: |
|
||||||
call "C:\Program Files\Microsoft Visual Studio\${{ matrix.os == 'windows-2025' && 18 || 2022 }}\Enterprise\VC\Auxiliary\Build\vcvars64.bat" && ^
|
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" && ^
|
||||||
nmake /NOLOGO /F Makefile.win && ^
|
nmake /NOLOGO /F Makefile.win && ^
|
||||||
nmake /NOLOGO /F Makefile.win install && ^
|
nmake /NOLOGO /F Makefile.win install && ^
|
||||||
nmake /NOLOGO /F Makefile.win installcheck ${{ matrix.postgres != 17 && 'PG_REGRESS=$(PGROOT)\bin\pg_regress' || '' }} && ^
|
nmake /NOLOGO /F Makefile.win installcheck && ^
|
||||||
nmake /NOLOGO /F Makefile.win clean && ^
|
nmake /NOLOGO /F Makefile.win clean && ^
|
||||||
nmake /NOLOGO /F Makefile.win uninstall
|
nmake /NOLOGO /F Makefile.win uninstall
|
||||||
shell: cmd
|
shell: cmd
|
||||||
@@ -133,10 +122,10 @@ jobs:
|
|||||||
if: ${{ !startsWith(github.ref_name, 'mac') && !startsWith(github.ref_name, 'windows') }}
|
if: ${{ !startsWith(github.ref_name, 'mac') && !startsWith(github.ref_name, 'windows') }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
- uses: ankane/setup-postgres-valgrind@v1
|
- uses: ankane/setup-postgres-valgrind@v1
|
||||||
with:
|
with:
|
||||||
postgres-version: 18
|
postgres-version: 16
|
||||||
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
|
||||||
|
|||||||
28
CHANGELOG.md
28
CHANGELOG.md
@@ -1,31 +1,9 @@
|
|||||||
## 0.8.4 (2026-06-30)
|
## 0.8.0 (unreleased)
|
||||||
|
|
||||||
- Fixed `hnsw graph not repaired` error with HNSW vacuuming
|
|
||||||
- Fixed possible error with inserts during HNSW vacuuming
|
|
||||||
|
|
||||||
## 0.8.3 (2026-06-17)
|
|
||||||
|
|
||||||
- Fixed possible index corruption with HNSW vacuuming
|
|
||||||
- Fixed performance regression with Hamming distance and Jaccard distance with Postgres 18
|
|
||||||
|
|
||||||
## 0.8.2 (2026-02-25)
|
|
||||||
|
|
||||||
- Fixed buffer overflow with parallel HNSW index build - [more info](https://github.com/pgvector/pgvector/issues/959)
|
|
||||||
- Improved `install` target on Windows
|
|
||||||
- Fixed `Index Searches` in `EXPLAIN` output for Postgres 18
|
|
||||||
|
|
||||||
## 0.8.1 (2025-09-04)
|
|
||||||
|
|
||||||
- Added support for Postgres 18 rc1
|
|
||||||
- Improved performance of `binary_quantize` function
|
|
||||||
|
|
||||||
## 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 for better index selection when filtering
|
- Improved cost estimation
|
||||||
- Improved performance of HNSW index scans
|
- Reduced memory usage for HNSW index scans
|
||||||
- Improved performance of HNSW inserts and on-disk index builds
|
|
||||||
- Dropped support for Postgres 12
|
- Dropped support for Postgres 12
|
||||||
|
|
||||||
## 0.7.4 (2024-08-05)
|
## 0.7.4 (2024-08-05)
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
# syntax=docker/dockerfile:1
|
ARG PG_MAJOR=16
|
||||||
|
FROM postgres:$PG_MAJOR
|
||||||
ARG PG_MAJOR=17
|
|
||||||
ARG DEBIAN_CODENAME=bookworm
|
|
||||||
FROM postgres:$PG_MAJOR-$DEBIAN_CODENAME
|
|
||||||
ARG PG_MAJOR
|
ARG PG_MAJOR
|
||||||
|
|
||||||
ADD https://github.com/pgvector/pgvector.git#v0.8.4 /tmp/pgvector
|
COPY . /tmp/pgvector
|
||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-mark hold locales && \
|
apt-mark hold locales && \
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
|||||||
Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
|
Portions Copyright (c) 1996-2024, 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
|
||||||
|
|
||||||
|
|||||||
10
META.json
10
META.json
@@ -2,17 +2,17 @@
|
|||||||
"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.8.4",
|
"version": "0.7.4",
|
||||||
"maintainer": [
|
"maintainer": [
|
||||||
"Andrew Kane <andrew@ankane.org>"
|
"Andrew Kane <andrew@ankane.org>"
|
||||||
],
|
],
|
||||||
"license": {
|
"license": {
|
||||||
"PostgreSQL": "https://www.postgresql.org/about/licence"
|
"PostgreSQL": "http://www.postgresql.org/about/licence"
|
||||||
},
|
},
|
||||||
"prereqs": {
|
"prereqs": {
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"requires": {
|
"requires": {
|
||||||
"PostgreSQL": "13.0.0"
|
"PostgreSQL": "12.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"vector": {
|
"vector": {
|
||||||
"file": "sql/vector.sql",
|
"file": "sql/vector.sql",
|
||||||
"docfile": "README.md",
|
"docfile": "README.md",
|
||||||
"version": "0.8.4",
|
"version": "0.7.4",
|
||||||
"abstract": "Open-source vector similarity search for Postgres"
|
"abstract": "Open-source vector similarity search for Postgres"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
"generated_by": "Andrew Kane",
|
"generated_by": "Andrew Kane",
|
||||||
"meta-spec": {
|
"meta-spec": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"url": "https://pgxn.org/meta/spec.txt"
|
"url": "http://pgxn.org/meta/spec.txt"
|
||||||
},
|
},
|
||||||
"tags": [
|
"tags": [
|
||||||
"vectors",
|
"vectors",
|
||||||
|
|||||||
16
Makefile
16
Makefile
@@ -1,5 +1,5 @@
|
|||||||
EXTENSION = vector
|
EXTENSION = vector
|
||||||
EXTVERSION = 0.8.4
|
EXTVERSION = 0.7.4
|
||||||
|
|
||||||
MODULE_big = vector
|
MODULE_big = vector
|
||||||
DATA = $(wildcard sql/*--*--*.sql)
|
DATA = $(wildcard sql/*--*--*.sql)
|
||||||
@@ -27,11 +27,6 @@ ifneq ($(filter ppc64%, $(shell uname -m)), )
|
|||||||
OPTFLAGS =
|
OPTFLAGS =
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# RISC-V64 doesn't support -march=native
|
|
||||||
ifeq ($(shell uname -m), riscv64)
|
|
||||||
OPTFLAGS =
|
|
||||||
endif
|
|
||||||
|
|
||||||
# For auto-vectorization:
|
# For auto-vectorization:
|
||||||
# - GCC (needs -ftree-vectorize OR -O3) - https://gcc.gnu.org/projects/tree-ssa/vectorization.html
|
# - GCC (needs -ftree-vectorize OR -O3) - https://gcc.gnu.org/projects/tree-ssa/vectorization.html
|
||||||
# - Clang (could use pragma instead) - https://llvm.org/docs/Vectorizers.html
|
# - Clang (could use pragma instead) - https://llvm.org/docs/Vectorizers.html
|
||||||
@@ -71,7 +66,7 @@ dist:
|
|||||||
git archive --format zip --prefix=$(EXTENSION)-$(EXTVERSION)/ --output dist/$(EXTENSION)-$(EXTVERSION).zip master
|
git archive --format zip --prefix=$(EXTENSION)-$(EXTVERSION)/ --output dist/$(EXTENSION)-$(EXTVERSION).zip master
|
||||||
|
|
||||||
# for Docker
|
# for Docker
|
||||||
PG_MAJOR ?= 17
|
PG_MAJOR ?= 16
|
||||||
|
|
||||||
.PHONY: docker
|
.PHONY: docker
|
||||||
|
|
||||||
@@ -81,9 +76,4 @@ docker:
|
|||||||
.PHONY: docker-release
|
.PHONY: docker-release
|
||||||
|
|
||||||
docker-release:
|
docker-release:
|
||||||
docker buildx build --push --pull --no-cache --platform linux/amd64,linux/arm64 --build-arg PG_MAJOR=$(PG_MAJOR) --build-arg DEBIAN_CODENAME=bookworm -t pgvector/pgvector:pg$(PG_MAJOR) -t pgvector/pgvector:pg$(PG_MAJOR)-bookworm -t pgvector/pgvector:$(EXTVERSION)-pg$(PG_MAJOR) -t pgvector/pgvector:$(EXTVERSION)-pg$(PG_MAJOR)-bookworm .
|
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) .
|
||||||
|
|
||||||
.PHONY: docker-release-trixie
|
|
||||||
|
|
||||||
docker-release-trixie:
|
|
||||||
docker buildx build --push --pull --no-cache --platform linux/amd64,linux/arm64 --build-arg PG_MAJOR=$(PG_MAJOR) --build-arg DEBIAN_CODENAME=trixie -t pgvector/pgvector:pg$(PG_MAJOR)-trixie -t pgvector/pgvector:$(EXTVERSION)-pg$(PG_MAJOR)-trixie .
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
EXTENSION = vector
|
EXTENSION = vector
|
||||||
EXTVERSION = 0.8.4
|
EXTVERSION = 0.7.4
|
||||||
|
|
||||||
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
|
||||||
@@ -31,9 +31,6 @@ LIBDIR = $(PGROOT)\lib
|
|||||||
PKGLIBDIR = $(PGROOT)\lib
|
PKGLIBDIR = $(PGROOT)\lib
|
||||||
SHAREDIR = $(PGROOT)\share
|
SHAREDIR = $(PGROOT)\share
|
||||||
|
|
||||||
# Use $(PGROOT)\bin\pg_regress for Postgres < 17
|
|
||||||
PG_REGRESS = $(LIBDIR)\pgxs\src\test\regress\pg_regress
|
|
||||||
|
|
||||||
CFLAGS = /nologo /I"$(INCLUDEDIR_SERVER)\port\win32_msvc" /I"$(INCLUDEDIR_SERVER)\port\win32" /I"$(INCLUDEDIR_SERVER)" /I"$(INCLUDEDIR)"
|
CFLAGS = /nologo /I"$(INCLUDEDIR_SERVER)\port\win32_msvc" /I"$(INCLUDEDIR_SERVER)\port\win32" /I"$(INCLUDEDIR_SERVER)" /I"$(INCLUDEDIR)"
|
||||||
|
|
||||||
CFLAGS = $(CFLAGS) $(PG_CFLAGS)
|
CFLAGS = $(CFLAGS) $(PG_CFLAGS)
|
||||||
@@ -57,11 +54,11 @@ install: all
|
|||||||
copy $(SHLIB) "$(PKGLIBDIR)"
|
copy $(SHLIB) "$(PKGLIBDIR)"
|
||||||
copy $(EXTENSION).control "$(SHAREDIR)\extension"
|
copy $(EXTENSION).control "$(SHAREDIR)\extension"
|
||||||
copy sql\$(EXTENSION)--*.sql "$(SHAREDIR)\extension"
|
copy sql\$(EXTENSION)--*.sql "$(SHAREDIR)\extension"
|
||||||
if not exist "$(INCLUDEDIR_SERVER)\extension\$(EXTENSION)" mkdir "$(INCLUDEDIR_SERVER)\extension\$(EXTENSION)"
|
mkdir "$(INCLUDEDIR_SERVER)\extension\$(EXTENSION)"
|
||||||
for %f in ($(HEADERS)) do copy %f "$(INCLUDEDIR_SERVER)\extension\$(EXTENSION)"
|
for %f in ($(HEADERS)) do copy %f "$(INCLUDEDIR_SERVER)\extension\$(EXTENSION)"
|
||||||
|
|
||||||
installcheck:
|
installcheck:
|
||||||
"$(PG_REGRESS)" --bindir="$(BINDIR)" $(REGRESS_OPTS) $(REGRESS)
|
"$(BINDIR)\pg_regress" --bindir="$(BINDIR)" $(REGRESS_OPTS) $(REGRESS)
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
del /f "$(PKGLIBDIR)\$(SHLIB)"
|
del /f "$(PKGLIBDIR)\$(SHLIB)"
|
||||||
|
|||||||
361
README.md
361
README.md
@@ -11,19 +11,17 @@ Store your vectors with the rest of your data. Supports:
|
|||||||
|
|
||||||
Plus [ACID](https://en.wikipedia.org/wiki/ACID) compliance, point-in-time recovery, JOINs, and all of the other [great features](https://www.postgresql.org/about/) of Postgres
|
Plus [ACID](https://en.wikipedia.org/wiki/ACID) compliance, point-in-time recovery, JOINs, and all of the other [great features](https://www.postgresql.org/about/) of Postgres
|
||||||
|
|
||||||
Have a lot of vectors? Use [quantization](#scaling) to scale
|
|
||||||
|
|
||||||
[](https://github.com/pgvector/pgvector/actions)
|
[](https://github.com/pgvector/pgvector/actions)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Linux and Mac
|
### Linux and Mac
|
||||||
|
|
||||||
Compile and install the extension (supports Postgres 13+)
|
Compile and install the extension (supports Postgres 12+)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd /tmp
|
cd /tmp
|
||||||
git clone --branch v0.8.4 https://github.com/pgvector/pgvector.git
|
git clone --branch v0.7.4 https://github.com/pgvector/pgvector.git
|
||||||
cd pgvector
|
cd pgvector
|
||||||
make
|
make
|
||||||
make install # may need sudo
|
make install # may need sudo
|
||||||
@@ -31,16 +29,24 @@ 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---linux-and-mac) if you run into issues
|
||||||
|
|
||||||
You can also install it with [Docker](#docker), [Homebrew](#homebrew), [PGXN](#pgxn), [APT](#apt), [Yum](#yum), [pkg](#pkg), [APK](#apk), 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), [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).
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
Ensure [C++ support in Visual Studio](https://learn.microsoft.com/en-us/cpp/build/building-on-the-command-line?view=msvc-170#download-and-install-the-tools) is installed and run `x64 Native Tools Command Prompt for VS [version]` as administrator. Then use `nmake` to build:
|
Ensure [C++ support in Visual Studio](https://learn.microsoft.com/en-us/cpp/build/building-on-the-command-line?view=msvc-170#download-and-install-the-tools) is installed, and run:
|
||||||
|
|
||||||
```cmd
|
```cmd
|
||||||
set "PGROOT=C:\Program Files\PostgreSQL\18"
|
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: The exact path will vary depending on your Visual Studio version and edition
|
||||||
|
|
||||||
|
Then use `nmake` to build:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
set "PGROOT=C:\Program Files\PostgreSQL\16"
|
||||||
cd %TEMP%
|
cd %TEMP%
|
||||||
git clone --branch v0.8.4 https://github.com/pgvector/pgvector.git
|
git clone --branch v0.7.4 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
|
||||||
@@ -76,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 (`<+>`)
|
Also supports inner product (`<#>`), cosine distance (`<=>`), and L1 distance (`<+>`, added in 0.7.0)
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@@ -94,8 +100,6 @@ Or add a vector column to an existing table
|
|||||||
ALTER TABLE items ADD COLUMN embedding vector(3);
|
ALTER TABLE items ADD COLUMN embedding vector(3);
|
||||||
```
|
```
|
||||||
|
|
||||||
Also supports [half-precision](#half-precision-vectors), [binary](#binary-vectors), and [sparse](#sparse-vectors) vectors
|
|
||||||
|
|
||||||
Insert vectors
|
Insert vectors
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
@@ -140,9 +144,7 @@ Supported distance functions are:
|
|||||||
- `<->` - L2 distance
|
- `<->` - L2 distance
|
||||||
- `<#>` - (negative) inner product
|
- `<#>` - (negative) inner product
|
||||||
- `<=>` - cosine distance
|
- `<=>` - cosine distance
|
||||||
- `<+>` - L1 distance
|
- `<+>` - L1 distance (added in 0.7.0)
|
||||||
- `<~>` - Hamming distance (binary vectors)
|
|
||||||
- `<%>` - Jaccard distance (binary vectors)
|
|
||||||
|
|
||||||
Get the nearest neighbors to a row
|
Get the nearest neighbors to a row
|
||||||
|
|
||||||
@@ -229,19 +231,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
|
L1 distance - added in 0.7.0
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE INDEX ON items USING hnsw (embedding vector_l1_ops);
|
CREATE INDEX ON items USING hnsw (embedding vector_l1_ops);
|
||||||
```
|
```
|
||||||
|
|
||||||
Hamming distance
|
Hamming distance - added in 0.7.0
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE INDEX ON items USING hnsw (embedding bit_hamming_ops);
|
CREATE INDEX ON items USING hnsw (embedding bit_hamming_ops);
|
||||||
```
|
```
|
||||||
|
|
||||||
Jaccard distance
|
Jaccard distance - added in 0.7.0
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE INDEX ON items USING hnsw (embedding bit_jaccard_ops);
|
CREATE INDEX ON items USING hnsw (embedding bit_jaccard_ops);
|
||||||
@@ -250,9 +252,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
|
- `halfvec` - up to 4,000 dimensions (added in 0.7.0)
|
||||||
- `bit` - up to 64,000 dimensions
|
- `bit` - up to 64,000 dimensions (added in 0.7.0)
|
||||||
- `sparsevec` - up to 1,000 non-zero elements
|
- `sparsevec` - up to 1,000 non-zero elements (added in 0.7.0)
|
||||||
|
|
||||||
### Index Options
|
### Index Options
|
||||||
|
|
||||||
@@ -306,21 +308,17 @@ 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
|
||||||
|
|
||||||
You can also speed up index creation by increasing the number of parallel workers (2 by default)
|
Starting with 0.6.0, 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 need to increase `max_parallel_workers` (8 by default)
|
For a large number of workers, you may also 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)
|
|
||||||
|
|
||||||
Use [binary quantization](#binary-quantization) for faster build times at scale
|
|
||||||
|
|
||||||
### Indexing Progress
|
### Indexing Progress
|
||||||
|
|
||||||
Check [indexing progress](https://www.postgresql.org/docs/current/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING)
|
Check [indexing progress](https://www.postgresql.org/docs/current/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING) with Postgres 12+
|
||||||
|
|
||||||
```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;
|
||||||
@@ -363,7 +361,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
|
Hamming distance - added in 0.7.0
|
||||||
|
|
||||||
```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);
|
||||||
@@ -372,8 +370,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
|
- `halfvec` - up to 4,000 dimensions (added in 0.7.0)
|
||||||
- `bit` - up to 64,000 dimensions
|
- `bit` - up to 64,000 dimensions (added in 0.7.0)
|
||||||
|
|
||||||
### Query Options
|
### Query Options
|
||||||
|
|
||||||
@@ -406,7 +404,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)
|
Check [indexing progress](https://www.postgresql.org/docs/current/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING) with Postgres 12+
|
||||||
|
|
||||||
```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;
|
||||||
@@ -423,110 +421,69 @@ 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;
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
Create an index on one [or more](https://www.postgresql.org/docs/current/indexes-multicolumn.html) of the `WHERE` columns for exact search
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE INDEX ON items (category_id);
|
CREATE INDEX ON items (category_id);
|
||||||
```
|
```
|
||||||
|
|
||||||
For multiple columns, consider a [multicolumn index](https://www.postgresql.org/docs/current/indexes-multicolumn.html).
|
Or a [partial index](https://www.postgresql.org/docs/current/indexes-partial.html) on the vector column for approximate search
|
||||||
|
|
||||||
```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, 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);
|
||||||
```
|
```
|
||||||
|
|
||||||
If filtering by many different values, consider [partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html).
|
Use [partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html) for approximate search on many different values of the `WHERE` columns
|
||||||
|
|
||||||
```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);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Iterative Index Scans
|
## Streaming Queries [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`).
|
*Added in 0.8.0*
|
||||||
|
|
||||||
Iterative scans can use strict or relaxed ordering.
|
With approximate indexes, you can end up with less results than expected due to filtering conditions in the query.
|
||||||
|
|
||||||
Strict ensures results are in the exact order by distance
|
Starting with 0.8.0, you can enable streaming queries. If too few results from the initial index scan match the query filters, it will resume scanning until enough results are found. This can significantly improve recall (at the cost of speed).
|
||||||
|
|
||||||
```sql
|
```tsql
|
||||||
SET hnsw.iterative_scan = strict_order;
|
SET hnsw.streaming = on;
|
||||||
|
-- or
|
||||||
|
SET ivfflat.streaming = on;
|
||||||
```
|
```
|
||||||
|
|
||||||
Relaxed allows results to be slightly out of order by distance, but provides better recall
|
### Streaming Options
|
||||||
|
|
||||||
```sql
|
Since scanning a large portion of the index is expensive, there are options to control when the scan ends.
|
||||||
SET hnsw.iterative_scan = relaxed_order;
|
|
||||||
# or
|
|
||||||
SET ivfflat.iterative_scan = relaxed_order;
|
|
||||||
```
|
|
||||||
|
|
||||||
With relaxed ordering, you can use a [materialized CTE](https://www.postgresql.org/docs/current/queries-with.html#QUERIES-WITH-CTE-MATERIALIZATION) to get strict ordering
|
|
||||||
|
|
||||||
```sql
|
|
||||||
WITH relaxed_results AS MATERIALIZED (
|
|
||||||
SELECT id, embedding <-> '[1,2,3]' AS distance FROM items WHERE category_id = 123 ORDER BY distance LIMIT 5
|
|
||||||
) SELECT * FROM relaxed_results ORDER BY distance + 0;
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: `+ 0` is needed for Postgres 17+
|
|
||||||
|
|
||||||
For queries that filter by distance, use a materialized CTE and place the distance filter outside of it for best performance (due to the [current behavior](https://www.postgresql.org/message-id/flat/CAOdR5yGUoMQ6j7M5hNUXrySzaqZVGf_Ne%2B8fwZMRKTFxU1nbJg%40mail.gmail.com) of the Postgres executor)
|
|
||||||
|
|
||||||
```sql
|
|
||||||
WITH nearest_results AS MATERIALIZED (
|
|
||||||
SELECT id, embedding <-> '[1,2,3]' AS distance FROM items ORDER BY distance LIMIT 5
|
|
||||||
) SELECT * FROM nearest_results WHERE distance < 5 ORDER BY distance;
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: Place any other filters inside the CTE
|
|
||||||
|
|
||||||
### Iterative Scan Options
|
|
||||||
|
|
||||||
Since scanning a large portion of an approximate index is expensive, there are options to control when a scan ends.
|
|
||||||
|
|
||||||
#### HNSW
|
#### HNSW
|
||||||
|
|
||||||
Specify the max number of tuples to visit (20,000 by default)
|
Specify the max number of additional tuples visited
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SET hnsw.max_scan_tuples = 20000;
|
SET hnsw.ef_stream = 10000;
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: This is approximate and does not affect the initial scan
|
The scan will also end if reaches `work_mem`, at which point a notice is shown
|
||||||
|
|
||||||
Specify the max amount of memory to use, as a multiple of `work_mem` (1 by default)
|
```text
|
||||||
|
NOTICE: hnsw index scan exceeded work_mem after 50000 tuples
|
||||||
|
HINT: Increase work_mem to scan more tuples.
|
||||||
|
```
|
||||||
|
|
||||||
|
Adjust this with:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SET hnsw.scan_mem_multiplier = 2;
|
SET work_mem = '8MB';
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: Try increasing this if increasing `hnsw.max_scan_tuples` does not improve recall
|
|
||||||
|
|
||||||
#### IVFFlat
|
#### IVFFlat
|
||||||
|
|
||||||
Specify the max number of probes
|
Specify the max number of probes
|
||||||
@@ -535,10 +492,20 @@ Specify the max number of probes
|
|||||||
SET ivfflat.max_probes = 100;
|
SET ivfflat.max_probes = 100;
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: If this is lower than `ivfflat.probes`, `ivfflat.probes` will be used
|
### Streaming Order
|
||||||
|
|
||||||
|
With streaming queries, it’s possible for rows to be slightly out of order by distance. For strict ordering, use:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
WITH approx_order AS MATERIALIZED (
|
||||||
|
SELECT *, embedding <-> '[1,2,3]' AS distance FROM items WHERE ... ORDER BY distance LIMIT 5
|
||||||
|
) SELECT * FROM approx_order ORDER BY distance;
|
||||||
|
```
|
||||||
|
|
||||||
## 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
|
||||||
@@ -547,6 +514,8 @@ 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
|
||||||
@@ -568,16 +537,24 @@ 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
|
Get the nearest neighbors by Hamming distance (added in 0.7.0)
|
||||||
|
|
||||||
```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
|
||||||
@@ -600,6 +577,8 @@ 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
|
||||||
@@ -633,6 +612,8 @@ 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
|
||||||
@@ -671,10 +652,6 @@ SHOW shared_buffers;
|
|||||||
|
|
||||||
Be sure to restart Postgres for changes to take effect.
|
Be sure to restart Postgres for changes to take effect.
|
||||||
|
|
||||||
### Storing
|
|
||||||
|
|
||||||
Use the `halfvec` type instead of `vector` for a smaller working set.
|
|
||||||
|
|
||||||
### Loading
|
### Loading
|
||||||
|
|
||||||
Use `COPY` for bulk loading data ([example](https://github.com/pgvector/pgvector-python/blob/master/examples/loading/example.py)).
|
Use `COPY` for bulk loading data ([example](https://github.com/pgvector/pgvector-python/blob/master/examples/loading/example.py)).
|
||||||
@@ -689,8 +666,6 @@ Add any indexes *after* loading the initial data for best performance.
|
|||||||
|
|
||||||
See index build time for [HNSW](#index-build-time) and [IVFFlat](#index-build-time-1).
|
See index build time for [HNSW](#index-build-time) and [IVFFlat](#index-build-time-1).
|
||||||
|
|
||||||
Use [binary quantization](#binary-quantization) for smaller indexes and faster build times at scale.
|
|
||||||
|
|
||||||
In production environments, create indexes concurrently to avoid blocking writes.
|
In production environments, create indexes concurrently to avoid blocking writes.
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
@@ -699,10 +674,10 @@ CREATE INDEX CONCURRENTLY ...
|
|||||||
|
|
||||||
### Querying
|
### Querying
|
||||||
|
|
||||||
Use `EXPLAIN (ANALYZE, BUFFERS)` to debug performance.
|
Use `EXPLAIN ANALYZE` to debug performance.
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 5;
|
EXPLAIN ANALYZE SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 5;
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Exact Search
|
#### Exact Search
|
||||||
@@ -721,8 +696,6 @@ SELECT * FROM items ORDER BY embedding <#> '[3,1,2]' LIMIT 5;
|
|||||||
|
|
||||||
#### Approximate Search
|
#### Approximate Search
|
||||||
|
|
||||||
Use [binary quantization](#binary-quantization) with re-ranking to keep indexes in-memory at scale.
|
|
||||||
|
|
||||||
To speed up queries with an IVFFlat index, increase the number of inverted lists (at the expense of recall).
|
To speed up queries with an IVFFlat index, increase the number of inverted lists (at the expense of recall).
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
@@ -738,20 +711,23 @@ REINDEX INDEX CONCURRENTLY index_name;
|
|||||||
VACUUM table_name;
|
VACUUM table_name;
|
||||||
```
|
```
|
||||||
|
|
||||||
## Scaling
|
|
||||||
|
|
||||||
For a smaller working set:
|
|
||||||
|
|
||||||
1. Use the `halfvec` type instead of `vector` for tables
|
|
||||||
2. Use [binary quantization](#binary-quantization) for indexes (with re-ranking for search)
|
|
||||||
|
|
||||||
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), [PgDog](https://github.com/pgdogdev/pgdog), or another approach for sharding ([example](https://github.com/pgvector/pgvector-python/blob/master/examples/citus/example.py)).
|
|
||||||
|
|
||||||
## Monitoring
|
## Monitoring
|
||||||
|
|
||||||
Use existing tools like [pg_stat_statements](https://www.postgresql.org/docs/current/pgstatstatements.html) or [PgHero](https://github.com/ankane/pghero) to monitor performance.
|
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.
|
Monitor recall by comparing results from approximate search with exact search.
|
||||||
|
|
||||||
@@ -762,46 +738,42 @@ SELECT ...
|
|||||||
COMMIT;
|
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/example.py)).
|
||||||
|
|
||||||
## Languages
|
## 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.
|
Use pgvector from any language with a Postgres client. You can even generate and store vectors in one language and query them in another.
|
||||||
|
|
||||||
Language | Libraries / Examples
|
Language | Libraries / Examples
|
||||||
--- | ---
|
--- | ---
|
||||||
Ada | [pgvector-ada](https://github.com/pgvector/pgvector-ada)
|
|
||||||
Algol | [pgvector-algol](https://github.com/pgvector/pgvector-algol)
|
|
||||||
C | [pgvector-c](https://github.com/pgvector/pgvector-c)
|
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)
|
||||||
COBOL | [pgvector-cobol](https://github.com/pgvector/pgvector-cobol)
|
|
||||||
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)
|
||||||
JavaScript, TypeScript | [pgvector-node](https://github.com/pgvector/pgvector-node)
|
JavaScript, TypeScript | [pgvector-node](https://github.com/pgvector/pgvector-node)
|
||||||
Julia | [Pgvector.jl](https://github.com/pgvector/Pgvector.jl)
|
Julia | [pgvector-julia](https://github.com/pgvector/pgvector-julia)
|
||||||
Lisp | [pgvector-lisp](https://github.com/pgvector/pgvector-lisp)
|
Lisp | [pgvector-lisp](https://github.com/pgvector/pgvector-lisp)
|
||||||
Lua | [pgvector-lua](https://github.com/pgvector/pgvector-lua)
|
Lua | [pgvector-lua](https://github.com/pgvector/pgvector-lua)
|
||||||
Nim | [pgvector-nim](https://github.com/pgvector/pgvector-nim)
|
Nim | [pgvector-nim](https://github.com/pgvector/pgvector-nim)
|
||||||
OCaml | [pgvector-ocaml](https://github.com/pgvector/pgvector-ocaml)
|
OCaml | [pgvector-ocaml](https://github.com/pgvector/pgvector-ocaml)
|
||||||
Pascal | [pgvector-pascal](https://github.com/pgvector/pgvector-pascal)
|
|
||||||
Perl | [pgvector-perl](https://github.com/pgvector/pgvector-perl)
|
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)
|
||||||
Prolog | [pgvector-prolog](https://github.com/pgvector/pgvector-prolog)
|
|
||||||
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)
|
||||||
Racket | [pgvector-racket](https://github.com/pgvector/pgvector-racket)
|
|
||||||
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)
|
||||||
Tcl | [pgvector-tcl](https://github.com/pgvector/pgvector-tcl)
|
|
||||||
Zig | [pgvector-zig](https://github.com/pgvector/pgvector-zig)
|
Zig | [pgvector-zig](https://github.com/pgvector/pgvector-zig)
|
||||||
|
|
||||||
## Frequently Asked Questions
|
## Frequently Asked Questions
|
||||||
@@ -816,11 +788,11 @@ Yes, pgvector uses the write-ahead log (WAL), which allows for replication and p
|
|||||||
|
|
||||||
#### What if I want to index vectors with more than 2,000 dimensions?
|
#### What if I want to index vectors with more than 2,000 dimensions?
|
||||||
|
|
||||||
You can use [half-precision vectors](#half-precision-vectors) or [half-precision indexing](#half-precision-indexing) to index up to 4,000 dimensions or [binary quantization](#binary-quantization) to index up to 64,000 dimensions. Other options are [indexing subvectors](#indexing-subvectors) (for models that support it) or [dimensionality reduction](https://en.wikipedia.org/wiki/Dimensionality_reduction).
|
You can use [half-precision indexing](#half-precision-indexing) to index up to 4,000 dimensions or [binary quantization](#binary-quantization) to index up to 64,000 dimensions. Another option is [dimensionality reduction](https://en.wikipedia.org/wiki/Dimensionality_reduction).
|
||||||
|
|
||||||
#### 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(n)`).
|
You can use `vector` as the type (instead of `vector(3)`).
|
||||||
|
|
||||||
```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));
|
||||||
@@ -875,8 +847,6 @@ No, but like other index types, you’ll likely see better performance if they d
|
|||||||
SELECT pg_size_pretty(pg_relation_size('index_name'));
|
SELECT pg_size_pretty(pg_relation_size('index_name'));
|
||||||
```
|
```
|
||||||
|
|
||||||
Use [half-precision indexing](#half-precision-indexing) or [binary quantization](#binary-quantization) for smaller indexes.
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
#### Why isn’t a query using an index?
|
#### Why isn’t a query using an index?
|
||||||
@@ -922,7 +892,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`), 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.
|
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).
|
Also, note that `NULL` vectors are not indexed (as well as zero vectors for cosine distance).
|
||||||
|
|
||||||
@@ -934,7 +904,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`). Enabling [iterative index scans](#iterative-index-scans) can address this.
|
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).
|
Also, note that `NULL` vectors are not indexed (as well as zero vectors for cosine distance).
|
||||||
|
|
||||||
@@ -1070,7 +1040,7 @@ l2_normalize(sparsevec) → sparsevec | Normalize with Euclidean norm | 0.7.0
|
|||||||
If your machine has multiple Postgres installations, specify the path to [pg_config](https://www.postgresql.org/docs/current/app-pgconfig.html) with:
|
If your machine has multiple Postgres installations, specify the path to [pg_config](https://www.postgresql.org/docs/current/app-pgconfig.html) with:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
export PG_CONFIG=/Library/PostgreSQL/18/bin/pg_config
|
export PG_CONFIG=/Library/PostgreSQL/16/bin/pg_config
|
||||||
```
|
```
|
||||||
|
|
||||||
Then re-run the installation instructions (run `make clean` before `make` if needed). If `sudo` is needed for `make install`, use:
|
Then re-run the installation instructions (run `make clean` before `make` if needed). If `sudo` is needed for `make install`, use:
|
||||||
@@ -1081,11 +1051,11 @@ sudo --preserve-env=PG_CONFIG make install
|
|||||||
|
|
||||||
A few common paths on Mac are:
|
A few common paths on Mac are:
|
||||||
|
|
||||||
- EDB installer - `/Library/PostgreSQL/18/bin/pg_config`
|
- EDB installer - `/Library/PostgreSQL/16/bin/pg_config`
|
||||||
- Homebrew (arm64) - `/opt/homebrew/opt/postgresql@18/bin/pg_config`
|
- Homebrew (arm64) - `/opt/homebrew/opt/postgresql@16/bin/pg_config`
|
||||||
- Homebrew (x86-64) - `/usr/local/opt/postgresql@18/bin/pg_config`
|
- Homebrew (x86-64) - `/usr/local/opt/postgresql@16/bin/pg_config`
|
||||||
|
|
||||||
Note: Replace `18` with your Postgres server version
|
Note: Replace `16` with your Postgres server version
|
||||||
|
|
||||||
### Missing Header
|
### Missing Header
|
||||||
|
|
||||||
@@ -1094,20 +1064,14 @@ If compilation fails with `fatal error: postgres.h: No such file or directory`,
|
|||||||
For Ubuntu and Debian, use:
|
For Ubuntu and Debian, use:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo apt install postgresql-server-dev-18
|
sudo apt install postgresql-server-dev-16
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: Replace `18` with your Postgres server version
|
Note: Replace `16` with your Postgres server version
|
||||||
|
|
||||||
### Missing SDK
|
### Missing SDK
|
||||||
|
|
||||||
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.
|
If compilation fails and the output includes `warning: no such sysroot directory` on Mac, reinstall Xcode Command Line Tools.
|
||||||
|
|
||||||
```sh
|
|
||||||
pg_config --cppflags
|
|
||||||
```
|
|
||||||
|
|
||||||
Reinstall Postgres to fix this.
|
|
||||||
|
|
||||||
### Portability
|
### Portability
|
||||||
|
|
||||||
@@ -1125,14 +1089,6 @@ 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 you’re using the `x64 Native Tools Command Prompt`. 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.
|
||||||
@@ -1144,38 +1100,17 @@ If installation fails with `Access is denied`, re-run the installation instructi
|
|||||||
Get the [Docker image](https://hub.docker.com/r/pgvector/pgvector) with:
|
Get the [Docker image](https://hub.docker.com/r/pgvector/pgvector) with:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker pull pgvector/pgvector:pg18-trixie
|
docker pull pgvector/pgvector:pg16
|
||||||
```
|
```
|
||||||
|
|
||||||
This adds pgvector to the [Postgres image](https://hub.docker.com/_/postgres) (replace `18` with your Postgres server version, and run it the same way).
|
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).
|
||||||
|
|
||||||
Supported tags are:
|
|
||||||
|
|
||||||
- `pg18-trixie`, `0.8.4-pg18-trixie`
|
|
||||||
- `pg18-bookworm`, `0.8.4-pg18-bookworm`, `pg18`, `0.8.4-pg18`
|
|
||||||
- `pg17-trixie`, `0.8.4-pg17-trixie`
|
|
||||||
- `pg17-bookworm`, `0.8.4-pg17-bookworm`, `pg17`, `0.8.4-pg17`
|
|
||||||
- `pg16-trixie`, `0.8.4-pg16-trixie`
|
|
||||||
- `pg16-bookworm`, `0.8.4-pg16-bookworm`, `pg16`, `0.8.4-pg16`
|
|
||||||
- `pg15-trixie`, `0.8.4-pg15-trixie`
|
|
||||||
- `pg15-bookworm`, `0.8.4-pg15-bookworm`, `pg15`, `0.8.4-pg15`
|
|
||||||
- `pg14-trixie`, `0.8.4-pg14-trixie`
|
|
||||||
- `pg14-bookworm`, `0.8.4-pg14-bookworm`, `pg14`, `0.8.4-pg14`
|
|
||||||
- `pg13-trixie`, `0.8.4-pg13-trixie`
|
|
||||||
- `pg13-bookworm`, `0.8.4-pg13-bookworm`, `pg13`, `0.8.4-pg13`
|
|
||||||
|
|
||||||
You can also build the image manually:
|
You can also build the image manually:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone --branch v0.8.4 https://github.com/pgvector/pgvector.git
|
git clone --branch v0.7.4 https://github.com/pgvector/pgvector.git
|
||||||
cd pgvector
|
cd pgvector
|
||||||
docker build --pull --build-arg PG_MAJOR=18 -t myuser/pgvector .
|
docker build --pull --build-arg PG_MAJOR=16 -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
|
||||||
@@ -1186,7 +1121,7 @@ With Homebrew Postgres, you can use:
|
|||||||
brew install pgvector
|
brew install pgvector
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: This only adds it to the `postgresql@18` and `postgresql@17` formulas
|
Note: This only adds it to the `postgresql@14` formula
|
||||||
|
|
||||||
### PGXN
|
### PGXN
|
||||||
|
|
||||||
@@ -1201,29 +1136,29 @@ pgxn install vector
|
|||||||
Debian and Ubuntu packages are available from the [PostgreSQL APT Repository](https://wiki.postgresql.org/wiki/Apt). Follow the [setup instructions](https://wiki.postgresql.org/wiki/Apt#Quickstart) and run:
|
Debian and Ubuntu packages are available from the [PostgreSQL APT Repository](https://wiki.postgresql.org/wiki/Apt). Follow the [setup instructions](https://wiki.postgresql.org/wiki/Apt#Quickstart) and run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo apt install postgresql-18-pgvector
|
sudo apt install postgresql-16-pgvector
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: Replace `18` with your Postgres server version
|
Note: Replace `16` with your Postgres server version
|
||||||
|
|
||||||
### Yum
|
### Yum
|
||||||
|
|
||||||
RPM packages are available from the [PostgreSQL Yum Repository](https://yum.postgresql.org/). Follow the [setup instructions](https://www.postgresql.org/download/linux/redhat/) for your distribution and run:
|
RPM packages are available from the [PostgreSQL Yum Repository](https://yum.postgresql.org/). Follow the [setup instructions](https://www.postgresql.org/download/linux/redhat/) for your distribution and run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo yum install pgvector_18
|
sudo yum install pgvector_16
|
||||||
# or
|
# or
|
||||||
sudo dnf install pgvector_18
|
sudo dnf install pgvector_16
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: Replace `18` with your Postgres server version
|
Note: Replace `16` with your Postgres server version
|
||||||
|
|
||||||
### pkg
|
### pkg
|
||||||
|
|
||||||
Install the FreeBSD package with:
|
Install the FreeBSD package with:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pkg install postgresql17-pgvector
|
pkg install postgresql15-pgvector
|
||||||
```
|
```
|
||||||
|
|
||||||
or the port with:
|
or the port with:
|
||||||
@@ -1233,14 +1168,6 @@ cd /usr/ports/databases/pgvector
|
|||||||
make install
|
make install
|
||||||
```
|
```
|
||||||
|
|
||||||
### APK
|
|
||||||
|
|
||||||
Install the Alpine package with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
apk add postgresql-pgvector
|
|
||||||
```
|
|
||||||
|
|
||||||
### conda-forge
|
### conda-forge
|
||||||
|
|
||||||
With Conda Postgres, install from [conda-forge](https://anaconda.org/conda-forge/pgvector) with:
|
With Conda Postgres, install from [conda-forge](https://anaconda.org/conda-forge/pgvector) with:
|
||||||
@@ -1273,6 +1200,36 @@ 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:
|
||||||
@@ -1329,7 +1286,7 @@ make clean && PG_CFLAGS="-DUSE_ASSERT_CHECKING" make && make install
|
|||||||
To enable benchmarking:
|
To enable benchmarking:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make clean && PG_CFLAGS="-DHNSW_BENCH -DIVFFLAT_BENCH" make && make install
|
make clean && PG_CFLAGS="-DIVFFLAT_BENCH" make && make install
|
||||||
```
|
```
|
||||||
|
|
||||||
To show memory usage:
|
To show memory usage:
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
|
||||||
\echo Use "ALTER EXTENSION vector UPDATE TO '0.8.1'" to load this file. \quit
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
|
||||||
\echo Use "ALTER EXTENSION vector UPDATE TO '0.8.2'" to load this file. \quit
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
|
||||||
\echo Use "ALTER EXTENSION vector UPDATE TO '0.8.3'" to load this file. \quit
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
|
||||||
\echo Use "ALTER EXTENSION vector UPDATE TO '0.8.4'" to load this file. \quit
|
|
||||||
@@ -31,12 +31,10 @@
|
|||||||
#define BIT_TARGET_CLONES
|
#define BIT_TARGET_CLONES
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Use built-ins when possible for Postgres < 19 for inlining */
|
/* Use built-ins when possible for inlining */
|
||||||
#if PG_VERSION_NUM >= 190000
|
#if defined(HAVE__BUILTIN_POPCOUNT) && defined(HAVE_LONG_INT_64)
|
||||||
#define popcount64(x) pg_popcount64(x)
|
|
||||||
#elif defined(HAVE__BUILTIN_POPCOUNT) && (defined(HAVE_LONG_INT_64) || SIZEOF_LONG == 8)
|
|
||||||
#define popcount64(x) __builtin_popcountl(x)
|
#define popcount64(x) __builtin_popcountl(x)
|
||||||
#elif defined(HAVE__BUILTIN_POPCOUNT) && (defined(HAVE_LONG_LONG_INT_64) || SIZEOF_LONG_LONG == 8)
|
#elif defined(HAVE__BUILTIN_POPCOUNT) && defined(HAVE_LONG_LONG_INT_64)
|
||||||
#define popcount64(x) __builtin_popcountll(x)
|
#define popcount64(x) __builtin_popcountll(x)
|
||||||
#elif !defined(_MSC_VER)
|
#elif !defined(_MSC_VER)
|
||||||
/* Fails to resolve with MSVC */
|
/* Fails to resolve with MSVC */
|
||||||
@@ -171,7 +169,7 @@ BitJaccardDistanceAvx512Popcount(uint32 bytes, unsigned char *ax, unsigned char
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
TARGET_XSAVE static bool
|
TARGET_XSAVE static bool
|
||||||
SupportsAvx512Popcount(void)
|
SupportsAvx512Popcount()
|
||||||
{
|
{
|
||||||
unsigned int exx[4] = {0, 0, 0, 0};
|
unsigned int exx[4] = {0, 0, 0, 0};
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include "bitutils.h"
|
#include "bitutils.h"
|
||||||
#include "bitvec.h"
|
#include "bitvec.h"
|
||||||
#include "fmgr.h"
|
|
||||||
#include "utils/varbit.h"
|
#include "utils/varbit.h"
|
||||||
#include "vector.h"
|
#include "vector.h"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include <math.h>
|
|
||||||
|
|
||||||
#include "halfutils.h"
|
#include "halfutils.h"
|
||||||
#include "halfvec.h"
|
#include "halfvec.h"
|
||||||
|
|
||||||
|
|||||||
@@ -13,20 +13,12 @@
|
|||||||
#include "port.h" /* for strtof() */
|
#include "port.h" /* for strtof() */
|
||||||
#include "sparsevec.h"
|
#include "sparsevec.h"
|
||||||
#include "utils/array.h"
|
#include "utils/array.h"
|
||||||
|
#include "utils/builtins.h"
|
||||||
#include "utils/float.h"
|
#include "utils/float.h"
|
||||||
#include "utils/fmgrprotos.h"
|
|
||||||
#include "utils/lsyscache.h"
|
#include "utils/lsyscache.h"
|
||||||
#include "utils/varbit.h"
|
#include "utils/numeric.h"
|
||||||
#include "vector.h"
|
#include "vector.h"
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 160000
|
|
||||||
#include "varatt.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 170000
|
|
||||||
#include "parser/scansup.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define STATE_DIMS(x) (ARR_DIMS(x)[0] - 1)
|
#define STATE_DIMS(x) (ARR_DIMS(x)[0] - 1)
|
||||||
#define CreateStateDatums(dim) palloc(sizeof(Datum) * (dim + 1))
|
#define CreateStateDatums(dim) palloc(sizeof(Datum) * (dim + 1))
|
||||||
|
|
||||||
@@ -137,9 +129,9 @@ InitHalfVector(int dim)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 170000
|
/*
|
||||||
#define halfvec_isspace(ch) scanner_isspace(ch)
|
* Check for whitespace, since array_isspace() is static
|
||||||
#else
|
*/
|
||||||
static inline bool
|
static inline bool
|
||||||
halfvec_isspace(char ch)
|
halfvec_isspace(char ch)
|
||||||
{
|
{
|
||||||
@@ -152,7 +144,6 @@ halfvec_isspace(char ch)
|
|||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check state array
|
* Check state array
|
||||||
@@ -907,21 +898,8 @@ halfvec_binary_quantize(PG_FUNCTION_ARGS)
|
|||||||
half *ax = a->x;
|
half *ax = a->x;
|
||||||
VarBit *result = InitBitVector(a->dim);
|
VarBit *result = InitBitVector(a->dim);
|
||||||
unsigned char *rx = VARBITS(result);
|
unsigned char *rx = VARBITS(result);
|
||||||
int i = 0;
|
|
||||||
int count = (a->dim / 8) * 8;
|
|
||||||
|
|
||||||
/* Auto-vectorized on aarch64 */
|
for (int i = 0; i < a->dim; i++)
|
||||||
for (; i < count; i += 8)
|
|
||||||
{
|
|
||||||
unsigned char result_byte = 0;
|
|
||||||
|
|
||||||
for (int j = 0; j < 8; j++)
|
|
||||||
result_byte |= (HalfToFloat4(ax[i + j]) > 0) << (7 - j);
|
|
||||||
|
|
||||||
rx[i / 8] = result_byte;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (; i < a->dim; i++)
|
|
||||||
rx[i / 8] |= (HalfToFloat4(ax[i]) > 0) << (7 - (i % 8));
|
rx[i / 8] |= (HalfToFloat4(ax[i]) > 0) << (7 - (i % 8));
|
||||||
|
|
||||||
PG_RETURN_VARBIT_P(result);
|
PG_RETURN_VARBIT_P(result);
|
||||||
|
|||||||
196
src/hnsw.c
196
src/hnsw.c
@@ -1,41 +1,26 @@
|
|||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include <float.h>
|
#include <float.h>
|
||||||
#include <limits.h>
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#include "access/amapi.h"
|
#include "access/amapi.h"
|
||||||
#include "access/genam.h"
|
|
||||||
#include "access/reloptions.h"
|
#include "access/reloptions.h"
|
||||||
#include "commands/progress.h"
|
#include "commands/progress.h"
|
||||||
#include "commands/vacuum.h"
|
#include "commands/vacuum.h"
|
||||||
#include "fmgr.h"
|
|
||||||
#include "hnsw.h"
|
#include "hnsw.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
#include "nodes/pg_list.h"
|
|
||||||
#include "storage/lwlock.h"
|
|
||||||
#include "utils/float.h"
|
#include "utils/float.h"
|
||||||
#include "utils/guc.h"
|
#include "utils/guc.h"
|
||||||
#include "utils/relcache.h"
|
|
||||||
#include "utils/selfuncs.h"
|
#include "utils/selfuncs.h"
|
||||||
#include "utils/spccache.h"
|
#include "utils/spccache.h"
|
||||||
#include "vector.h"
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 150000
|
#if PG_VERSION_NUM < 150000
|
||||||
#define MarkGUCPrefixReserved(x) EmitWarningsOnPlaceholders(x)
|
#define MarkGUCPrefixReserved(x) EmitWarningsOnPlaceholders(x)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static const struct config_enum_entry hnsw_iterative_scan_options[] = {
|
|
||||||
{"off", HNSW_ITERATIVE_SCAN_OFF, false},
|
|
||||||
{"relaxed_order", HNSW_ITERATIVE_SCAN_RELAXED, false},
|
|
||||||
{"strict_order", HNSW_ITERATIVE_SCAN_STRICT, false},
|
|
||||||
{NULL, 0, false}
|
|
||||||
};
|
|
||||||
|
|
||||||
int hnsw_ef_search;
|
int hnsw_ef_search;
|
||||||
int hnsw_iterative_scan;
|
int hnsw_ef_stream;
|
||||||
int hnsw_max_scan_tuples;
|
bool hnsw_streaming;
|
||||||
double hnsw_scan_mem_multiplier;
|
|
||||||
int hnsw_lock_tranche_id;
|
int hnsw_lock_tranche_id;
|
||||||
static relopt_kind hnsw_relopt_kind;
|
static relopt_kind hnsw_relopt_kind;
|
||||||
|
|
||||||
@@ -59,20 +44,12 @@ HnswInitLockTranche(void)
|
|||||||
sizeof(int) * 1,
|
sizeof(int) * 1,
|
||||||
&found);
|
&found);
|
||||||
if (!found)
|
if (!found)
|
||||||
{
|
|
||||||
#if PG_VERSION_NUM >= 190000
|
|
||||||
tranche_ids[0] = LWLockNewTrancheId("HnswBuild");
|
|
||||||
#else
|
|
||||||
tranche_ids[0] = LWLockNewTrancheId();
|
tranche_ids[0] = LWLockNewTrancheId();
|
||||||
#endif
|
|
||||||
}
|
|
||||||
hnsw_lock_tranche_id = tranche_ids[0];
|
hnsw_lock_tranche_id = tranche_ids[0];
|
||||||
LWLockRelease(AddinShmemInitLock);
|
LWLockRelease(AddinShmemInitLock);
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 190000
|
|
||||||
/* Per-backend registration of the tranche ID */
|
/* Per-backend registration of the tranche ID */
|
||||||
LWLockRegisterTranche(hnsw_lock_tranche_id, "HnswBuild");
|
LWLockRegisterTranche(hnsw_lock_tranche_id, "HnswBuild");
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -94,19 +71,16 @@ HnswInit(void)
|
|||||||
"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, 0, 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",
|
/* TODO Figure out name */
|
||||||
NULL, &hnsw_iterative_scan,
|
DefineCustomBoolVariable("hnsw.streaming", "Use streaming mode",
|
||||||
HNSW_ITERATIVE_SCAN_OFF, hnsw_iterative_scan_options, PGC_USERSET, 0, NULL, NULL, NULL);
|
NULL, &hnsw_streaming,
|
||||||
|
HNSW_DEFAULT_STREAMING, PGC_USERSET, 0, NULL, NULL, NULL);
|
||||||
|
|
||||||
/* This is approximate and does not affect the initial scan */
|
/* TODO Figure out name */
|
||||||
DefineCustomIntVariable("hnsw.max_scan_tuples", "Sets the max number of tuples to visit for iterative scans",
|
/* TODO Use same value as ivfflat.max_probes for "all" */
|
||||||
NULL, &hnsw_max_scan_tuples,
|
DefineCustomIntVariable("hnsw.ef_stream", "Sets the max number of additional candidates to visit for streaming search",
|
||||||
20000, 1, INT_MAX, PGC_USERSET, 0, NULL, NULL, NULL);
|
"-1 means all", &hnsw_ef_stream,
|
||||||
|
HNSW_DEFAULT_EF_STREAM, HNSW_MIN_EF_STREAM, HNSW_MAX_EF_STREAM, PGC_USERSET, 0, NULL, NULL, NULL);
|
||||||
/* Same range as hash_mem_multiplier */
|
|
||||||
DefineCustomRealVariable("hnsw.scan_mem_multiplier", "Sets the multiple of work_mem to use for iterative scans",
|
|
||||||
NULL, &hnsw_scan_mem_multiplier,
|
|
||||||
1, 1, 1000, PGC_USERSET, 0, NULL, NULL, NULL);
|
|
||||||
|
|
||||||
MarkGUCPrefixReserved("hnsw");
|
MarkGUCPrefixReserved("hnsw");
|
||||||
}
|
}
|
||||||
@@ -128,6 +102,33 @@ hnswbuildphasename(int64 phasenum)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Estimate ef needed for iterative scans
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
EstimateEf(PlannerInfo *root, IndexPath *path)
|
||||||
|
{
|
||||||
|
double selectivity = 1;
|
||||||
|
ListCell *lc;
|
||||||
|
|
||||||
|
/* Cannot estimate without limit */
|
||||||
|
/* limit_tuples includes offset */
|
||||||
|
if (root->limit_tuples < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Get the selectivity of non-index conditions */
|
||||||
|
foreach(lc, path->indexinfo->indrestrictinfo)
|
||||||
|
{
|
||||||
|
RestrictInfo *rinfo = lfirst(lc);
|
||||||
|
|
||||||
|
/* Skip DEFAULT_INEQ_SEL since it may be a distance filter */
|
||||||
|
if (rinfo->norm_selec >= 0 && rinfo->norm_selec <= 1 && rinfo->norm_selec != (Selectivity) DEFAULT_INEQ_SEL)
|
||||||
|
selectivity *= rinfo->norm_selec;
|
||||||
|
}
|
||||||
|
|
||||||
|
return root->limit_tuples / Max(selectivity, 0.00001);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Estimate the cost of an index scan
|
* Estimate the cost of an index scan
|
||||||
*/
|
*/
|
||||||
@@ -139,34 +140,33 @@ hnswcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
|||||||
{
|
{
|
||||||
GenericCosts costs;
|
GenericCosts costs;
|
||||||
int m;
|
int m;
|
||||||
double ratio;
|
int ef;
|
||||||
double startupPages;
|
int entryLevel;
|
||||||
|
int layer0TuplesMax;
|
||||||
|
double layer0Selectivity;
|
||||||
|
double scalingFactor = 0.55;
|
||||||
double spc_seq_page_cost;
|
double spc_seq_page_cost;
|
||||||
Relation index;
|
Relation index;
|
||||||
|
|
||||||
/* Never use index without order */
|
/* Never use index without order */
|
||||||
if (path->indexorderbys == NIL)
|
if (path->indexorderbys == NULL)
|
||||||
{
|
{
|
||||||
*indexStartupCost = get_float8_infinity();
|
*indexStartupCost = get_float8_infinity();
|
||||||
*indexTotalCost = get_float8_infinity();
|
*indexTotalCost = get_float8_infinity();
|
||||||
*indexSelectivity = 0;
|
*indexSelectivity = 0;
|
||||||
*indexCorrelation = 0;
|
*indexCorrelation = 0;
|
||||||
*indexPages = 0;
|
*indexPages = 0;
|
||||||
#if PG_VERSION_NUM >= 180000
|
|
||||||
/* See "On disable_cost" thread on pgsql-hackers */
|
|
||||||
path->path.disabled_nodes = 2;
|
|
||||||
#endif
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MemSet(&costs, 0, sizeof(costs));
|
MemSet(&costs, 0, sizeof(costs));
|
||||||
|
|
||||||
genericcostestimate(root, path, loop_count, &costs);
|
|
||||||
|
|
||||||
index = index_open(path->indexinfo->indexoid, NoLock);
|
index = index_open(path->indexinfo->indexoid, NoLock);
|
||||||
HnswGetMetaPageInfo(index, &m, NULL);
|
HnswGetMetaPageInfo(index, &m, NULL);
|
||||||
index_close(index, NoLock);
|
index_close(index, NoLock);
|
||||||
|
|
||||||
|
ef = hnsw_streaming ? Max(hnsw_ef_search, EstimateEf(root, path)) : hnsw_ef_search;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* HNSW cost estimation follows a formula that accounts for the total
|
* HNSW cost estimation follows a formula that accounts for the total
|
||||||
* number of tuples indexed combined with the parameters that most
|
* number of tuples indexed combined with the parameters that most
|
||||||
@@ -194,38 +194,30 @@ hnswcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
|||||||
* at L0, accounting for previously visited tuples, multiplied by the
|
* at L0, accounting for previously visited tuples, multiplied by the
|
||||||
* "scalingFactor" (currently hardcoded).
|
* "scalingFactor" (currently hardcoded).
|
||||||
*/
|
*/
|
||||||
if (path->indexinfo->tuples > 0)
|
entryLevel = (int) (log(path->indexinfo->tuples + 1) * HnswGetMl(m));
|
||||||
{
|
layer0TuplesMax = HnswGetLayerM(m, 0) * ef;
|
||||||
double scalingFactor = 0.55;
|
layer0Selectivity = (scalingFactor * log(path->indexinfo->tuples + 1)) /
|
||||||
int entryLevel = (int) (log(path->indexinfo->tuples) * HnswGetMl(m));
|
(log(m) * (1 + log(ef)));
|
||||||
int layer0TuplesMax = HnswGetLayerM(m, 0) * hnsw_ef_search;
|
|
||||||
double layer0Selectivity = scalingFactor * log(path->indexinfo->tuples) / (log(m) * (1 + log(hnsw_ef_search)));
|
|
||||||
|
|
||||||
ratio = (entryLevel * m + layer0TuplesMax * layer0Selectivity) / path->indexinfo->tuples;
|
costs.numIndexTuples = (entryLevel * m) +
|
||||||
|
(layer0TuplesMax * layer0Selectivity);
|
||||||
|
|
||||||
if (ratio > 1)
|
genericcostestimate(root, path, loop_count, &costs);
|
||||||
ratio = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
ratio = 1;
|
|
||||||
|
|
||||||
get_tablespace_page_costs(path->indexinfo->reltablespace, NULL, &spc_seq_page_cost);
|
get_tablespace_page_costs(path->indexinfo->reltablespace, NULL, &spc_seq_page_cost);
|
||||||
|
|
||||||
/* Startup cost is cost before returning the first row */
|
|
||||||
costs.indexStartupCost = costs.indexTotalCost * ratio;
|
|
||||||
|
|
||||||
/* Adjust cost if needed since TOAST not included in seq scan cost */
|
/* Adjust cost if needed since TOAST not included in seq scan cost */
|
||||||
startupPages = costs.numIndexPages * ratio;
|
if (costs.numIndexPages > path->indexinfo->rel->pages)
|
||||||
if (startupPages > path->indexinfo->rel->pages && ratio < 0.5)
|
|
||||||
{
|
{
|
||||||
/* Change all page cost from random to sequential */
|
/* Change all page cost from random to sequential */
|
||||||
costs.indexStartupCost -= startupPages * (costs.spc_random_page_cost - spc_seq_page_cost);
|
costs.indexTotalCost -= costs.numIndexPages * (costs.spc_random_page_cost - spc_seq_page_cost);
|
||||||
|
|
||||||
/* Remove cost of extra pages */
|
/* Remove cost of extra pages */
|
||||||
costs.indexStartupCost -= (startupPages - path->indexinfo->rel->pages) * spc_seq_page_cost;
|
costs.indexTotalCost -= (costs.numIndexPages - path->indexinfo->rel->pages) * spc_seq_page_cost;
|
||||||
}
|
}
|
||||||
|
|
||||||
*indexStartupCost = costs.indexStartupCost;
|
/* Use total cost since most work happens before first tuple is returned */
|
||||||
|
*indexStartupCost = costs.indexTotalCost;
|
||||||
*indexTotalCost = costs.indexTotalCost;
|
*indexTotalCost = costs.indexTotalCost;
|
||||||
*indexSelectivity = costs.indexSelectivity;
|
*indexSelectivity = costs.indexSelectivity;
|
||||||
*indexCorrelation = costs.indexCorrelation;
|
*indexCorrelation = costs.indexCorrelation;
|
||||||
@@ -267,64 +259,6 @@ FUNCTION_PREFIX PG_FUNCTION_INFO_V1(hnswhandler);
|
|||||||
Datum
|
Datum
|
||||||
hnswhandler(PG_FUNCTION_ARGS)
|
hnswhandler(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
#if PG_VERSION_NUM >= 190000
|
|
||||||
static const IndexAmRoutine amroutine = {
|
|
||||||
.type = T_IndexAmRoutine,
|
|
||||||
.amstrategies = 0,
|
|
||||||
.amsupport = 3,
|
|
||||||
.amoptsprocnum = 0,
|
|
||||||
.amcanorder = false,
|
|
||||||
.amcanorderbyop = true,
|
|
||||||
.amcanhash = false,
|
|
||||||
.amconsistentequality = false,
|
|
||||||
.amconsistentordering = false,
|
|
||||||
.amcanbackward = false,
|
|
||||||
.amcanunique = false,
|
|
||||||
.amcanmulticol = false,
|
|
||||||
.amoptionalkey = true,
|
|
||||||
.amsearcharray = false,
|
|
||||||
.amsearchnulls = false,
|
|
||||||
.amstorage = false,
|
|
||||||
.amclusterable = false,
|
|
||||||
.ampredlocks = false,
|
|
||||||
.amcanparallel = false,
|
|
||||||
.amcanbuildparallel = true,
|
|
||||||
.amcaninclude = false,
|
|
||||||
.amusemaintenanceworkmem = false,
|
|
||||||
.amsummarizing = false,
|
|
||||||
.amparallelvacuumoptions = VACUUM_OPTION_PARALLEL_BULKDEL,
|
|
||||||
.amkeytype = InvalidOid,
|
|
||||||
|
|
||||||
.ambuild = hnswbuild,
|
|
||||||
.ambuildempty = hnswbuildempty,
|
|
||||||
.aminsert = hnswinsert,
|
|
||||||
.aminsertcleanup = NULL,
|
|
||||||
.ambulkdelete = hnswbulkdelete,
|
|
||||||
.amvacuumcleanup = hnswvacuumcleanup,
|
|
||||||
.amcanreturn = NULL,
|
|
||||||
.amcostestimate = hnswcostestimate,
|
|
||||||
.amgettreeheight = NULL,
|
|
||||||
.amoptions = hnswoptions,
|
|
||||||
.amproperty = NULL,
|
|
||||||
.ambuildphasename = hnswbuildphasename,
|
|
||||||
.amvalidate = hnswvalidate,
|
|
||||||
.amadjustmembers = NULL,
|
|
||||||
.ambeginscan = hnswbeginscan,
|
|
||||||
.amrescan = hnswrescan,
|
|
||||||
.amgettuple = hnswgettuple,
|
|
||||||
.amgetbitmap = NULL,
|
|
||||||
.amendscan = hnswendscan,
|
|
||||||
.ammarkpos = NULL,
|
|
||||||
.amrestrpos = NULL,
|
|
||||||
.amestimateparallelscan = NULL,
|
|
||||||
.aminitparallelscan = NULL,
|
|
||||||
.amparallelrescan = NULL,
|
|
||||||
.amtranslatestrategy = NULL,
|
|
||||||
.amtranslatecmptype = NULL,
|
|
||||||
};
|
|
||||||
|
|
||||||
PG_RETURN_POINTER(&amroutine);
|
|
||||||
#else
|
|
||||||
IndexAmRoutine *amroutine = makeNode(IndexAmRoutine);
|
IndexAmRoutine *amroutine = makeNode(IndexAmRoutine);
|
||||||
|
|
||||||
amroutine->amstrategies = 0;
|
amroutine->amstrategies = 0;
|
||||||
@@ -332,11 +266,6 @@ 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;
|
||||||
@@ -369,9 +298,6 @@ 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;
|
||||||
@@ -392,11 +318,5 @@ 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);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|||||||
96
src/hnsw.h
96
src/hnsw.h
@@ -3,17 +3,11 @@
|
|||||||
|
|
||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include <math.h>
|
|
||||||
|
|
||||||
#include "access/genam.h"
|
#include "access/genam.h"
|
||||||
#include "access/parallel.h"
|
#include "access/parallel.h"
|
||||||
#include "lib/pairingheap.h"
|
#include "lib/pairingheap.h"
|
||||||
#include "nodes/execnodes.h"
|
#include "nodes/execnodes.h"
|
||||||
#include "port.h" /* for random() */
|
#include "port.h" /* for random() */
|
||||||
#include "storage/bufpage.h"
|
|
||||||
#include "storage/condition_variable.h"
|
|
||||||
#include "storage/lwlock.h"
|
|
||||||
#include "storage/s_lock.h"
|
|
||||||
#include "utils/relptr.h"
|
#include "utils/relptr.h"
|
||||||
#include "utils/sampling.h"
|
#include "utils/sampling.h"
|
||||||
#include "vector.h"
|
#include "vector.h"
|
||||||
@@ -22,10 +16,6 @@
|
|||||||
#include "portability/instr_time.h"
|
#include "portability/instr_time.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 190000
|
|
||||||
typedef Pointer Item;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define HNSW_MAX_DIM 2000
|
#define HNSW_MAX_DIM 2000
|
||||||
#define HNSW_MAX_NNZ 1000
|
#define HNSW_MAX_NNZ 1000
|
||||||
|
|
||||||
@@ -56,6 +46,10 @@ typedef Pointer Item;
|
|||||||
#define HNSW_DEFAULT_EF_SEARCH 40
|
#define HNSW_DEFAULT_EF_SEARCH 40
|
||||||
#define HNSW_MIN_EF_SEARCH 1
|
#define HNSW_MIN_EF_SEARCH 1
|
||||||
#define HNSW_MAX_EF_SEARCH 1000
|
#define HNSW_MAX_EF_SEARCH 1000
|
||||||
|
#define HNSW_DEFAULT_STREAMING false
|
||||||
|
#define HNSW_DEFAULT_EF_STREAM -1
|
||||||
|
#define HNSW_MIN_EF_STREAM -1
|
||||||
|
#define HNSW_MAX_EF_STREAM INT_MAX
|
||||||
|
|
||||||
/* Tuple types */
|
/* Tuple types */
|
||||||
#define HNSW_ELEMENT_TUPLE_TYPE 1
|
#define HNSW_ELEMENT_TUPLE_TYPE 1
|
||||||
@@ -117,9 +111,6 @@ typedef Pointer Item;
|
|||||||
/* Ensure fits on page and in uint8 */
|
/* Ensure fits on page and in uint8 */
|
||||||
#define HnswGetMaxLevel(m) Min(((BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(HnswPageOpaqueData)) - offsetof(HnswNeighborTupleData, indextids) - sizeof(ItemIdData)) / (sizeof(ItemPointerData)) / (m)) - 2, 255)
|
#define HnswGetMaxLevel(m) Min(((BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(HnswPageOpaqueData)) - offsetof(HnswNeighborTupleData, indextids) - sizeof(ItemIdData)) / (sizeof(ItemPointerData)) / (m)) - 2, 255)
|
||||||
|
|
||||||
#define HnswGetSearchCandidate(membername, ptr) pairingheap_container(HnswSearchCandidate, membername, ptr)
|
|
||||||
#define HnswGetSearchCandidateConst(membername, ptr) pairingheap_const_container(HnswSearchCandidate, membername, ptr)
|
|
||||||
|
|
||||||
#define HnswGetValue(base, element) PointerGetDatum(HnswPtrAccess(base, (element)->value))
|
#define HnswGetValue(base, element) PointerGetDatum(HnswPtrAccess(base, (element)->value))
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 140005
|
#if PG_VERSION_NUM < 140005
|
||||||
@@ -138,24 +129,16 @@ typedef Pointer Item;
|
|||||||
|
|
||||||
/* Variables */
|
/* Variables */
|
||||||
extern int hnsw_ef_search;
|
extern int hnsw_ef_search;
|
||||||
extern int hnsw_iterative_scan;
|
extern int hnsw_ef_stream;
|
||||||
extern int hnsw_max_scan_tuples;
|
extern bool hnsw_streaming;
|
||||||
extern double hnsw_scan_mem_multiplier;
|
|
||||||
extern int hnsw_lock_tranche_id;
|
extern int hnsw_lock_tranche_id;
|
||||||
|
|
||||||
typedef enum HnswIterativeScanMode
|
|
||||||
{
|
|
||||||
HNSW_ITERATIVE_SCAN_OFF,
|
|
||||||
HNSW_ITERATIVE_SCAN_RELAXED,
|
|
||||||
HNSW_ITERATIVE_SCAN_STRICT
|
|
||||||
} HnswIterativeScanMode;
|
|
||||||
|
|
||||||
typedef struct HnswElementData HnswElementData;
|
typedef struct HnswElementData HnswElementData;
|
||||||
typedef struct HnswNeighborArray HnswNeighborArray;
|
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 */
|
||||||
@@ -203,9 +186,12 @@ typedef struct HnswSearchCandidate
|
|||||||
pairingheap_node c_node;
|
pairingheap_node c_node;
|
||||||
pairingheap_node w_node;
|
pairingheap_node w_node;
|
||||||
HnswElementPtr element;
|
HnswElementPtr element;
|
||||||
double distance;
|
float distance;
|
||||||
} HnswSearchCandidate;
|
} HnswSearchCandidate;
|
||||||
|
|
||||||
|
#define HnswGetSearchCandidate(membername, ptr) pairingheap_container(HnswSearchCandidate, membername, ptr)
|
||||||
|
#define HnswGetSearchCandidateConst(membername, ptr) pairingheap_const_container(HnswSearchCandidate, membername, ptr)
|
||||||
|
|
||||||
/* HNSW index options */
|
/* HNSW index options */
|
||||||
typedef struct HnswOptions
|
typedef struct HnswOptions
|
||||||
{
|
{
|
||||||
@@ -228,8 +214,8 @@ typedef struct HnswGraph
|
|||||||
|
|
||||||
/* Allocations state */
|
/* Allocations state */
|
||||||
LWLock allocatorLock;
|
LWLock allocatorLock;
|
||||||
Size memoryUsed;
|
long memoryUsed;
|
||||||
Size memoryTotal;
|
long memoryTotal;
|
||||||
|
|
||||||
/* Flushed state */
|
/* Flushed state */
|
||||||
LWLock flushLock;
|
LWLock flushLock;
|
||||||
@@ -280,18 +266,6 @@ typedef struct HnswTypeInfo
|
|||||||
void (*checkValue) (Pointer v);
|
void (*checkValue) (Pointer v);
|
||||||
} HnswTypeInfo;
|
} HnswTypeInfo;
|
||||||
|
|
||||||
typedef struct HnswSupport
|
|
||||||
{
|
|
||||||
FmgrInfo *procinfo;
|
|
||||||
FmgrInfo *normprocinfo;
|
|
||||||
Oid collation;
|
|
||||||
} HnswSupport;
|
|
||||||
|
|
||||||
typedef struct HnswQuery
|
|
||||||
{
|
|
||||||
Datum value;
|
|
||||||
} HnswQuery;
|
|
||||||
|
|
||||||
typedef struct HnswBuildState
|
typedef struct HnswBuildState
|
||||||
{
|
{
|
||||||
/* Info */
|
/* Info */
|
||||||
@@ -311,7 +285,9 @@ typedef struct HnswBuildState
|
|||||||
double reltuples;
|
double reltuples;
|
||||||
|
|
||||||
/* Support functions */
|
/* Support functions */
|
||||||
HnswSupport support;
|
FmgrInfo *procinfo;
|
||||||
|
FmgrInfo *normprocinfo;
|
||||||
|
Oid collation;
|
||||||
|
|
||||||
/* Variables */
|
/* Variables */
|
||||||
HnswGraph graphData;
|
HnswGraph graphData;
|
||||||
@@ -385,12 +361,6 @@ typedef union
|
|||||||
struct tidhash_hash *tids;
|
struct tidhash_hash *tids;
|
||||||
} visited_hash;
|
} visited_hash;
|
||||||
|
|
||||||
typedef union
|
|
||||||
{
|
|
||||||
HnswElement element;
|
|
||||||
ItemPointerData indextid;
|
|
||||||
} HnswUnvisited;
|
|
||||||
|
|
||||||
typedef struct HnswScanOpaqueData
|
typedef struct HnswScanOpaqueData
|
||||||
{
|
{
|
||||||
const HnswTypeInfo *typeInfo;
|
const HnswTypeInfo *typeInfo;
|
||||||
@@ -398,15 +368,16 @@ typedef struct HnswScanOpaqueData
|
|||||||
List *w;
|
List *w;
|
||||||
visited_hash v;
|
visited_hash v;
|
||||||
pairingheap *discarded;
|
pairingheap *discarded;
|
||||||
HnswQuery q;
|
Datum q;
|
||||||
int m;
|
int m;
|
||||||
int64 tuples;
|
int64 tuples;
|
||||||
double previousDistance;
|
double previousDistance;
|
||||||
Size maxMemory;
|
|
||||||
MemoryContext tmpCtx;
|
MemoryContext tmpCtx;
|
||||||
|
|
||||||
/* Support functions */
|
/* Support functions */
|
||||||
HnswSupport support;
|
FmgrInfo *procinfo;
|
||||||
|
FmgrInfo *normprocinfo;
|
||||||
|
Oid collation;
|
||||||
} HnswScanOpaqueData;
|
} HnswScanOpaqueData;
|
||||||
|
|
||||||
typedef HnswScanOpaqueData * HnswScanOpaque;
|
typedef HnswScanOpaqueData * HnswScanOpaque;
|
||||||
@@ -424,14 +395,14 @@ typedef struct HnswVacuumState
|
|||||||
int efConstruction;
|
int efConstruction;
|
||||||
|
|
||||||
/* Support functions */
|
/* Support functions */
|
||||||
HnswSupport support;
|
FmgrInfo *procinfo;
|
||||||
|
Oid collation;
|
||||||
|
|
||||||
/* Variables */
|
/* Variables */
|
||||||
struct tidhash_hash *deleting;
|
struct tidhash_hash *deleted;
|
||||||
BufferAccessStrategy bas;
|
BufferAccessStrategy bas;
|
||||||
HnswNeighborTuple ntup;
|
HnswNeighborTuple ntup;
|
||||||
HnswElementData highestPoint;
|
HnswElementData highestPoint;
|
||||||
HnswElementData fallbackPoint;
|
|
||||||
|
|
||||||
/* Memory */
|
/* Memory */
|
||||||
MemoryContext tmpCtx;
|
MemoryContext tmpCtx;
|
||||||
@@ -441,33 +412,30 @@ typedef struct HnswVacuumState
|
|||||||
int HnswGetM(Relation index);
|
int HnswGetM(Relation index);
|
||||||
int HnswGetEfConstruction(Relation index);
|
int HnswGetEfConstruction(Relation index);
|
||||||
FmgrInfo *HnswOptionalProcInfo(Relation index, uint16 procnum);
|
FmgrInfo *HnswOptionalProcInfo(Relation index, uint16 procnum);
|
||||||
void HnswInitSupport(HnswSupport * support, Relation index);
|
|
||||||
Datum HnswNormValue(const HnswTypeInfo * typeInfo, Oid collation, Datum value);
|
Datum HnswNormValue(const HnswTypeInfo * typeInfo, Oid collation, Datum value);
|
||||||
bool HnswCheckNorm(HnswSupport * support, Datum value);
|
bool HnswCheckNorm(FmgrInfo *procinfo, Oid collation, Datum value);
|
||||||
Buffer HnswNewBuffer(Relation index, ForkNumber forkNum);
|
Buffer HnswNewBuffer(Relation index, ForkNumber forkNum);
|
||||||
void HnswInitPage(Buffer buf, Page page);
|
void HnswInitPage(Buffer buf, Page page);
|
||||||
void HnswInit(void);
|
void HnswInit(void);
|
||||||
List *HnswSearchLayer(char *base, HnswQuery * q, List *ep, int ef, int lc, Relation index, HnswSupport * support, int m, bool inserting, HnswElement skipElement, visited_hash * v, pairingheap **discarded, bool initVisited, int64 *tuples);
|
List *HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *procinfo, Oid collation, int m, bool inserting, HnswElement skipElement, visited_hash * v, pairingheap **discarded, bool initVisited, int64 *tuples);
|
||||||
HnswElement HnswGetEntryPoint(Relation index);
|
HnswElement HnswGetEntryPoint(Relation index);
|
||||||
void HnswGetMetaPageInfo(Relation index, int *m, HnswElement * entryPoint);
|
void HnswGetMetaPageInfo(Relation index, int *m, HnswElement * entryPoint);
|
||||||
void *HnswAlloc(HnswAllocator * allocator, Size size);
|
void *HnswAlloc(HnswAllocator * allocator, Size size);
|
||||||
HnswElement HnswInitElement(char *base, ItemPointer tid, int m, double ml, int maxLevel, HnswAllocator * alloc);
|
HnswElement HnswInitElement(char *base, ItemPointer tid, int m, double ml, int maxLevel, HnswAllocator * alloc);
|
||||||
HnswElement HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno);
|
HnswElement HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno);
|
||||||
void HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint, Relation index, HnswSupport * support, int m, int efConstruction, bool existing);
|
void HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint, Relation index, FmgrInfo *procinfo, Oid collation, int m, int efConstruction, bool existing);
|
||||||
HnswSearchCandidate *HnswEntryCandidate(char *base, HnswElement entryPoint, HnswQuery * q, Relation index, HnswSupport * support, bool loadVec);
|
HnswSearchCandidate *HnswEntryCandidate(char *base, HnswElement em, Datum q, Relation rel, FmgrInfo *procinfo, Oid collation, bool loadVec);
|
||||||
void HnswUpdateMetaPage(Relation index, int updateEntry, HnswElement entryPoint, BlockNumber insertPage, ForkNumber forkNum, bool building);
|
void HnswUpdateMetaPage(Relation index, int updateEntry, HnswElement entryPoint, BlockNumber insertPage, ForkNumber forkNum, bool building);
|
||||||
void HnswSetNeighborTuple(char *base, HnswNeighborTuple ntup, HnswElement e, int m);
|
void HnswSetNeighborTuple(char *base, HnswNeighborTuple ntup, HnswElement e, int m);
|
||||||
void HnswAddHeapTid(HnswElement element, ItemPointer heaptid);
|
void HnswAddHeapTid(HnswElement element, ItemPointer heaptid);
|
||||||
HnswNeighborArray *HnswInitNeighborArray(int lm, HnswAllocator * allocator);
|
|
||||||
void HnswInitNeighbors(char *base, HnswElement element, int m, HnswAllocator * alloc);
|
void HnswInitNeighbors(char *base, HnswElement element, int m, HnswAllocator * alloc);
|
||||||
bool HnswInsertTupleOnDisk(Relation index, HnswSupport * support, Datum value, ItemPointer heaptid, bool building);
|
bool HnswInsertTupleOnDisk(Relation index, Datum value, Datum *values, bool *isnull, ItemPointer heap_tid, bool building);
|
||||||
void HnswUpdateNeighborsOnDisk(Relation index, HnswSupport * support, HnswElement e, int m, bool checkExisting, bool building);
|
void HnswUpdateNeighborsOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting, bool building);
|
||||||
void HnswLoadElementFromTuple(HnswElement element, HnswElementTuple etup, bool loadHeaptids, bool loadVec);
|
void HnswLoadElementFromTuple(HnswElement element, HnswElementTuple etup, bool loadHeaptids, bool loadVec);
|
||||||
void HnswLoadElement(HnswElement element, double *distance, HnswQuery * q, Relation index, HnswSupport * support, bool loadVec, double *maxDistance);
|
void HnswLoadElement(HnswElement element, float *distance, Datum *q, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec, float *maxDistance);
|
||||||
bool HnswFormIndexValue(Datum *out, Datum *values, bool *isnull, const HnswTypeInfo * typeInfo, HnswSupport * support);
|
|
||||||
void HnswSetElementTuple(char *base, HnswElementTuple etup, HnswElement element);
|
void HnswSetElementTuple(char *base, HnswElementTuple etup, HnswElement element);
|
||||||
void HnswUpdateConnection(char *base, HnswNeighborArray * neighbors, HnswElement newElement, float distance, int lm, int *updateIdx, Relation index, HnswSupport * support);
|
void HnswUpdateConnection(char *base, HnswElement element, HnswCandidate * hc, int lm, int lc, int *updateIdx, Relation index, FmgrInfo *procinfo, Oid collation);
|
||||||
bool HnswLoadNeighborTids(HnswElement element, ItemPointerData *indextids, Relation index, int m, int lm, int lc);
|
void HnswLoadNeighbors(HnswElement element, Relation index, int m);
|
||||||
void HnswInitLockTranche(void);
|
void HnswInitLockTranche(void);
|
||||||
const HnswTypeInfo *HnswGetTypeInfo(Relation index);
|
const HnswTypeInfo *HnswGetTypeInfo(Relation index);
|
||||||
PGDLLEXPORT void HnswParallelBuildMain(dsm_segment *seg, shm_toc *toc);
|
PGDLLEXPORT void HnswParallelBuildMain(dsm_segment *seg, shm_toc *toc);
|
||||||
|
|||||||
102
src/hnswbuild.c
102
src/hnswbuild.c
@@ -36,14 +36,11 @@
|
|||||||
*/
|
*/
|
||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include <limits.h>
|
#include <math.h>
|
||||||
|
|
||||||
#include "access/genam.h"
|
|
||||||
#include "access/parallel.h"
|
#include "access/parallel.h"
|
||||||
#include "access/relscan.h"
|
|
||||||
#include "access/table.h"
|
#include "access/table.h"
|
||||||
#include "access/tableam.h"
|
#include "access/tableam.h"
|
||||||
#include "access/tupdesc.h"
|
|
||||||
#include "access/xact.h"
|
#include "access/xact.h"
|
||||||
#include "access/xloginsert.h"
|
#include "access/xloginsert.h"
|
||||||
#include "catalog/index.h"
|
#include "catalog/index.h"
|
||||||
@@ -51,19 +48,11 @@
|
|||||||
#include "commands/progress.h"
|
#include "commands/progress.h"
|
||||||
#include "hnsw.h"
|
#include "hnsw.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
#include "nodes/execnodes.h"
|
|
||||||
#include "optimizer/optimizer.h"
|
#include "optimizer/optimizer.h"
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
#include "storage/condition_variable.h"
|
|
||||||
#include "tcop/tcopprot.h"
|
#include "tcop/tcopprot.h"
|
||||||
#include "utils/datum.h"
|
#include "utils/datum.h"
|
||||||
#include "utils/memutils.h"
|
#include "utils/memutils.h"
|
||||||
#include "utils/rel.h"
|
|
||||||
#include "utils/snapmgr.h"
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 160000
|
|
||||||
#include "varatt.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 140000
|
#if PG_VERSION_NUM >= 140000
|
||||||
#include "utils/backend_progress.h"
|
#include "utils/backend_progress.h"
|
||||||
@@ -80,8 +69,6 @@
|
|||||||
#define PARALLEL_KEY_HNSW_AREA UINT64CONST(0xA000000000000002)
|
#define PARALLEL_KEY_HNSW_AREA UINT64CONST(0xA000000000000002)
|
||||||
#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xA000000000000003)
|
#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xA000000000000003)
|
||||||
|
|
||||||
#define HNSW_MAX_GRAPH_MEMORY (SIZE_MAX / 2)
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create the metapage
|
* Create the metapage
|
||||||
*/
|
*/
|
||||||
@@ -379,7 +366,7 @@ AddElementInMemory(char *base, HnswGraph * graph, HnswElement element)
|
|||||||
* Update neighbors
|
* Update neighbors
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
UpdateNeighborsInMemory(char *base, HnswSupport * support, HnswElement e, int m)
|
UpdateNeighborsInMemory(char *base, FmgrInfo *procinfo, Oid collation, HnswElement e, int m)
|
||||||
{
|
{
|
||||||
for (int lc = e->level; lc >= 0; lc--)
|
for (int lc = e->level; lc >= 0; lc--)
|
||||||
{
|
{
|
||||||
@@ -401,7 +388,7 @@ UpdateNeighborsInMemory(char *base, HnswSupport * support, HnswElement e, int m)
|
|||||||
Assert(neighborElement);
|
Assert(neighborElement);
|
||||||
|
|
||||||
LWLockAcquire(&neighborElement->lock, LW_EXCLUSIVE);
|
LWLockAcquire(&neighborElement->lock, LW_EXCLUSIVE);
|
||||||
HnswUpdateConnection(base, HnswGetNeighbors(base, neighborElement, lc), e, hc->distance, lm, NULL, NULL, support);
|
HnswUpdateConnection(base, e, hc, lm, lc, NULL, NULL, procinfo, collation);
|
||||||
LWLockRelease(&neighborElement->lock);
|
LWLockRelease(&neighborElement->lock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -411,7 +398,7 @@ UpdateNeighborsInMemory(char *base, HnswSupport * support, HnswElement e, int m)
|
|||||||
* Update graph in memory
|
* Update graph in memory
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
UpdateGraphInMemory(HnswSupport * support, HnswElement element, int m, HnswElement entryPoint, HnswBuildState * buildstate)
|
UpdateGraphInMemory(FmgrInfo *procinfo, Oid collation, HnswElement element, int m, int efConstruction, HnswElement entryPoint, HnswBuildState * buildstate)
|
||||||
{
|
{
|
||||||
HnswGraph *graph = buildstate->graph;
|
HnswGraph *graph = buildstate->graph;
|
||||||
char *base = buildstate->hnswarea;
|
char *base = buildstate->hnswarea;
|
||||||
@@ -424,7 +411,7 @@ UpdateGraphInMemory(HnswSupport * support, HnswElement element, int m, HnswEleme
|
|||||||
AddElementInMemory(base, graph, element);
|
AddElementInMemory(base, graph, element);
|
||||||
|
|
||||||
/* Update neighbors */
|
/* Update neighbors */
|
||||||
UpdateNeighborsInMemory(base, support, element, m);
|
UpdateNeighborsInMemory(base, procinfo, collation, element, m);
|
||||||
|
|
||||||
/* Update entry point if needed (already have lock) */
|
/* Update entry point if needed (already have lock) */
|
||||||
if (entryPoint == NULL || element->level > entryPoint->level)
|
if (entryPoint == NULL || element->level > entryPoint->level)
|
||||||
@@ -437,8 +424,9 @@ UpdateGraphInMemory(HnswSupport * support, HnswElement element, int m, HnswEleme
|
|||||||
static void
|
static void
|
||||||
InsertTupleInMemory(HnswBuildState * buildstate, HnswElement element)
|
InsertTupleInMemory(HnswBuildState * buildstate, HnswElement element)
|
||||||
{
|
{
|
||||||
|
FmgrInfo *procinfo = buildstate->procinfo;
|
||||||
|
Oid collation = buildstate->collation;
|
||||||
HnswGraph *graph = buildstate->graph;
|
HnswGraph *graph = buildstate->graph;
|
||||||
HnswSupport *support = &buildstate->support;
|
|
||||||
HnswElement entryPoint;
|
HnswElement entryPoint;
|
||||||
LWLock *entryLock = &graph->entryLock;
|
LWLock *entryLock = &graph->entryLock;
|
||||||
LWLock *entryWaitLock = &graph->entryWaitLock;
|
LWLock *entryWaitLock = &graph->entryWaitLock;
|
||||||
@@ -470,10 +458,10 @@ InsertTupleInMemory(HnswBuildState * buildstate, HnswElement element)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Find neighbors for element */
|
/* Find neighbors for element */
|
||||||
HnswFindElementNeighbors(base, element, entryPoint, NULL, support, m, efConstruction, false);
|
HnswFindElementNeighbors(base, element, entryPoint, NULL, procinfo, collation, m, efConstruction, false);
|
||||||
|
|
||||||
/* Update graph in memory */
|
/* Update graph in memory */
|
||||||
UpdateGraphInMemory(support, element, m, entryPoint, buildstate);
|
UpdateGraphInMemory(procinfo, collation, element, m, efConstruction, entryPoint, buildstate);
|
||||||
|
|
||||||
/* Release entry lock */
|
/* Release entry lock */
|
||||||
LWLockRelease(entryLock);
|
LWLockRelease(entryLock);
|
||||||
@@ -485,27 +473,34 @@ InsertTupleInMemory(HnswBuildState * buildstate, HnswElement element)
|
|||||||
static bool
|
static bool
|
||||||
InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heaptid, HnswBuildState * buildstate)
|
InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heaptid, HnswBuildState * buildstate)
|
||||||
{
|
{
|
||||||
|
const HnswTypeInfo *typeInfo = buildstate->typeInfo;
|
||||||
HnswGraph *graph = buildstate->graph;
|
HnswGraph *graph = buildstate->graph;
|
||||||
HnswElement element;
|
HnswElement element;
|
||||||
HnswAllocator *allocator = &buildstate->allocator;
|
HnswAllocator *allocator = &buildstate->allocator;
|
||||||
HnswSupport *support = &buildstate->support;
|
|
||||||
Size valueSize;
|
Size valueSize;
|
||||||
Pointer valuePtr;
|
Pointer valuePtr;
|
||||||
LWLock *flushLock = &graph->flushLock;
|
LWLock *flushLock = &graph->flushLock;
|
||||||
char *base = buildstate->hnswarea;
|
char *base = buildstate->hnswarea;
|
||||||
Datum value;
|
|
||||||
Size memoryMargin;
|
|
||||||
|
|
||||||
/* Form index value */
|
/* Detoast once for all calls */
|
||||||
if (!HnswFormIndexValue(&value, values, isnull, buildstate->typeInfo, support))
|
Datum value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
||||||
return false;
|
|
||||||
|
/* Check value */
|
||||||
|
if (typeInfo->checkValue != NULL)
|
||||||
|
typeInfo->checkValue(DatumGetPointer(value));
|
||||||
|
|
||||||
|
/* Normalize if needed */
|
||||||
|
if (buildstate->normprocinfo != NULL)
|
||||||
|
{
|
||||||
|
if (!HnswCheckNorm(buildstate->normprocinfo, buildstate->collation, value))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
value = HnswNormValue(typeInfo, buildstate->collation, value);
|
||||||
|
}
|
||||||
|
|
||||||
/* Get datum size */
|
/* Get datum size */
|
||||||
valueSize = VARSIZE_ANY(DatumGetPointer(value));
|
valueSize = VARSIZE_ANY(DatumGetPointer(value));
|
||||||
|
|
||||||
/* In a parallel build, add a margin so allocations never fail */
|
|
||||||
memoryMargin = base == NULL ? 0 : 1024 * 1024;
|
|
||||||
|
|
||||||
/* Ensure graph not flushed when inserting */
|
/* Ensure graph not flushed when inserting */
|
||||||
LWLockAcquire(flushLock, LW_SHARED);
|
LWLockAcquire(flushLock, LW_SHARED);
|
||||||
|
|
||||||
@@ -514,7 +509,7 @@ InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heaptid, Hn
|
|||||||
{
|
{
|
||||||
LWLockRelease(flushLock);
|
LWLockRelease(flushLock);
|
||||||
|
|
||||||
return HnswInsertTupleOnDisk(index, support, value, heaptid, true);
|
return HnswInsertTupleOnDisk(index, value, values, isnull, heaptid, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -527,7 +522,7 @@ InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heaptid, Hn
|
|||||||
* Check that we have enough memory available for the new element now that
|
* Check that we have enough memory available for the new element now that
|
||||||
* we have the allocator lock, and flush pages if needed.
|
* we have the allocator lock, and flush pages if needed.
|
||||||
*/
|
*/
|
||||||
if (graph->memoryUsed + memoryMargin >= graph->memoryTotal)
|
if (graph->memoryUsed >= graph->memoryTotal)
|
||||||
{
|
{
|
||||||
LWLockRelease(&graph->allocatorLock);
|
LWLockRelease(&graph->allocatorLock);
|
||||||
|
|
||||||
@@ -546,7 +541,7 @@ InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heaptid, Hn
|
|||||||
|
|
||||||
LWLockRelease(flushLock);
|
LWLockRelease(flushLock);
|
||||||
|
|
||||||
return HnswInsertTupleOnDisk(index, support, value, heaptid, true);
|
return HnswInsertTupleOnDisk(index, value, values, isnull, heaptid, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ok, we can proceed to allocate the element */
|
/* Ok, we can proceed to allocate the element */
|
||||||
@@ -562,7 +557,7 @@ InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heaptid, Hn
|
|||||||
|
|
||||||
/* Copy the datum */
|
/* Copy the datum */
|
||||||
memcpy(valuePtr, DatumGetPointer(value), valueSize);
|
memcpy(valuePtr, DatumGetPointer(value), valueSize);
|
||||||
HnswPtrStore(base, element->value, (char *) valuePtr);
|
HnswPtrStore(base, element->value, valuePtr);
|
||||||
|
|
||||||
/* Create a lock for the element */
|
/* Create a lock for the element */
|
||||||
LWLockInitialize(&element->lock, hnsw_lock_tranche_id);
|
LWLockInitialize(&element->lock, hnsw_lock_tranche_id);
|
||||||
@@ -612,7 +607,7 @@ BuildCallback(Relation index, ItemPointer tid, Datum *values,
|
|||||||
* Initialize the graph
|
* Initialize the graph
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
InitGraph(HnswGraph * graph, char *base, Size memoryTotal)
|
InitGraph(HnswGraph * graph, char *base, long memoryTotal)
|
||||||
{
|
{
|
||||||
/* Initialize the lock tranche if needed */
|
/* Initialize the lock tranche if needed */
|
||||||
HnswInitLockTranche();
|
HnswInitLockTranche();
|
||||||
@@ -620,7 +615,7 @@ InitGraph(HnswGraph * graph, char *base, Size memoryTotal)
|
|||||||
HnswPtrStore(base, graph->head, (HnswElement) NULL);
|
HnswPtrStore(base, graph->head, (HnswElement) NULL);
|
||||||
HnswPtrStore(base, graph->entryPoint, (HnswElement) NULL);
|
HnswPtrStore(base, graph->entryPoint, (HnswElement) NULL);
|
||||||
graph->memoryUsed = 0;
|
graph->memoryUsed = 0;
|
||||||
graph->memoryTotal = Min(memoryTotal, HNSW_MAX_GRAPH_MEMORY);
|
graph->memoryTotal = memoryTotal;
|
||||||
graph->flushed = false;
|
graph->flushed = false;
|
||||||
graph->indtuples = 0;
|
graph->indtuples = 0;
|
||||||
SpinLockInit(&graph->lock);
|
SpinLockInit(&graph->lock);
|
||||||
@@ -661,17 +656,9 @@ static void *
|
|||||||
HnswSharedMemoryAlloc(Size size, void *state)
|
HnswSharedMemoryAlloc(Size size, void *state)
|
||||||
{
|
{
|
||||||
HnswBuildState *buildstate = (HnswBuildState *) state;
|
HnswBuildState *buildstate = (HnswBuildState *) state;
|
||||||
Size alignedSize = MAXALIGN(size);
|
void *chunk = buildstate->hnswarea + buildstate->graph->memoryUsed;
|
||||||
void *chunk;
|
|
||||||
|
|
||||||
if (alignedSize > 1024 * 1024)
|
buildstate->graph->memoryUsed += MAXALIGN(size);
|
||||||
elog(ERROR, "hnsw allocation too large");
|
|
||||||
|
|
||||||
if (buildstate->graph->memoryUsed + alignedSize > buildstate->graph->memoryTotal)
|
|
||||||
elog(ERROR, "hnsw allocator out of memory");
|
|
||||||
|
|
||||||
chunk = buildstate->hnswarea + buildstate->graph->memoryUsed;
|
|
||||||
buildstate->graph->memoryUsed += alignedSize;
|
|
||||||
return chunk;
|
return chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -717,9 +704,11 @@ InitBuildState(HnswBuildState * buildstate, Relation heap, Relation index, Index
|
|||||||
buildstate->indtuples = 0;
|
buildstate->indtuples = 0;
|
||||||
|
|
||||||
/* Get support functions */
|
/* Get support functions */
|
||||||
HnswInitSupport(&buildstate->support, index);
|
buildstate->procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC);
|
||||||
|
buildstate->normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC);
|
||||||
|
buildstate->collation = index->rd_indcollation[0];
|
||||||
|
|
||||||
InitGraph(&buildstate->graphData, NULL, maintenance_work_mem * (Size) 1024);
|
InitGraph(&buildstate->graphData, NULL, maintenance_work_mem * 1024L);
|
||||||
buildstate->graph = &buildstate->graphData;
|
buildstate->graph = &buildstate->graphData;
|
||||||
buildstate->ml = HnswGetMl(buildstate->m);
|
buildstate->ml = HnswGetMl(buildstate->m);
|
||||||
buildstate->maxLevel = HnswGetMaxLevel(buildstate->m);
|
buildstate->maxLevel = HnswGetMaxLevel(buildstate->m);
|
||||||
@@ -803,11 +792,7 @@ HnswParallelScanAndInsert(Relation heapRel, Relation indexRel, HnswShared * hnsw
|
|||||||
buildstate.hnswarea = hnswarea;
|
buildstate.hnswarea = hnswarea;
|
||||||
InitAllocator(&buildstate.allocator, &HnswSharedMemoryAlloc, &buildstate);
|
InitAllocator(&buildstate.allocator, &HnswSharedMemoryAlloc, &buildstate);
|
||||||
scan = table_beginscan_parallel(heapRel,
|
scan = table_beginscan_parallel(heapRel,
|
||||||
ParallelTableScanFromHnswShared(hnswshared)
|
ParallelTableScanFromHnswShared(hnswshared));
|
||||||
#if PG_VERSION_NUM >= 190000
|
|
||||||
,SO_NONE
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
reltuples = table_index_build_scan(heapRel, indexRel, indexInfo,
|
reltuples = table_index_build_scan(heapRel, indexRel, indexInfo,
|
||||||
true, progress, BuildCallback,
|
true, progress, BuildCallback,
|
||||||
(void *) &buildstate, scan);
|
(void *) &buildstate, scan);
|
||||||
@@ -956,13 +941,11 @@ HnswBeginParallel(HnswBuildState * buildstate, bool isconcurrent, int request)
|
|||||||
/* Leave space for other objects in shared memory */
|
/* Leave space for other objects in shared memory */
|
||||||
/* Docker has a default limit of 64 MB for shm_size */
|
/* Docker has a default limit of 64 MB for shm_size */
|
||||||
/* which happens to be the default value of maintenance_work_mem */
|
/* which happens to be the default value of maintenance_work_mem */
|
||||||
esthnswarea = maintenance_work_mem * (Size) 1024;
|
esthnswarea = maintenance_work_mem * 1024L;
|
||||||
estother = 3 * 1024 * 1024;
|
estother = 3 * 1024 * 1024;
|
||||||
if (esthnswarea > estother)
|
if (esthnswarea > estother)
|
||||||
esthnswarea -= estother;
|
esthnswarea -= estother;
|
||||||
|
|
||||||
esthnswarea = Min(esthnswarea, HNSW_MAX_GRAPH_MEMORY);
|
|
||||||
|
|
||||||
shm_toc_estimate_chunk(&pcxt->estimator, esthnswarea);
|
shm_toc_estimate_chunk(&pcxt->estimator, esthnswarea);
|
||||||
shm_toc_estimate_keys(&pcxt->estimator, 2);
|
shm_toc_estimate_keys(&pcxt->estimator, 2);
|
||||||
|
|
||||||
@@ -1005,7 +988,8 @@ HnswBeginParallel(HnswBuildState * buildstate, bool isconcurrent, int request)
|
|||||||
snapshot);
|
snapshot);
|
||||||
|
|
||||||
hnswarea = (char *) shm_toc_allocate(pcxt->toc, esthnswarea);
|
hnswarea = (char *) shm_toc_allocate(pcxt->toc, esthnswarea);
|
||||||
InitGraph(&hnswshared->graphData, hnswarea, esthnswarea);
|
/* Report less than allocated so never fails */
|
||||||
|
InitGraph(&hnswshared->graphData, hnswarea, esthnswarea - 1024 * 1024);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Avoid base address for relptr for Postgres < 14.5
|
* Avoid base address for relptr for Postgres < 14.5
|
||||||
@@ -1084,7 +1068,7 @@ ComputeParallelWorkers(Relation heap, Relation index)
|
|||||||
* Build graph
|
* Build graph
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
BuildGraph(HnswBuildState * buildstate)
|
BuildGraph(HnswBuildState * buildstate, ForkNumber forkNum)
|
||||||
{
|
{
|
||||||
int parallel_workers = 0;
|
int parallel_workers = 0;
|
||||||
|
|
||||||
@@ -1132,7 +1116,7 @@ BuildIndex(Relation heap, Relation index, IndexInfo *indexInfo,
|
|||||||
|
|
||||||
InitBuildState(buildstate, heap, index, indexInfo, forkNum);
|
InitBuildState(buildstate, heap, index, indexInfo, forkNum);
|
||||||
|
|
||||||
BuildGraph(buildstate);
|
BuildGraph(buildstate, forkNum);
|
||||||
|
|
||||||
if (RelationNeedsWAL(index) || forkNum == INIT_FORKNUM)
|
if (RelationNeedsWAL(index) || forkNum == INIT_FORKNUM)
|
||||||
log_newpage_range(index, forkNum, 0, RelationGetNumberOfBlocksInFork(index, forkNum), true);
|
log_newpage_range(index, forkNum, 0, RelationGetNumberOfBlocksInFork(index, forkNum), true);
|
||||||
|
|||||||
321
src/hnswinsert.c
321
src/hnswinsert.c
@@ -1,19 +1,13 @@
|
|||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include "access/genam.h"
|
#include <math.h>
|
||||||
|
|
||||||
#include "access/generic_xlog.h"
|
#include "access/generic_xlog.h"
|
||||||
#include "hnsw.h"
|
#include "hnsw.h"
|
||||||
#include "nodes/execnodes.h"
|
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
#include "storage/lmgr.h"
|
#include "storage/lmgr.h"
|
||||||
#include "storage/lwlock.h"
|
|
||||||
#include "utils/datum.h"
|
#include "utils/datum.h"
|
||||||
#include "utils/memutils.h"
|
#include "utils/memutils.h"
|
||||||
#include "utils/rel.h"
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 160000
|
|
||||||
#include "varatt.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the insert page
|
* Get the insert page
|
||||||
@@ -346,107 +340,6 @@ AddElementOnDisk(Relation index, HnswElement e, int m, BlockNumber insertPage, B
|
|||||||
*updatedInsertPage = newInsertPage;
|
*updatedInsertPage = newInsertPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Load neighbors
|
|
||||||
*/
|
|
||||||
static HnswNeighborArray *
|
|
||||||
HnswLoadNeighbors(HnswElement element, Relation index, int m, int lm, int lc)
|
|
||||||
{
|
|
||||||
char *base = NULL;
|
|
||||||
HnswNeighborArray *neighbors = HnswInitNeighborArray(lm, NULL);
|
|
||||||
ItemPointerData indextids[HNSW_MAX_M * 2];
|
|
||||||
|
|
||||||
if (!HnswLoadNeighborTids(element, indextids, index, m, lm, lc))
|
|
||||||
return neighbors;
|
|
||||||
|
|
||||||
for (int i = 0; i < lm; i++)
|
|
||||||
{
|
|
||||||
ItemPointer indextid = &indextids[i];
|
|
||||||
HnswElement e;
|
|
||||||
HnswCandidate *hc;
|
|
||||||
|
|
||||||
if (!ItemPointerIsValid(indextid))
|
|
||||||
break;
|
|
||||||
|
|
||||||
e = HnswInitElementFromBlock(ItemPointerGetBlockNumber(indextid), ItemPointerGetOffsetNumber(indextid));
|
|
||||||
hc = &neighbors->items[neighbors->length++];
|
|
||||||
HnswPtrStore(base, hc->element, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return neighbors;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Load elements for insert
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
LoadElementsForInsert(HnswNeighborArray * neighbors, HnswQuery * q, int *idx, Relation index, HnswSupport * support)
|
|
||||||
{
|
|
||||||
char *base = NULL;
|
|
||||||
|
|
||||||
for (int i = 0; i < neighbors->length; i++)
|
|
||||||
{
|
|
||||||
HnswCandidate *hc = &neighbors->items[i];
|
|
||||||
HnswElement element = HnswPtrAccess(base, hc->element);
|
|
||||||
double distance;
|
|
||||||
|
|
||||||
HnswLoadElement(element, &distance, q, index, support, true, NULL);
|
|
||||||
hc->distance = distance;
|
|
||||||
|
|
||||||
/* Prune element if being deleted */
|
|
||||||
if (element->heaptidsLength == 0)
|
|
||||||
{
|
|
||||||
*idx = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get update index
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
GetUpdateIndex(HnswElement element, HnswElement newElement, float distance, int m, int lm, int lc, Relation index, HnswSupport * support, MemoryContext updateCtx)
|
|
||||||
{
|
|
||||||
char *base = NULL;
|
|
||||||
int idx = -1;
|
|
||||||
HnswNeighborArray *neighbors;
|
|
||||||
MemoryContext oldCtx = MemoryContextSwitchTo(updateCtx);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get latest neighbors since they may have changed. Do not lock yet since
|
|
||||||
* selecting neighbors can take time. Could use optimistic locking to
|
|
||||||
* retry if another update occurs before getting exclusive lock.
|
|
||||||
*/
|
|
||||||
neighbors = HnswLoadNeighbors(element, index, m, lm, lc);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Could improve performance for vacuuming by checking neighbors against
|
|
||||||
* list of elements being deleted to find index. It's important to exclude
|
|
||||||
* already deleted elements for this since they can be replaced at any
|
|
||||||
* time.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (neighbors->length < lm)
|
|
||||||
idx = -2;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
HnswQuery q;
|
|
||||||
|
|
||||||
q.value = HnswGetValue(base, element);
|
|
||||||
|
|
||||||
LoadElementsForInsert(neighbors, &q, &idx, index, support);
|
|
||||||
|
|
||||||
if (idx == -1)
|
|
||||||
HnswUpdateConnection(base, neighbors, newElement, distance, lm, &idx, index, support);
|
|
||||||
}
|
|
||||||
|
|
||||||
MemoryContextSwitchTo(oldCtx);
|
|
||||||
MemoryContextReset(updateCtx);
|
|
||||||
|
|
||||||
return idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check if connection already exists
|
* Check if connection already exists
|
||||||
*/
|
*/
|
||||||
@@ -467,94 +360,14 @@ ConnectionExists(HnswElement e, HnswNeighborTuple ntup, int startIdx, int lm)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Update neighbor
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
UpdateNeighborOnDisk(HnswElement element, HnswElement newElement, int idx, int m, int lm, int lc, Relation index, bool checkExisting, bool building)
|
|
||||||
{
|
|
||||||
Buffer buf;
|
|
||||||
Page page;
|
|
||||||
GenericXLogState *state;
|
|
||||||
HnswNeighborTuple ntup;
|
|
||||||
int startIdx;
|
|
||||||
OffsetNumber offno = element->neighborOffno;
|
|
||||||
|
|
||||||
/* Register page */
|
|
||||||
buf = ReadBuffer(index, element->neighborPage);
|
|
||||||
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
|
||||||
if (building)
|
|
||||||
{
|
|
||||||
state = NULL;
|
|
||||||
page = BufferGetPage(buf);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
state = GenericXLogStart(index);
|
|
||||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get tuple */
|
|
||||||
ntup = (HnswNeighborTuple) PageGetItem(page, PageGetItemId(page, offno));
|
|
||||||
|
|
||||||
/* Calculate index for update */
|
|
||||||
startIdx = (element->level - lc) * m;
|
|
||||||
|
|
||||||
/* Check for existing connection */
|
|
||||||
if (checkExisting && ConnectionExists(newElement, ntup, startIdx, lm))
|
|
||||||
idx = -1;
|
|
||||||
else if (idx == -2)
|
|
||||||
{
|
|
||||||
/* Find free offset if still exists */
|
|
||||||
/* TODO Retry updating connections if not */
|
|
||||||
for (int j = 0; j < lm; j++)
|
|
||||||
{
|
|
||||||
if (!ItemPointerIsValid(&ntup->indextids[startIdx + j]))
|
|
||||||
{
|
|
||||||
idx = startIdx + j;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
idx += startIdx;
|
|
||||||
|
|
||||||
/* Make robust to issues */
|
|
||||||
if (idx >= 0 && idx < ntup->count)
|
|
||||||
{
|
|
||||||
ItemPointer indextid = &ntup->indextids[idx];
|
|
||||||
|
|
||||||
/* Update neighbor on the buffer */
|
|
||||||
ItemPointerSet(indextid, newElement->blkno, newElement->offno);
|
|
||||||
|
|
||||||
/* Commit */
|
|
||||||
if (building)
|
|
||||||
MarkBufferDirty(buf);
|
|
||||||
else
|
|
||||||
GenericXLogFinish(state);
|
|
||||||
}
|
|
||||||
else if (!building)
|
|
||||||
GenericXLogAbort(state);
|
|
||||||
|
|
||||||
UnlockReleaseBuffer(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Update neighbors
|
* Update neighbors
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
HnswUpdateNeighborsOnDisk(Relation index, HnswSupport * support, HnswElement e, int m, bool checkExisting, bool building)
|
HnswUpdateNeighborsOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting, bool building)
|
||||||
{
|
{
|
||||||
char *base = NULL;
|
char *base = NULL;
|
||||||
|
|
||||||
/* Use separate memory context to improve performance for larger vectors */
|
|
||||||
MemoryContext updateCtx = GenerationContextCreate(CurrentMemoryContext,
|
|
||||||
"Hnsw insert update context",
|
|
||||||
#if PG_VERSION_NUM >= 150000
|
|
||||||
128 * 1024, 128 * 1024,
|
|
||||||
#endif
|
|
||||||
128 * 1024);
|
|
||||||
|
|
||||||
for (int lc = e->level; lc >= 0; lc--)
|
for (int lc = e->level; lc >= 0; lc--)
|
||||||
{
|
{
|
||||||
int lm = HnswGetLayerM(m, lc);
|
int lm = HnswGetLayerM(m, lc);
|
||||||
@@ -563,20 +376,96 @@ HnswUpdateNeighborsOnDisk(Relation index, HnswSupport * support, HnswElement e,
|
|||||||
for (int i = 0; i < neighbors->length; i++)
|
for (int i = 0; i < neighbors->length; i++)
|
||||||
{
|
{
|
||||||
HnswCandidate *hc = &neighbors->items[i];
|
HnswCandidate *hc = &neighbors->items[i];
|
||||||
|
Buffer buf;
|
||||||
|
Page page;
|
||||||
|
GenericXLogState *state;
|
||||||
|
HnswNeighborTuple ntup;
|
||||||
|
int idx = -1;
|
||||||
|
int startIdx;
|
||||||
HnswElement neighborElement = HnswPtrAccess(base, hc->element);
|
HnswElement neighborElement = HnswPtrAccess(base, hc->element);
|
||||||
int idx;
|
OffsetNumber offno = neighborElement->neighborOffno;
|
||||||
|
|
||||||
idx = GetUpdateIndex(neighborElement, e, hc->distance, m, lm, lc, index, support, updateCtx);
|
/*
|
||||||
|
* Get latest neighbors since they may have changed. Do not lock
|
||||||
|
* yet since selecting neighbors can take time. Could use
|
||||||
|
* optimistic locking to retry if another update occurs before
|
||||||
|
* getting exclusive lock.
|
||||||
|
*/
|
||||||
|
HnswLoadNeighbors(neighborElement, index, m);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Could improve performance for vacuuming by checking neighbors
|
||||||
|
* against list of elements being deleted to find index. It's
|
||||||
|
* important to exclude already deleted elements for this since
|
||||||
|
* they can be replaced at any time.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Select neighbors */
|
||||||
|
HnswUpdateConnection(NULL, e, hc, lm, lc, &idx, index, procinfo, collation);
|
||||||
|
|
||||||
/* New element was not selected as a neighbor */
|
/* New element was not selected as a neighbor */
|
||||||
if (idx == -1)
|
if (idx == -1)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
UpdateNeighborOnDisk(neighborElement, e, idx, m, lm, lc, index, checkExisting, building);
|
/* Register page */
|
||||||
|
buf = ReadBuffer(index, neighborElement->neighborPage);
|
||||||
|
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
||||||
|
if (building)
|
||||||
|
{
|
||||||
|
state = NULL;
|
||||||
|
page = BufferGetPage(buf);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = GenericXLogStart(index);
|
||||||
|
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get tuple */
|
||||||
|
ntup = (HnswNeighborTuple) PageGetItem(page, PageGetItemId(page, offno));
|
||||||
|
|
||||||
|
/* Calculate index for update */
|
||||||
|
startIdx = (neighborElement->level - lc) * m;
|
||||||
|
|
||||||
|
/* Check for existing connection */
|
||||||
|
if (checkExisting && ConnectionExists(e, ntup, startIdx, lm))
|
||||||
|
idx = -1;
|
||||||
|
else if (idx == -2)
|
||||||
|
{
|
||||||
|
/* Find free offset if still exists */
|
||||||
|
/* TODO Retry updating connections if not */
|
||||||
|
for (int j = 0; j < lm; j++)
|
||||||
|
{
|
||||||
|
if (!ItemPointerIsValid(&ntup->indextids[startIdx + j]))
|
||||||
|
{
|
||||||
|
idx = startIdx + j;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
idx += startIdx;
|
||||||
|
|
||||||
|
/* Make robust to issues */
|
||||||
|
if (idx >= 0 && idx < ntup->count)
|
||||||
|
{
|
||||||
|
ItemPointer indextid = &ntup->indextids[idx];
|
||||||
|
|
||||||
|
/* Update neighbor on the buffer */
|
||||||
|
ItemPointerSet(indextid, e->blkno, e->offno);
|
||||||
|
|
||||||
|
/* Commit */
|
||||||
|
if (building)
|
||||||
|
MarkBufferDirty(buf);
|
||||||
|
else
|
||||||
|
GenericXLogFinish(state);
|
||||||
|
}
|
||||||
|
else if (!building)
|
||||||
|
GenericXLogAbort(state);
|
||||||
|
|
||||||
|
UnlockReleaseBuffer(buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MemoryContextDelete(updateCtx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -666,7 +555,7 @@ FindDuplicateOnDisk(Relation index, HnswElement element, bool building)
|
|||||||
* Update graph on disk
|
* Update graph on disk
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
UpdateGraphOnDisk(Relation index, HnswSupport * support, HnswElement element, int m, HnswElement entryPoint, bool building)
|
UpdateGraphOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement element, int m, int efConstruction, HnswElement entryPoint, bool building)
|
||||||
{
|
{
|
||||||
BlockNumber newInsertPage = InvalidBlockNumber;
|
BlockNumber newInsertPage = InvalidBlockNumber;
|
||||||
|
|
||||||
@@ -682,7 +571,7 @@ UpdateGraphOnDisk(Relation index, HnswSupport * support, HnswElement element, in
|
|||||||
HnswUpdateMetaPage(index, 0, NULL, newInsertPage, MAIN_FORKNUM, building);
|
HnswUpdateMetaPage(index, 0, NULL, newInsertPage, MAIN_FORKNUM, building);
|
||||||
|
|
||||||
/* Update neighbors */
|
/* Update neighbors */
|
||||||
HnswUpdateNeighborsOnDisk(index, support, element, m, false, building);
|
HnswUpdateNeighborsOnDisk(index, procinfo, collation, element, m, false, building);
|
||||||
|
|
||||||
/* Update entry point if needed */
|
/* Update entry point if needed */
|
||||||
if (entryPoint == NULL || element->level > entryPoint->level)
|
if (entryPoint == NULL || element->level > entryPoint->level)
|
||||||
@@ -693,12 +582,14 @@ UpdateGraphOnDisk(Relation index, HnswSupport * support, HnswElement element, in
|
|||||||
* Insert a tuple into the index
|
* Insert a tuple into the index
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
HnswInsertTupleOnDisk(Relation index, HnswSupport * support, Datum value, ItemPointer heaptid, bool building)
|
HnswInsertTupleOnDisk(Relation index, Datum value, Datum *values, bool *isnull, ItemPointer heap_tid, bool building)
|
||||||
{
|
{
|
||||||
HnswElement entryPoint;
|
HnswElement entryPoint;
|
||||||
HnswElement element;
|
HnswElement element;
|
||||||
int m;
|
int m;
|
||||||
int efConstruction = HnswGetEfConstruction(index);
|
int efConstruction = HnswGetEfConstruction(index);
|
||||||
|
FmgrInfo *procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC);
|
||||||
|
Oid collation = index->rd_indcollation[0];
|
||||||
LOCKMODE lockmode = ShareLock;
|
LOCKMODE lockmode = ShareLock;
|
||||||
char *base = NULL;
|
char *base = NULL;
|
||||||
|
|
||||||
@@ -713,8 +604,8 @@ HnswInsertTupleOnDisk(Relation index, HnswSupport * support, Datum value, ItemPo
|
|||||||
HnswGetMetaPageInfo(index, &m, &entryPoint);
|
HnswGetMetaPageInfo(index, &m, &entryPoint);
|
||||||
|
|
||||||
/* Create an element */
|
/* Create an element */
|
||||||
element = HnswInitElement(base, heaptid, m, HnswGetMl(m), HnswGetMaxLevel(m), NULL);
|
element = HnswInitElement(base, heap_tid, m, HnswGetMl(m), HnswGetMaxLevel(m), NULL);
|
||||||
HnswPtrStore(base, element->value, (char *) DatumGetPointer(value));
|
HnswPtrStore(base, element->value, DatumGetPointer(value));
|
||||||
|
|
||||||
/* Prevent concurrent inserts when likely updating entry point */
|
/* Prevent concurrent inserts when likely updating entry point */
|
||||||
if (entryPoint == NULL || element->level > entryPoint->level)
|
if (entryPoint == NULL || element->level > entryPoint->level)
|
||||||
@@ -731,10 +622,10 @@ HnswInsertTupleOnDisk(Relation index, HnswSupport * support, Datum value, ItemPo
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Find neighbors for element */
|
/* Find neighbors for element */
|
||||||
HnswFindElementNeighbors(base, element, entryPoint, index, support, m, efConstruction, false);
|
HnswFindElementNeighbors(base, element, entryPoint, index, procinfo, collation, m, efConstruction, false);
|
||||||
|
|
||||||
/* Update graph on disk */
|
/* Update graph on disk */
|
||||||
UpdateGraphOnDisk(index, support, element, m, entryPoint, building);
|
UpdateGraphOnDisk(index, procinfo, collation, element, m, efConstruction, entryPoint, building);
|
||||||
|
|
||||||
/* Release lock */
|
/* Release lock */
|
||||||
UnlockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
UnlockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
||||||
@@ -746,19 +637,31 @@ HnswInsertTupleOnDisk(Relation index, HnswSupport * support, Datum value, ItemPo
|
|||||||
* Insert a tuple into the index
|
* Insert a tuple into the index
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heaptid)
|
HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid)
|
||||||
{
|
{
|
||||||
Datum value;
|
Datum value;
|
||||||
const HnswTypeInfo *typeInfo = HnswGetTypeInfo(index);
|
const HnswTypeInfo *typeInfo = HnswGetTypeInfo(index);
|
||||||
HnswSupport support;
|
FmgrInfo *normprocinfo;
|
||||||
|
Oid collation = index->rd_indcollation[0];
|
||||||
|
|
||||||
HnswInitSupport(&support, index);
|
/* Detoast once for all calls */
|
||||||
|
value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
||||||
|
|
||||||
/* Form index value */
|
/* Check value */
|
||||||
if (!HnswFormIndexValue(&value, values, isnull, typeInfo, &support))
|
if (typeInfo->checkValue != NULL)
|
||||||
return;
|
typeInfo->checkValue(DatumGetPointer(value));
|
||||||
|
|
||||||
HnswInsertTupleOnDisk(index, &support, value, heaptid, false);
|
/* Normalize if needed */
|
||||||
|
normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC);
|
||||||
|
if (normprocinfo != NULL)
|
||||||
|
{
|
||||||
|
if (!HnswCheckNorm(normprocinfo, collation, value))
|
||||||
|
return;
|
||||||
|
|
||||||
|
value = HnswNormValue(typeInfo, collation, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
HnswInsertTupleOnDisk(index, value, values, isnull, heap_tid, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
138
src/hnswscan.c
138
src/hnswscan.c
@@ -1,58 +1,48 @@
|
|||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include <limits.h>
|
#include <float.h>
|
||||||
|
|
||||||
#include "access/genam.h"
|
|
||||||
#include "access/relscan.h"
|
#include "access/relscan.h"
|
||||||
#include "hnsw.h"
|
#include "hnsw.h"
|
||||||
#include "lib/pairingheap.h"
|
|
||||||
#include "miscadmin.h"
|
|
||||||
#include "nodes/pg_list.h"
|
|
||||||
#include "pgstat.h"
|
#include "pgstat.h"
|
||||||
|
#include "storage/bufmgr.h"
|
||||||
#include "storage/lmgr.h"
|
#include "storage/lmgr.h"
|
||||||
#include "utils/float.h"
|
|
||||||
#include "utils/memutils.h"
|
#include "utils/memutils.h"
|
||||||
#include "utils/relcache.h"
|
|
||||||
#include "utils/snapmgr.h"
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 160000
|
|
||||||
#include "varatt.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Algorithm 5 from paper
|
* Algorithm 5 from paper
|
||||||
*/
|
*/
|
||||||
static List *
|
static List *
|
||||||
GetScanItems(IndexScanDesc scan, Datum value)
|
GetScanItems(IndexScanDesc scan, Datum q)
|
||||||
{
|
{
|
||||||
HnswScanOpaque so = (HnswScanOpaque) scan->opaque;
|
HnswScanOpaque so = (HnswScanOpaque) scan->opaque;
|
||||||
Relation index = scan->indexRelation;
|
Relation index = scan->indexRelation;
|
||||||
HnswSupport *support = &so->support;
|
FmgrInfo *procinfo = so->procinfo;
|
||||||
|
Oid collation = so->collation;
|
||||||
List *ep;
|
List *ep;
|
||||||
List *w;
|
List *w;
|
||||||
int m;
|
int m;
|
||||||
HnswElement entryPoint;
|
HnswElement entryPoint;
|
||||||
char *base = NULL;
|
char *base = NULL;
|
||||||
HnswQuery *q = &so->q;
|
|
||||||
|
|
||||||
/* Get m and entry point */
|
/* Get m and entry point */
|
||||||
HnswGetMetaPageInfo(index, &m, &entryPoint);
|
HnswGetMetaPageInfo(index, &m, &entryPoint);
|
||||||
|
|
||||||
q->value = value;
|
so->q = q;
|
||||||
so->m = m;
|
so->m = m;
|
||||||
|
|
||||||
if (entryPoint == NULL)
|
if (entryPoint == NULL)
|
||||||
return NIL;
|
return NIL;
|
||||||
|
|
||||||
ep = list_make1(HnswEntryCandidate(base, entryPoint, q, index, support, false));
|
ep = list_make1(HnswEntryCandidate(base, entryPoint, q, index, procinfo, collation, false));
|
||||||
|
|
||||||
for (int lc = entryPoint->level; lc >= 1; lc--)
|
for (int lc = entryPoint->level; lc >= 1; lc--)
|
||||||
{
|
{
|
||||||
w = HnswSearchLayer(base, q, ep, 1, lc, index, support, m, false, NULL, NULL, NULL, true, NULL);
|
w = HnswSearchLayer(base, q, ep, 1, lc, index, procinfo, collation, m, false, NULL, NULL, NULL, true, NULL);
|
||||||
ep = w;
|
ep = w;
|
||||||
}
|
}
|
||||||
|
|
||||||
return HnswSearchLayer(base, q, ep, hnsw_ef_search, 0, index, support, m, false, NULL, &so->v, hnsw_iterative_scan != HNSW_ITERATIVE_SCAN_OFF ? &so->discarded : NULL, true, &so->tuples);
|
return HnswSearchLayer(base, q, ep, hnsw_ef_search, 0, index, procinfo, collation, m, false, NULL, &so->v, hnsw_streaming ? &so->discarded : NULL, true, &so->tuples);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -63,6 +53,8 @@ ResumeScanItems(IndexScanDesc scan)
|
|||||||
{
|
{
|
||||||
HnswScanOpaque so = (HnswScanOpaque) scan->opaque;
|
HnswScanOpaque so = (HnswScanOpaque) scan->opaque;
|
||||||
Relation index = scan->indexRelation;
|
Relation index = scan->indexRelation;
|
||||||
|
FmgrInfo *procinfo = so->procinfo;
|
||||||
|
Oid collation = so->collation;
|
||||||
List *ep = NIL;
|
List *ep = NIL;
|
||||||
char *base = NULL;
|
char *base = NULL;
|
||||||
int batch_size = hnsw_ef_search;
|
int batch_size = hnsw_ef_search;
|
||||||
@@ -73,17 +65,17 @@ ResumeScanItems(IndexScanDesc scan)
|
|||||||
/* Get next batch of candidates */
|
/* Get next batch of candidates */
|
||||||
for (int i = 0; i < batch_size; i++)
|
for (int i = 0; i < batch_size; i++)
|
||||||
{
|
{
|
||||||
HnswSearchCandidate *sc;
|
HnswSearchCandidate *hc;
|
||||||
|
|
||||||
if (pairingheap_is_empty(so->discarded))
|
if (pairingheap_is_empty(so->discarded))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
sc = HnswGetSearchCandidate(w_node, pairingheap_remove_first(so->discarded));
|
hc = HnswGetSearchCandidate(w_node, pairingheap_remove_first(so->discarded));
|
||||||
|
|
||||||
ep = lappend(ep, sc);
|
ep = lappend(ep, hc);
|
||||||
}
|
}
|
||||||
|
|
||||||
return HnswSearchLayer(base, &so->q, ep, batch_size, 0, index, &so->support, so->m, false, NULL, &so->v, &so->discarded, false, &so->tuples);
|
return HnswSearchLayer(base, so->q, ep, batch_size, 0, index, procinfo, collation, so->m, false, NULL, &so->v, &so->discarded, false, &so->tuples);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -106,24 +98,13 @@ GetScanValue(IndexScanDesc scan)
|
|||||||
Assert(!VARATT_IS_EXTENDED(DatumGetPointer(value)));
|
Assert(!VARATT_IS_EXTENDED(DatumGetPointer(value)));
|
||||||
|
|
||||||
/* Normalize if needed */
|
/* Normalize if needed */
|
||||||
if (so->support.normprocinfo != NULL)
|
if (so->normprocinfo != NULL)
|
||||||
value = HnswNormValue(so->typeInfo, so->support.collation, value);
|
value = HnswNormValue(so->typeInfo, so->collation, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(HNSW_MEMORY)
|
|
||||||
/*
|
|
||||||
* Show memory usage
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
ShowMemoryUsage(HnswScanOpaque so)
|
|
||||||
{
|
|
||||||
elog(INFO, "memory: %zu KB, tuples: " INT64_FORMAT, MemoryContextMemAllocated(so->tmpCtx, false) / 1024, so->tuples);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prepare for an index scan
|
* Prepare for an index scan
|
||||||
*/
|
*/
|
||||||
@@ -132,28 +113,22 @@ hnswbeginscan(Relation index, int nkeys, int norderbys)
|
|||||||
{
|
{
|
||||||
IndexScanDesc scan;
|
IndexScanDesc scan;
|
||||||
HnswScanOpaque so;
|
HnswScanOpaque so;
|
||||||
double maxMemory;
|
|
||||||
|
|
||||||
scan = RelationGetIndexScan(index, nkeys, norderbys);
|
scan = RelationGetIndexScan(index, nkeys, norderbys);
|
||||||
|
|
||||||
so = (HnswScanOpaque) palloc(sizeof(HnswScanOpaqueData));
|
so = (HnswScanOpaque) palloc(sizeof(HnswScanOpaqueData));
|
||||||
so->typeInfo = HnswGetTypeInfo(index);
|
so->typeInfo = HnswGetTypeInfo(index);
|
||||||
|
so->first = true;
|
||||||
/* Set support functions */
|
so->v.tids = NULL;
|
||||||
HnswInitSupport(&so->support, index);
|
so->discarded = NULL;
|
||||||
|
|
||||||
/*
|
|
||||||
* Use a lower max allocation size than default to allow scanning more
|
|
||||||
* tuples for iterative search before exceeding work_mem
|
|
||||||
*/
|
|
||||||
so->tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
|
so->tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
|
||||||
"Hnsw scan temporary context",
|
"Hnsw scan temporary context",
|
||||||
0, 8 * 1024, 256 * 1024);
|
ALLOCSET_DEFAULT_SIZES);
|
||||||
|
|
||||||
/* Calculate max memory */
|
/* Set support functions */
|
||||||
/* Add 256 extra bytes to fill last block when close */
|
so->procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC);
|
||||||
maxMemory = (double) work_mem * hnsw_scan_mem_multiplier * 1024.0 + 256;
|
so->normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC);
|
||||||
so->maxMemory = Min(maxMemory, (double) (SIZE_MAX / 2));
|
so->collation = index->rd_indcollation[0];
|
||||||
|
|
||||||
scan->opaque = so;
|
scan->opaque = so;
|
||||||
|
|
||||||
@@ -168,12 +143,15 @@ hnswrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int no
|
|||||||
{
|
{
|
||||||
HnswScanOpaque so = (HnswScanOpaque) scan->opaque;
|
HnswScanOpaque so = (HnswScanOpaque) scan->opaque;
|
||||||
|
|
||||||
|
if (so->v.tids != NULL)
|
||||||
|
tidhash_reset(so->v.tids);
|
||||||
|
|
||||||
|
if (so->discarded != NULL)
|
||||||
|
pairingheap_reset(so->discarded);
|
||||||
|
|
||||||
so->first = true;
|
so->first = true;
|
||||||
/* v and discarded are allocated in tmpCtx */
|
|
||||||
so->v.tids = NULL;
|
|
||||||
so->discarded = NULL;
|
|
||||||
so->tuples = 0;
|
so->tuples = 0;
|
||||||
so->previousDistance = -get_float8_infinity();
|
so->previousDistance = -INFINITY;
|
||||||
MemoryContextReset(so->tmpCtx);
|
MemoryContextReset(so->tmpCtx);
|
||||||
|
|
||||||
if (keys && scan->numberOfKeys > 0)
|
if (keys && scan->numberOfKeys > 0)
|
||||||
@@ -204,10 +182,6 @@ hnswgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
|
|
||||||
/* Count index scan for stats */
|
/* Count index scan for stats */
|
||||||
pgstat_count_index_scan(scan->indexRelation);
|
pgstat_count_index_scan(scan->indexRelation);
|
||||||
#if PG_VERSION_NUM >= 180000
|
|
||||||
if (scan->instrument)
|
|
||||||
scan->instrument->nsearches++;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Safety check */
|
/* Safety check */
|
||||||
if (scan->orderByData == NULL)
|
if (scan->orderByData == NULL)
|
||||||
@@ -227,7 +201,7 @@ hnswgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
*/
|
*/
|
||||||
LockPage(scan->indexRelation, HNSW_SCAN_LOCK, ShareLock);
|
LockPage(scan->indexRelation, HNSW_SCAN_LOCK, ShareLock);
|
||||||
|
|
||||||
so->w = GetScanItems(scan, value);
|
HnswBench("scan iteration", so->w = GetScanItems(scan, value));
|
||||||
|
|
||||||
/* Release shared lock */
|
/* Release shared lock */
|
||||||
UnlockPage(scan->indexRelation, HNSW_SCAN_LOCK, ShareLock);
|
UnlockPage(scan->indexRelation, HNSW_SCAN_LOCK, ShareLock);
|
||||||
@@ -235,28 +209,28 @@ hnswgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
so->first = false;
|
so->first = false;
|
||||||
|
|
||||||
#if defined(HNSW_MEMORY)
|
#if defined(HNSW_MEMORY)
|
||||||
ShowMemoryUsage(so);
|
elog(INFO, "memory: %zu KB", MemoryContextMemAllocated(so->tmpCtx, false) / 1024);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
char *base = NULL;
|
char *base = NULL;
|
||||||
HnswSearchCandidate *sc;
|
HnswSearchCandidate *hc;
|
||||||
HnswElement element;
|
HnswElement element;
|
||||||
ItemPointer heaptid;
|
ItemPointer heaptid;
|
||||||
|
|
||||||
if (list_length(so->w) == 0)
|
if (list_length(so->w) == 0)
|
||||||
{
|
{
|
||||||
if (hnsw_iterative_scan == HNSW_ITERATIVE_SCAN_OFF)
|
if (!hnsw_streaming)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* Empty index */
|
/* Empty index */
|
||||||
if (so->discarded == NULL)
|
if (so->discarded == NULL)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* Reached max number of tuples or memory limit */
|
/* Reached max number of additional tuples */
|
||||||
if (so->tuples >= hnsw_max_scan_tuples || MemoryContextMemAllocated(so->tmpCtx, false) > so->maxMemory)
|
if (hnsw_ef_stream != -1 && so->tuples >= hnsw_ef_search + hnsw_ef_stream)
|
||||||
{
|
{
|
||||||
if (pairingheap_is_empty(so->discarded))
|
if (pairingheap_is_empty(so->discarded))
|
||||||
break;
|
break;
|
||||||
@@ -264,6 +238,21 @@ 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) > (Size) work_mem * 1024L)
|
||||||
|
{
|
||||||
|
if (pairingheap_is_empty(so->discarded))
|
||||||
|
{
|
||||||
|
ereport(NOTICE,
|
||||||
|
(errmsg("hnsw index scan exceeded work_mem after " INT64_FORMAT " tuples", so->tuples),
|
||||||
|
errhint("Increase work_mem to scan more tuples.")));
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return remaining tuples */
|
||||||
|
so->w = lappend(so->w, HnswGetSearchCandidate(w_node, pairingheap_remove_first(so->discarded)));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
@@ -277,12 +266,12 @@ hnswgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
*/
|
*/
|
||||||
LockPage(scan->indexRelation, HNSW_SCAN_LOCK, ShareLock);
|
LockPage(scan->indexRelation, HNSW_SCAN_LOCK, ShareLock);
|
||||||
|
|
||||||
so->w = ResumeScanItems(scan);
|
HnswBench("scan iteration", so->w = ResumeScanItems(scan));
|
||||||
|
|
||||||
UnlockPage(scan->indexRelation, HNSW_SCAN_LOCK, ShareLock);
|
UnlockPage(scan->indexRelation, HNSW_SCAN_LOCK, ShareLock);
|
||||||
|
|
||||||
#if defined(HNSW_MEMORY)
|
#if defined(HNSW_MEMORY)
|
||||||
ShowMemoryUsage(so);
|
elog(INFO, "memory: %zu KB", MemoryContextMemAllocated(so->tmpCtx, false) / 1024);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,8 +279,8 @@ hnswgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc = llast(so->w);
|
hc = llast(so->w);
|
||||||
element = HnswPtrAccess(base, sc->element);
|
element = HnswPtrAccess(base, hc->element);
|
||||||
|
|
||||||
/* Move to next element if no valid heap TIDs */
|
/* Move to next element if no valid heap TIDs */
|
||||||
if (element->heaptidsLength == 0)
|
if (element->heaptidsLength == 0)
|
||||||
@@ -299,10 +288,10 @@ hnswgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
so->w = list_delete_last(so->w);
|
so->w = list_delete_last(so->w);
|
||||||
|
|
||||||
/* Mark memory as free for next iteration */
|
/* Mark memory as free for next iteration */
|
||||||
if (hnsw_iterative_scan != HNSW_ITERATIVE_SCAN_OFF)
|
if (hnsw_streaming)
|
||||||
{
|
{
|
||||||
pfree(element);
|
pfree(element);
|
||||||
pfree(sc);
|
pfree(hc);
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -310,13 +299,10 @@ hnswgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
|
|
||||||
heaptid = &element->heaptids[--element->heaptidsLength];
|
heaptid = &element->heaptids[--element->heaptidsLength];
|
||||||
|
|
||||||
if (hnsw_iterative_scan == HNSW_ITERATIVE_SCAN_STRICT)
|
if (hc->distance < so->previousDistance)
|
||||||
{
|
continue;
|
||||||
if (sc->distance < so->previousDistance)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
so->previousDistance = sc->distance;
|
so->previousDistance = hc->distance;
|
||||||
}
|
|
||||||
|
|
||||||
MemoryContextSwitchTo(oldCtx);
|
MemoryContextSwitchTo(oldCtx);
|
||||||
|
|
||||||
|
|||||||
391
src/hnswutils.c
391
src/hnswutils.c
@@ -2,24 +2,18 @@
|
|||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#include "access/genam.h"
|
|
||||||
#include "access/generic_xlog.h"
|
#include "access/generic_xlog.h"
|
||||||
|
#include "catalog/pg_type.h"
|
||||||
|
#include "catalog/pg_type_d.h"
|
||||||
#include "common/hashfn.h"
|
#include "common/hashfn.h"
|
||||||
#include "fmgr.h"
|
#include "fmgr.h"
|
||||||
#include "hnsw.h"
|
#include "hnsw.h"
|
||||||
#include "lib/pairingheap.h"
|
#include "lib/pairingheap.h"
|
||||||
#include "nodes/pg_list.h"
|
|
||||||
#include "port/atomics.h"
|
|
||||||
#include "sparsevec.h"
|
#include "sparsevec.h"
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
#include "utils/datum.h"
|
#include "utils/datum.h"
|
||||||
#include "utils/memdebug.h"
|
#include "utils/memdebug.h"
|
||||||
#include "utils/rel.h"
|
#include "utils/rel.h"
|
||||||
#include "vector.h"
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 160000
|
|
||||||
#include "varatt.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 170000
|
#if PG_VERSION_NUM < 170000
|
||||||
static inline uint64
|
static inline uint64
|
||||||
@@ -106,6 +100,12 @@ hash_offset(Size offset)
|
|||||||
#define SH_DEFINE
|
#define SH_DEFINE
|
||||||
#include "lib/simplehash.h"
|
#include "lib/simplehash.h"
|
||||||
|
|
||||||
|
typedef union
|
||||||
|
{
|
||||||
|
HnswElement element;
|
||||||
|
ItemPointerData indextid;
|
||||||
|
} HnswUnvisited;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the max number of connections in an upper layer for each element in the index
|
* Get the max number of connections in an upper layer for each element in the index
|
||||||
*/
|
*/
|
||||||
@@ -146,17 +146,6 @@ HnswOptionalProcInfo(Relation index, uint16 procnum)
|
|||||||
return index_getprocinfo(index, 1, procnum);
|
return index_getprocinfo(index, 1, procnum);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Init support functions
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
HnswInitSupport(HnswSupport * support, Relation index)
|
|
||||||
{
|
|
||||||
support->procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC);
|
|
||||||
support->collation = index->rd_indcollation[0];
|
|
||||||
support->normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Normalize value
|
* Normalize value
|
||||||
*/
|
*/
|
||||||
@@ -170,9 +159,9 @@ HnswNormValue(const HnswTypeInfo * typeInfo, Oid collation, Datum value)
|
|||||||
* Check if non-zero norm
|
* Check if non-zero norm
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
HnswCheckNorm(HnswSupport * support, Datum value)
|
HnswCheckNorm(FmgrInfo *procinfo, Oid collation, Datum value)
|
||||||
{
|
{
|
||||||
return DatumGetFloat8(FunctionCall1Coll(support->normprocinfo, support->collation, value)) > 0;
|
return DatumGetFloat8(FunctionCall1Coll(procinfo, collation, value)) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -201,7 +190,7 @@ HnswInitPage(Buffer buf, Page page)
|
|||||||
/*
|
/*
|
||||||
* Allocate a neighbor array
|
* Allocate a neighbor array
|
||||||
*/
|
*/
|
||||||
HnswNeighborArray *
|
static HnswNeighborArray *
|
||||||
HnswInitNeighborArray(int lm, HnswAllocator * allocator)
|
HnswInitNeighborArray(int lm, HnswAllocator * allocator)
|
||||||
{
|
{
|
||||||
HnswNeighborArray *a = HnswAlloc(allocator, HNSW_NEIGHBOR_ARRAY_SIZE(lm));
|
HnswNeighborArray *a = HnswAlloc(allocator, HNSW_NEIGHBOR_ARRAY_SIZE(lm));
|
||||||
@@ -262,7 +251,7 @@ HnswInitElement(char *base, ItemPointer heaptid, int m, double ml, int maxLevel,
|
|||||||
|
|
||||||
HnswInitNeighbors(base, element, m, allocator);
|
HnswInitNeighbors(base, element, m, allocator);
|
||||||
|
|
||||||
HnswPtrStore(base, element->value, (char *) NULL);
|
HnswPtrStore(base, element->value, (Pointer) NULL);
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
@@ -288,7 +277,7 @@ HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno)
|
|||||||
element->blkno = blkno;
|
element->blkno = blkno;
|
||||||
element->offno = offno;
|
element->offno = offno;
|
||||||
HnswPtrStore(base, element->neighbors, (HnswNeighborArrayPtr *) NULL);
|
HnswPtrStore(base, element->neighbors, (HnswNeighborArrayPtr *) NULL);
|
||||||
HnswPtrStore(base, element->value, (char *) NULL);
|
HnswPtrStore(base, element->value, (Pointer) NULL);
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,33 +389,6 @@ HnswUpdateMetaPage(Relation index, int updateEntry, HnswElement entryPoint, Bloc
|
|||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Form index value
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
HnswFormIndexValue(Datum *out, Datum *values, bool *isnull, const HnswTypeInfo * typeInfo, HnswSupport * support)
|
|
||||||
{
|
|
||||||
/* Detoast once for all calls */
|
|
||||||
Datum value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
|
||||||
|
|
||||||
/* Check value */
|
|
||||||
if (typeInfo->checkValue != NULL)
|
|
||||||
typeInfo->checkValue(DatumGetPointer(value));
|
|
||||||
|
|
||||||
/* Normalize if needed */
|
|
||||||
if (support->normprocinfo != NULL)
|
|
||||||
{
|
|
||||||
if (!HnswCheckNorm(support, value))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
value = HnswNormValue(typeInfo, support->collation, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
*out = value;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set element tuple, except for neighbor info
|
* Set element tuple, except for neighbor info
|
||||||
*/
|
*/
|
||||||
@@ -484,6 +446,69 @@ HnswSetNeighborTuple(char *base, HnswNeighborTuple ntup, HnswElement e, int m)
|
|||||||
ntup->version = e->version;
|
ntup->version = e->version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Load neighbors from page
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
LoadNeighborsFromPage(HnswElement element, Relation index, Page page, int m)
|
||||||
|
{
|
||||||
|
char *base = NULL;
|
||||||
|
|
||||||
|
HnswNeighborTuple ntup = (HnswNeighborTuple) PageGetItem(page, PageGetItemId(page, element->neighborOffno));
|
||||||
|
int neighborCount = (element->level + 2) * m;
|
||||||
|
|
||||||
|
Assert(HnswIsNeighborTuple(ntup));
|
||||||
|
|
||||||
|
HnswInitNeighbors(base, element, m, NULL);
|
||||||
|
|
||||||
|
/* Ensure expected neighbors */
|
||||||
|
if (ntup->count != neighborCount)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int i = 0; i < neighborCount; i++)
|
||||||
|
{
|
||||||
|
HnswElement e;
|
||||||
|
int level;
|
||||||
|
HnswCandidate *hc;
|
||||||
|
ItemPointer indextid;
|
||||||
|
HnswNeighborArray *neighbors;
|
||||||
|
|
||||||
|
indextid = &ntup->indextids[i];
|
||||||
|
|
||||||
|
if (!ItemPointerIsValid(indextid))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
e = HnswInitElementFromBlock(ItemPointerGetBlockNumber(indextid), ItemPointerGetOffsetNumber(indextid));
|
||||||
|
|
||||||
|
/* Calculate level based on offset */
|
||||||
|
level = element->level - i / m;
|
||||||
|
if (level < 0)
|
||||||
|
level = 0;
|
||||||
|
|
||||||
|
neighbors = HnswGetNeighbors(base, element, level);
|
||||||
|
hc = &neighbors->items[neighbors->length++];
|
||||||
|
HnswPtrStore(base, hc->element, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Load neighbors
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
HnswLoadNeighbors(HnswElement element, Relation index, int m)
|
||||||
|
{
|
||||||
|
Buffer buf;
|
||||||
|
Page page;
|
||||||
|
|
||||||
|
buf = ReadBuffer(index, element->neighborPage);
|
||||||
|
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
||||||
|
page = BufferGetPage(buf);
|
||||||
|
|
||||||
|
LoadNeighborsFromPage(element, index, page, m);
|
||||||
|
|
||||||
|
UnlockReleaseBuffer(buf);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Load an element from a tuple
|
* Load an element from a tuple
|
||||||
*/
|
*/
|
||||||
@@ -514,24 +539,15 @@ HnswLoadElementFromTuple(HnswElement element, HnswElementTuple etup, bool loadHe
|
|||||||
char *base = NULL;
|
char *base = NULL;
|
||||||
Datum value = datumCopy(PointerGetDatum(&etup->data), false, -1);
|
Datum value = datumCopy(PointerGetDatum(&etup->data), false, -1);
|
||||||
|
|
||||||
HnswPtrStore(base, element->value, (char *) DatumGetPointer(value));
|
HnswPtrStore(base, element->value, DatumGetPointer(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Calculate the distance between values
|
|
||||||
*/
|
|
||||||
static inline double
|
|
||||||
HnswGetDistance(Datum a, Datum b, HnswSupport * support)
|
|
||||||
{
|
|
||||||
return DatumGetFloat8(FunctionCall2Coll(support->procinfo, support->collation, a, b));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Load an element and optionally get its distance from q
|
* Load an element and optionally get its distance from q
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
HnswLoadElementImpl(BlockNumber blkno, OffsetNumber offno, double *distance, HnswQuery * q, Relation index, HnswSupport * support, bool loadVec, double *maxDistance, HnswElement * element)
|
HnswLoadElementImpl(BlockNumber blkno, OffsetNumber offno, float *distance, Datum *q, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec, float *maxDistance, HnswElement * element)
|
||||||
{
|
{
|
||||||
Buffer buf;
|
Buffer buf;
|
||||||
Page page;
|
Page page;
|
||||||
@@ -546,16 +562,13 @@ HnswLoadElementImpl(BlockNumber blkno, OffsetNumber offno, double *distance, Hns
|
|||||||
|
|
||||||
Assert(HnswIsElementTuple(etup));
|
Assert(HnswIsElementTuple(etup));
|
||||||
|
|
||||||
if (unlikely(etup->deleted))
|
|
||||||
elog(ERROR, "cannot load deleted element");
|
|
||||||
|
|
||||||
/* Calculate distance */
|
/* Calculate distance */
|
||||||
if (distance != NULL)
|
if (distance != NULL)
|
||||||
{
|
{
|
||||||
if (DatumGetPointer(q->value) == NULL)
|
if (DatumGetPointer(*q) == NULL)
|
||||||
*distance = 0;
|
*distance = 0;
|
||||||
else
|
else
|
||||||
*distance = HnswGetDistance(q->value, PointerGetDatum(&etup->data), support);
|
*distance = (float) DatumGetFloat8(FunctionCall2Coll(procinfo, collation, *q, PointerGetDatum(&etup->data)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Load element */
|
/* Load element */
|
||||||
@@ -574,50 +587,36 @@ HnswLoadElementImpl(BlockNumber blkno, OffsetNumber offno, double *distance, Hns
|
|||||||
* Load an element and optionally get its distance from q
|
* Load an element and optionally get its distance from q
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
HnswLoadElement(HnswElement element, double *distance, HnswQuery * q, Relation index, HnswSupport * support, bool loadVec, double *maxDistance)
|
HnswLoadElement(HnswElement element, float *distance, Datum *q, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec, float *maxDistance)
|
||||||
{
|
{
|
||||||
HnswLoadElementImpl(element->blkno, element->offno, distance, q, index, support, loadVec, maxDistance, &element);
|
HnswLoadElementImpl(element->blkno, element->offno, distance, q, index, procinfo, collation, loadVec, maxDistance, &element);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the distance for an element
|
* Get the distance for an element
|
||||||
*/
|
*/
|
||||||
static double
|
static float
|
||||||
GetElementDistance(char *base, HnswElement element, HnswQuery * q, HnswSupport * support)
|
GetElementDistance(char *base, HnswElement element, Datum q, FmgrInfo *procinfo, Oid collation)
|
||||||
{
|
{
|
||||||
Datum value = HnswGetValue(base, element);
|
Datum value = HnswGetValue(base, element);
|
||||||
|
|
||||||
return HnswGetDistance(q->value, value, support);
|
return DatumGetFloat8(FunctionCall2Coll(procinfo, collation, q, value));
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Allocate a search candidate
|
|
||||||
*/
|
|
||||||
static HnswSearchCandidate *
|
|
||||||
HnswInitSearchCandidate(char *base, HnswElement element, double distance)
|
|
||||||
{
|
|
||||||
HnswSearchCandidate *sc = palloc(sizeof(HnswSearchCandidate));
|
|
||||||
|
|
||||||
HnswPtrStore(base, sc->element, element);
|
|
||||||
sc->distance = distance;
|
|
||||||
return sc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create a candidate for the entry point
|
* Create a candidate for the entry point
|
||||||
*/
|
*/
|
||||||
HnswSearchCandidate *
|
HnswSearchCandidate *
|
||||||
HnswEntryCandidate(char *base, HnswElement entryPoint, HnswQuery * q, Relation index, HnswSupport * support, bool loadVec)
|
HnswEntryCandidate(char *base, HnswElement entryPoint, Datum q, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec)
|
||||||
{
|
{
|
||||||
bool inMemory = index == NULL;
|
HnswSearchCandidate *hc = palloc(sizeof(HnswSearchCandidate));
|
||||||
double distance;
|
|
||||||
|
|
||||||
if (inMemory)
|
HnswPtrStore(base, hc->element, entryPoint);
|
||||||
distance = GetElementDistance(base, entryPoint, q, support);
|
if (index == NULL)
|
||||||
|
hc->distance = GetElementDistance(base, entryPoint, q, procinfo, collation);
|
||||||
else
|
else
|
||||||
HnswLoadElement(entryPoint, &distance, q, index, support, loadVec, NULL);
|
HnswLoadElement(entryPoint, &hc->distance, &q, index, procinfo, collation, loadVec, NULL);
|
||||||
|
return hc;
|
||||||
return HnswInitSearchCandidate(base, entryPoint, distance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -669,9 +668,9 @@ CompareFurthestCandidates(const pairingheap_node *a, const pairingheap_node *b,
|
|||||||
* Init visited
|
* Init visited
|
||||||
*/
|
*/
|
||||||
static inline void
|
static inline void
|
||||||
InitVisited(char *base, visited_hash * v, bool inMemory, int ef, int m)
|
InitVisited(char *base, visited_hash * v, Relation index, int ef, int m)
|
||||||
{
|
{
|
||||||
if (!inMemory)
|
if (index != NULL)
|
||||||
v->tids = tidhash_create(CurrentMemoryContext, ef * m * 2, NULL);
|
v->tids = tidhash_create(CurrentMemoryContext, ef * m * 2, NULL);
|
||||||
else if (base != NULL)
|
else if (base != NULL)
|
||||||
v->offsets = offsethash_create(CurrentMemoryContext, ef * m * 2, NULL);
|
v->offsets = offsethash_create(CurrentMemoryContext, ef * m * 2, NULL);
|
||||||
@@ -683,9 +682,9 @@ InitVisited(char *base, visited_hash * v, bool inMemory, int ef, int m)
|
|||||||
* Add to visited
|
* Add to visited
|
||||||
*/
|
*/
|
||||||
static inline void
|
static inline void
|
||||||
AddToVisited(char *base, visited_hash * v, HnswElementPtr elementPtr, bool inMemory, bool *found)
|
AddToVisited(char *base, visited_hash * v, HnswElementPtr elementPtr, Relation index, bool *found)
|
||||||
{
|
{
|
||||||
if (!inMemory)
|
if (index != NULL)
|
||||||
{
|
{
|
||||||
HnswElement element = HnswPtrAccess(base, elementPtr);
|
HnswElement element = HnswPtrAccess(base, elementPtr);
|
||||||
ItemPointerData indextid;
|
ItemPointerData indextid;
|
||||||
@@ -746,7 +745,7 @@ HnswLoadUnvisitedFromMemory(char *base, HnswElement element, HnswUnvisited * unv
|
|||||||
HnswCandidate *hc = &localNeighborhood->items[i];
|
HnswCandidate *hc = &localNeighborhood->items[i];
|
||||||
bool found;
|
bool found;
|
||||||
|
|
||||||
AddToVisited(base, v, hc->element, true, &found);
|
AddToVisited(base, v, hc->element, NULL, &found);
|
||||||
|
|
||||||
if (!found)
|
if (!found)
|
||||||
unvisited[(*unvisitedLength)++].element = HnswPtrAccess(base, hc->element);
|
unvisited[(*unvisitedLength)++].element = HnswPtrAccess(base, hc->element);
|
||||||
@@ -754,15 +753,18 @@ HnswLoadUnvisitedFromMemory(char *base, HnswElement element, HnswUnvisited * unv
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Load neighbor index TIDs
|
* Load unvisited neighbors from disk
|
||||||
*/
|
*/
|
||||||
bool
|
static void
|
||||||
HnswLoadNeighborTids(HnswElement element, ItemPointerData *indextids, Relation index, int m, int lm, int lc)
|
HnswLoadUnvisitedFromDisk(HnswElement element, HnswUnvisited * unvisited, int *unvisitedLength, visited_hash * v, Relation index, int m, int lm, int lc)
|
||||||
{
|
{
|
||||||
Buffer buf;
|
Buffer buf;
|
||||||
Page page;
|
Page page;
|
||||||
HnswNeighborTuple ntup;
|
HnswNeighborTuple ntup;
|
||||||
int start;
|
int start;
|
||||||
|
ItemPointerData indextids[HNSW_MAX_M * 2];
|
||||||
|
|
||||||
|
*unvisitedLength = 0;
|
||||||
|
|
||||||
buf = ReadBuffer(index, element->neighborPage);
|
buf = ReadBuffer(index, element->neighborPage);
|
||||||
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
||||||
@@ -777,29 +779,14 @@ HnswLoadNeighborTids(HnswElement element, ItemPointerData *indextids, Relation i
|
|||||||
if (ntup->version != element->version || ntup->count != (element->level + 2) * m)
|
if (ntup->version != element->version || ntup->count != (element->level + 2) * m)
|
||||||
{
|
{
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Copy to minimize lock time */
|
/* Copy to minimize lock time */
|
||||||
start = (element->level - lc) * m;
|
start = (element->level - lc) * m;
|
||||||
memcpy(indextids, ntup->indextids + start, lm * sizeof(ItemPointerData));
|
memcpy(&indextids, ntup->indextids + start, lm * sizeof(ItemPointerData));
|
||||||
|
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Load unvisited neighbors from disk
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
HnswLoadUnvisitedFromDisk(HnswElement element, HnswUnvisited * unvisited, int *unvisitedLength, visited_hash * v, Relation index, int m, int lm, int lc)
|
|
||||||
{
|
|
||||||
ItemPointerData indextids[HNSW_MAX_M * 2];
|
|
||||||
|
|
||||||
*unvisitedLength = 0;
|
|
||||||
|
|
||||||
if (!HnswLoadNeighborTids(element, indextids, index, m, lm, lc))
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (int i = 0; i < lm; i++)
|
for (int i = 0; i < lm; i++)
|
||||||
{
|
{
|
||||||
@@ -820,7 +807,7 @@ HnswLoadUnvisitedFromDisk(HnswElement element, HnswUnvisited * unvisited, int *u
|
|||||||
* Algorithm 2 from paper
|
* Algorithm 2 from paper
|
||||||
*/
|
*/
|
||||||
List *
|
List *
|
||||||
HnswSearchLayer(char *base, HnswQuery * q, List *ep, int ef, int lc, Relation index, HnswSupport * support, int m, bool inserting, HnswElement skipElement, visited_hash * v, pairingheap **discarded, bool initVisited, int64 *tuples)
|
HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *procinfo, Oid collation, int m, bool inserting, HnswElement skipElement, visited_hash * v, pairingheap **discarded, bool initVisited, int64 *tuples)
|
||||||
{
|
{
|
||||||
List *w = NIL;
|
List *w = NIL;
|
||||||
pairingheap *C = pairingheap_allocate(CompareNearestCandidates, NULL);
|
pairingheap *C = pairingheap_allocate(CompareNearestCandidates, NULL);
|
||||||
@@ -833,7 +820,6 @@ HnswSearchLayer(char *base, HnswQuery * q, List *ep, int ef, int lc, Relation in
|
|||||||
int lm = HnswGetLayerM(m, lc);
|
int lm = HnswGetLayerM(m, lc);
|
||||||
HnswUnvisited *unvisited = palloc(lm * sizeof(HnswUnvisited));
|
HnswUnvisited *unvisited = palloc(lm * sizeof(HnswUnvisited));
|
||||||
int unvisitedLength;
|
int unvisitedLength;
|
||||||
bool inMemory = index == NULL;
|
|
||||||
|
|
||||||
if (v == NULL)
|
if (v == NULL)
|
||||||
{
|
{
|
||||||
@@ -843,14 +829,14 @@ HnswSearchLayer(char *base, HnswQuery * q, List *ep, int ef, int lc, Relation in
|
|||||||
|
|
||||||
if (initVisited)
|
if (initVisited)
|
||||||
{
|
{
|
||||||
InitVisited(base, v, inMemory, ef, m);
|
InitVisited(base, v, index, ef, m);
|
||||||
|
|
||||||
if (discarded != NULL)
|
if (discarded != NULL)
|
||||||
*discarded = pairingheap_allocate(CompareNearestDiscardedCandidates, NULL);
|
*discarded = pairingheap_allocate(CompareNearestDiscardedCandidates, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create local memory for neighborhood if needed */
|
/* Create local memory for neighborhood if needed */
|
||||||
if (inMemory)
|
if (index == NULL)
|
||||||
{
|
{
|
||||||
neighborhoodSize = HNSW_NEIGHBOR_ARRAY_SIZE(lm);
|
neighborhoodSize = HNSW_NEIGHBOR_ARRAY_SIZE(lm);
|
||||||
localNeighborhood = palloc(neighborhoodSize);
|
localNeighborhood = palloc(neighborhoodSize);
|
||||||
@@ -859,27 +845,26 @@ HnswSearchLayer(char *base, HnswQuery * q, List *ep, int ef, int lc, Relation in
|
|||||||
/* Add entry points to v, C, and W */
|
/* Add entry points to v, C, and W */
|
||||||
foreach(lc2, ep)
|
foreach(lc2, ep)
|
||||||
{
|
{
|
||||||
HnswSearchCandidate *sc = (HnswSearchCandidate *) lfirst(lc2);
|
HnswSearchCandidate *hc = (HnswSearchCandidate *) lfirst(lc2);
|
||||||
bool found;
|
bool found;
|
||||||
|
|
||||||
if (initVisited)
|
if (initVisited)
|
||||||
{
|
{
|
||||||
AddToVisited(base, v, sc->element, inMemory, &found);
|
AddToVisited(base, v, hc->element, index, &found);
|
||||||
|
|
||||||
/* OK to count elements instead of tuples */
|
|
||||||
if (tuples != NULL)
|
if (tuples != NULL)
|
||||||
(*tuples)++;
|
(*tuples)++;
|
||||||
}
|
}
|
||||||
|
|
||||||
pairingheap_add(C, &sc->c_node);
|
pairingheap_add(C, &hc->c_node);
|
||||||
pairingheap_add(W, &sc->w_node);
|
pairingheap_add(W, &hc->w_node);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Do not count elements being deleted towards ef when vacuuming. It
|
* Do not count elements being deleted towards ef when vacuuming. It
|
||||||
* would be ideal to do this for inserts as well, but this could
|
* would be ideal to do this for inserts as well, but this could
|
||||||
* affect insert performance.
|
* affect insert performance.
|
||||||
*/
|
*/
|
||||||
if (CountElement(skipElement, HnswPtrAccess(base, sc->element)))
|
if (CountElement(skipElement, HnswPtrAccess(base, hc->element)))
|
||||||
wlen++;
|
wlen++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -894,12 +879,11 @@ HnswSearchLayer(char *base, HnswQuery * q, List *ep, int ef, int lc, Relation in
|
|||||||
|
|
||||||
cElement = HnswPtrAccess(base, c->element);
|
cElement = HnswPtrAccess(base, c->element);
|
||||||
|
|
||||||
if (inMemory)
|
if (index == NULL)
|
||||||
HnswLoadUnvisitedFromMemory(base, cElement, unvisited, &unvisitedLength, v, lc, localNeighborhood, neighborhoodSize);
|
HnswLoadUnvisitedFromMemory(base, cElement, unvisited, &unvisitedLength, v, lc, localNeighborhood, neighborhoodSize);
|
||||||
else
|
else
|
||||||
HnswLoadUnvisitedFromDisk(cElement, unvisited, &unvisitedLength, v, index, m, lm, lc);
|
HnswLoadUnvisitedFromDisk(cElement, unvisited, &unvisitedLength, v, index, m, lm, lc);
|
||||||
|
|
||||||
/* OK to count elements instead of tuples */
|
|
||||||
if (tuples != NULL)
|
if (tuples != NULL)
|
||||||
(*tuples) += unvisitedLength;
|
(*tuples) += unvisitedLength;
|
||||||
|
|
||||||
@@ -907,15 +891,15 @@ HnswSearchLayer(char *base, HnswQuery * q, List *ep, int ef, int lc, Relation in
|
|||||||
{
|
{
|
||||||
HnswElement eElement;
|
HnswElement eElement;
|
||||||
HnswSearchCandidate *e;
|
HnswSearchCandidate *e;
|
||||||
double eDistance;
|
float eDistance;
|
||||||
bool alwaysAdd = wlen < ef;
|
bool alwaysAdd = wlen < ef;
|
||||||
|
|
||||||
f = HnswGetSearchCandidate(w_node, pairingheap_first(W));
|
f = HnswGetSearchCandidate(w_node, pairingheap_first(W));
|
||||||
|
|
||||||
if (inMemory)
|
if (index == NULL)
|
||||||
{
|
{
|
||||||
eElement = unvisited[i].element;
|
eElement = unvisited[i].element;
|
||||||
eDistance = GetElementDistance(base, eElement, q, support);
|
eDistance = GetElementDistance(base, eElement, q, procinfo, collation);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -925,18 +909,17 @@ HnswSearchLayer(char *base, HnswQuery * q, List *ep, int ef, int lc, Relation in
|
|||||||
|
|
||||||
/* Avoid any allocations if not adding */
|
/* Avoid any allocations if not adding */
|
||||||
eElement = NULL;
|
eElement = NULL;
|
||||||
HnswLoadElementImpl(blkno, offno, &eDistance, q, index, support, inserting, alwaysAdd || discarded != NULL ? NULL : &f->distance, &eElement);
|
HnswLoadElementImpl(blkno, offno, &eDistance, &q, index, procinfo, collation, inserting, alwaysAdd || discarded != NULL ? NULL : &f->distance, &eElement);
|
||||||
|
|
||||||
if (eElement == NULL)
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(eDistance < f->distance || alwaysAdd))
|
if (eElement == NULL || !(eDistance < f->distance || alwaysAdd))
|
||||||
{
|
{
|
||||||
if (discarded != NULL)
|
if (discarded != NULL)
|
||||||
{
|
{
|
||||||
/* Create a new candidate */
|
/* Create a new candidate */
|
||||||
e = HnswInitSearchCandidate(base, eElement, eDistance);
|
e = palloc(sizeof(HnswSearchCandidate));
|
||||||
|
HnswPtrStore(base, e->element, eElement);
|
||||||
|
e->distance = eDistance;
|
||||||
pairingheap_add(*discarded, &e->w_node);
|
pairingheap_add(*discarded, &e->w_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -948,7 +931,9 @@ HnswSearchLayer(char *base, HnswQuery * q, List *ep, int ef, int lc, Relation in
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Create a new candidate */
|
/* Create a new candidate */
|
||||||
e = HnswInitSearchCandidate(base, eElement, eDistance);
|
e = palloc(sizeof(HnswSearchCandidate));
|
||||||
|
HnswPtrStore(base, e->element, eElement);
|
||||||
|
e->distance = eDistance;
|
||||||
pairingheap_add(C, &e->c_node);
|
pairingheap_add(C, &e->c_node);
|
||||||
pairingheap_add(W, &e->w_node);
|
pairingheap_add(W, &e->w_node);
|
||||||
|
|
||||||
@@ -976,9 +961,9 @@ HnswSearchLayer(char *base, HnswQuery * q, List *ep, int ef, int lc, Relation in
|
|||||||
/* Add each element of W to w */
|
/* Add each element of W to w */
|
||||||
while (!pairingheap_is_empty(W))
|
while (!pairingheap_is_empty(W))
|
||||||
{
|
{
|
||||||
HnswSearchCandidate *sc = HnswGetSearchCandidate(w_node, pairingheap_remove_first(W));
|
HnswSearchCandidate *hc = HnswGetSearchCandidate(w_node, pairingheap_remove_first(W));
|
||||||
|
|
||||||
w = lappend(w, sc);
|
w = lappend(w, hc);
|
||||||
}
|
}
|
||||||
|
|
||||||
return w;
|
return w;
|
||||||
@@ -1032,22 +1017,32 @@ CompareCandidateDistancesOffset(const ListCell *a, const ListCell *b)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate the distance between elements
|
||||||
|
*/
|
||||||
|
static float
|
||||||
|
HnswGetDistance(char *base, HnswElement a, HnswElement b, FmgrInfo *procinfo, Oid collation)
|
||||||
|
{
|
||||||
|
Datum aValue = HnswGetValue(base, a);
|
||||||
|
Datum bValue = HnswGetValue(base, b);
|
||||||
|
|
||||||
|
return DatumGetFloat8(FunctionCall2Coll(procinfo, collation, aValue, bValue));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check if an element is closer to q than any element from R
|
* Check if an element is closer to q than any element from R
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool
|
||||||
CheckElementCloser(char *base, HnswCandidate * e, List *r, HnswSupport * support)
|
CheckElementCloser(char *base, HnswCandidate * e, List *r, FmgrInfo *procinfo, Oid collation)
|
||||||
{
|
{
|
||||||
HnswElement eElement = HnswPtrAccess(base, e->element);
|
HnswElement eElement = HnswPtrAccess(base, e->element);
|
||||||
Datum eValue = HnswGetValue(base, eElement);
|
|
||||||
ListCell *lc2;
|
ListCell *lc2;
|
||||||
|
|
||||||
foreach(lc2, r)
|
foreach(lc2, r)
|
||||||
{
|
{
|
||||||
HnswCandidate *ri = lfirst(lc2);
|
HnswCandidate *ri = lfirst(lc2);
|
||||||
HnswElement riElement = HnswPtrAccess(base, ri->element);
|
HnswElement riElement = HnswPtrAccess(base, ri->element);
|
||||||
Datum riValue = HnswGetValue(base, riElement);
|
float distance = HnswGetDistance(base, eElement, riElement, procinfo, collation);
|
||||||
float distance = HnswGetDistance(eValue, riValue, support);
|
|
||||||
|
|
||||||
if (distance <= e->distance)
|
if (distance <= e->distance)
|
||||||
return false;
|
return false;
|
||||||
@@ -1060,14 +1055,15 @@ CheckElementCloser(char *base, HnswCandidate * e, List *r, HnswSupport * support
|
|||||||
* Algorithm 4 from paper
|
* Algorithm 4 from paper
|
||||||
*/
|
*/
|
||||||
static List *
|
static List *
|
||||||
SelectNeighbors(char *base, List *c, int lm, HnswSupport * support, bool *closerSet, HnswCandidate * newCandidate, HnswCandidate * *pruned, bool sortCandidates)
|
SelectNeighbors(char *base, List *c, int lm, int lc, FmgrInfo *procinfo, Oid collation, HnswElement e2, HnswCandidate * newCandidate, HnswCandidate * *pruned, bool sortCandidates)
|
||||||
{
|
{
|
||||||
List *r = NIL;
|
List *r = NIL;
|
||||||
List *w = list_copy(c);
|
List *w = list_copy(c);
|
||||||
HnswCandidate **wd;
|
HnswCandidate **wd;
|
||||||
int wdlen = 0;
|
int wdlen = 0;
|
||||||
int wdoff = 0;
|
int wdoff = 0;
|
||||||
bool mustCalculate = !(*closerSet);
|
HnswNeighborArray *neighbors = HnswGetNeighbors(base, e2, lc);
|
||||||
|
bool mustCalculate = !neighbors->closerSet;
|
||||||
List *added = NIL;
|
List *added = NIL;
|
||||||
bool removedAny = false;
|
bool removedAny = false;
|
||||||
|
|
||||||
@@ -1094,7 +1090,7 @@ SelectNeighbors(char *base, List *c, int lm, HnswSupport * support, bool *closer
|
|||||||
|
|
||||||
/* Use previous state of r and wd to skip work when possible */
|
/* Use previous state of r and wd to skip work when possible */
|
||||||
if (mustCalculate)
|
if (mustCalculate)
|
||||||
e->closer = CheckElementCloser(base, e, r, support);
|
e->closer = CheckElementCloser(base, e, r, procinfo, collation);
|
||||||
else if (list_length(added) > 0)
|
else if (list_length(added) > 0)
|
||||||
{
|
{
|
||||||
/* Keep Valgrind happy for in-memory, parallel builds */
|
/* Keep Valgrind happy for in-memory, parallel builds */
|
||||||
@@ -1107,7 +1103,7 @@ SelectNeighbors(char *base, List *c, int lm, HnswSupport * support, bool *closer
|
|||||||
*/
|
*/
|
||||||
if (e->closer)
|
if (e->closer)
|
||||||
{
|
{
|
||||||
e->closer = CheckElementCloser(base, e, added, support);
|
e->closer = CheckElementCloser(base, e, added, procinfo, collation);
|
||||||
|
|
||||||
if (!e->closer)
|
if (!e->closer)
|
||||||
removedAny = true;
|
removedAny = true;
|
||||||
@@ -1120,7 +1116,7 @@ SelectNeighbors(char *base, List *c, int lm, HnswSupport * support, bool *closer
|
|||||||
*/
|
*/
|
||||||
if (removedAny)
|
if (removedAny)
|
||||||
{
|
{
|
||||||
e->closer = CheckElementCloser(base, e, r, support);
|
e->closer = CheckElementCloser(base, e, r, procinfo, collation);
|
||||||
if (e->closer)
|
if (e->closer)
|
||||||
added = lappend(added, e);
|
added = lappend(added, e);
|
||||||
}
|
}
|
||||||
@@ -1128,7 +1124,7 @@ SelectNeighbors(char *base, List *c, int lm, HnswSupport * support, bool *closer
|
|||||||
}
|
}
|
||||||
else if (e == newCandidate)
|
else if (e == newCandidate)
|
||||||
{
|
{
|
||||||
e->closer = CheckElementCloser(base, e, r, support);
|
e->closer = CheckElementCloser(base, e, r, procinfo, collation);
|
||||||
if (e->closer)
|
if (e->closer)
|
||||||
added = lappend(added, e);
|
added = lappend(added, e);
|
||||||
}
|
}
|
||||||
@@ -1144,7 +1140,7 @@ SelectNeighbors(char *base, List *c, int lm, HnswSupport * support, bool *closer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Cached value can only be used in future if sorted deterministically */
|
/* Cached value can only be used in future if sorted deterministically */
|
||||||
*closerSet = sortCandidates;
|
neighbors->closerSet = sortCandidates;
|
||||||
|
|
||||||
/* Keep pruned connections */
|
/* Keep pruned connections */
|
||||||
while (wdoff < wdlen && list_length(r) < lm)
|
while (wdoff < wdlen && list_length(r) < lm)
|
||||||
@@ -1179,16 +1175,18 @@ AddConnections(char *base, HnswElement element, List *neighbors, int lc)
|
|||||||
* Update connections
|
* Update connections
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
HnswUpdateConnection(char *base, HnswNeighborArray * neighbors, HnswElement newElement, float distance, int lm, int *updateIdx, Relation index, HnswSupport * support)
|
HnswUpdateConnection(char *base, HnswElement element, HnswCandidate * hc, int lm, int lc, int *updateIdx, Relation index, FmgrInfo *procinfo, Oid collation)
|
||||||
{
|
{
|
||||||
HnswCandidate newHc;
|
HnswElement hce = HnswPtrAccess(base, hc->element);
|
||||||
|
HnswNeighborArray *currentNeighbors = HnswGetNeighbors(base, hce, lc);
|
||||||
|
HnswCandidate hc2;
|
||||||
|
|
||||||
HnswPtrStore(base, newHc.element, newElement);
|
HnswPtrStore(base, hc2.element, element);
|
||||||
newHc.distance = distance;
|
hc2.distance = hc->distance;
|
||||||
|
|
||||||
if (neighbors->length < lm)
|
if (currentNeighbors->length < lm)
|
||||||
{
|
{
|
||||||
neighbors->items[neighbors->length++] = newHc;
|
currentNeighbors->items[currentNeighbors->length++] = hc2;
|
||||||
|
|
||||||
/* Track update */
|
/* Track update */
|
||||||
if (updateIdx != NULL)
|
if (updateIdx != NULL)
|
||||||
@@ -1197,26 +1195,54 @@ HnswUpdateConnection(char *base, HnswNeighborArray * neighbors, HnswElement newE
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* Shrink connections */
|
/* Shrink connections */
|
||||||
List *c = NIL;
|
|
||||||
HnswCandidate *pruned = NULL;
|
HnswCandidate *pruned = NULL;
|
||||||
|
|
||||||
/* Add candidates */
|
/* Load elements on insert */
|
||||||
for (int i = 0; i < neighbors->length; i++)
|
if (index != NULL)
|
||||||
c = lappend(c, &neighbors->items[i]);
|
{
|
||||||
c = lappend(c, &newHc);
|
Datum q = HnswGetValue(base, hce);
|
||||||
|
|
||||||
SelectNeighbors(base, c, lm, support, &neighbors->closerSet, &newHc, &pruned, true);
|
for (int i = 0; i < currentNeighbors->length; i++)
|
||||||
|
{
|
||||||
|
HnswCandidate *hc3 = ¤tNeighbors->items[i];
|
||||||
|
HnswElement hc3Element = HnswPtrAccess(base, hc3->element);
|
||||||
|
|
||||||
|
if (HnswPtrIsNull(base, hc3Element->value))
|
||||||
|
HnswLoadElement(hc3Element, &hc3->distance, &q, index, procinfo, collation, true, NULL);
|
||||||
|
else
|
||||||
|
hc3->distance = GetElementDistance(base, hc3Element, q, procinfo, collation);
|
||||||
|
|
||||||
|
/* Prune element if being deleted */
|
||||||
|
if (hc3Element->heaptidsLength == 0)
|
||||||
|
{
|
||||||
|
pruned = ¤tNeighbors->items[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Should not happen */
|
|
||||||
if (pruned == NULL)
|
if (pruned == NULL)
|
||||||
return;
|
{
|
||||||
|
List *c = NIL;
|
||||||
|
|
||||||
|
/* Add candidates */
|
||||||
|
for (int i = 0; i < currentNeighbors->length; i++)
|
||||||
|
c = lappend(c, ¤tNeighbors->items[i]);
|
||||||
|
c = lappend(c, &hc2);
|
||||||
|
|
||||||
|
SelectNeighbors(base, c, lm, lc, procinfo, collation, hce, &hc2, &pruned, true);
|
||||||
|
|
||||||
|
/* Should not happen */
|
||||||
|
if (pruned == NULL)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* Find and replace the pruned element */
|
/* Find and replace the pruned element */
|
||||||
for (int i = 0; i < neighbors->length; i++)
|
for (int i = 0; i < currentNeighbors->length; i++)
|
||||||
{
|
{
|
||||||
if (HnswPtrEqual(base, neighbors->items[i].element, pruned->element))
|
if (HnswPtrEqual(base, currentNeighbors->items[i].element, pruned->element))
|
||||||
{
|
{
|
||||||
neighbors->items[i] = newHc;
|
currentNeighbors->items[i] = hc2;
|
||||||
|
|
||||||
/* Track update */
|
/* Track update */
|
||||||
if (updateIdx != NULL)
|
if (updateIdx != NULL)
|
||||||
@@ -1276,20 +1302,17 @@ PrecomputeHash(char *base, HnswElement element)
|
|||||||
* Algorithm 1 from paper
|
* Algorithm 1 from paper
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint, Relation index, HnswSupport * support, int m, int efConstruction, bool existing)
|
HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint, Relation index, FmgrInfo *procinfo, Oid collation, int m, int efConstruction, bool existing)
|
||||||
{
|
{
|
||||||
List *ep;
|
List *ep;
|
||||||
List *w;
|
List *w;
|
||||||
int level = element->level;
|
int level = element->level;
|
||||||
int entryLevel;
|
int entryLevel;
|
||||||
HnswQuery q;
|
Datum q = HnswGetValue(base, element);
|
||||||
HnswElement skipElement = existing ? element : NULL;
|
HnswElement skipElement = existing ? element : NULL;
|
||||||
bool inMemory = index == NULL;
|
|
||||||
|
|
||||||
q.value = HnswGetValue(base, element);
|
|
||||||
|
|
||||||
/* Precompute hash */
|
/* Precompute hash */
|
||||||
if (inMemory)
|
if (index == NULL)
|
||||||
PrecomputeHash(base, element);
|
PrecomputeHash(base, element);
|
||||||
|
|
||||||
/* No neighbors if no entry point */
|
/* No neighbors if no entry point */
|
||||||
@@ -1297,13 +1320,13 @@ HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
/* Get entry point and level */
|
/* Get entry point and level */
|
||||||
ep = list_make1(HnswEntryCandidate(base, entryPoint, &q, index, support, true));
|
ep = list_make1(HnswEntryCandidate(base, entryPoint, q, index, procinfo, collation, true));
|
||||||
entryLevel = entryPoint->level;
|
entryLevel = entryPoint->level;
|
||||||
|
|
||||||
/* 1st phase: greedy search to insert level */
|
/* 1st phase: greedy search to insert level */
|
||||||
for (int lc = entryLevel; lc >= level + 1; lc--)
|
for (int lc = entryLevel; lc >= level + 1; lc--)
|
||||||
{
|
{
|
||||||
w = HnswSearchLayer(base, &q, ep, 1, lc, index, support, m, true, skipElement, NULL, NULL, true, NULL);
|
w = HnswSearchLayer(base, q, ep, 1, lc, index, procinfo, collation, m, true, skipElement, NULL, NULL, true, NULL);
|
||||||
ep = w;
|
ep = w;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1322,7 +1345,7 @@ HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint
|
|||||||
List *lw = NIL;
|
List *lw = NIL;
|
||||||
ListCell *lc2;
|
ListCell *lc2;
|
||||||
|
|
||||||
w = HnswSearchLayer(base, &q, ep, efConstruction, lc, index, support, m, true, skipElement, NULL, NULL, true, NULL);
|
w = HnswSearchLayer(base, q, ep, efConstruction, lc, index, procinfo, collation, m, true, skipElement, NULL, NULL, true, NULL);
|
||||||
|
|
||||||
/* Convert search candidates to candidates */
|
/* Convert search candidates to candidates */
|
||||||
foreach(lc2, w)
|
foreach(lc2, w)
|
||||||
@@ -1338,7 +1361,7 @@ HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint
|
|||||||
|
|
||||||
/* Elements being deleted or skipped can help with search */
|
/* Elements being deleted or skipped can help with search */
|
||||||
/* but should be removed before selecting neighbors */
|
/* but should be removed before selecting neighbors */
|
||||||
if (!inMemory)
|
if (index != NULL)
|
||||||
lw = RemoveElements(base, lw, skipElement);
|
lw = RemoveElements(base, lw, skipElement);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -1346,7 +1369,7 @@ HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint
|
|||||||
* sortCandidates to true for in-memory builds to enable closer
|
* sortCandidates to true for in-memory builds to enable closer
|
||||||
* caching, but there does not seem to be a difference in performance.
|
* caching, but there does not seem to be a difference in performance.
|
||||||
*/
|
*/
|
||||||
neighbors = SelectNeighbors(base, lw, lm, support, &HnswGetNeighbors(base, element, lc)->closerSet, NULL, NULL, false);
|
neighbors = SelectNeighbors(base, lw, lm, lc, procinfo, collation, element, NULL, NULL, false);
|
||||||
|
|
||||||
AddConnections(base, element, neighbors, lc);
|
AddConnections(base, element, neighbors, lc);
|
||||||
|
|
||||||
@@ -1402,7 +1425,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
|
||||||
@@ -1415,7 +1438,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
|
||||||
@@ -1428,4 +1451,4 @@ hnsw_sparsevec_support(PG_FUNCTION_ARGS)
|
|||||||
};
|
};
|
||||||
|
|
||||||
PG_RETURN_POINTER(&typeInfo);
|
PG_RETURN_POINTER(&typeInfo);
|
||||||
}
|
};
|
||||||
|
|||||||
244
src/hnswvacuum.c
244
src/hnswvacuum.c
@@ -1,30 +1,21 @@
|
|||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include "access/genam.h"
|
#include <math.h>
|
||||||
|
|
||||||
#include "access/generic_xlog.h"
|
#include "access/generic_xlog.h"
|
||||||
#include "commands/vacuum.h"
|
#include "commands/vacuum.h"
|
||||||
#include "hnsw.h"
|
#include "hnsw.h"
|
||||||
#include "nodes/pg_list.h"
|
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
#include "storage/lmgr.h"
|
#include "storage/lmgr.h"
|
||||||
#include "utils/memutils.h"
|
#include "utils/memutils.h"
|
||||||
#include "utils/rel.h"
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 160000
|
|
||||||
#include "varatt.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 180000
|
|
||||||
#define vacuum_delay_point() vacuum_delay_point(false)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check if deletion list contains an element
|
* Check if deleted list contains an index TID
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool
|
||||||
DeletingElement(tidhash_hash * deleting, ItemPointer indextid)
|
DeletedContains(tidhash_hash * deleted, ItemPointer indextid)
|
||||||
{
|
{
|
||||||
return tidhash_lookup(deleting, *indextid) != NULL;
|
return tidhash_lookup(deleted, *indextid) != NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -37,20 +28,17 @@ RemoveHeapTids(HnswVacuumState * vacuumstate)
|
|||||||
{
|
{
|
||||||
BlockNumber blkno = HNSW_HEAD_BLKNO;
|
BlockNumber blkno = HNSW_HEAD_BLKNO;
|
||||||
HnswElement highestPoint = &vacuumstate->highestPoint;
|
HnswElement highestPoint = &vacuumstate->highestPoint;
|
||||||
HnswElement fallbackPoint = &vacuumstate->fallbackPoint;
|
|
||||||
Relation index = vacuumstate->index;
|
Relation index = vacuumstate->index;
|
||||||
BufferAccessStrategy bas = vacuumstate->bas;
|
BufferAccessStrategy bas = vacuumstate->bas;
|
||||||
|
HnswElement entryPoint = HnswGetEntryPoint(vacuumstate->index);
|
||||||
IndexBulkDeleteResult *stats = vacuumstate->stats;
|
IndexBulkDeleteResult *stats = vacuumstate->stats;
|
||||||
|
|
||||||
/* Store separately since HnswElement level is uint8 */
|
/* Store separately since highestPoint.level is uint8 */
|
||||||
int highestLevel = -1;
|
int highestLevel = -1;
|
||||||
int fallbackLevel = -1;
|
|
||||||
|
|
||||||
/* Initialize highest point and fallback point */
|
/* Initialize highest point */
|
||||||
highestPoint->blkno = InvalidBlockNumber;
|
highestPoint->blkno = InvalidBlockNumber;
|
||||||
highestPoint->offno = InvalidOffsetNumber;
|
highestPoint->offno = InvalidOffsetNumber;
|
||||||
fallbackPoint->blkno = InvalidBlockNumber;
|
|
||||||
fallbackPoint->offno = InvalidOffsetNumber;
|
|
||||||
|
|
||||||
while (BlockNumberIsValid(blkno))
|
while (BlockNumberIsValid(blkno))
|
||||||
{
|
{
|
||||||
@@ -80,14 +68,6 @@ RemoveHeapTids(HnswVacuumState * vacuumstate)
|
|||||||
if (!HnswIsElementTuple(etup))
|
if (!HnswIsElementTuple(etup))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/*
|
|
||||||
* Skip deleted tuples. It is important they are not added to the
|
|
||||||
* deletion list to avoid false positives in NeedsUpdated and
|
|
||||||
* ConfirmRepaired.
|
|
||||||
*/
|
|
||||||
if (etup->deleted)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (ItemPointerIsValid(&etup->heaptids[0]))
|
if (ItemPointerIsValid(&etup->heaptids[0]))
|
||||||
{
|
{
|
||||||
for (int i = 0; i < HNSW_HEAPTIDS; i++)
|
for (int i = 0; i < HNSW_HEAPTIDS; i++)
|
||||||
@@ -121,40 +101,23 @@ RemoveHeapTids(HnswVacuumState * vacuumstate)
|
|||||||
|
|
||||||
if (!ItemPointerIsValid(&etup->heaptids[0]))
|
if (!ItemPointerIsValid(&etup->heaptids[0]))
|
||||||
{
|
{
|
||||||
ItemPointerData indextid;
|
ItemPointerData ip;
|
||||||
bool found;
|
bool found;
|
||||||
|
|
||||||
/* Add to deletion list */
|
/* Add to deleted list */
|
||||||
ItemPointerSet(&indextid, blkno, offno);
|
ItemPointerSet(&ip, blkno, offno);
|
||||||
|
|
||||||
tidhash_insert(vacuumstate->deleting, indextid, &found);
|
tidhash_insert(vacuumstate->deleted, ip, &found);
|
||||||
Assert(!found);
|
Assert(!found);
|
||||||
}
|
}
|
||||||
else if (etup->level > highestLevel)
|
else if (etup->level > highestLevel && !(entryPoint != NULL && blkno == entryPoint->blkno && offno == entryPoint->offno))
|
||||||
{
|
{
|
||||||
if (BlockNumberIsValid(highestPoint->blkno))
|
/* Keep track of highest non-entry point */
|
||||||
{
|
|
||||||
/* Current highest point becomes fallback */
|
|
||||||
fallbackPoint->blkno = highestPoint->blkno;
|
|
||||||
fallbackPoint->offno = highestPoint->offno;
|
|
||||||
fallbackPoint->level = highestPoint->level;
|
|
||||||
fallbackLevel = highestLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Keep track of highest point */
|
|
||||||
highestPoint->blkno = blkno;
|
highestPoint->blkno = blkno;
|
||||||
highestPoint->offno = offno;
|
highestPoint->offno = offno;
|
||||||
highestPoint->level = etup->level;
|
highestPoint->level = etup->level;
|
||||||
highestLevel = etup->level;
|
highestLevel = etup->level;
|
||||||
}
|
}
|
||||||
else if (etup->level > fallbackLevel)
|
|
||||||
{
|
|
||||||
/* Keep track of second highest point */
|
|
||||||
fallbackPoint->blkno = blkno;
|
|
||||||
fallbackPoint->offno = offno;
|
|
||||||
fallbackPoint->level = etup->level;
|
|
||||||
fallbackLevel = etup->level;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
blkno = HnswPageGetOpaque(page)->nextblkno;
|
blkno = HnswPageGetOpaque(page)->nextblkno;
|
||||||
@@ -166,10 +129,6 @@ RemoveHeapTids(HnswVacuumState * vacuumstate)
|
|||||||
|
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HNSW_MEMORY
|
|
||||||
elog(INFO, "memory: %zu KB", MemoryContextMemAllocated(CurrentMemoryContext, true) / 1024);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -200,8 +159,8 @@ NeedsUpdated(HnswVacuumState * vacuumstate, HnswElement element)
|
|||||||
if (!ItemPointerIsValid(indextid))
|
if (!ItemPointerIsValid(indextid))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Check if in deletion list */
|
/* Check if in deleted list */
|
||||||
if (DeletingElement(vacuumstate->deleting, indextid))
|
if (DeletedContains(vacuumstate->deleted, indextid))
|
||||||
{
|
{
|
||||||
needsUpdated = true;
|
needsUpdated = true;
|
||||||
break;
|
break;
|
||||||
@@ -210,8 +169,7 @@ NeedsUpdated(HnswVacuumState * vacuumstate, HnswElement element)
|
|||||||
|
|
||||||
/* Also update if layer 0 is not full */
|
/* Also update if layer 0 is not full */
|
||||||
/* This could indicate too many candidates being deleted during insert */
|
/* This could indicate too many candidates being deleted during insert */
|
||||||
/* There should always be more than zero indextids, but check for safety */
|
if (!needsUpdated)
|
||||||
if (!needsUpdated && ntup->count > 0)
|
|
||||||
needsUpdated = !ItemPointerIsValid(&ntup->indextids[ntup->count - 1]);
|
needsUpdated = !ItemPointerIsValid(&ntup->indextids[ntup->count - 1]);
|
||||||
|
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
@@ -226,12 +184,13 @@ static void
|
|||||||
RepairGraphElement(HnswVacuumState * vacuumstate, HnswElement element, HnswElement entryPoint)
|
RepairGraphElement(HnswVacuumState * vacuumstate, HnswElement element, HnswElement entryPoint)
|
||||||
{
|
{
|
||||||
Relation index = vacuumstate->index;
|
Relation index = vacuumstate->index;
|
||||||
HnswSupport *support = &vacuumstate->support;
|
|
||||||
Buffer buf;
|
Buffer buf;
|
||||||
Page page;
|
Page page;
|
||||||
GenericXLogState *state;
|
GenericXLogState *state;
|
||||||
int m = vacuumstate->m;
|
int m = vacuumstate->m;
|
||||||
int efConstruction = vacuumstate->efConstruction;
|
int efConstruction = vacuumstate->efConstruction;
|
||||||
|
FmgrInfo *procinfo = vacuumstate->procinfo;
|
||||||
|
Oid collation = vacuumstate->collation;
|
||||||
BufferAccessStrategy bas = vacuumstate->bas;
|
BufferAccessStrategy bas = vacuumstate->bas;
|
||||||
HnswNeighborTuple ntup = vacuumstate->ntup;
|
HnswNeighborTuple ntup = vacuumstate->ntup;
|
||||||
Size ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(element->level, m);
|
Size ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(element->level, m);
|
||||||
@@ -246,7 +205,7 @@ RepairGraphElement(HnswVacuumState * vacuumstate, HnswElement element, HnswEleme
|
|||||||
element->heaptidsLength = 0;
|
element->heaptidsLength = 0;
|
||||||
|
|
||||||
/* Find neighbors for element, skipping itself */
|
/* Find neighbors for element, skipping itself */
|
||||||
HnswFindElementNeighbors(base, element, entryPoint, index, support, m, efConstruction, true);
|
HnswFindElementNeighbors(base, element, entryPoint, index, procinfo, collation, m, efConstruction, true);
|
||||||
|
|
||||||
/* Zero memory for each element */
|
/* Zero memory for each element */
|
||||||
MemSet(ntup, 0, HNSW_TUPLE_ALLOC_SIZE);
|
MemSet(ntup, 0, HNSW_TUPLE_ALLOC_SIZE);
|
||||||
@@ -270,7 +229,7 @@ RepairGraphElement(HnswVacuumState * vacuumstate, HnswElement element, HnswEleme
|
|||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
|
|
||||||
/* Update neighbors */
|
/* Update neighbors */
|
||||||
HnswUpdateNeighborsOnDisk(index, support, element, m, true, false);
|
HnswUpdateNeighborsOnDisk(index, procinfo, collation, element, m, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -280,7 +239,6 @@ static void
|
|||||||
RepairGraphEntryPoint(HnswVacuumState * vacuumstate)
|
RepairGraphEntryPoint(HnswVacuumState * vacuumstate)
|
||||||
{
|
{
|
||||||
Relation index = vacuumstate->index;
|
Relation index = vacuumstate->index;
|
||||||
HnswSupport *support = &vacuumstate->support;
|
|
||||||
HnswElement highestPoint = &vacuumstate->highestPoint;
|
HnswElement highestPoint = &vacuumstate->highestPoint;
|
||||||
HnswElement entryPoint;
|
HnswElement entryPoint;
|
||||||
MemoryContext oldCtx = MemoryContextSwitchTo(vacuumstate->tmpCtx);
|
MemoryContext oldCtx = MemoryContextSwitchTo(vacuumstate->tmpCtx);
|
||||||
@@ -297,27 +255,12 @@ RepairGraphEntryPoint(HnswVacuumState * vacuumstate)
|
|||||||
/* Get a shared lock */
|
/* Get a shared lock */
|
||||||
LockPage(index, HNSW_UPDATE_LOCK, ShareLock);
|
LockPage(index, HNSW_UPDATE_LOCK, ShareLock);
|
||||||
|
|
||||||
/* Get latest entry point */
|
/* Load element */
|
||||||
entryPoint = HnswGetEntryPoint(index);
|
HnswLoadElement(highestPoint, NULL, NULL, index, vacuumstate->procinfo, vacuumstate->collation, true, NULL);
|
||||||
|
|
||||||
/* Use fallback point if highest point is entry point */
|
/* Repair if needed */
|
||||||
if (entryPoint != NULL && entryPoint->blkno == highestPoint->blkno && entryPoint->offno == highestPoint->offno)
|
if (NeedsUpdated(vacuumstate, highestPoint))
|
||||||
{
|
RepairGraphElement(vacuumstate, highestPoint, HnswGetEntryPoint(index));
|
||||||
highestPoint = &vacuumstate->fallbackPoint;
|
|
||||||
|
|
||||||
if (!BlockNumberIsValid(highestPoint->blkno))
|
|
||||||
highestPoint = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (highestPoint != NULL)
|
|
||||||
{
|
|
||||||
/* Load element */
|
|
||||||
HnswLoadElement(highestPoint, NULL, NULL, index, support, true, NULL);
|
|
||||||
|
|
||||||
/* Repair if needed */
|
|
||||||
if (NeedsUpdated(vacuumstate, highestPoint))
|
|
||||||
RepairGraphElement(vacuumstate, highestPoint, entryPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Release lock */
|
/* Release lock */
|
||||||
UnlockPage(index, HNSW_UPDATE_LOCK, ShareLock);
|
UnlockPage(index, HNSW_UPDATE_LOCK, ShareLock);
|
||||||
@@ -335,7 +278,7 @@ RepairGraphEntryPoint(HnswVacuumState * vacuumstate)
|
|||||||
|
|
||||||
ItemPointerSet(&epData, entryPoint->blkno, entryPoint->offno);
|
ItemPointerSet(&epData, entryPoint->blkno, entryPoint->offno);
|
||||||
|
|
||||||
if (DeletingElement(vacuumstate->deleting, &epData))
|
if (DeletedContains(vacuumstate->deleted, &epData))
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Replace the entry point with the highest point. If highest
|
* Replace the entry point with the highest point. If highest
|
||||||
@@ -351,7 +294,7 @@ RepairGraphEntryPoint(HnswVacuumState * vacuumstate)
|
|||||||
* is outdated, this can remove connections at higher levels in
|
* is outdated, this can remove connections at higher levels in
|
||||||
* the graph until they are repaired, but this should be fine.
|
* the graph until they are repaired, but this should be fine.
|
||||||
*/
|
*/
|
||||||
HnswLoadElement(entryPoint, NULL, NULL, index, support, true, NULL);
|
HnswLoadElement(entryPoint, NULL, NULL, index, vacuumstate->procinfo, vacuumstate->collation, true, NULL);
|
||||||
|
|
||||||
if (NeedsUpdated(vacuumstate, entryPoint))
|
if (NeedsUpdated(vacuumstate, entryPoint))
|
||||||
{
|
{
|
||||||
@@ -421,10 +364,6 @@ RepairGraph(HnswVacuumState * vacuumstate)
|
|||||||
if (!HnswIsElementTuple(etup))
|
if (!HnswIsElementTuple(etup))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Skip deleted tuples */
|
|
||||||
if (etup->deleted)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/* Skip updating neighbors if being deleted */
|
/* Skip updating neighbors if being deleted */
|
||||||
if (!ItemPointerIsValid(&etup->heaptids[0]))
|
if (!ItemPointerIsValid(&etup->heaptids[0]))
|
||||||
continue;
|
continue;
|
||||||
@@ -488,103 +427,6 @@ RepairGraph(HnswVacuumState * vacuumstate)
|
|||||||
/* Reset memory context */
|
/* Reset memory context */
|
||||||
MemoryContextSwitchTo(oldCtx);
|
MemoryContextSwitchTo(oldCtx);
|
||||||
MemoryContextReset(vacuumstate->tmpCtx);
|
MemoryContextReset(vacuumstate->tmpCtx);
|
||||||
|
|
||||||
#ifdef HNSW_VACUUM_PROGRESS
|
|
||||||
if (!BlockNumberIsValid(blkno) || (blkno - HNSW_HEAD_BLKNO) % 1000 == 0)
|
|
||||||
{
|
|
||||||
BlockNumber totalBlocks = RelationGetNumberOfBlocks(index);
|
|
||||||
BlockNumber currentBlocks = BlockNumberIsValid(blkno) ? blkno : totalBlocks;
|
|
||||||
|
|
||||||
elog(INFO, "hnsw vacuum progress: %.1f%%", 100.0 * currentBlocks / totalBlocks);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Confirm graph was repaired
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
ConfirmRepaired(HnswVacuumState * vacuumstate)
|
|
||||||
{
|
|
||||||
BlockNumber blkno = HNSW_HEAD_BLKNO;
|
|
||||||
Relation index = vacuumstate->index;
|
|
||||||
BufferAccessStrategy bas = vacuumstate->bas;
|
|
||||||
|
|
||||||
while (BlockNumberIsValid(blkno))
|
|
||||||
{
|
|
||||||
Buffer buf;
|
|
||||||
Page page;
|
|
||||||
OffsetNumber offno;
|
|
||||||
OffsetNumber maxoffno;
|
|
||||||
|
|
||||||
vacuum_delay_point();
|
|
||||||
|
|
||||||
buf = ReadBufferExtended(index, MAIN_FORKNUM, blkno, RBM_NORMAL, bas);
|
|
||||||
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
|
||||||
page = BufferGetPage(buf);
|
|
||||||
maxoffno = PageGetMaxOffsetNumber(page);
|
|
||||||
|
|
||||||
/* Iterate over nodes */
|
|
||||||
for (offno = FirstOffsetNumber; offno <= maxoffno; offno = OffsetNumberNext(offno))
|
|
||||||
{
|
|
||||||
HnswElementTuple etup = (HnswElementTuple) PageGetItem(page, PageGetItemId(page, offno));
|
|
||||||
HnswNeighborTuple ntup;
|
|
||||||
Buffer nbuf;
|
|
||||||
Page npage;
|
|
||||||
BlockNumber neighborPage;
|
|
||||||
OffsetNumber neighborOffno;
|
|
||||||
|
|
||||||
/* Skip neighbor tuples */
|
|
||||||
if (!HnswIsElementTuple(etup))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/* Skip deleted tuples */
|
|
||||||
if (etup->deleted)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/* Skip if being deleted */
|
|
||||||
if (!ItemPointerIsValid(&etup->heaptids[0]))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/* Get neighbor page */
|
|
||||||
neighborPage = ItemPointerGetBlockNumber(&etup->neighbortid);
|
|
||||||
neighborOffno = ItemPointerGetOffsetNumber(&etup->neighbortid);
|
|
||||||
|
|
||||||
if (neighborPage == blkno)
|
|
||||||
{
|
|
||||||
nbuf = buf;
|
|
||||||
npage = page;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
nbuf = ReadBufferExtended(index, MAIN_FORKNUM, neighborPage, RBM_NORMAL, bas);
|
|
||||||
LockBuffer(nbuf, BUFFER_LOCK_SHARE);
|
|
||||||
npage = BufferGetPage(nbuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
ntup = (HnswNeighborTuple) PageGetItem(npage, PageGetItemId(npage, neighborOffno));
|
|
||||||
|
|
||||||
/* Check neighbors */
|
|
||||||
for (int i = 0; i < ntup->count; i++)
|
|
||||||
{
|
|
||||||
ItemPointer indextid = &ntup->indextids[i];
|
|
||||||
|
|
||||||
if (!ItemPointerIsValid(indextid))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/* Check if in deletion list */
|
|
||||||
if (DeletingElement(vacuumstate->deleting, indextid))
|
|
||||||
elog(ERROR, "hnsw graph not repaired");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nbuf != buf)
|
|
||||||
UnlockReleaseBuffer(nbuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
blkno = HnswPageGetOpaque(page)->nextblkno;
|
|
||||||
|
|
||||||
UnlockReleaseBuffer(buf);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -600,15 +442,10 @@ MarkDeleted(HnswVacuumState * vacuumstate)
|
|||||||
BufferAccessStrategy bas = vacuumstate->bas;
|
BufferAccessStrategy bas = vacuumstate->bas;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Wait for inserts and index scans to complete. Inserts and scans before
|
* Wait for index scans to complete. Scans before this point may contain
|
||||||
* this point may visit tuples about to be deleted. Inserts and scans
|
* tuples about to be deleted. Scans after this point will not, since the
|
||||||
* after this point will not, since the graph has been repaired.
|
* graph has been repaired.
|
||||||
*/
|
*/
|
||||||
LockPage(index, HNSW_UPDATE_LOCK, ExclusiveLock);
|
|
||||||
UnlockPage(index, HNSW_UPDATE_LOCK, ExclusiveLock);
|
|
||||||
|
|
||||||
ConfirmRepaired(vacuumstate);
|
|
||||||
|
|
||||||
LockPage(index, HNSW_SCAN_LOCK, ExclusiveLock);
|
LockPage(index, HNSW_SCAN_LOCK, ExclusiveLock);
|
||||||
UnlockPage(index, HNSW_SCAN_LOCK, ExclusiveLock);
|
UnlockPage(index, HNSW_SCAN_LOCK, ExclusiveLock);
|
||||||
|
|
||||||
@@ -683,9 +520,8 @@ MarkDeleted(HnswVacuumState * vacuumstate)
|
|||||||
ntup = (HnswNeighborTuple) PageGetItem(npage, PageGetItemId(npage, neighborOffno));
|
ntup = (HnswNeighborTuple) PageGetItem(npage, PageGetItemId(npage, neighborOffno));
|
||||||
|
|
||||||
/* Overwrite element */
|
/* Overwrite element */
|
||||||
/* Use memset instead of MemSet to keep clang-tidy happy */
|
|
||||||
etup->deleted = 1;
|
etup->deleted = 1;
|
||||||
memset(&etup->data, 0, VARSIZE_ANY(&etup->data));
|
MemSet(&etup->data, 0, VARSIZE_ANY(&etup->data));
|
||||||
|
|
||||||
/* Overwrite neighbors */
|
/* Overwrite neighbors */
|
||||||
for (int i = 0; i < ntup->count; i++)
|
for (int i = 0; i < ntup->count; i++)
|
||||||
@@ -745,18 +581,18 @@ InitVacuumState(HnswVacuumState * vacuumstate, IndexVacuumInfo *info, IndexBulkD
|
|||||||
vacuumstate->callback_state = callback_state;
|
vacuumstate->callback_state = callback_state;
|
||||||
vacuumstate->efConstruction = HnswGetEfConstruction(index);
|
vacuumstate->efConstruction = HnswGetEfConstruction(index);
|
||||||
vacuumstate->bas = GetAccessStrategy(BAS_BULKREAD);
|
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->ntup = palloc0(HNSW_TUPLE_ALLOC_SIZE);
|
||||||
vacuumstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
|
vacuumstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
|
||||||
"Hnsw vacuum temporary context",
|
"Hnsw vacuum temporary context",
|
||||||
ALLOCSET_DEFAULT_SIZES);
|
ALLOCSET_DEFAULT_SIZES);
|
||||||
|
|
||||||
HnswInitSupport(&vacuumstate->support, index);
|
|
||||||
|
|
||||||
/* Get m from metapage */
|
/* Get m from metapage */
|
||||||
HnswGetMetaPageInfo(index, &vacuumstate->m, NULL);
|
HnswGetMetaPageInfo(index, &vacuumstate->m, NULL);
|
||||||
|
|
||||||
/* Create hash table */
|
/* Create hash table */
|
||||||
vacuumstate->deleting = tidhash_create(CurrentMemoryContext, 256, NULL);
|
vacuumstate->deleted = tidhash_create(CurrentMemoryContext, 256, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -765,7 +601,7 @@ InitVacuumState(HnswVacuumState * vacuumstate, IndexVacuumInfo *info, IndexBulkD
|
|||||||
static void
|
static void
|
||||||
FreeVacuumState(HnswVacuumState * vacuumstate)
|
FreeVacuumState(HnswVacuumState * vacuumstate)
|
||||||
{
|
{
|
||||||
tidhash_destroy(vacuumstate->deleting);
|
tidhash_destroy(vacuumstate->deleted);
|
||||||
FreeAccessStrategy(vacuumstate->bas);
|
FreeAccessStrategy(vacuumstate->bas);
|
||||||
pfree(vacuumstate->ntup);
|
pfree(vacuumstate->ntup);
|
||||||
MemoryContextDelete(vacuumstate->tmpCtx);
|
MemoryContextDelete(vacuumstate->tmpCtx);
|
||||||
@@ -783,13 +619,13 @@ hnswbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
|
|||||||
InitVacuumState(&vacuumstate, info, stats, callback, callback_state);
|
InitVacuumState(&vacuumstate, info, stats, callback, callback_state);
|
||||||
|
|
||||||
/* Pass 1: Remove heap TIDs */
|
/* Pass 1: Remove heap TIDs */
|
||||||
HnswBench("RemoveHeapTids", RemoveHeapTids(&vacuumstate));
|
RemoveHeapTids(&vacuumstate);
|
||||||
|
|
||||||
/* Pass 2: Repair graph */
|
/* Pass 2: Repair graph */
|
||||||
HnswBench("RepairGraph", RepairGraph(&vacuumstate));
|
RepairGraph(&vacuumstate);
|
||||||
|
|
||||||
/* Passes 3 and 4: Confirm repaired and mark as deleted */
|
/* Pass 3: Mark as deleted */
|
||||||
HnswBench("MarkDeleted", MarkDeleted(&vacuumstate));
|
MarkDeleted(&vacuumstate);
|
||||||
|
|
||||||
FreeVacuumState(&vacuumstate);
|
FreeVacuumState(&vacuumstate);
|
||||||
|
|
||||||
|
|||||||
105
src/ivfbuild.c
105
src/ivfbuild.c
@@ -2,37 +2,23 @@
|
|||||||
|
|
||||||
#include <float.h>
|
#include <float.h>
|
||||||
|
|
||||||
#include "access/genam.h"
|
|
||||||
#include "access/generic_xlog.h"
|
|
||||||
#include "access/itup.h"
|
|
||||||
#include "access/relscan.h"
|
|
||||||
#include "access/table.h"
|
#include "access/table.h"
|
||||||
#include "access/tableam.h"
|
#include "access/tableam.h"
|
||||||
#include "access/tupdesc.h"
|
|
||||||
#include "access/parallel.h"
|
#include "access/parallel.h"
|
||||||
#include "access/xact.h"
|
#include "access/xact.h"
|
||||||
#include "access/xloginsert.h"
|
#include "bitvec.h"
|
||||||
#include "catalog/index.h"
|
#include "catalog/index.h"
|
||||||
#include "catalog/pg_operator_d.h"
|
#include "catalog/pg_operator_d.h"
|
||||||
#include "catalog/pg_type_d.h"
|
#include "catalog/pg_type_d.h"
|
||||||
#include "commands/progress.h"
|
#include "commands/progress.h"
|
||||||
#include "fmgr.h"
|
#include "halfvec.h"
|
||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
#include "nodes/execnodes.h"
|
|
||||||
#include "optimizer/optimizer.h"
|
#include "optimizer/optimizer.h"
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
#include "storage/condition_variable.h"
|
|
||||||
#include "tcop/tcopprot.h"
|
#include "tcop/tcopprot.h"
|
||||||
#include "utils/memutils.h"
|
#include "utils/memutils.h"
|
||||||
#include "utils/rel.h"
|
#include "vector.h"
|
||||||
#include "utils/sampling.h"
|
|
||||||
#include "utils/snapmgr.h"
|
|
||||||
#include "utils/tuplesort.h"
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 160000
|
|
||||||
#include "varatt.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 140000
|
#if PG_VERSION_NUM >= 140000
|
||||||
#include "utils/backend_progress.h"
|
#include "utils/backend_progress.h"
|
||||||
@@ -63,13 +49,15 @@ AddSample(Datum *values, IvfflatBuildState * buildstate)
|
|||||||
Datum value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
Datum value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check with KMEANS_NORM_PROC that the value can be normalized since
|
* Normalize with KMEANS_NORM_PROC since spherical distance function
|
||||||
* spherical distance function expects unit vectors
|
* expects unit vectors
|
||||||
*/
|
*/
|
||||||
if (buildstate->kmeansnormprocinfo != NULL)
|
if (buildstate->kmeansnormprocinfo != NULL)
|
||||||
{
|
{
|
||||||
if (!IvfflatCheckNorm(buildstate->kmeansnormprocinfo, buildstate->collation, value))
|
if (!IvfflatCheckNorm(buildstate->kmeansnormprocinfo, buildstate->collation, value))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
value = IvfflatNormValue(buildstate->typeInfo, buildstate->collation, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (samples->length < targsamples)
|
if (samples->length < targsamples)
|
||||||
@@ -80,7 +68,7 @@ AddSample(Datum *values, IvfflatBuildState * buildstate)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (buildstate->rowstoskip < 0)
|
if (buildstate->rowstoskip < 0)
|
||||||
buildstate->rowstoskip = reservoir_get_next_S(&buildstate->rstate, buildstate->samplerows, targsamples);
|
buildstate->rowstoskip = reservoir_get_next_S(&buildstate->rstate, samples->length, targsamples);
|
||||||
|
|
||||||
if (buildstate->rowstoskip <= 0)
|
if (buildstate->rowstoskip <= 0)
|
||||||
{
|
{
|
||||||
@@ -96,9 +84,6 @@ AddSample(Datum *values, IvfflatBuildState * buildstate)
|
|||||||
|
|
||||||
buildstate->rowstoskip -= 1;
|
buildstate->rowstoskip -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Increment after reservoir_get_next_S */
|
|
||||||
buildstate->samplerows += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -135,7 +120,6 @@ SampleRows(IvfflatBuildState * buildstate)
|
|||||||
int targsamples = buildstate->samples->maxlen;
|
int targsamples = buildstate->samples->maxlen;
|
||||||
BlockNumber totalblocks = RelationGetNumberOfBlocks(buildstate->heap);
|
BlockNumber totalblocks = RelationGetNumberOfBlocks(buildstate->heap);
|
||||||
|
|
||||||
buildstate->samplerows = 0;
|
|
||||||
buildstate->rowstoskip = -1;
|
buildstate->rowstoskip = -1;
|
||||||
|
|
||||||
BlockSampler_Init(&buildstate->bs, totalblocks, targsamples, RandomInt());
|
BlockSampler_Init(&buildstate->bs, totalblocks, targsamples, RandomInt());
|
||||||
@@ -145,21 +129,16 @@ SampleRows(IvfflatBuildState * buildstate)
|
|||||||
{
|
{
|
||||||
BlockNumber targblock = BlockSampler_Next(&buildstate->bs);
|
BlockNumber targblock = BlockSampler_Next(&buildstate->bs);
|
||||||
|
|
||||||
/* Set anyvisible to false like table_index_build_scan */
|
|
||||||
table_index_build_range_scan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
table_index_build_range_scan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||||
false, false, false, targblock, 1, SampleCallback, (void *) buildstate, NULL);
|
false, true, false, targblock, 1, SampleCallback, (void *) buildstate, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Normalize if needed */
|
|
||||||
if (buildstate->kmeansnormprocinfo != NULL)
|
|
||||||
IvfflatNormVectors(buildstate->typeInfo, buildstate->collation, buildstate->samples, buildstate->tmpCtx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add tuple to sort
|
* Add tuple to sort
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
AddTupleToSort(ItemPointer tid, Datum *values, IvfflatBuildState * buildstate)
|
AddTupleToSort(Relation index, ItemPointer tid, Datum *values, IvfflatBuildState * buildstate)
|
||||||
{
|
{
|
||||||
double distance;
|
double distance;
|
||||||
double minDistance = DBL_MAX;
|
double minDistance = DBL_MAX;
|
||||||
@@ -236,7 +215,7 @@ BuildCallback(Relation index, ItemPointer tid, Datum *values,
|
|||||||
oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
|
oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
|
||||||
|
|
||||||
/* Add tuple to sort */
|
/* Add tuple to sort */
|
||||||
AddTupleToSort(tid, values, buildstate);
|
AddTupleToSort(index, tid, values, buildstate);
|
||||||
|
|
||||||
/* Reset memory context */
|
/* Reset memory context */
|
||||||
MemoryContextSwitchTo(oldCtx);
|
MemoryContextSwitchTo(oldCtx);
|
||||||
@@ -249,11 +228,11 @@ BuildCallback(Relation index, ItemPointer tid, Datum *values,
|
|||||||
static inline void
|
static inline void
|
||||||
GetNextTuple(Tuplesortstate *sortstate, TupleDesc tupdesc, TupleTableSlot *slot, IndexTuple *itup, int *list)
|
GetNextTuple(Tuplesortstate *sortstate, TupleDesc tupdesc, TupleTableSlot *slot, IndexTuple *itup, int *list)
|
||||||
{
|
{
|
||||||
|
Datum value;
|
||||||
|
bool isnull;
|
||||||
|
|
||||||
if (tuplesort_gettupleslot(sortstate, true, false, slot, NULL))
|
if (tuplesort_gettupleslot(sortstate, true, false, slot, NULL))
|
||||||
{
|
{
|
||||||
Datum value;
|
|
||||||
bool isnull;
|
|
||||||
|
|
||||||
*list = DatumGetInt32(slot_getattr(slot, 1, &isnull));
|
*list = DatumGetInt32(slot_getattr(slot, 1, &isnull));
|
||||||
value = slot_getattr(slot, 3, &isnull);
|
value = slot_getattr(slot, 3, &isnull);
|
||||||
|
|
||||||
@@ -275,8 +254,8 @@ InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum)
|
|||||||
IndexTuple itup = NULL; /* silence compiler warning */
|
IndexTuple itup = NULL; /* silence compiler warning */
|
||||||
int64 inserted = 0;
|
int64 inserted = 0;
|
||||||
|
|
||||||
TupleTableSlot *slot = MakeSingleTupleTableSlot(buildstate->sortdesc, &TTSOpsMinimalTuple);
|
TupleTableSlot *slot = MakeSingleTupleTableSlot(buildstate->tupdesc, &TTSOpsMinimalTuple);
|
||||||
TupleDesc tupdesc = buildstate->tupdesc;
|
TupleDesc tupdesc = RelationGetDescr(index);
|
||||||
|
|
||||||
pgstat_progress_update_param(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_LOAD);
|
pgstat_progress_update_param(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_LOAD);
|
||||||
|
|
||||||
@@ -340,7 +319,6 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
|
|||||||
buildstate->index = index;
|
buildstate->index = index;
|
||||||
buildstate->indexInfo = indexInfo;
|
buildstate->indexInfo = indexInfo;
|
||||||
buildstate->typeInfo = IvfflatGetTypeInfo(index);
|
buildstate->typeInfo = IvfflatGetTypeInfo(index);
|
||||||
buildstate->tupdesc = RelationGetDescr(index);
|
|
||||||
|
|
||||||
buildstate->lists = IvfflatGetLists(index);
|
buildstate->lists = IvfflatGetLists(index);
|
||||||
buildstate->dimensions = TupleDescAttr(index->rd_att, 0)->atttypmod;
|
buildstate->dimensions = TupleDescAttr(index->rd_att, 0)->atttypmod;
|
||||||
@@ -378,24 +356,14 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
|
|||||||
errmsg("dimensions must be greater than one for this opclass")));
|
errmsg("dimensions must be greater than one for this opclass")));
|
||||||
|
|
||||||
/* Create tuple description for sorting */
|
/* Create tuple description for sorting */
|
||||||
buildstate->sortdesc = CreateTemplateTupleDesc(3);
|
buildstate->tupdesc = CreateTemplateTupleDesc(3);
|
||||||
TupleDescInitEntry(buildstate->sortdesc, (AttrNumber) 1, "list", INT4OID, -1, 0);
|
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 1, "list", INT4OID, -1, 0);
|
||||||
TupleDescInitEntry(buildstate->sortdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0);
|
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0);
|
||||||
TupleDescInitEntry(buildstate->sortdesc, (AttrNumber) 3, "vector", TupleDescAttr(buildstate->tupdesc, 0)->atttypid, -1, 0);
|
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 3, "vector", RelationGetDescr(index)->attrs[0].atttypid, -1, 0);
|
||||||
#if PG_VERSION_NUM >= 190000
|
|
||||||
TupleDescFinalize(buildstate->sortdesc);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
buildstate->slot = MakeSingleTupleTableSlot(buildstate->sortdesc, &TTSOpsVirtual);
|
buildstate->slot = MakeSingleTupleTableSlot(buildstate->tupdesc, &TTSOpsVirtual);
|
||||||
|
|
||||||
buildstate->memoryUsed = 0;
|
buildstate->centers = VectorArrayInit(buildstate->lists, buildstate->dimensions, buildstate->typeInfo->itemSize(buildstate->dimensions));
|
||||||
buildstate->itemsize = buildstate->typeInfo->itemSize(buildstate->dimensions);
|
|
||||||
|
|
||||||
buildstate->memoryUsed += VECTOR_ARRAY_SIZE(buildstate->lists, buildstate->itemsize);
|
|
||||||
IvfflatCheckMemoryUsage(buildstate->memoryUsed);
|
|
||||||
buildstate->centers = VectorArrayInit(buildstate->lists, buildstate->dimensions, buildstate->itemsize);
|
|
||||||
|
|
||||||
/* TODO Move allocation to page creation */
|
|
||||||
buildstate->listInfo = palloc(sizeof(ListInfo) * buildstate->lists);
|
buildstate->listInfo = palloc(sizeof(ListInfo) * buildstate->lists);
|
||||||
|
|
||||||
buildstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
|
buildstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
|
||||||
@@ -449,12 +417,11 @@ ComputeCenters(IvfflatBuildState * buildstate)
|
|||||||
numSamples = 1;
|
numSamples = 1;
|
||||||
|
|
||||||
/* Sample rows */
|
/* Sample rows */
|
||||||
buildstate->memoryUsed += VECTOR_ARRAY_SIZE(numSamples, buildstate->itemsize);
|
/* TODO Ensure within maintenance_work_mem */
|
||||||
IvfflatCheckMemoryUsage(buildstate->memoryUsed);
|
buildstate->samples = VectorArrayInit(numSamples, buildstate->dimensions, buildstate->centers->itemsize);
|
||||||
buildstate->samples = VectorArrayInit(numSamples, buildstate->dimensions, buildstate->itemsize);
|
|
||||||
if (buildstate->heap != NULL)
|
if (buildstate->heap != NULL)
|
||||||
{
|
{
|
||||||
IvfflatBench("sample rows", SampleRows(buildstate));
|
SampleRows(buildstate);
|
||||||
|
|
||||||
if (buildstate->samples->length < buildstate->lists)
|
if (buildstate->samples->length < buildstate->lists)
|
||||||
{
|
{
|
||||||
@@ -466,7 +433,7 @@ ComputeCenters(IvfflatBuildState * buildstate)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Calculate centers */
|
/* Calculate centers */
|
||||||
IvfflatBench("k-means", IvfflatKmeans(buildstate->index, buildstate->samples, buildstate->centers, buildstate->typeInfo, buildstate->memoryUsed));
|
IvfflatBench("k-means", IvfflatKmeans(buildstate->index, buildstate->samples, buildstate->centers, buildstate->typeInfo));
|
||||||
|
|
||||||
/* Free samples before we allocate more memory */
|
/* Free samples before we allocate more memory */
|
||||||
VectorArrayFree(buildstate->samples);
|
VectorArrayFree(buildstate->samples);
|
||||||
@@ -502,8 +469,8 @@ CreateMetaPage(Relation index, int dimensions, int lists, ForkNumber forkNum)
|
|||||||
* Create list pages
|
* Create list pages
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
CreateListPages(Relation index, VectorArray centers, int lists,
|
CreateListPages(Relation index, VectorArray centers, int dimensions,
|
||||||
ForkNumber forkNum, ListInfo * *listInfo)
|
int lists, ForkNumber forkNum, ListInfo * *listInfo)
|
||||||
{
|
{
|
||||||
Buffer buf;
|
Buffer buf;
|
||||||
Page page;
|
Page page;
|
||||||
@@ -666,14 +633,10 @@ IvfflatParallelScanAndSort(IvfflatSpool * ivfspool, IvfflatShared * ivfshared, S
|
|||||||
InitBuildState(&buildstate, ivfspool->heap, ivfspool->index, indexInfo);
|
InitBuildState(&buildstate, ivfspool->heap, ivfspool->index, indexInfo);
|
||||||
memcpy(buildstate.centers->items, ivfcenters, buildstate.centers->itemsize * buildstate.centers->maxlen);
|
memcpy(buildstate.centers->items, ivfcenters, buildstate.centers->itemsize * buildstate.centers->maxlen);
|
||||||
buildstate.centers->length = buildstate.centers->maxlen;
|
buildstate.centers->length = buildstate.centers->maxlen;
|
||||||
ivfspool->sortstate = InitBuildSortState(buildstate.sortdesc, sortmem, coordinate);
|
ivfspool->sortstate = InitBuildSortState(buildstate.tupdesc, sortmem, coordinate);
|
||||||
buildstate.sortstate = ivfspool->sortstate;
|
buildstate.sortstate = ivfspool->sortstate;
|
||||||
scan = table_beginscan_parallel(ivfspool->heap,
|
scan = table_beginscan_parallel(ivfspool->heap,
|
||||||
ParallelTableScanFromIvfflatShared(ivfshared)
|
ParallelTableScanFromIvfflatShared(ivfshared));
|
||||||
#if PG_VERSION_NUM >= 190000
|
|
||||||
,SO_NONE
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
reltuples = table_index_build_scan(ivfspool->heap, ivfspool->index, indexInfo,
|
reltuples = table_index_build_scan(ivfspool->heap, ivfspool->index, indexInfo,
|
||||||
true, progress, BuildCallback,
|
true, progress, BuildCallback,
|
||||||
(void *) &buildstate, scan);
|
(void *) &buildstate, scan);
|
||||||
@@ -987,7 +950,7 @@ AssignTuples(IvfflatBuildState * buildstate)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Begin serial/leader tuplesort */
|
/* Begin serial/leader tuplesort */
|
||||||
buildstate->sortstate = InitBuildSortState(buildstate->sortdesc, maintenance_work_mem, coordinate);
|
buildstate->sortstate = InitBuildSortState(buildstate->tupdesc, maintenance_work_mem, coordinate);
|
||||||
|
|
||||||
/* Add tuples to sort */
|
/* Add tuples to sort */
|
||||||
if (buildstate->heap != NULL)
|
if (buildstate->heap != NULL)
|
||||||
@@ -1040,7 +1003,7 @@ BuildIndex(Relation heap, Relation index, IndexInfo *indexInfo,
|
|||||||
|
|
||||||
/* Create pages */
|
/* Create pages */
|
||||||
CreateMetaPage(index, buildstate->dimensions, buildstate->lists, forkNum);
|
CreateMetaPage(index, buildstate->dimensions, buildstate->lists, forkNum);
|
||||||
CreateListPages(index, buildstate->centers, buildstate->lists, forkNum, &buildstate->listInfo);
|
CreateListPages(index, buildstate->centers, buildstate->dimensions, buildstate->lists, forkNum, &buildstate->listInfo);
|
||||||
CreateEntryPages(buildstate, forkNum);
|
CreateEntryPages(buildstate, forkNum);
|
||||||
|
|
||||||
/* Write WAL for initialization fork since GenericXLog functions do not */
|
/* Write WAL for initialization fork since GenericXLog functions do not */
|
||||||
@@ -1059,10 +1022,6 @@ ivfflatbuild(Relation heap, Relation index, IndexInfo *indexInfo)
|
|||||||
IndexBuildResult *result;
|
IndexBuildResult *result;
|
||||||
IvfflatBuildState buildstate;
|
IvfflatBuildState buildstate;
|
||||||
|
|
||||||
#ifdef IVFFLAT_BENCH
|
|
||||||
SeedRandom(42);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
BuildIndex(heap, index, indexInfo, &buildstate, MAIN_FORKNUM);
|
BuildIndex(heap, index, indexInfo, &buildstate, MAIN_FORKNUM);
|
||||||
|
|
||||||
result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult));
|
result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult));
|
||||||
|
|||||||
136
src/ivfflat.c
136
src/ivfflat.c
@@ -3,35 +3,22 @@
|
|||||||
#include <float.h>
|
#include <float.h>
|
||||||
|
|
||||||
#include "access/amapi.h"
|
#include "access/amapi.h"
|
||||||
#include "access/genam.h"
|
|
||||||
#include "access/reloptions.h"
|
#include "access/reloptions.h"
|
||||||
#include "commands/progress.h"
|
#include "commands/progress.h"
|
||||||
#include "commands/vacuum.h"
|
#include "commands/vacuum.h"
|
||||||
#include "fmgr.h"
|
|
||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "nodes/pg_list.h"
|
|
||||||
#include "utils/float.h"
|
#include "utils/float.h"
|
||||||
#include "utils/guc.h"
|
#include "utils/guc.h"
|
||||||
#include "utils/relcache.h"
|
|
||||||
#include "utils/selfuncs.h"
|
#include "utils/selfuncs.h"
|
||||||
#include "utils/spccache.h"
|
#include "utils/spccache.h"
|
||||||
#include "vector.h"
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 150000
|
#if PG_VERSION_NUM < 150000
|
||||||
#define MarkGUCPrefixReserved(x) EmitWarningsOnPlaceholders(x)
|
#define MarkGUCPrefixReserved(x) EmitWarningsOnPlaceholders(x)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int ivfflat_probes;
|
int ivfflat_probes;
|
||||||
int ivfflat_iterative_scan;
|
|
||||||
int ivfflat_max_probes;
|
|
||||||
static relopt_kind ivfflat_relopt_kind;
|
static relopt_kind ivfflat_relopt_kind;
|
||||||
|
|
||||||
static const struct config_enum_entry ivfflat_iterative_scan_options[] = {
|
|
||||||
{"off", IVFFLAT_ITERATIVE_SCAN_OFF, false},
|
|
||||||
{"relaxed_order", IVFFLAT_ITERATIVE_SCAN_RELAXED, false},
|
|
||||||
{NULL, 0, false}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Initialize index options and variables
|
* Initialize index options and variables
|
||||||
*/
|
*/
|
||||||
@@ -46,15 +33,6 @@ IvfflatInit(void)
|
|||||||
"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, 0, 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",
|
|
||||||
NULL, &ivfflat_iterative_scan,
|
|
||||||
IVFFLAT_ITERATIVE_SCAN_OFF, ivfflat_iterative_scan_options, PGC_USERSET, 0, NULL, NULL, NULL);
|
|
||||||
|
|
||||||
/* If this is less than probes, probes is used */
|
|
||||||
DefineCustomIntVariable("ivfflat.max_probes", "Sets the max number of probes for iterative scans",
|
|
||||||
NULL, &ivfflat_max_probes,
|
|
||||||
IVFFLAT_MAX_LISTS, IVFFLAT_MIN_LISTS, IVFFLAT_MAX_LISTS, PGC_USERSET, 0, NULL, NULL, NULL);
|
|
||||||
|
|
||||||
MarkGUCPrefixReserved("ivfflat");
|
MarkGUCPrefixReserved("ivfflat");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,30 +69,22 @@ ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
|||||||
GenericCosts costs;
|
GenericCosts costs;
|
||||||
int lists;
|
int lists;
|
||||||
double ratio;
|
double ratio;
|
||||||
double sequentialRatio = 0.5;
|
|
||||||
double startupPages;
|
|
||||||
double spc_seq_page_cost;
|
double spc_seq_page_cost;
|
||||||
Relation index;
|
Relation index;
|
||||||
|
|
||||||
/* Never use index without order */
|
/* Never use index without order */
|
||||||
if (path->indexorderbys == NIL)
|
if (path->indexorderbys == NULL)
|
||||||
{
|
{
|
||||||
*indexStartupCost = get_float8_infinity();
|
*indexStartupCost = get_float8_infinity();
|
||||||
*indexTotalCost = get_float8_infinity();
|
*indexTotalCost = get_float8_infinity();
|
||||||
*indexSelectivity = 0;
|
*indexSelectivity = 0;
|
||||||
*indexCorrelation = 0;
|
*indexCorrelation = 0;
|
||||||
*indexPages = 0;
|
*indexPages = 0;
|
||||||
#if PG_VERSION_NUM >= 180000
|
|
||||||
/* See "On disable_cost" thread on pgsql-hackers */
|
|
||||||
path->path.disabled_nodes = 2;
|
|
||||||
#endif
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MemSet(&costs, 0, sizeof(costs));
|
MemSet(&costs, 0, sizeof(costs));
|
||||||
|
|
||||||
genericcostestimate(root, path, loop_count, &costs);
|
|
||||||
|
|
||||||
index = index_open(path->indexinfo->indexoid, NoLock);
|
index = index_open(path->indexinfo->indexoid, NoLock);
|
||||||
IvfflatGetMetaPageInfo(index, &lists, NULL);
|
IvfflatGetMetaPageInfo(index, &lists, NULL);
|
||||||
index_close(index, NoLock);
|
index_close(index, NoLock);
|
||||||
@@ -124,26 +94,34 @@ ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
|||||||
if (ratio > 1.0)
|
if (ratio > 1.0)
|
||||||
ratio = 1.0;
|
ratio = 1.0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This gives us the subset of tuples to visit. This value is passed into
|
||||||
|
* the generic cost estimator to determine the number of pages to visit
|
||||||
|
* during the index scan.
|
||||||
|
*/
|
||||||
|
costs.numIndexTuples = path->indexinfo->tuples * ratio;
|
||||||
|
|
||||||
|
genericcostestimate(root, path, loop_count, &costs);
|
||||||
|
|
||||||
get_tablespace_page_costs(path->indexinfo->reltablespace, NULL, &spc_seq_page_cost);
|
get_tablespace_page_costs(path->indexinfo->reltablespace, NULL, &spc_seq_page_cost);
|
||||||
|
|
||||||
/* Change some page cost from random to sequential */
|
|
||||||
costs.indexTotalCost -= sequentialRatio * costs.numIndexPages * (costs.spc_random_page_cost - spc_seq_page_cost);
|
|
||||||
|
|
||||||
/* Startup cost is cost before returning the first row */
|
|
||||||
costs.indexStartupCost = costs.indexTotalCost * ratio;
|
|
||||||
|
|
||||||
/* Adjust cost if needed since TOAST not included in seq scan cost */
|
/* Adjust cost if needed since TOAST not included in seq scan cost */
|
||||||
startupPages = costs.numIndexPages * ratio;
|
if (costs.numIndexPages > path->indexinfo->rel->pages && ratio < 0.5)
|
||||||
if (startupPages > path->indexinfo->rel->pages && ratio < 0.5)
|
|
||||||
{
|
{
|
||||||
/* Change rest of page cost from random to sequential */
|
/* Change all page cost from random to sequential */
|
||||||
costs.indexStartupCost -= (1 - sequentialRatio) * startupPages * (costs.spc_random_page_cost - spc_seq_page_cost);
|
costs.indexTotalCost -= costs.numIndexPages * (costs.spc_random_page_cost - spc_seq_page_cost);
|
||||||
|
|
||||||
/* Remove cost of extra pages */
|
/* Remove cost of extra pages */
|
||||||
costs.indexStartupCost -= (startupPages - path->indexinfo->rel->pages) * spc_seq_page_cost;
|
costs.indexTotalCost -= (costs.numIndexPages - path->indexinfo->rel->pages) * spc_seq_page_cost;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Change some page cost from random to sequential */
|
||||||
|
costs.indexTotalCost -= 0.5 * costs.numIndexPages * (costs.spc_random_page_cost - spc_seq_page_cost);
|
||||||
}
|
}
|
||||||
|
|
||||||
*indexStartupCost = costs.indexStartupCost;
|
/* Use total cost since most work happens before first tuple is returned */
|
||||||
|
*indexStartupCost = costs.indexTotalCost;
|
||||||
*indexTotalCost = costs.indexTotalCost;
|
*indexTotalCost = costs.indexTotalCost;
|
||||||
*indexSelectivity = costs.indexSelectivity;
|
*indexSelectivity = costs.indexSelectivity;
|
||||||
*indexCorrelation = costs.indexCorrelation;
|
*indexCorrelation = costs.indexCorrelation;
|
||||||
@@ -184,64 +162,6 @@ FUNCTION_PREFIX PG_FUNCTION_INFO_V1(ivfflathandler);
|
|||||||
Datum
|
Datum
|
||||||
ivfflathandler(PG_FUNCTION_ARGS)
|
ivfflathandler(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
#if PG_VERSION_NUM >= 190000
|
|
||||||
static const IndexAmRoutine amroutine = {
|
|
||||||
.type = T_IndexAmRoutine,
|
|
||||||
.amstrategies = 0,
|
|
||||||
.amsupport = 5,
|
|
||||||
.amoptsprocnum = 0,
|
|
||||||
.amcanorder = false,
|
|
||||||
.amcanorderbyop = true,
|
|
||||||
.amcanhash = false,
|
|
||||||
.amconsistentequality = false,
|
|
||||||
.amconsistentordering = false,
|
|
||||||
.amcanbackward = false,
|
|
||||||
.amcanunique = false,
|
|
||||||
.amcanmulticol = false,
|
|
||||||
.amoptionalkey = true,
|
|
||||||
.amsearcharray = false,
|
|
||||||
.amsearchnulls = false,
|
|
||||||
.amstorage = false,
|
|
||||||
.amclusterable = false,
|
|
||||||
.ampredlocks = false,
|
|
||||||
.amcanparallel = false,
|
|
||||||
.amcanbuildparallel = true,
|
|
||||||
.amcaninclude = false,
|
|
||||||
.amusemaintenanceworkmem = false,
|
|
||||||
.amsummarizing = false,
|
|
||||||
.amparallelvacuumoptions = VACUUM_OPTION_PARALLEL_BULKDEL,
|
|
||||||
.amkeytype = InvalidOid,
|
|
||||||
|
|
||||||
.ambuild = ivfflatbuild,
|
|
||||||
.ambuildempty = ivfflatbuildempty,
|
|
||||||
.aminsert = ivfflatinsert,
|
|
||||||
.aminsertcleanup = NULL,
|
|
||||||
.ambulkdelete = ivfflatbulkdelete,
|
|
||||||
.amvacuumcleanup = ivfflatvacuumcleanup,
|
|
||||||
.amcanreturn = NULL,
|
|
||||||
.amcostestimate = ivfflatcostestimate,
|
|
||||||
.amgettreeheight = NULL,
|
|
||||||
.amoptions = ivfflatoptions,
|
|
||||||
.amproperty = NULL,
|
|
||||||
.ambuildphasename = ivfflatbuildphasename,
|
|
||||||
.amvalidate = ivfflatvalidate,
|
|
||||||
.amadjustmembers = NULL,
|
|
||||||
.ambeginscan = ivfflatbeginscan,
|
|
||||||
.amrescan = ivfflatrescan,
|
|
||||||
.amgettuple = ivfflatgettuple,
|
|
||||||
.amgetbitmap = NULL,
|
|
||||||
.amendscan = ivfflatendscan,
|
|
||||||
.ammarkpos = NULL,
|
|
||||||
.amrestrpos = NULL,
|
|
||||||
.amestimateparallelscan = NULL,
|
|
||||||
.aminitparallelscan = NULL,
|
|
||||||
.amparallelrescan = NULL,
|
|
||||||
.amtranslatestrategy = NULL,
|
|
||||||
.amtranslatecmptype = NULL,
|
|
||||||
};
|
|
||||||
|
|
||||||
PG_RETURN_POINTER(&amroutine);
|
|
||||||
#else
|
|
||||||
IndexAmRoutine *amroutine = makeNode(IndexAmRoutine);
|
IndexAmRoutine *amroutine = makeNode(IndexAmRoutine);
|
||||||
|
|
||||||
amroutine->amstrategies = 0;
|
amroutine->amstrategies = 0;
|
||||||
@@ -249,11 +169,6 @@ 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;
|
||||||
@@ -286,9 +201,6 @@ 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;
|
||||||
@@ -309,11 +221,5 @@ 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);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,15 +9,10 @@
|
|||||||
#include "lib/pairingheap.h"
|
#include "lib/pairingheap.h"
|
||||||
#include "nodes/execnodes.h"
|
#include "nodes/execnodes.h"
|
||||||
#include "port.h" /* for random() */
|
#include "port.h" /* for random() */
|
||||||
#include "storage/condition_variable.h"
|
|
||||||
#include "utils/sampling.h"
|
#include "utils/sampling.h"
|
||||||
#include "utils/tuplesort.h"
|
#include "utils/tuplesort.h"
|
||||||
#include "vector.h"
|
#include "vector.h"
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 160000
|
|
||||||
#include "varatt.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 150000
|
#if PG_VERSION_NUM >= 150000
|
||||||
#include "common/pg_prng.h"
|
#include "common/pg_prng.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -26,10 +21,6 @@
|
|||||||
#include "portability/instr_time.h"
|
#include "portability/instr_time.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 190000
|
|
||||||
typedef Pointer Item;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define IVFFLAT_MAX_DIM 2000
|
#define IVFFLAT_MAX_DIM 2000
|
||||||
|
|
||||||
/* Support functions */
|
/* Support functions */
|
||||||
@@ -82,23 +73,13 @@ typedef Pointer Item;
|
|||||||
#if PG_VERSION_NUM >= 150000
|
#if PG_VERSION_NUM >= 150000
|
||||||
#define RandomDouble() pg_prng_double(&pg_global_prng_state)
|
#define RandomDouble() pg_prng_double(&pg_global_prng_state)
|
||||||
#define RandomInt() pg_prng_uint32(&pg_global_prng_state)
|
#define RandomInt() pg_prng_uint32(&pg_global_prng_state)
|
||||||
#define SeedRandom(seed) pg_prng_seed(&pg_global_prng_state, seed)
|
|
||||||
#else
|
#else
|
||||||
#define RandomDouble() (((double) random()) / MAX_RANDOM_VALUE)
|
#define RandomDouble() (((double) random()) / MAX_RANDOM_VALUE)
|
||||||
#define RandomInt() random()
|
#define RandomInt() random()
|
||||||
#define SeedRandom(seed) srandom(seed)
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Variables */
|
/* Variables */
|
||||||
extern int ivfflat_probes;
|
extern int ivfflat_probes;
|
||||||
extern int ivfflat_iterative_scan;
|
|
||||||
extern int ivfflat_max_probes;
|
|
||||||
|
|
||||||
typedef enum IvfflatIterativeScanMode
|
|
||||||
{
|
|
||||||
IVFFLAT_ITERATIVE_SCAN_OFF,
|
|
||||||
IVFFLAT_ITERATIVE_SCAN_RELAXED
|
|
||||||
} IvfflatIterativeScanMode;
|
|
||||||
|
|
||||||
typedef struct VectorArrayData
|
typedef struct VectorArrayData
|
||||||
{
|
{
|
||||||
@@ -184,7 +165,6 @@ typedef struct IvfflatBuildState
|
|||||||
Relation index;
|
Relation index;
|
||||||
IndexInfo *indexInfo;
|
IndexInfo *indexInfo;
|
||||||
const IvfflatTypeInfo *typeInfo;
|
const IvfflatTypeInfo *typeInfo;
|
||||||
TupleDesc tupdesc;
|
|
||||||
|
|
||||||
/* Settings */
|
/* Settings */
|
||||||
int dimensions;
|
int dimensions;
|
||||||
@@ -204,7 +184,6 @@ typedef struct IvfflatBuildState
|
|||||||
VectorArray samples;
|
VectorArray samples;
|
||||||
VectorArray centers;
|
VectorArray centers;
|
||||||
ListInfo *listInfo;
|
ListInfo *listInfo;
|
||||||
Size itemsize;
|
|
||||||
|
|
||||||
#ifdef IVFFLAT_KMEANS_DEBUG
|
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||||
double inertia;
|
double inertia;
|
||||||
@@ -215,16 +194,14 @@ typedef struct IvfflatBuildState
|
|||||||
/* Sampling */
|
/* Sampling */
|
||||||
BlockSamplerData bs;
|
BlockSamplerData bs;
|
||||||
ReservoirStateData rstate;
|
ReservoirStateData rstate;
|
||||||
double samplerows;
|
int rowstoskip;
|
||||||
double rowstoskip;
|
|
||||||
|
|
||||||
/* Sorting */
|
/* Sorting */
|
||||||
Tuplesortstate *sortstate;
|
Tuplesortstate *sortstate;
|
||||||
TupleDesc sortdesc;
|
TupleDesc tupdesc;
|
||||||
TupleTableSlot *slot;
|
TupleTableSlot *slot;
|
||||||
|
|
||||||
/* Memory */
|
/* Memory */
|
||||||
Size memoryUsed;
|
|
||||||
MemoryContext tmpCtx;
|
MemoryContext tmpCtx;
|
||||||
|
|
||||||
/* Parallel builds */
|
/* Parallel builds */
|
||||||
@@ -270,11 +247,8 @@ typedef struct IvfflatScanOpaqueData
|
|||||||
{
|
{
|
||||||
const IvfflatTypeInfo *typeInfo;
|
const IvfflatTypeInfo *typeInfo;
|
||||||
int probes;
|
int probes;
|
||||||
int maxProbes;
|
|
||||||
int dimensions;
|
int dimensions;
|
||||||
bool first;
|
bool first;
|
||||||
Datum value;
|
|
||||||
MemoryContext tmpCtx;
|
|
||||||
|
|
||||||
/* Sorting */
|
/* Sorting */
|
||||||
Tuplesortstate *sortstate;
|
Tuplesortstate *sortstate;
|
||||||
@@ -291,9 +265,7 @@ typedef struct IvfflatScanOpaqueData
|
|||||||
|
|
||||||
/* Lists */
|
/* Lists */
|
||||||
pairingheap *listQueue;
|
pairingheap *listQueue;
|
||||||
BlockNumber *listPages;
|
IvfflatScanList lists[FLEXIBLE_ARRAY_MEMBER]; /* must come last */
|
||||||
int listIndex;
|
|
||||||
IvfflatScanList *lists;
|
|
||||||
} IvfflatScanOpaqueData;
|
} IvfflatScanOpaqueData;
|
||||||
|
|
||||||
typedef IvfflatScanOpaqueData * IvfflatScanOpaque;
|
typedef IvfflatScanOpaqueData * IvfflatScanOpaque;
|
||||||
@@ -305,32 +277,22 @@ typedef IvfflatScanOpaqueData * IvfflatScanOpaque;
|
|||||||
static inline Pointer
|
static inline Pointer
|
||||||
VectorArrayGet(VectorArray arr, int offset)
|
VectorArrayGet(VectorArray arr, int offset)
|
||||||
{
|
{
|
||||||
if (offset >= arr->maxlen)
|
|
||||||
elog(ERROR, "safety check failed");
|
|
||||||
|
|
||||||
return ((char *) arr->items) + (offset * arr->itemsize);
|
return ((char *) arr->items) + (offset * arr->itemsize);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
VectorArraySet(VectorArray arr, int offset, Pointer val)
|
VectorArraySet(VectorArray arr, int offset, Pointer val)
|
||||||
{
|
{
|
||||||
Size size = VARSIZE_ANY(val);
|
memcpy(VectorArrayGet(arr, offset), val, VARSIZE_ANY(val));
|
||||||
|
|
||||||
if (size > arr->itemsize)
|
|
||||||
elog(ERROR, "safety check failed");
|
|
||||||
|
|
||||||
memcpy(VectorArrayGet(arr, offset), val, size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Methods */
|
/* Methods */
|
||||||
VectorArray VectorArrayInit(int maxlen, int dimensions, Size itemsize);
|
VectorArray VectorArrayInit(int maxlen, int dimensions, Size itemsize);
|
||||||
void VectorArrayFree(VectorArray arr);
|
void VectorArrayFree(VectorArray arr);
|
||||||
void IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers, const IvfflatTypeInfo * typeInfo, Size memoryUsed);
|
void IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers, const IvfflatTypeInfo * typeInfo);
|
||||||
FmgrInfo *IvfflatOptionalProcInfo(Relation index, uint16 procnum);
|
FmgrInfo *IvfflatOptionalProcInfo(Relation index, uint16 procnum);
|
||||||
Datum IvfflatNormValue(const IvfflatTypeInfo * typeInfo, Oid collation, Datum value);
|
Datum IvfflatNormValue(const IvfflatTypeInfo * typeInfo, Oid collation, Datum value);
|
||||||
bool IvfflatCheckNorm(FmgrInfo *procinfo, Oid collation, Datum value);
|
bool IvfflatCheckNorm(FmgrInfo *procinfo, Oid collation, Datum value);
|
||||||
void IvfflatNormVectors(const IvfflatTypeInfo * typeInfo, Oid collation, VectorArray arr, MemoryContext tmpCtx);
|
|
||||||
void IvfflatCheckMemoryUsage(Size totalSize);
|
|
||||||
int IvfflatGetLists(Relation index);
|
int IvfflatGetLists(Relation index);
|
||||||
void IvfflatGetMetaPageInfo(Relation index, int *lists, int *dimensions);
|
void IvfflatGetMetaPageInfo(Relation index, int *lists, int *dimensions);
|
||||||
void IvfflatUpdateList(Relation index, ListInfo listInfo, BlockNumber insertPage, BlockNumber originalInsertPage, BlockNumber startPage, ForkNumber forkNum);
|
void IvfflatUpdateList(Relation index, ListInfo listInfo, BlockNumber insertPage, BlockNumber originalInsertPage, BlockNumber startPage, ForkNumber forkNum);
|
||||||
|
|||||||
@@ -2,16 +2,11 @@
|
|||||||
|
|
||||||
#include <float.h>
|
#include <float.h>
|
||||||
|
|
||||||
#include "access/genam.h"
|
|
||||||
#include "access/generic_xlog.h"
|
#include "access/generic_xlog.h"
|
||||||
#include "access/itup.h"
|
|
||||||
#include "fmgr.h"
|
|
||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "nodes/execnodes.h"
|
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
#include "storage/lmgr.h"
|
#include "storage/lmgr.h"
|
||||||
#include "utils/memutils.h"
|
#include "utils/memutils.h"
|
||||||
#include "utils/rel.h"
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Find the list that minimizes the distance function
|
* Find the list that minimizes the distance function
|
||||||
@@ -70,7 +65,7 @@ FindInsertPage(Relation index, Datum *values, BlockNumber *insertPage, ListInfo
|
|||||||
* Insert a tuple into the index
|
* Insert a tuple into the index
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid)
|
InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel)
|
||||||
{
|
{
|
||||||
const IvfflatTypeInfo *typeInfo = IvfflatGetTypeInfo(index);
|
const IvfflatTypeInfo *typeInfo = IvfflatGetTypeInfo(index);
|
||||||
IndexTuple itup;
|
IndexTuple itup;
|
||||||
@@ -103,7 +98,7 @@ InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid)
|
|||||||
IvfflatGetMetaPageInfo(index, NULL, NULL);
|
IvfflatGetMetaPageInfo(index, NULL, NULL);
|
||||||
|
|
||||||
/* Find the insert page - sets the page and list info */
|
/* Find the insert page - sets the page and list info */
|
||||||
FindInsertPage(index, &value, &insertPage, &listInfo);
|
FindInsertPage(index, values, &insertPage, &listInfo);
|
||||||
Assert(BlockNumberIsValid(insertPage));
|
Assert(BlockNumberIsValid(insertPage));
|
||||||
originalInsertPage = insertPage;
|
originalInsertPage = insertPage;
|
||||||
|
|
||||||
@@ -209,7 +204,7 @@ ivfflatinsert(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid,
|
|||||||
oldCtx = MemoryContextSwitchTo(insertCtx);
|
oldCtx = MemoryContextSwitchTo(insertCtx);
|
||||||
|
|
||||||
/* Insert tuple */
|
/* Insert tuple */
|
||||||
InsertTuple(index, values, isnull, heap_tid);
|
InsertTuple(index, values, isnull, heap_tid, heap);
|
||||||
|
|
||||||
/* Delete memory context */
|
/* Delete memory context */
|
||||||
MemoryContextSwitchTo(oldCtx);
|
MemoryContextSwitchTo(oldCtx);
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include <float.h>
|
#include <float.h>
|
||||||
#include <limits.h>
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#include "access/genam.h"
|
#include "bitvec.h"
|
||||||
#include "fmgr.h"
|
#include "halfutils.h"
|
||||||
|
#include "halfvec.h"
|
||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
|
#include "utils/builtins.h"
|
||||||
|
#include "utils/datum.h"
|
||||||
#include "utils/memutils.h"
|
#include "utils/memutils.h"
|
||||||
#include "utils/relcache.h"
|
#include "vector.h"
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 160000
|
|
||||||
#include "varatt.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Initialize with kmeans++
|
* Initialize with kmeans++
|
||||||
@@ -99,8 +97,22 @@ NormCenters(const IvfflatTypeInfo * typeInfo, Oid collation, VectorArray centers
|
|||||||
MemoryContext normCtx = AllocSetContextCreate(CurrentMemoryContext,
|
MemoryContext normCtx = AllocSetContextCreate(CurrentMemoryContext,
|
||||||
"Ivfflat norm temporary context",
|
"Ivfflat norm temporary context",
|
||||||
ALLOCSET_DEFAULT_SIZES);
|
ALLOCSET_DEFAULT_SIZES);
|
||||||
|
MemoryContext oldCtx = MemoryContextSwitchTo(normCtx);
|
||||||
|
|
||||||
IvfflatNormVectors(typeInfo, collation, centers, normCtx);
|
for (int j = 0; j < centers->length; j++)
|
||||||
|
{
|
||||||
|
Datum center = PointerGetDatum(VectorArrayGet(centers, j));
|
||||||
|
Datum newCenter = IvfflatNormValue(typeInfo, collation, center);
|
||||||
|
Size size = VARSIZE_ANY(DatumGetPointer(newCenter));
|
||||||
|
|
||||||
|
if (size > centers->itemsize)
|
||||||
|
elog(ERROR, "safety check failed");
|
||||||
|
|
||||||
|
memcpy(DatumGetPointer(center), DatumGetPointer(newCenter), size);
|
||||||
|
MemoryContextReset(normCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryContextSwitchTo(oldCtx);
|
||||||
MemoryContextDelete(normCtx);
|
MemoryContextDelete(normCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +256,7 @@ ComputeNewCenters(VectorArray samples, float *agg, VectorArray newCenters, int *
|
|||||||
* https://www.aaai.org/Papers/ICML/2003/ICML03-022.pdf
|
* https://www.aaai.org/Papers/ICML/2003/ICML03-022.pdf
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
ElkanKmeans(Relation index, VectorArray samples, VectorArray centers, const IvfflatTypeInfo * typeInfo, Size memoryUsed)
|
ElkanKmeans(Relation index, VectorArray samples, VectorArray centers, const IvfflatTypeInfo * typeInfo)
|
||||||
{
|
{
|
||||||
FmgrInfo *procinfo;
|
FmgrInfo *procinfo;
|
||||||
FmgrInfo *normprocinfo;
|
FmgrInfo *normprocinfo;
|
||||||
@@ -263,6 +275,8 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers, const Ivff
|
|||||||
float *newcdist;
|
float *newcdist;
|
||||||
|
|
||||||
/* Calculate allocation sizes */
|
/* Calculate allocation sizes */
|
||||||
|
Size samplesSize = VECTOR_ARRAY_SIZE(samples->maxlen, samples->itemsize);
|
||||||
|
Size centersSize = VECTOR_ARRAY_SIZE(centers->maxlen, centers->itemsize);
|
||||||
Size newCentersSize = VECTOR_ARRAY_SIZE(numCenters, centers->itemsize);
|
Size newCentersSize = VECTOR_ARRAY_SIZE(numCenters, centers->itemsize);
|
||||||
Size aggSize = sizeof(float) * (int64) numCenters * dimensions;
|
Size aggSize = sizeof(float) * (int64) numCenters * dimensions;
|
||||||
Size centerCountsSize = sizeof(int) * numCenters;
|
Size centerCountsSize = sizeof(int) * numCenters;
|
||||||
@@ -274,13 +288,18 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers, const Ivff
|
|||||||
Size newcdistSize = sizeof(float) * numCenters;
|
Size newcdistSize = sizeof(float) * numCenters;
|
||||||
|
|
||||||
/* Calculate total size */
|
/* Calculate total size */
|
||||||
Size totalSize = memoryUsed + newCentersSize + aggSize + centerCountsSize + closestCentersSize + lowerBoundSize + upperBoundSize + sSize + halfcdistSize + newcdistSize;
|
Size totalSize = samplesSize + centersSize + newCentersSize + aggSize + centerCountsSize + closestCentersSize + lowerBoundSize + upperBoundSize + sSize + halfcdistSize + newcdistSize;
|
||||||
|
|
||||||
/* Check memory requirements */
|
/* Check memory requirements */
|
||||||
IvfflatCheckMemoryUsage(totalSize);
|
/* Add one to error message to ceil */
|
||||||
|
if (totalSize > (Size) maintenance_work_mem * 1024L)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
||||||
|
errmsg("memory required is %zu MB, maintenance_work_mem is %d MB",
|
||||||
|
totalSize / (1024 * 1024) + 1, maintenance_work_mem / 1024)));
|
||||||
|
|
||||||
/* Ensure indexing does not overflow */
|
/* Ensure indexing does not overflow */
|
||||||
if (numCenters > INT_MAX / numCenters)
|
if (numCenters * numCenters > INT_MAX)
|
||||||
elog(ERROR, "Indexing overflow detected. Please report a bug.");
|
elog(ERROR, "Indexing overflow detected. Please report a bug.");
|
||||||
|
|
||||||
/* Set support functions */
|
/* Set support functions */
|
||||||
@@ -541,7 +560,7 @@ CheckCenters(Relation index, VectorArray centers, const IvfflatTypeInfo * typeIn
|
|||||||
* We use spherical k-means for inner product and cosine
|
* We use spherical k-means for inner product and cosine
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers, const IvfflatTypeInfo * typeInfo, Size memoryUsed)
|
IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers, const IvfflatTypeInfo * typeInfo)
|
||||||
{
|
{
|
||||||
MemoryContext kmeansCtx = AllocSetContextCreate(CurrentMemoryContext,
|
MemoryContext kmeansCtx = AllocSetContextCreate(CurrentMemoryContext,
|
||||||
"Ivfflat kmeans temporary context",
|
"Ivfflat kmeans temporary context",
|
||||||
@@ -551,7 +570,7 @@ IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers, const Iv
|
|||||||
if (samples->length == 0)
|
if (samples->length == 0)
|
||||||
RandomCenters(index, centers, typeInfo);
|
RandomCenters(index, centers, typeInfo);
|
||||||
else
|
else
|
||||||
ElkanKmeans(index, samples, centers, typeInfo, memoryUsed);
|
ElkanKmeans(index, samples, centers, typeInfo);
|
||||||
|
|
||||||
CheckCenters(index, centers, typeInfo);
|
CheckCenters(index, centers, typeInfo);
|
||||||
|
|
||||||
|
|||||||
117
src/ivfscan.c
117
src/ivfscan.c
@@ -2,25 +2,17 @@
|
|||||||
|
|
||||||
#include <float.h>
|
#include <float.h>
|
||||||
|
|
||||||
#include "access/genam.h"
|
|
||||||
#include "access/itup.h"
|
|
||||||
#include "access/relscan.h"
|
#include "access/relscan.h"
|
||||||
#include "access/tupdesc.h"
|
|
||||||
#include "catalog/pg_operator_d.h"
|
#include "catalog/pg_operator_d.h"
|
||||||
#include "catalog/pg_type_d.h"
|
#include "catalog/pg_type_d.h"
|
||||||
#include "fmgr.h"
|
|
||||||
#include "lib/pairingheap.h"
|
#include "lib/pairingheap.h"
|
||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
#include "pgstat.h"
|
#include "pgstat.h"
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
#include "utils/memutils.h"
|
|
||||||
#include "utils/rel.h"
|
|
||||||
#include "utils/snapmgr.h"
|
|
||||||
#include "utils/tuplesort.h"
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 160000
|
#ifdef IVFFLAT_MEMORY
|
||||||
#include "varatt.h"
|
#include "utils/memutils.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define GetScanList(ptr) pairingheap_container(IvfflatScanList, ph_node, ptr)
|
#define GetScanList(ptr) pairingheap_container(IvfflatScanList, ph_node, ptr)
|
||||||
@@ -73,7 +65,7 @@ GetScanLists(IndexScanDesc scan, Datum value)
|
|||||||
/* Use procinfo from the index instead of scan key for performance */
|
/* Use procinfo from the index instead of scan key for performance */
|
||||||
distance = DatumGetFloat8(so->distfunc(so->procinfo, so->collation, PointerGetDatum(&list->center), value));
|
distance = DatumGetFloat8(so->distfunc(so->procinfo, so->collation, PointerGetDatum(&list->center), value));
|
||||||
|
|
||||||
if (listCount < so->maxProbes)
|
if (listCount < so->probes)
|
||||||
{
|
{
|
||||||
IvfflatScanList *scanlist;
|
IvfflatScanList *scanlist;
|
||||||
|
|
||||||
@@ -86,7 +78,7 @@ GetScanLists(IndexScanDesc scan, Datum value)
|
|||||||
pairingheap_add(so->listQueue, &scanlist->ph_node);
|
pairingheap_add(so->listQueue, &scanlist->ph_node);
|
||||||
|
|
||||||
/* Calculate max distance */
|
/* Calculate max distance */
|
||||||
if (listCount == so->maxProbes)
|
if (listCount == so->probes)
|
||||||
maxDistance = GetScanList(pairingheap_first(so->listQueue))->distance;
|
maxDistance = GetScanList(pairingheap_first(so->listQueue))->distance;
|
||||||
}
|
}
|
||||||
else if (distance < maxDistance)
|
else if (distance < maxDistance)
|
||||||
@@ -110,11 +102,6 @@ GetScanLists(IndexScanDesc scan, Datum value)
|
|||||||
|
|
||||||
UnlockReleaseBuffer(cbuf);
|
UnlockReleaseBuffer(cbuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = listCount - 1; i >= 0; i--)
|
|
||||||
so->listPages[i] = GetScanList(pairingheap_remove_first(so->listQueue))->startPage;
|
|
||||||
|
|
||||||
Assert(pairingheap_is_empty(so->listQueue));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -125,15 +112,13 @@ 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;
|
|
||||||
|
|
||||||
tuplesort_reset(so->sortstate);
|
|
||||||
|
|
||||||
/* Search closest probes lists */
|
/* Search closest probes lists */
|
||||||
while (so->listIndex < so->maxProbes && (++batchProbes) <= so->probes)
|
while (!pairingheap_is_empty(so->listQueue))
|
||||||
{
|
{
|
||||||
BlockNumber searchPage = so->listPages[so->listIndex++];
|
BlockNumber searchPage = GetScanList(pairingheap_remove_first(so->listQueue))->startPage;
|
||||||
|
|
||||||
/* Search all entry pages for list */
|
/* Search all entry pages for list */
|
||||||
while (BlockNumberIsValid(searchPage))
|
while (BlockNumberIsValid(searchPage))
|
||||||
@@ -171,6 +156,8 @@ 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;
|
||||||
@@ -179,11 +166,13 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tuplesort_performsort(so->sortstate);
|
if (tuples < 100)
|
||||||
|
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.")));
|
||||||
|
|
||||||
#if defined(IVFFLAT_MEMORY)
|
tuplesort_performsort(so->sortstate);
|
||||||
elog(INFO, "memory: %zu MB", MemoryContextMemAllocated(CurrentMemoryContext, true) / (1024 * 1024));
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -220,13 +209,7 @@ GetScanValue(IndexScanDesc scan)
|
|||||||
|
|
||||||
/* Normalize if needed */
|
/* Normalize if needed */
|
||||||
if (so->normprocinfo != NULL)
|
if (so->normprocinfo != NULL)
|
||||||
{
|
|
||||||
MemoryContext oldCtx = MemoryContextSwitchTo(so->tmpCtx);
|
|
||||||
|
|
||||||
value = IvfflatNormValue(so->typeInfo, so->collation, value);
|
value = IvfflatNormValue(so->typeInfo, so->collation, value);
|
||||||
|
|
||||||
MemoryContextSwitchTo(oldCtx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
@@ -257,30 +240,19 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
|||||||
int lists;
|
int lists;
|
||||||
int dimensions;
|
int dimensions;
|
||||||
int probes = ivfflat_probes;
|
int probes = ivfflat_probes;
|
||||||
int maxProbes;
|
|
||||||
MemoryContext oldCtx;
|
|
||||||
|
|
||||||
scan = RelationGetIndexScan(index, nkeys, norderbys);
|
scan = RelationGetIndexScan(index, nkeys, norderbys);
|
||||||
|
|
||||||
/* Get lists and dimensions from metapage */
|
/* Get lists and dimensions from metapage */
|
||||||
IvfflatGetMetaPageInfo(index, &lists, &dimensions);
|
IvfflatGetMetaPageInfo(index, &lists, &dimensions);
|
||||||
|
|
||||||
if (ivfflat_iterative_scan != IVFFLAT_ITERATIVE_SCAN_OFF)
|
|
||||||
maxProbes = Max(ivfflat_max_probes, probes);
|
|
||||||
else
|
|
||||||
maxProbes = probes;
|
|
||||||
|
|
||||||
if (probes > lists)
|
if (probes > lists)
|
||||||
probes = lists;
|
probes = lists;
|
||||||
|
|
||||||
if (maxProbes > lists)
|
so = (IvfflatScanOpaque) palloc(offsetof(IvfflatScanOpaqueData, lists) + probes * sizeof(IvfflatScanList));
|
||||||
maxProbes = lists;
|
|
||||||
|
|
||||||
so = (IvfflatScanOpaque) palloc(sizeof(IvfflatScanOpaqueData));
|
|
||||||
so->typeInfo = IvfflatGetTypeInfo(index);
|
so->typeInfo = IvfflatGetTypeInfo(index);
|
||||||
so->first = true;
|
so->first = true;
|
||||||
so->probes = probes;
|
so->probes = probes;
|
||||||
so->maxProbes = maxProbes;
|
|
||||||
so->dimensions = dimensions;
|
so->dimensions = dimensions;
|
||||||
|
|
||||||
/* Set support functions */
|
/* Set support functions */
|
||||||
@@ -288,19 +260,10 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
|||||||
so->normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC);
|
so->normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC);
|
||||||
so->collation = index->rd_indcollation[0];
|
so->collation = index->rd_indcollation[0];
|
||||||
|
|
||||||
so->tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
|
|
||||||
"Ivfflat scan temporary context",
|
|
||||||
ALLOCSET_DEFAULT_SIZES);
|
|
||||||
|
|
||||||
oldCtx = MemoryContextSwitchTo(so->tmpCtx);
|
|
||||||
|
|
||||||
/* Create tuple description for sorting */
|
/* Create tuple description for sorting */
|
||||||
so->tupdesc = CreateTemplateTupleDesc(2);
|
so->tupdesc = CreateTemplateTupleDesc(2);
|
||||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 1, "distance", FLOAT8OID, -1, 0);
|
TupleDescInitEntry(so->tupdesc, (AttrNumber) 1, "distance", FLOAT8OID, -1, 0);
|
||||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 2, "heaptid", TIDOID, -1, 0);
|
TupleDescInitEntry(so->tupdesc, (AttrNumber) 2, "heaptid", TIDOID, -1, 0);
|
||||||
#if PG_VERSION_NUM >= 190000
|
|
||||||
TupleDescFinalize(so->tupdesc);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Prep sort */
|
/* Prep sort */
|
||||||
so->sortstate = InitScanSortState(so->tupdesc);
|
so->sortstate = InitScanSortState(so->tupdesc);
|
||||||
@@ -317,11 +280,6 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
|||||||
so->bas = GetAccessStrategy(BAS_BULKREAD);
|
so->bas = GetAccessStrategy(BAS_BULKREAD);
|
||||||
|
|
||||||
so->listQueue = pairingheap_allocate(CompareLists, scan);
|
so->listQueue = pairingheap_allocate(CompareLists, scan);
|
||||||
so->listPages = palloc(maxProbes * sizeof(BlockNumber));
|
|
||||||
so->listIndex = 0;
|
|
||||||
so->lists = palloc(maxProbes * sizeof(IvfflatScanList));
|
|
||||||
|
|
||||||
MemoryContextSwitchTo(oldCtx);
|
|
||||||
|
|
||||||
scan->opaque = so;
|
scan->opaque = so;
|
||||||
|
|
||||||
@@ -336,9 +294,11 @@ ivfflatrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int
|
|||||||
{
|
{
|
||||||
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
||||||
|
|
||||||
|
if (!so->first)
|
||||||
|
tuplesort_reset(so->sortstate);
|
||||||
|
|
||||||
so->first = true;
|
so->first = true;
|
||||||
pairingheap_reset(so->listQueue);
|
pairingheap_reset(so->listQueue);
|
||||||
so->listIndex = 0;
|
|
||||||
|
|
||||||
if (keys && scan->numberOfKeys > 0)
|
if (keys && scan->numberOfKeys > 0)
|
||||||
memmove(scan->keyData, keys, scan->numberOfKeys * sizeof(ScanKeyData));
|
memmove(scan->keyData, keys, scan->numberOfKeys * sizeof(ScanKeyData));
|
||||||
@@ -354,8 +314,6 @@ bool
|
|||||||
ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
|
ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
|
||||||
{
|
{
|
||||||
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
||||||
ItemPointer heaptid;
|
|
||||||
bool isnull;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Index can be used to scan backward, but Postgres doesn't support
|
* Index can be used to scan backward, but Postgres doesn't support
|
||||||
@@ -369,10 +327,6 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
|
|
||||||
/* Count index scan for stats */
|
/* Count index scan for stats */
|
||||||
pgstat_count_index_scan(scan->indexRelation);
|
pgstat_count_index_scan(scan->indexRelation);
|
||||||
#if PG_VERSION_NUM >= 180000
|
|
||||||
if (scan->instrument)
|
|
||||||
scan->instrument->nsearches++;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Safety check */
|
/* Safety check */
|
||||||
if (scan->orderByData == NULL)
|
if (scan->orderByData == NULL)
|
||||||
@@ -387,23 +341,28 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
IvfflatBench("GetScanLists", GetScanLists(scan, value));
|
IvfflatBench("GetScanLists", GetScanLists(scan, value));
|
||||||
IvfflatBench("GetScanItems", GetScanItems(scan, value));
|
IvfflatBench("GetScanItems", GetScanItems(scan, value));
|
||||||
so->first = false;
|
so->first = false;
|
||||||
so->value = value;
|
|
||||||
|
#if defined(IVFFLAT_MEMORY)
|
||||||
|
elog(INFO, "memory: %zu MB", MemoryContextMemAllocated(CurrentMemoryContext, true) / (1024 * 1024));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Clean up if we allocated a new value */
|
||||||
|
if (value != scan->orderByData->sk_argument)
|
||||||
|
pfree(DatumGetPointer(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!tuplesort_gettupleslot(so->sortstate, true, false, so->mslot, NULL))
|
if (tuplesort_gettupleslot(so->sortstate, true, false, so->mslot, NULL))
|
||||||
{
|
{
|
||||||
if (so->listIndex == so->maxProbes)
|
bool isnull;
|
||||||
return false;
|
ItemPointer heaptid = (ItemPointer) DatumGetPointer(slot_getattr(so->mslot, 2, &isnull));
|
||||||
|
|
||||||
IvfflatBench("GetScanItems", GetScanItems(scan, so->value));
|
scan->xs_heaptid = *heaptid;
|
||||||
|
scan->xs_recheck = false;
|
||||||
|
scan->xs_recheckorderby = false;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
heaptid = (ItemPointer) DatumGetPointer(slot_getattr(so->mslot, 2, &isnull));
|
return false;
|
||||||
|
|
||||||
scan->xs_heaptid = *heaptid;
|
|
||||||
scan->xs_recheck = false;
|
|
||||||
scan->xs_recheckorderby = false;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -414,10 +373,12 @@ ivfflatendscan(IndexScanDesc scan)
|
|||||||
{
|
{
|
||||||
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
||||||
|
|
||||||
/* Free any temporary files */
|
pairingheap_free(so->listQueue);
|
||||||
tuplesort_end(so->sortstate);
|
tuplesort_end(so->sortstate);
|
||||||
|
FreeAccessStrategy(so->bas);
|
||||||
|
FreeTupleDesc(so->tupdesc);
|
||||||
|
|
||||||
MemoryContextDelete(so->tmpCtx);
|
/* TODO Free vslot and mslot without freeing TupleDesc */
|
||||||
|
|
||||||
pfree(so);
|
pfree(so);
|
||||||
scan->opaque = NULL;
|
scan->opaque = NULL;
|
||||||
|
|||||||
@@ -1,21 +1,13 @@
|
|||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include "access/genam.h"
|
|
||||||
#include "access/generic_xlog.h"
|
#include "access/generic_xlog.h"
|
||||||
|
#include "bitvec.h"
|
||||||
|
#include "catalog/pg_type.h"
|
||||||
#include "fmgr.h"
|
#include "fmgr.h"
|
||||||
#include "halfutils.h"
|
#include "halfutils.h"
|
||||||
#include "halfvec.h"
|
#include "halfvec.h"
|
||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "miscadmin.h"
|
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
#include "utils/memutils.h"
|
|
||||||
#include "utils/relcache.h"
|
|
||||||
#include "utils/varbit.h"
|
|
||||||
#include "vector.h"
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 160000
|
|
||||||
#include "varatt.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Allocate a vector array
|
* Allocate a vector array
|
||||||
@@ -90,40 +82,6 @@ IvfflatCheckNorm(FmgrInfo *procinfo, Oid collation, Datum value)
|
|||||||
return DatumGetFloat8(FunctionCall1Coll(procinfo, collation, value)) > 0;
|
return DatumGetFloat8(FunctionCall1Coll(procinfo, collation, value)) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Normalize vectors
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
IvfflatNormVectors(const IvfflatTypeInfo * typeInfo, Oid collation, VectorArray arr, MemoryContext tmpCtx)
|
|
||||||
{
|
|
||||||
MemoryContext oldCtx = MemoryContextSwitchTo(tmpCtx);
|
|
||||||
|
|
||||||
for (int i = 0; i < arr->length; i++)
|
|
||||||
{
|
|
||||||
Datum value = PointerGetDatum(VectorArrayGet(arr, i));
|
|
||||||
Datum newValue = IvfflatNormValue(typeInfo, collation, value);
|
|
||||||
|
|
||||||
VectorArraySet(arr, i, DatumGetPointer(newValue));
|
|
||||||
MemoryContextReset(tmpCtx);
|
|
||||||
}
|
|
||||||
|
|
||||||
MemoryContextSwitchTo(oldCtx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Check memory usage
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
IvfflatCheckMemoryUsage(Size totalSize)
|
|
||||||
{
|
|
||||||
/* Add one to error message to ceil */
|
|
||||||
if (totalSize > maintenance_work_mem * (Size) 1024)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
|
||||||
errmsg("memory required is %zu MB, maintenance_work_mem is %d MB",
|
|
||||||
totalSize / (1024 * 1024) + 1, maintenance_work_mem / 1024)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* New buffer
|
* New buffer
|
||||||
*/
|
*/
|
||||||
@@ -301,8 +259,8 @@ VectorUpdateCenter(Pointer v, int dimensions, float *x)
|
|||||||
SET_VARSIZE(vec, VECTOR_SIZE(dimensions));
|
SET_VARSIZE(vec, VECTOR_SIZE(dimensions));
|
||||||
vec->dim = dimensions;
|
vec->dim = dimensions;
|
||||||
|
|
||||||
for (int i = 0; i < dimensions; i++)
|
for (int k = 0; k < dimensions; k++)
|
||||||
vec->x[i] = x[i];
|
vec->x[k] = x[k];
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -313,8 +271,8 @@ HalfvecUpdateCenter(Pointer v, int dimensions, float *x)
|
|||||||
SET_VARSIZE(vec, HALFVEC_SIZE(dimensions));
|
SET_VARSIZE(vec, HALFVEC_SIZE(dimensions));
|
||||||
vec->dim = dimensions;
|
vec->dim = dimensions;
|
||||||
|
|
||||||
for (int i = 0; i < dimensions; i++)
|
for (int k = 0; k < dimensions; k++)
|
||||||
vec->x[i] = Float4ToHalfUnchecked(x[i]);
|
vec->x[k] = Float4ToHalfUnchecked(x[k]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -326,33 +284,29 @@ BitUpdateCenter(Pointer v, int dimensions, float *x)
|
|||||||
SET_VARSIZE(vec, VARBITTOTALLEN(dimensions));
|
SET_VARSIZE(vec, VARBITTOTALLEN(dimensions));
|
||||||
VARBITLEN(vec) = dimensions;
|
VARBITLEN(vec) = dimensions;
|
||||||
|
|
||||||
for (uint32 i = 0; i < VARBITBYTES(vec); i++)
|
for (uint32 k = 0; k < VARBITBYTES(vec); k++)
|
||||||
nx[i] = 0;
|
nx[k] = 0;
|
||||||
|
|
||||||
for (int i = 0; i < dimensions; i++)
|
for (int k = 0; k < dimensions; k++)
|
||||||
nx[i / 8] |= (x[i] > 0.5 ? 1 : 0) << (7 - (i % 8));
|
nx[k / 8] |= (x[k] > 0.5 ? 1 : 0) << (7 - (k % 8));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
VectorSumCenter(Pointer v, float *x)
|
VectorSumCenter(Pointer v, float *x)
|
||||||
{
|
{
|
||||||
Vector *vec = (Vector *) v;
|
Vector *vec = (Vector *) v;
|
||||||
int dim = vec->dim;
|
|
||||||
|
|
||||||
/* Auto-vectorized */
|
for (int k = 0; k < vec->dim; k++)
|
||||||
for (int i = 0; i < dim; i++)
|
x[k] += vec->x[k];
|
||||||
x[i] += vec->x[i];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
HalfvecSumCenter(Pointer v, float *x)
|
HalfvecSumCenter(Pointer v, float *x)
|
||||||
{
|
{
|
||||||
HalfVector *vec = (HalfVector *) v;
|
HalfVector *vec = (HalfVector *) v;
|
||||||
int dim = vec->dim;
|
|
||||||
|
|
||||||
/* Auto-vectorized on aarch64 */
|
for (int k = 0; k < vec->dim; k++)
|
||||||
for (int i = 0; i < dim; i++)
|
x[k] += HalfToFloat4(vec->x[k]);
|
||||||
x[i] += HalfToFloat4(vec->x[i]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -360,8 +314,8 @@ BitSumCenter(Pointer v, float *x)
|
|||||||
{
|
{
|
||||||
VarBit *vec = (VarBit *) v;
|
VarBit *vec = (VarBit *) v;
|
||||||
|
|
||||||
for (int i = 0; i < VARBITLEN(vec); i++)
|
for (int k = 0; k < VARBITLEN(vec); k++)
|
||||||
x[i] += (float) (((VARBITS(vec)[i / 8]) >> (7 - (i % 8))) & 0x01);
|
x[k] += (float) (((VARBITS(vec)[k / 8]) >> (7 - (k % 8))) & 0x01);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -401,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
|
||||||
@@ -416,4 +370,4 @@ ivfflat_bit_support(PG_FUNCTION_ARGS)
|
|||||||
};
|
};
|
||||||
|
|
||||||
PG_RETURN_POINTER(&typeInfo);
|
PG_RETURN_POINTER(&typeInfo);
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,16 +1,9 @@
|
|||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include "access/genam.h"
|
|
||||||
#include "access/generic_xlog.h"
|
#include "access/generic_xlog.h"
|
||||||
#include "access/itup.h"
|
|
||||||
#include "commands/vacuum.h"
|
#include "commands/vacuum.h"
|
||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
#include "utils/relcache.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
|
||||||
@@ -33,7 +26,7 @@ ivfflatbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
|
|||||||
Page cpage;
|
Page cpage;
|
||||||
OffsetNumber coffno;
|
OffsetNumber coffno;
|
||||||
OffsetNumber cmaxoffno;
|
OffsetNumber cmaxoffno;
|
||||||
BlockNumber listPages[MaxOffsetNumber];
|
BlockNumber startPages[MaxOffsetNumber];
|
||||||
ListInfo listInfo;
|
ListInfo listInfo;
|
||||||
|
|
||||||
cbuf = ReadBuffer(index, blkno);
|
cbuf = ReadBuffer(index, blkno);
|
||||||
@@ -47,7 +40,7 @@ ivfflatbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
|
|||||||
{
|
{
|
||||||
IvfflatList list = (IvfflatList) PageGetItem(cpage, PageGetItemId(cpage, coffno));
|
IvfflatList list = (IvfflatList) PageGetItem(cpage, PageGetItemId(cpage, coffno));
|
||||||
|
|
||||||
listPages[coffno - FirstOffsetNumber] = list->startPage;
|
startPages[coffno - FirstOffsetNumber] = list->startPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
listInfo.blkno = blkno;
|
listInfo.blkno = blkno;
|
||||||
@@ -57,7 +50,7 @@ ivfflatbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
|
|||||||
|
|
||||||
for (coffno = FirstOffsetNumber; coffno <= cmaxoffno; coffno = OffsetNumberNext(coffno))
|
for (coffno = FirstOffsetNumber; coffno <= cmaxoffno; coffno = OffsetNumberNext(coffno))
|
||||||
{
|
{
|
||||||
BlockNumber searchPage = listPages[coffno - FirstOffsetNumber];
|
BlockNumber searchPage = startPages[coffno - FirstOffsetNumber];
|
||||||
BlockNumber insertPage = InvalidBlockNumber;
|
BlockNumber insertPage = InvalidBlockNumber;
|
||||||
|
|
||||||
/* Iterate over entry pages */
|
/* Iterate over entry pages */
|
||||||
|
|||||||
@@ -4,26 +4,23 @@
|
|||||||
#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 "fmgr.h"
|
#include "fmgr.h"
|
||||||
#include "halfutils.h"
|
#include "halfutils.h"
|
||||||
#include "halfvec.h"
|
#include "halfvec.h"
|
||||||
#include "lib/stringinfo.h"
|
|
||||||
#include "libpq/pqformat.h"
|
#include "libpq/pqformat.h"
|
||||||
#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/fmgrprotos.h"
|
|
||||||
#include "utils/lsyscache.h"
|
#include "utils/lsyscache.h"
|
||||||
#include "vector.h"
|
#include "vector.h"
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 160000
|
#if PG_VERSION_NUM >= 120000
|
||||||
#include "varatt.h"
|
#include "common/shortest_dec.h"
|
||||||
#endif
|
#include "utils/float.h"
|
||||||
|
#else
|
||||||
#if PG_VERSION_NUM >= 170000
|
#include <float.h>
|
||||||
#include "parser/scansup.h"
|
#include "utils/builtins.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef struct SparseInputElement
|
typedef struct SparseInputElement
|
||||||
@@ -159,9 +156,9 @@ InitSparseVector(int dim, int nnz)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 170000
|
/*
|
||||||
#define sparsevec_isspace(ch) scanner_isspace(ch)
|
* Check for whitespace, since array_isspace() is static
|
||||||
#else
|
*/
|
||||||
static inline bool
|
static inline bool
|
||||||
sparsevec_isspace(char ch)
|
sparsevec_isspace(char ch)
|
||||||
{
|
{
|
||||||
@@ -174,7 +171,6 @@ sparsevec_isspace(char ch)
|
|||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Compare indices
|
* Compare indices
|
||||||
@@ -182,10 +178,10 @@ sparsevec_isspace(char ch)
|
|||||||
static int
|
static int
|
||||||
CompareIndices(const void *a, const void *b)
|
CompareIndices(const void *a, const void *b)
|
||||||
{
|
{
|
||||||
if (((const SparseInputElement *) a)->index < ((const SparseInputElement *) b)->index)
|
if (((SparseInputElement *) a)->index < ((SparseInputElement *) b)->index)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (((const SparseInputElement *) a)->index > ((const SparseInputElement *) b)->index)
|
if (((SparseInputElement *) a)->index > ((SparseInputElement *) b)->index)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
42
src/vector.c
42
src/vector.c
@@ -16,20 +16,16 @@
|
|||||||
#include "port.h" /* for strtof() */
|
#include "port.h" /* for strtof() */
|
||||||
#include "sparsevec.h"
|
#include "sparsevec.h"
|
||||||
#include "utils/array.h"
|
#include "utils/array.h"
|
||||||
|
#include "utils/builtins.h"
|
||||||
#include "utils/float.h"
|
#include "utils/float.h"
|
||||||
#include "utils/fmgrprotos.h"
|
|
||||||
#include "utils/lsyscache.h"
|
#include "utils/lsyscache.h"
|
||||||
#include "utils/varbit.h"
|
#include "utils/numeric.h"
|
||||||
#include "vector.h"
|
#include "vector.h"
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 160000
|
#if PG_VERSION_NUM >= 160000
|
||||||
#include "varatt.h"
|
#include "varatt.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 170000
|
|
||||||
#include "parser/scansup.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define STATE_DIMS(x) (ARR_DIMS(x)[0] - 1)
|
#define STATE_DIMS(x) (ARR_DIMS(x)[0] - 1)
|
||||||
#define CreateStateDatums(dim) palloc(sizeof(Datum) * (dim + 1))
|
#define CreateStateDatums(dim) palloc(sizeof(Datum) * (dim + 1))
|
||||||
|
|
||||||
@@ -39,11 +35,7 @@
|
|||||||
#define VECTOR_TARGET_CLONES
|
#define VECTOR_TARGET_CLONES
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 180000
|
|
||||||
PG_MODULE_MAGIC_EXT(.name = "vector", .version = "0.8.4");
|
|
||||||
#else
|
|
||||||
PG_MODULE_MAGIC;
|
PG_MODULE_MAGIC;
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Initialize index options and variables
|
* Initialize index options and variables
|
||||||
@@ -133,9 +125,9 @@ InitVector(int dim)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 170000
|
/*
|
||||||
#define vector_isspace(ch) scanner_isspace(ch)
|
* Check for whitespace, since array_isspace() is static
|
||||||
#else
|
*/
|
||||||
static inline bool
|
static inline bool
|
||||||
vector_isspace(char ch)
|
vector_isspace(char ch)
|
||||||
{
|
{
|
||||||
@@ -148,7 +140,6 @@ vector_isspace(char ch)
|
|||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check state array
|
* Check state array
|
||||||
@@ -929,13 +920,11 @@ vector_concat(PG_FUNCTION_ARGS)
|
|||||||
CheckDim(dim);
|
CheckDim(dim);
|
||||||
result = InitVector(dim);
|
result = InitVector(dim);
|
||||||
|
|
||||||
/* Auto-vectorized */
|
for (int i = 0; i < a->dim; i++)
|
||||||
for (int i = 0, imax = a->dim; i < imax; i++)
|
|
||||||
result->x[i] = a->x[i];
|
result->x[i] = a->x[i];
|
||||||
|
|
||||||
/* Auto-vectorized */
|
for (int i = 0; i < b->dim; i++)
|
||||||
for (int i = 0, imax = b->dim, start = a->dim; i < imax; i++)
|
result->x[i + a->dim] = b->x[i];
|
||||||
result->x[i + start] = b->x[i];
|
|
||||||
|
|
||||||
PG_RETURN_POINTER(result);
|
PG_RETURN_POINTER(result);
|
||||||
}
|
}
|
||||||
@@ -951,21 +940,8 @@ binary_quantize(PG_FUNCTION_ARGS)
|
|||||||
float *ax = a->x;
|
float *ax = a->x;
|
||||||
VarBit *result = InitBitVector(a->dim);
|
VarBit *result = InitBitVector(a->dim);
|
||||||
unsigned char *rx = VARBITS(result);
|
unsigned char *rx = VARBITS(result);
|
||||||
int i = 0;
|
|
||||||
int count = (a->dim / 8) * 8;
|
|
||||||
|
|
||||||
/* Auto-vectorized */
|
for (int i = 0; i < a->dim; i++)
|
||||||
for (; i < count; i += 8)
|
|
||||||
{
|
|
||||||
unsigned char result_byte = 0;
|
|
||||||
|
|
||||||
for (int j = 0; j < 8; j++)
|
|
||||||
result_byte |= (ax[i + j] > 0) << (7 - j);
|
|
||||||
|
|
||||||
rx[i / 8] = result_byte;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (; i < a->dim; i++)
|
|
||||||
rx[i / 8] |= (ax[i] > 0) << (7 - (i % 8));
|
rx[i / 8] |= (ax[i] > 0) << (7 - (i % 8));
|
||||||
|
|
||||||
PG_RETURN_VARBIT_P(result);
|
PG_RETURN_VARBIT_P(result);
|
||||||
|
|||||||
@@ -540,12 +540,6 @@ SELECT binary_quantize('[0,0.1,-0.2,-0.3,0.4,0.5,0.6,-0.7,0.8,-0.9,1]'::halfvec)
|
|||||||
01001110101
|
01001110101
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT binary_quantize('[1,2,3,-4,5,6,-7,8,1,-2,-3,4,5,-6,7,8,-1,2,3]'::halfvec);
|
|
||||||
binary_quantize
|
|
||||||
---------------------
|
|
||||||
1110110110011011011
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT subvector('[1,2,3,4,5]'::halfvec, 1, 3);
|
SELECT subvector('[1,2,3,4,5]'::halfvec, 1, 3);
|
||||||
subvector
|
subvector
|
||||||
-----------
|
-----------
|
||||||
|
|||||||
@@ -99,38 +99,6 @@ SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <+> (SELECT NULL::vector)) t2
|
|||||||
4
|
4
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
DROP TABLE t;
|
|
||||||
-- iterative
|
|
||||||
CREATE TABLE t (val vector(3));
|
|
||||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
|
||||||
CREATE INDEX ON t USING hnsw (val vector_l2_ops);
|
|
||||||
SET hnsw.iterative_scan = strict_order;
|
|
||||||
SET hnsw.ef_search = 1;
|
|
||||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
|
||||||
val
|
|
||||||
---------
|
|
||||||
[1,2,3]
|
|
||||||
[1,1,1]
|
|
||||||
[0,0,0]
|
|
||||||
(3 rows)
|
|
||||||
|
|
||||||
SET hnsw.iterative_scan = relaxed_order;
|
|
||||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
|
||||||
val
|
|
||||||
---------
|
|
||||||
[1,2,3]
|
|
||||||
[1,1,1]
|
|
||||||
[0,0,0]
|
|
||||||
(3 rows)
|
|
||||||
|
|
||||||
TRUNCATE t;
|
|
||||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
|
||||||
val
|
|
||||||
-----
|
|
||||||
(0 rows)
|
|
||||||
|
|
||||||
RESET hnsw.iterative_scan;
|
|
||||||
RESET hnsw.ef_search;
|
|
||||||
DROP TABLE t;
|
DROP TABLE t;
|
||||||
-- unlogged
|
-- unlogged
|
||||||
CREATE UNLOGGED TABLE t (val vector(3));
|
CREATE UNLOGGED TABLE t (val vector(3));
|
||||||
@@ -171,31 +139,4 @@ SET hnsw.ef_search = 0;
|
|||||||
ERROR: 0 is outside the valid range for parameter "hnsw.ef_search" (1 .. 1000)
|
ERROR: 0 is outside the valid range for parameter "hnsw.ef_search" (1 .. 1000)
|
||||||
SET hnsw.ef_search = 1001;
|
SET hnsw.ef_search = 1001;
|
||||||
ERROR: 1001 is outside the valid range for parameter "hnsw.ef_search" (1 .. 1000)
|
ERROR: 1001 is outside the valid range for parameter "hnsw.ef_search" (1 .. 1000)
|
||||||
SHOW hnsw.iterative_scan;
|
|
||||||
hnsw.iterative_scan
|
|
||||||
---------------------
|
|
||||||
off
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SET hnsw.iterative_scan = on;
|
|
||||||
ERROR: invalid value for parameter "hnsw.iterative_scan": "on"
|
|
||||||
HINT: Available values: off, relaxed_order, strict_order.
|
|
||||||
SHOW hnsw.max_scan_tuples;
|
|
||||||
hnsw.max_scan_tuples
|
|
||||||
----------------------
|
|
||||||
20000
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SET hnsw.max_scan_tuples = 0;
|
|
||||||
ERROR: 0 is outside the valid range for parameter "hnsw.max_scan_tuples" (1 .. 2147483647)
|
|
||||||
SHOW hnsw.scan_mem_multiplier;
|
|
||||||
hnsw.scan_mem_multiplier
|
|
||||||
--------------------------
|
|
||||||
1
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SET hnsw.scan_mem_multiplier = 0;
|
|
||||||
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;
|
||||||
|
|||||||
@@ -81,46 +81,6 @@ SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> (SELECT NULL::vector)) t2
|
|||||||
3
|
3
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
DROP TABLE t;
|
|
||||||
-- iterative
|
|
||||||
CREATE TABLE t (val vector(3));
|
|
||||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
|
||||||
CREATE INDEX ON t USING ivfflat (val vector_l2_ops) WITH (lists = 3);
|
|
||||||
SET ivfflat.iterative_scan = relaxed_order;
|
|
||||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
|
||||||
val
|
|
||||||
---------
|
|
||||||
[1,2,3]
|
|
||||||
[1,1,1]
|
|
||||||
[0,0,0]
|
|
||||||
(3 rows)
|
|
||||||
|
|
||||||
SET ivfflat.max_probes = 1;
|
|
||||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
|
||||||
val
|
|
||||||
---------
|
|
||||||
[1,2,3]
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SET ivfflat.max_probes = 2;
|
|
||||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
|
||||||
val
|
|
||||||
---------
|
|
||||||
[1,2,3]
|
|
||||||
[1,1,1]
|
|
||||||
(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.max_probes;
|
|
||||||
DROP TABLE t;
|
DROP TABLE t;
|
||||||
-- unlogged
|
-- unlogged
|
||||||
CREATE UNLOGGED TABLE t (val vector(3));
|
CREATE UNLOGGED TABLE t (val vector(3));
|
||||||
@@ -149,27 +109,4 @@ SHOW ivfflat.probes;
|
|||||||
1
|
1
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SET ivfflat.probes = 0;
|
|
||||||
ERROR: 0 is outside the valid range for parameter "ivfflat.probes" (1 .. 32768)
|
|
||||||
SET ivfflat.probes = 32769;
|
|
||||||
ERROR: 32769 is outside the valid range for parameter "ivfflat.probes" (1 .. 32768)
|
|
||||||
SHOW ivfflat.iterative_scan;
|
|
||||||
ivfflat.iterative_scan
|
|
||||||
------------------------
|
|
||||||
off
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SET ivfflat.iterative_scan = on;
|
|
||||||
ERROR: invalid value for parameter "ivfflat.iterative_scan": "on"
|
|
||||||
HINT: Available values: off, relaxed_order.
|
|
||||||
SHOW ivfflat.max_probes;
|
|
||||||
ivfflat.max_probes
|
|
||||||
--------------------
|
|
||||||
32768
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SET ivfflat.max_probes = 0;
|
|
||||||
ERROR: 0 is outside the valid range for parameter "ivfflat.max_probes" (1 .. 32768)
|
|
||||||
SET ivfflat.max_probes = 32769;
|
|
||||||
ERROR: 32769 is outside the valid range for parameter "ivfflat.max_probes" (1 .. 32768)
|
|
||||||
DROP TABLE t;
|
DROP TABLE t;
|
||||||
|
|||||||
@@ -576,12 +576,6 @@ SELECT binary_quantize('[0,0.1,-0.2,-0.3,0.4,0.5,0.6,-0.7,0.8,-0.9,1]'::vector);
|
|||||||
01001110101
|
01001110101
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT binary_quantize('[1,2,3,-4,5,6,-7,8,1,-2,-3,4,5,-6,7,8,-1,2,3]'::vector);
|
|
||||||
binary_quantize
|
|
||||||
---------------------
|
|
||||||
1110110110011011011
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT subvector('[1,2,3,4,5]'::vector, 1, 3);
|
SELECT subvector('[1,2,3,4,5]'::vector, 1, 3);
|
||||||
subvector
|
subvector
|
||||||
-----------
|
-----------
|
||||||
|
|||||||
@@ -121,7 +121,6 @@ SELECT l2_normalize('[65504]'::halfvec);
|
|||||||
|
|
||||||
SELECT binary_quantize('[1,0,-1]'::halfvec);
|
SELECT binary_quantize('[1,0,-1]'::halfvec);
|
||||||
SELECT binary_quantize('[0,0.1,-0.2,-0.3,0.4,0.5,0.6,-0.7,0.8,-0.9,1]'::halfvec);
|
SELECT binary_quantize('[0,0.1,-0.2,-0.3,0.4,0.5,0.6,-0.7,0.8,-0.9,1]'::halfvec);
|
||||||
SELECT binary_quantize('[1,2,3,-4,5,6,-7,8,1,-2,-3,4,5,-6,7,8,-1,2,3]'::halfvec);
|
|
||||||
|
|
||||||
SELECT subvector('[1,2,3,4,5]'::halfvec, 1, 3);
|
SELECT subvector('[1,2,3,4,5]'::halfvec, 1, 3);
|
||||||
SELECT subvector('[1,2,3,4,5]'::halfvec, 3, 2);
|
SELECT subvector('[1,2,3,4,5]'::halfvec, 3, 2);
|
||||||
|
|||||||
@@ -57,26 +57,6 @@ SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <+> (SELECT NULL::vector)) t2
|
|||||||
|
|
||||||
DROP TABLE t;
|
DROP TABLE t;
|
||||||
|
|
||||||
-- iterative
|
|
||||||
|
|
||||||
CREATE TABLE t (val vector(3));
|
|
||||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
|
||||||
CREATE INDEX ON t USING hnsw (val vector_l2_ops);
|
|
||||||
|
|
||||||
SET hnsw.iterative_scan = strict_order;
|
|
||||||
SET hnsw.ef_search = 1;
|
|
||||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
|
||||||
|
|
||||||
SET hnsw.iterative_scan = relaxed_order;
|
|
||||||
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.ef_search;
|
|
||||||
DROP TABLE t;
|
|
||||||
|
|
||||||
-- unlogged
|
-- unlogged
|
||||||
|
|
||||||
CREATE UNLOGGED TABLE t (val vector(3));
|
CREATE UNLOGGED TABLE t (val vector(3));
|
||||||
@@ -101,17 +81,4 @@ SHOW hnsw.ef_search;
|
|||||||
SET hnsw.ef_search = 0;
|
SET hnsw.ef_search = 0;
|
||||||
SET hnsw.ef_search = 1001;
|
SET hnsw.ef_search = 1001;
|
||||||
|
|
||||||
SHOW hnsw.iterative_scan;
|
|
||||||
|
|
||||||
SET hnsw.iterative_scan = on;
|
|
||||||
|
|
||||||
SHOW hnsw.max_scan_tuples;
|
|
||||||
|
|
||||||
SET hnsw.max_scan_tuples = 0;
|
|
||||||
|
|
||||||
SHOW hnsw.scan_mem_multiplier;
|
|
||||||
|
|
||||||
SET hnsw.scan_mem_multiplier = 0;
|
|
||||||
SET hnsw.scan_mem_multiplier = 1001;
|
|
||||||
|
|
||||||
DROP TABLE t;
|
DROP TABLE t;
|
||||||
|
|||||||
@@ -44,28 +44,6 @@ SELECT COUNT(*) FROM (SELECT * FROM t ORDER BY val <=> (SELECT NULL::vector)) t2
|
|||||||
|
|
||||||
DROP TABLE t;
|
DROP TABLE t;
|
||||||
|
|
||||||
-- iterative
|
|
||||||
|
|
||||||
CREATE TABLE t (val vector(3));
|
|
||||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
|
||||||
CREATE INDEX ON t USING ivfflat (val vector_l2_ops) WITH (lists = 3);
|
|
||||||
|
|
||||||
SET ivfflat.iterative_scan = relaxed_order;
|
|
||||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
|
||||||
|
|
||||||
SET ivfflat.max_probes = 1;
|
|
||||||
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
|
|
||||||
|
|
||||||
SET ivfflat.max_probes = 2;
|
|
||||||
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.max_probes;
|
|
||||||
DROP TABLE t;
|
|
||||||
|
|
||||||
-- unlogged
|
-- unlogged
|
||||||
|
|
||||||
CREATE UNLOGGED TABLE t (val vector(3));
|
CREATE UNLOGGED TABLE t (val vector(3));
|
||||||
@@ -84,16 +62,4 @@ CREATE INDEX ON t USING ivfflat (val vector_l2_ops) WITH (lists = 32769);
|
|||||||
|
|
||||||
SHOW ivfflat.probes;
|
SHOW ivfflat.probes;
|
||||||
|
|
||||||
SET ivfflat.probes = 0;
|
|
||||||
SET ivfflat.probes = 32769;
|
|
||||||
|
|
||||||
SHOW ivfflat.iterative_scan;
|
|
||||||
|
|
||||||
SET ivfflat.iterative_scan = on;
|
|
||||||
|
|
||||||
SHOW ivfflat.max_probes;
|
|
||||||
|
|
||||||
SET ivfflat.max_probes = 0;
|
|
||||||
SET ivfflat.max_probes = 32769;
|
|
||||||
|
|
||||||
DROP TABLE t;
|
DROP TABLE t;
|
||||||
|
|||||||
@@ -128,7 +128,6 @@ SELECT l2_normalize('[3e38]'::vector);
|
|||||||
|
|
||||||
SELECT binary_quantize('[1,0,-1]'::vector);
|
SELECT binary_quantize('[1,0,-1]'::vector);
|
||||||
SELECT binary_quantize('[0,0.1,-0.2,-0.3,0.4,0.5,0.6,-0.7,0.8,-0.9,1]'::vector);
|
SELECT binary_quantize('[0,0.1,-0.2,-0.3,0.4,0.5,0.6,-0.7,0.8,-0.9,1]'::vector);
|
||||||
SELECT binary_quantize('[1,2,3,-4,5,6,-7,8,1,-2,-3,4,5,-6,7,8,-1,2,3]'::vector);
|
|
||||||
|
|
||||||
SELECT subvector('[1,2,3,4,5]'::vector, 1, 3);
|
SELECT subvector('[1,2,3,4,5]'::vector, 1, 3);
|
||||||
SELECT subvector('[1,2,3,4,5]'::vector, 3, 2);
|
SELECT subvector('[1,2,3,4,5]'::vector, 3, 2);
|
||||||
|
|||||||
@@ -6,7 +6,13 @@ use Test::More;
|
|||||||
|
|
||||||
my $dim = 3;
|
my $dim = 3;
|
||||||
|
|
||||||
my $array_sql = join(",", ('random()') x $dim);
|
my @r = ();
|
||||||
|
for (1 .. $dim)
|
||||||
|
{
|
||||||
|
my $v = int(rand(1000)) + 1;
|
||||||
|
push(@r, "i % $v");
|
||||||
|
}
|
||||||
|
my $array_sql = join(", ", @r);
|
||||||
|
|
||||||
# Initialize node
|
# Initialize node
|
||||||
my $node = PostgreSQL::Test::Cluster->new('node');
|
my $node = PostgreSQL::Test::Cluster->new('node');
|
||||||
@@ -17,20 +23,19 @@ $node->start;
|
|||||||
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
||||||
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector($dim));");
|
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector($dim));");
|
||||||
$node->safe_psql("postgres",
|
$node->safe_psql("postgres",
|
||||||
"INSERT INTO tst SELECT i, ARRAY[$array_sql] FROM generate_series(1, 100000) i;"
|
"INSERT INTO tst SELECT i % 10, ARRAY[$array_sql] FROM generate_series(1, 100000) i;"
|
||||||
);
|
);
|
||||||
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v vector_l2_ops);");
|
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v vector_l2_ops);");
|
||||||
|
|
||||||
# Get size
|
# Get size
|
||||||
my $size = $node->safe_psql("postgres", "SELECT pg_total_relation_size('tst_v_idx');");
|
my $size = $node->safe_psql("postgres", "SELECT pg_total_relation_size('tst_v_idx');");
|
||||||
|
|
||||||
# Store values
|
|
||||||
$node->safe_psql("postgres", "CREATE TABLE tmp AS SELECT * FROM tst;");
|
|
||||||
|
|
||||||
# Delete all, vacuum, and insert same data
|
# Delete all, vacuum, and insert same data
|
||||||
$node->safe_psql("postgres", "DELETE FROM tst;");
|
$node->safe_psql("postgres", "DELETE FROM tst;");
|
||||||
$node->safe_psql("postgres", "VACUUM tst;");
|
$node->safe_psql("postgres", "VACUUM tst;");
|
||||||
$node->safe_psql("postgres", "INSERT INTO tst SELECT * FROM tmp;");
|
$node->safe_psql("postgres",
|
||||||
|
"INSERT INTO tst SELECT i % 10, ARRAY[$array_sql] FROM generate_series(1, 100000) i;"
|
||||||
|
);
|
||||||
|
|
||||||
# Check size
|
# Check size
|
||||||
my $new_size = $node->safe_psql("postgres", "SELECT pg_total_relation_size('tst_v_idx');");
|
my $new_size = $node->safe_psql("postgres", "SELECT pg_total_relation_size('tst_v_idx');");
|
||||||
|
|||||||
@@ -16,19 +16,29 @@ $node->safe_psql("postgres",
|
|||||||
"INSERT INTO tst SELECT i, ARRAY[random(), random(), random()] FROM generate_series(1, 100000) i;"
|
"INSERT INTO tst SELECT i, ARRAY[random(), random(), random()] FROM generate_series(1, 100000) i;"
|
||||||
);
|
);
|
||||||
|
|
||||||
# Add index
|
# Check each index type
|
||||||
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v vector_l2_ops);");
|
my @operators = ("<->", "<#>", "<=>");
|
||||||
|
my @opclasses = ("vector_l2_ops", "vector_ip_ops", "vector_cosine_ops");
|
||||||
|
|
||||||
# Test 100% recall
|
for my $i (0 .. $#operators)
|
||||||
for (1 .. 20)
|
|
||||||
{
|
{
|
||||||
my $id = int(rand() * 100000);
|
my $operator = $operators[$i];
|
||||||
my $query = $node->safe_psql("postgres", "SELECT v FROM tst WHERE i = $id;");
|
my $opclass = $opclasses[$i];
|
||||||
my $res = $node->safe_psql("postgres", qq(
|
|
||||||
SET enable_seqscan = off;
|
# Add index
|
||||||
SELECT v FROM tst ORDER BY v <-> '$query' LIMIT 1;
|
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v $opclass);");
|
||||||
));
|
|
||||||
is($res, $query);
|
# Test 100% recall
|
||||||
|
for (1 .. 20)
|
||||||
|
{
|
||||||
|
my $id = int(rand() * 100000);
|
||||||
|
my $query = $node->safe_psql("postgres", "SELECT v FROM tst WHERE i = $id;");
|
||||||
|
my $res = $node->safe_psql("postgres", qq(
|
||||||
|
SET enable_seqscan = off;
|
||||||
|
SELECT v FROM tst ORDER BY v <-> '$query' LIMIT 1;
|
||||||
|
));
|
||||||
|
is($res, $query);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
done_testing();
|
done_testing();
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ my $c = int(rand() * $nc);
|
|||||||
my $explain = $node->safe_psql("postgres", qq(
|
my $explain = $node->safe_psql("postgres", qq(
|
||||||
EXPLAIN ANALYZE SELECT i FROM tst WHERE c = $c ORDER BY v <-> '$query' LIMIT $limit;
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE c = $c ORDER BY v <-> '$query' LIMIT $limit;
|
||||||
));
|
));
|
||||||
like($explain, qr/Seq Scan/);
|
# TODO Do not use index
|
||||||
|
like($explain, qr/Index Scan using idx/);
|
||||||
|
|
||||||
# Test attribute filtering with few rows removed
|
# Test attribute filtering with few rows removed
|
||||||
$explain = $node->safe_psql("postgres", qq(
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
@@ -59,7 +60,8 @@ like($explain, qr/Index Scan using idx/);
|
|||||||
$explain = $node->safe_psql("postgres", qq(
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
EXPLAIN ANALYZE SELECT i FROM tst WHERE c < 1 ORDER BY v <-> '$query' LIMIT $limit;
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE c < 1 ORDER BY v <-> '$query' LIMIT $limit;
|
||||||
));
|
));
|
||||||
like($explain, qr/Seq Scan/);
|
# TODO Do not use index
|
||||||
|
like($explain, qr/Index Scan using idx/);
|
||||||
|
|
||||||
# Test attribute filtering with few rows removed like
|
# Test attribute filtering with few rows removed like
|
||||||
$explain = $node->safe_psql("postgres", qq(
|
$explain = $node->safe_psql("postgres", qq(
|
||||||
|
|||||||
@@ -17,11 +17,12 @@ $node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
|||||||
for my $dim (@dims)
|
for my $dim (@dims)
|
||||||
{
|
{
|
||||||
my $array_sql = join(",", ('random()') x $dim);
|
my $array_sql = join(",", ('random()') x $dim);
|
||||||
|
my $n = $dim == 384 ? 2000 : 1000;
|
||||||
|
|
||||||
# Create table and index
|
# Create table and index
|
||||||
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector($dim));");
|
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector($dim));");
|
||||||
$node->safe_psql("postgres",
|
$node->safe_psql("postgres",
|
||||||
"INSERT INTO tst SELECT i, ARRAY[$array_sql] FROM generate_series(1, 2000) i;"
|
"INSERT INTO tst SELECT i, ARRAY[$array_sql] FROM generate_series(1, $n) i;"
|
||||||
);
|
);
|
||||||
$node->safe_psql("postgres", "CREATE INDEX idx ON tst USING hnsw (v vector_l2_ops);");
|
$node->safe_psql("postgres", "CREATE INDEX idx ON tst USING hnsw (v vector_l2_ops);");
|
||||||
$node->safe_psql("postgres", "ANALYZE tst;");
|
$node->safe_psql("postgres", "ANALYZE tst;");
|
||||||
@@ -39,21 +40,6 @@ for my $dim (@dims)
|
|||||||
));
|
));
|
||||||
like($explain, qr/Index Scan using idx/);
|
like($explain, qr/Index Scan using idx/);
|
||||||
|
|
||||||
# 3x the rows are needed for distance filters
|
|
||||||
# since the planner uses DEFAULT_INEQ_SEL for the selectivity (should be 1)
|
|
||||||
# Recreate index for performance
|
|
||||||
$node->safe_psql("postgres", "DROP INDEX idx;");
|
|
||||||
$node->safe_psql("postgres",
|
|
||||||
"INSERT INTO tst SELECT i, ARRAY[$array_sql] FROM generate_series(2001, 6000) i;"
|
|
||||||
);
|
|
||||||
$node->safe_psql("postgres", "CREATE INDEX idx ON tst USING hnsw (v vector_l2_ops);");
|
|
||||||
$node->safe_psql("postgres", "ANALYZE tst;");
|
|
||||||
|
|
||||||
$explain = $node->safe_psql("postgres", qq(
|
|
||||||
EXPLAIN ANALYZE SELECT i FROM tst WHERE v <-> '$query' < 1 ORDER BY v <-> '$query' LIMIT $limit;
|
|
||||||
));
|
|
||||||
like($explain, qr/Index Scan using idx/);
|
|
||||||
|
|
||||||
$node->safe_psql("postgres", "DROP TABLE tst;");
|
$node->safe_psql("postgres", "DROP TABLE tst;");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,11 +39,6 @@ for my $dim (@dims)
|
|||||||
));
|
));
|
||||||
like($explain, qr/Index Scan using idx/);
|
like($explain, qr/Index Scan using idx/);
|
||||||
|
|
||||||
$explain = $node->safe_psql("postgres", qq(
|
|
||||||
EXPLAIN ANALYZE SELECT i FROM tst WHERE v <-> '$query' < 1 ORDER BY v <-> '$query' LIMIT $limit;
|
|
||||||
));
|
|
||||||
like($explain, qr/Index Scan using idx/);
|
|
||||||
|
|
||||||
$node->safe_psql("postgres", "DROP TABLE tst;");
|
$node->safe_psql("postgres", "DROP TABLE tst;");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,26 +26,25 @@ $node->safe_psql("postgres", qq(
|
|||||||
|
|
||||||
my $count = $node->safe_psql("postgres", qq(
|
my $count = $node->safe_psql("postgres", qq(
|
||||||
SET enable_seqscan = off;
|
SET enable_seqscan = off;
|
||||||
SET hnsw.iterative_scan = relaxed_order;
|
SET hnsw.streaming = on;
|
||||||
SET hnsw.max_scan_tuples = 100000;
|
SET work_mem = '8MB';
|
||||||
SET hnsw.scan_mem_multiplier = 2;
|
|
||||||
SELECT COUNT(*) FROM (SELECT v FROM tst WHERE i % 10000 = 0 ORDER BY v <-> (SELECT v FROM tst LIMIT 1) LIMIT 11) t;
|
SELECT COUNT(*) FROM (SELECT v FROM tst WHERE i % 10000 = 0 ORDER BY v <-> (SELECT v FROM tst LIMIT 1) LIMIT 11) t;
|
||||||
));
|
));
|
||||||
is($count, 10);
|
is($count, 10);
|
||||||
|
|
||||||
foreach ((30000, 50000, 70000))
|
foreach ((30000, 50000, 70000))
|
||||||
{
|
{
|
||||||
my $max_tuples = $_;
|
my $ef_stream = $_;
|
||||||
my $expected = $max_tuples / 10000;
|
my $expected = $ef_stream / 10000;
|
||||||
my $sum = 0;
|
my $sum = 0;
|
||||||
|
|
||||||
for my $i (1 .. 20)
|
for my $i (1 .. 20)
|
||||||
{
|
{
|
||||||
$count = $node->safe_psql("postgres", qq(
|
$count = $node->safe_psql("postgres", qq(
|
||||||
SET enable_seqscan = off;
|
SET enable_seqscan = off;
|
||||||
SET hnsw.iterative_scan = relaxed_order;
|
SET hnsw.streaming = on;
|
||||||
SET hnsw.max_scan_tuples = $max_tuples;
|
SET hnsw.ef_stream = $ef_stream;
|
||||||
SET hnsw.scan_mem_multiplier = 2;
|
SET work_mem = '8MB';
|
||||||
SELECT COUNT(*) FROM (SELECT v FROM tst WHERE i % 10000 = 0 ORDER BY v <-> (SELECT v FROM tst WHERE i = $i) LIMIT 11) t;
|
SELECT COUNT(*) FROM (SELECT v FROM tst WHERE i % 10000 = 0 ORDER BY v <-> (SELECT v FROM tst WHERE i = $i) LIMIT 11) t;
|
||||||
));
|
));
|
||||||
$sum += $count;
|
$sum += $count;
|
||||||
@@ -56,4 +55,12 @@ 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.streaming = on;
|
||||||
|
SET work_mem = '2MB';
|
||||||
|
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 exceeded work_mem after \d+ tuples/);
|
||||||
|
|
||||||
done_testing();
|
done_testing();
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
use strict;
|
|
||||||
use warnings FATAL => 'all';
|
|
||||||
use PostgreSQL::Test::Cluster;
|
|
||||||
use PostgreSQL::Test::Utils;
|
|
||||||
use Test::More;
|
|
||||||
|
|
||||||
my $dim = 3;
|
|
||||||
my $array_sql = join(",", ('random()') x $dim);
|
|
||||||
|
|
||||||
# Initialize node
|
|
||||||
my $node = PostgreSQL::Test::Cluster->new('node');
|
|
||||||
$node->init;
|
|
||||||
$node->start;
|
|
||||||
|
|
||||||
# Create table
|
|
||||||
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
|
||||||
$node->safe_psql("postgres", "CREATE TABLE tst (i int4 PRIMARY KEY, v vector($dim));");
|
|
||||||
$node->safe_psql("postgres",
|
|
||||||
"INSERT INTO tst SELECT i, ARRAY[$array_sql] FROM generate_series(1, 100000) i;"
|
|
||||||
);
|
|
||||||
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v vector_l2_ops);");
|
|
||||||
|
|
||||||
my $count = $node->safe_psql("postgres", qq(
|
|
||||||
SET enable_seqscan = off;
|
|
||||||
SET ivfflat.probes = 10;
|
|
||||||
SET ivfflat.iterative_scan = relaxed_order;
|
|
||||||
SELECT COUNT(*) FROM (SELECT v FROM tst WHERE i % 10000 = 0 ORDER BY v <-> (SELECT v FROM tst LIMIT 1) LIMIT 11) t;
|
|
||||||
));
|
|
||||||
is($count, 10);
|
|
||||||
|
|
||||||
foreach ((30, 50, 70))
|
|
||||||
{
|
|
||||||
my $max_probes = $_;
|
|
||||||
my $expected = $max_probes / 10;
|
|
||||||
my $sum = 0;
|
|
||||||
|
|
||||||
for my $i (1 .. 20)
|
|
||||||
{
|
|
||||||
$count = $node->safe_psql("postgres", qq(
|
|
||||||
SET enable_seqscan = off;
|
|
||||||
SET ivfflat.probes = 10;
|
|
||||||
SET ivfflat.iterative_scan = relaxed_order;
|
|
||||||
SET ivfflat.max_probes = $max_probes;
|
|
||||||
SELECT COUNT(*) FROM (SELECT v FROM tst WHERE i % 10000 = 0 ORDER BY v <-> (SELECT v FROM tst WHERE i = $i) LIMIT 11) t;
|
|
||||||
));
|
|
||||||
$sum += $count;
|
|
||||||
}
|
|
||||||
|
|
||||||
my $avg = $sum / 20;
|
|
||||||
cmp_ok($avg, '>', $expected - 2);
|
|
||||||
cmp_ok($avg, '<', $expected + 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
done_testing();
|
|
||||||
@@ -10,18 +10,18 @@ my @expected;
|
|||||||
my $limit = 20;
|
my $limit = 20;
|
||||||
my $dim = 3;
|
my $dim = 3;
|
||||||
my $array_sql = join(",", ('random()') x $dim);
|
my $array_sql = join(",", ('random()') x $dim);
|
||||||
my @cs = (50, 500);
|
my @cs = (100, 1000);
|
||||||
|
|
||||||
sub test_recall
|
sub test_recall
|
||||||
{
|
{
|
||||||
my ($c, $ef_search, $min, $operator, $mode) = @_;
|
my ($c, $ef_search, $min, $operator) = @_;
|
||||||
my $correct = 0;
|
my $correct = 0;
|
||||||
my $total = 0;
|
my $total = 0;
|
||||||
|
|
||||||
my $explain = $node->safe_psql("postgres", qq(
|
my $explain = $node->safe_psql("postgres", qq(
|
||||||
SET enable_seqscan = off;
|
SET enable_seqscan = off;
|
||||||
SET hnsw.ef_search = $ef_search;
|
SET hnsw.ef_search = $ef_search;
|
||||||
SET hnsw.iterative_scan = $mode;
|
SET hnsw.streaming = on;
|
||||||
EXPLAIN ANALYZE SELECT i FROM tst WHERE i % $c = 0 ORDER BY v $operator '$queries[0]' LIMIT $limit;
|
EXPLAIN ANALYZE SELECT i FROM tst WHERE i % $c = 0 ORDER BY v $operator '$queries[0]' LIMIT $limit;
|
||||||
));
|
));
|
||||||
like($explain, qr/Index Scan using idx on tst/);
|
like($explain, qr/Index Scan using idx on tst/);
|
||||||
@@ -31,7 +31,7 @@ sub test_recall
|
|||||||
my $actual = $node->safe_psql("postgres", qq(
|
my $actual = $node->safe_psql("postgres", qq(
|
||||||
SET enable_seqscan = off;
|
SET enable_seqscan = off;
|
||||||
SET hnsw.ef_search = $ef_search;
|
SET hnsw.ef_search = $ef_search;
|
||||||
SET hnsw.iterative_scan = $mode;
|
SET hnsw.streaming = on;
|
||||||
SELECT i FROM tst WHERE i % $c = 0 ORDER BY v $operator '$queries[$i]' LIMIT $limit;
|
SELECT i FROM tst WHERE i % $c = 0 ORDER BY v $operator '$queries[$i]' LIMIT $limit;
|
||||||
));
|
));
|
||||||
my @actual_ids = split("\n", $actual);
|
my @actual_ids = split("\n", $actual);
|
||||||
@@ -50,7 +50,7 @@ sub test_recall
|
|||||||
$total += $limit;
|
$total += $limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
cmp_ok($correct / $total, ">=", $min, "$operator $mode $c");
|
cmp_ok($correct / $total, ">=", $min, $operator);
|
||||||
}
|
}
|
||||||
|
|
||||||
# Initialize node
|
# Initialize node
|
||||||
@@ -62,7 +62,7 @@ $node->start;
|
|||||||
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
||||||
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector($dim));");
|
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector($dim));");
|
||||||
$node->safe_psql("postgres",
|
$node->safe_psql("postgres",
|
||||||
"INSERT INTO tst SELECT i, ARRAY[$array_sql] FROM generate_series(1, 50000) i;"
|
"INSERT INTO tst SELECT i, ARRAY[$array_sql] FROM generate_series(1, 100000) i;"
|
||||||
);
|
);
|
||||||
|
|
||||||
# Generate queries
|
# Generate queries
|
||||||
@@ -108,8 +108,21 @@ for my $i (0 .. $#operators)
|
|||||||
push(@expected, $res);
|
push(@expected, $res);
|
||||||
}
|
}
|
||||||
|
|
||||||
test_recall($c, 40, 0.99, $operator, "strict_order");
|
if ($c == 100)
|
||||||
test_recall($c, 40, 0.99, $operator, "relaxed_order");
|
{
|
||||||
|
test_recall($c, 40, 0.99, $operator);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ($operator eq "<->")
|
||||||
|
{
|
||||||
|
test_recall($c, 40, 0.99, $operator);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
test_recall($c, 40, 0.99, $operator);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$node->safe_psql("postgres", "DROP INDEX idx;");
|
$node->safe_psql("postgres", "DROP INDEX idx;");
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
use strict;
|
|
||||||
use warnings FATAL => 'all';
|
|
||||||
use PostgreSQL::Test::Cluster;
|
|
||||||
use PostgreSQL::Test::Utils;
|
|
||||||
use Test::More;
|
|
||||||
|
|
||||||
my $node;
|
|
||||||
my @queries = ();
|
|
||||||
my @expected;
|
|
||||||
my $limit = 20;
|
|
||||||
my @cs = (100, 1000);
|
|
||||||
|
|
||||||
sub test_recall
|
|
||||||
{
|
|
||||||
my ($c, $probes, $min, $operator) = @_;
|
|
||||||
my $correct = 0;
|
|
||||||
my $total = 0;
|
|
||||||
|
|
||||||
my $explain = $node->safe_psql("postgres", qq(
|
|
||||||
SET enable_seqscan = off;
|
|
||||||
SET ivfflat.probes = $probes;
|
|
||||||
SET ivfflat.iterative_scan = relaxed_order;
|
|
||||||
EXPLAIN ANALYZE SELECT i FROM tst WHERE i % $c = 0 ORDER BY v $operator '$queries[0]' LIMIT $limit;
|
|
||||||
));
|
|
||||||
like($explain, qr/Index Scan using idx on tst/);
|
|
||||||
|
|
||||||
for my $i (0 .. $#queries)
|
|
||||||
{
|
|
||||||
my $actual = $node->safe_psql("postgres", qq(
|
|
||||||
SET enable_seqscan = off;
|
|
||||||
SET ivfflat.probes = $probes;
|
|
||||||
SET ivfflat.iterative_scan = relaxed_order;
|
|
||||||
SELECT i FROM tst WHERE i % $c = 0 ORDER BY v $operator '$queries[$i]' LIMIT $limit;
|
|
||||||
));
|
|
||||||
my @actual_ids = split("\n", $actual);
|
|
||||||
|
|
||||||
my @expected_ids = split("\n", $expected[$i]);
|
|
||||||
my %expected_set = map { $_ => 1 } @expected_ids;
|
|
||||||
|
|
||||||
foreach (@actual_ids)
|
|
||||||
{
|
|
||||||
if (exists($expected_set{$_}))
|
|
||||||
{
|
|
||||||
$correct++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$total += $limit;
|
|
||||||
}
|
|
||||||
|
|
||||||
cmp_ok($correct / $total, ">=", $min, "$operator $c");
|
|
||||||
}
|
|
||||||
|
|
||||||
# Initialize node
|
|
||||||
$node = PostgreSQL::Test::Cluster->new('node');
|
|
||||||
$node->init;
|
|
||||||
$node->start;
|
|
||||||
|
|
||||||
# Create table
|
|
||||||
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
|
||||||
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector(3));");
|
|
||||||
$node->safe_psql("postgres",
|
|
||||||
"INSERT INTO tst SELECT i, ARRAY[random(), random(), random()] FROM generate_series(1, 100000) i;"
|
|
||||||
);
|
|
||||||
|
|
||||||
# Generate queries
|
|
||||||
for (1 .. 20)
|
|
||||||
{
|
|
||||||
my $r1 = rand();
|
|
||||||
my $r2 = rand();
|
|
||||||
my $r3 = rand();
|
|
||||||
push(@queries, "[$r1,$r2,$r3]");
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check each index type
|
|
||||||
my @operators = ("<->", "<=>");
|
|
||||||
my @opclasses = ("vector_l2_ops", "vector_cosine_ops");
|
|
||||||
|
|
||||||
for my $i (0 .. $#operators)
|
|
||||||
{
|
|
||||||
my $operator = $operators[$i];
|
|
||||||
my $opclass = $opclasses[$i];
|
|
||||||
|
|
||||||
$node->safe_psql("postgres", "CREATE INDEX idx ON tst USING ivfflat (v $opclass);");
|
|
||||||
|
|
||||||
foreach (@cs)
|
|
||||||
{
|
|
||||||
my $c = $_;
|
|
||||||
|
|
||||||
# Get exact results
|
|
||||||
@expected = ();
|
|
||||||
foreach (@queries)
|
|
||||||
{
|
|
||||||
my $res = $node->safe_psql("postgres", qq(
|
|
||||||
SET enable_indexscan = off;
|
|
||||||
WITH top AS (
|
|
||||||
SELECT v $operator '$_' AS distance FROM tst WHERE i % $c = 0 ORDER BY distance LIMIT $limit
|
|
||||||
)
|
|
||||||
SELECT i FROM tst WHERE (v $operator '$_') <= (SELECT MAX(distance) FROM top)
|
|
||||||
));
|
|
||||||
push(@expected, $res);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($c == 100)
|
|
||||||
{
|
|
||||||
test_recall($c, 1, 0.57, $operator);
|
|
||||||
test_recall($c, 10, 0.98, $operator);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if ($operator eq "<->")
|
|
||||||
{
|
|
||||||
test_recall($c, 1, 0.80, $operator);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
test_recall($c, 1, 0.88, $operator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$node->safe_psql("postgres", "DROP INDEX idx;");
|
|
||||||
}
|
|
||||||
|
|
||||||
done_testing();
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
use strict;
|
|
||||||
use warnings FATAL => 'all';
|
|
||||||
use PostgreSQL::Test::Cluster;
|
|
||||||
use PostgreSQL::Test::Utils;
|
|
||||||
use Test::More;
|
|
||||||
|
|
||||||
# Initialize node
|
|
||||||
my $node = PostgreSQL::Test::Cluster->new('node');
|
|
||||||
$node->init;
|
|
||||||
$node->start;
|
|
||||||
|
|
||||||
# Create table
|
|
||||||
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
|
||||||
$node->safe_psql("postgres", "CREATE TABLE tst (v vector(3));");
|
|
||||||
$node->safe_psql("postgres",
|
|
||||||
"INSERT INTO tst SELECT ARRAY[random(), random(), random()] FROM generate_series(1, 1000) i;"
|
|
||||||
);
|
|
||||||
|
|
||||||
my ($ret, $stdout, $stderr) = $node->psql("postgres", qq(
|
|
||||||
SET client_min_messages = DEBUG;
|
|
||||||
SET maintenance_work_mem = '3073kB';
|
|
||||||
ALTER TABLE tst SET (parallel_workers = 1);
|
|
||||||
CREATE INDEX ON tst USING hnsw (v vector_l2_ops);
|
|
||||||
));
|
|
||||||
is($ret, 0, $stderr);
|
|
||||||
like($stderr, qr/using \d+ parallel workers/);
|
|
||||||
like($stderr, qr/hnsw graph no longer fits into maintenance_work_mem after 0 tuples/);
|
|
||||||
|
|
||||||
done_testing();
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
use strict;
|
|
||||||
use warnings FATAL => 'all';
|
|
||||||
use PostgreSQL::Test::Cluster;
|
|
||||||
use PostgreSQL::Test::Utils;
|
|
||||||
use Test::More;
|
|
||||||
|
|
||||||
my $dim = 3;
|
|
||||||
my $array_sql = join(",", ('random()') x $dim);
|
|
||||||
|
|
||||||
# Initialize node
|
|
||||||
my $node = PostgreSQL::Test::Cluster->new('node');
|
|
||||||
$node->init;
|
|
||||||
$node->start;
|
|
||||||
|
|
||||||
# Create table and index
|
|
||||||
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
|
||||||
$node->safe_psql("postgres", "CREATE TABLE tst (i serial, v vector($dim));");
|
|
||||||
$node->safe_psql("postgres", "ALTER TABLE tst SET (autovacuum_enabled = false);");
|
|
||||||
$node->safe_psql("postgres",
|
|
||||||
"INSERT INTO tst (v) SELECT ARRAY[$array_sql] FROM generate_series(1, 1000) i;"
|
|
||||||
);
|
|
||||||
$node->safe_psql("postgres", "CREATE INDEX ON tst USING hnsw (v vector_l2_ops);");
|
|
||||||
$node->safe_psql("postgres", "DELETE FROM tst");
|
|
||||||
|
|
||||||
# Test HNSW_SCAN_LOCK at the beginning of MarkDeleted is effective
|
|
||||||
$node->pgbench(
|
|
||||||
"--no-vacuum --client=5 --transactions=1000",
|
|
||||||
0,
|
|
||||||
[qr{actually processed}],
|
|
||||||
[qr{^$}],
|
|
||||||
"concurrent SELECTs and VACUUM",
|
|
||||||
{
|
|
||||||
"046_hnsw_vacuum_scan_select\@1000" => "SELECT i FROM tst ORDER BY v <-> '[0,0,0]' LIMIT 10;",
|
|
||||||
"046_hnsw_vacuum_scan_vacuum\@1" => "VACUUM tst;"
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
done_testing();
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
use strict;
|
|
||||||
use warnings FATAL => 'all';
|
|
||||||
use PostgreSQL::Test::Cluster;
|
|
||||||
use PostgreSQL::Test::Utils;
|
|
||||||
use Test::More;
|
|
||||||
|
|
||||||
my $dim = 3;
|
|
||||||
my $array_sql = join(",", ('random()') x $dim);
|
|
||||||
|
|
||||||
# Initialize node
|
|
||||||
my $node = PostgreSQL::Test::Cluster->new('node');
|
|
||||||
$node->init;
|
|
||||||
$node->start;
|
|
||||||
|
|
||||||
# Create table and index
|
|
||||||
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
|
||||||
$node->safe_psql("postgres", "CREATE TABLE tst (i serial, v vector($dim));");
|
|
||||||
$node->safe_psql("postgres", "ALTER TABLE tst SET (autovacuum_enabled = false);");
|
|
||||||
$node->safe_psql("postgres",
|
|
||||||
"INSERT INTO tst (v) SELECT ARRAY[$array_sql] FROM generate_series(1, 1000) i;"
|
|
||||||
);
|
|
||||||
$node->safe_psql("postgres", "CREATE INDEX ON tst USING hnsw (v vector_l2_ops);");
|
|
||||||
|
|
||||||
# Test no "hnsw graph not repaired" errors
|
|
||||||
$node->pgbench(
|
|
||||||
"--no-vacuum --client=5 --transactions=1000",
|
|
||||||
0,
|
|
||||||
[qr{actually processed}],
|
|
||||||
[qr{^$}],
|
|
||||||
"concurrent INSERTs, DELETEs, SELECTs, and VACUUM",
|
|
||||||
{
|
|
||||||
"047_hnsw_vacuum_insert_insert\@500" => "INSERT INTO tst (v) VALUES (ARRAY[$array_sql]);",
|
|
||||||
"047_hnsw_vacuum_insert_delete\@500" => "DELETE FROM tst WHERE i = (SELECT i FROM tst LIMIT 1);",
|
|
||||||
"047_hnsw_vacuum_insert_select\@20" => "SELECT i FROM tst ORDER BY v <-> '[0,0,0]' LIMIT 10;",
|
|
||||||
"047_hnsw_vacuum_insert_vacuum\@1" => "VACUUM tst;"
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
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.8.4'
|
default_version = '0.7.4'
|
||||||
module_pathname = '$libdir/vector'
|
module_pathname = '$libdir/vector'
|
||||||
relocatable = true
|
relocatable = true
|
||||||
|
|||||||
Reference in New Issue
Block a user