Compare commits

...

182 Commits

Author SHA1 Message Date
Andrew Kane
a7f712b5a4 Version bump to 0.3.2 [skip ci] 2022-11-22 13:46:33 -08:00
Andrew Kane
a532d82bda Use palloc_extended for halfcdist 2022-11-17 19:52:46 -08:00
Andrew Kane
ccec96ba54 Fixed invalid memory alloc request size error - fixes #43 2022-11-17 14:12:49 -08:00
Andrew Kane
b7c5849ba7 Switched to dev-files option on CI 2022-11-03 15:24:12 -07:00
Andrew Kane
135147893a Updated comment [skip ci] 2022-11-02 16:55:20 -07:00
Andrew Kane
00391953dd Updated comment [skip ci] 2022-11-02 16:54:27 -07:00
Andrew Kane
81bba1f37e Improved order of insert operations 2022-11-02 16:41:56 -07:00
Andrew Kane
2d8b7e5f81 Version bump to 0.3.1 [skip ci] 2022-11-02 15:24:29 -07:00
Andrew Kane
cebae5e5ff Fixed link [skip ci] 2022-11-02 15:21:44 -07:00
Andrew Kane
4c3332fc4d Added upgrade notes [skip ci] 2022-11-02 15:20:53 -07:00
Andrew Kane
386a3b5dd5 Improved test for inserts 2022-11-02 14:29:41 -07:00
Andrew Kane
fb819eb8b2 Added comment [skip ci] 2022-11-02 12:56:32 -07:00
Andrew Kane
3f8407460e Improved test 2022-11-02 12:50:50 -07:00
Andrew Kane
059e88dda5 Fixed randomness in WAL test 2022-11-02 12:41:54 -07:00
Andrew Kane
12f158d4bb Fixed warning 2022-11-02 12:27:09 -07:00
Andrew Kane
2a3c0a769a Ensure only one backend tries to extend relation at the same time - fixes #41 2022-11-02 12:23:49 -07:00
Andrew Kane
349d844fa1 Fixed issue with uninitialized pages - fixes #41 2022-11-02 11:01:47 -07:00
Andrew Kane
a57ca81d8f Switched to runtime check 2022-10-30 12:07:03 -07:00
Andrew Kane
dc4a79a7e7 Added check for indexing overflow 2022-10-30 12:01:24 -07:00
Andrew Kane
1652a130c5 Removed unused assignments 2022-10-30 11:55:17 -07:00
Andrew Kane
a6743bd206 Added .bc to .gitignore 2022-10-30 11:54:58 -07:00
Andrew Kane
e25627074f Removed unused code [skip ci] 2022-10-30 11:49:06 -07:00
Andrew Kane
b3cad93f3a Fixed segmentation fault with index creation when lists > 6500 - fixes #42 2022-10-30 11:14:58 -07:00
Andrew Kane
379a76098e Fixed version in META.json [skip ci] 2022-10-15 14:44:44 -07:00
Andrew Kane
5af768752f Version bump to 0.3.0 [skip ci] 2022-10-15 14:38:43 -07:00
Andrew Kane
f03bcc4fd8 Updated Dockerfile to Postgres 15 [skip ci] 2022-10-14 16:01:25 -07:00
Andrew Kane
74fc4b9aaa Added .pm to .editorconfig [skip ci] 2022-10-14 14:05:20 -07:00
Andrew Kane
6870ba96ba Removed Postgres 9.6 from CI 2022-10-14 13:47:07 -07:00
Andrew Kane
2575cfdaa8 Fixed CI 2022-10-14 13:45:16 -07:00
Andrew Kane
9ff5ceacca Dropped support for Postgres 9.6 [skip ci] 2022-10-14 13:44:13 -07:00
Andrew Kane
9a58ee609a Added Postgres 15 to CI 2022-10-14 13:42:04 -07:00
Andrew Kane
66a31bef36 Simplified define [skip ci] 2022-10-10 15:01:16 -07:00
Andrew Kane
163ecdf063 Use define for RandomDouble 2022-10-10 14:59:10 -07:00
Andrew Kane
c9c6b96eed Added support for Postgres 15 [skip ci] 2022-10-05 16:11:37 -07:00
Andrew Kane
48e9a6830b Added link to pgvector-elixir [skip ci] 2022-08-03 23:12:25 -07:00
Andrew Kane
f22b47ca46 Added link to pgvector-php - #30 [skip ci] 2022-08-03 13:50:34 -07:00
Andrew Kane
2a057c055c Updated example [skip ci] 2022-08-01 00:06:45 -07:00
Andrew Kane
b89b58b893 Updated examples to be runnable - fixes #24 [skip ci] 2022-07-31 22:36:46 -07:00
Andrew Kane
2e77650e1d Updated example [skip ci] 2022-07-31 22:34:03 -07:00
Andrew Kane
0a68b040fb DRY code 2022-07-31 21:56:44 -07:00
Andrew Kane
434dc904c1 Version bump to 0.2.7 [skip ci] 2022-07-31 21:42:15 -07:00
Andrew Kane
92a671eeb1 Revert "Commit pages with tuple"
This reverts commit 94eeb83abe.
2022-07-31 21:36:46 -07:00
Andrew Kane
94eeb83abe Commit pages with tuple 2022-07-31 21:29:29 -07:00
Andrew Kane
6866939090 Fixed indentation [skip ci] 2022-07-31 21:21:06 -07:00
Andrew Kane
ba2fb6ad8c Fixed order in IvfflatAppendPage 2022-07-31 21:03:57 -07:00
Andrew Kane
51fe8aff02 Fixed unexpected data beyond EOF error - fixes #29 2022-07-31 20:48:15 -07:00
Andrew Kane
df3dc151d5 Updated readme [skip ci] 2022-07-27 17:38:13 -07:00
Andrew Kane
6687430589 Fixed CI 2022-07-10 16:30:45 -04:00
Andrew Kane
f128a455d1 Made dimensions configurable for WAL test - #29 2022-07-10 16:01:05 -04:00
Andrew Kane
56cb5f3503 Version bump to 0.2.6 [skip ci] 2022-05-22 18:21:15 -07:00
Andrew Kane
ecbf46938f Moved metrics to separate function [skip ci] 2022-02-15 19:13:23 -08:00
Andrew Kane
4ca264ba02 Added Davies-Bouldin index [skip ci] 2022-02-15 19:08:47 -08:00
Andrew Kane
f5458414b8 Made recall test less flaky 2022-02-15 18:17:05 -08:00
Andrew Kane
e64ed39acb Disabled scan progress for sampling 2022-02-15 18:14:59 -08:00
Andrew Kane
0d025be9d3 Improved performance of index creation for Postgres < 12 2022-02-15 18:05:33 -08:00
Andrew Kane
fed60dce78 Added tests for lists 2022-02-15 17:04:20 -08:00
Andrew Kane
a37f5eea4a Improved compare method [skip ci] 2022-02-15 11:50:35 -08:00
Andrew Kane
4bdb27e85a Fixed style [skip ci] 2022-02-14 23:15:27 -08:00
Andrew Kane
38f869e0bd Added test for 100% recall 2022-02-14 23:13:36 -08:00
Andrew Kane
ad8acc00d4 Fixed spacing [skip ci] 2022-02-14 21:58:20 -08:00
Andrew Kane
01926a418e Print operator name for failed recall test [skip ci] 2022-02-14 21:52:31 -08:00
Andrew Kane
9658d3c1ad Made recall test less flaky 2022-02-14 21:49:08 -08:00
Andrew Kane
6b9c6516f4 Improved code 2022-02-14 21:41:27 -08:00
Andrew Kane
88be03a3fa Removed unused code 2022-02-14 21:37:53 -08:00
Andrew Kane
bf5b2c8d7e Use tuple id directly 2022-02-14 21:31:08 -08:00
Andrew Kane
ac65ec2856 Improved code [skip ci] 2022-02-14 19:38:59 -08:00
Andrew Kane
7bba0e2a01 Test recall for all operators 2022-02-14 15:22:53 -08:00
Andrew Kane
c35e9f3b84 Removed comment [skip ci] 2022-02-13 03:53:30 -08:00
Andrew Kane
9549d93260 Improved recall test 2022-02-13 03:08:39 -08:00
Andrew Kane
310809d0e5 Fixed warnings 2022-02-13 03:03:17 -08:00
Andrew Kane
ce72ca8620 Switched to heap for nearest lists for performance 2022-02-13 02:59:27 -08:00
Andrew Kane
4f2c937a1f Improved recall test 2022-02-13 02:35:28 -08:00
Andrew Kane
ff400ce5f1 Use macro for UpdateProgress 2022-02-12 23:56:28 -08:00
Andrew Kane
41d11c62d6 Fixed style [skip ci] 2022-02-12 22:14:57 -08:00
Andrew Kane
fa5e90585d Added inertia 2022-02-12 21:23:53 -08:00
Andrew Kane
71d2908be1 Fixed style [skip ci] 2022-02-12 20:47:25 -08:00
Andrew Kane
d0a1c1d0ed Added test for centers 2022-02-12 20:44:05 -08:00
Andrew Kane
8063201cdf Fixed indentation [skip ci] 2022-02-12 20:43:48 -08:00
Andrew Kane
2f52127bee Version bump to 0.2.5 [skip ci] 2022-02-11 18:50:04 -08:00
Andrew Kane
4778039ffa Added benchmarking to readme [skip ci] 2022-02-11 16:09:45 -08:00
Andrew Kane
e3c9d70acc Added to existing PG_CFLAGS 2022-02-11 16:06:25 -08:00
Andrew Kane
5971e28f8c Added more benchmarking [skip ci] 2022-02-11 16:05:45 -08:00
Andrew Kane
2da27e2f01 Updated name [skip ci] 2022-02-11 15:52:59 -08:00
Andrew Kane
7dd934913d Improved output [skip ci] 2022-02-11 15:51:14 -08:00
Andrew Kane
9b46987e73 Added support for benchmarking 2022-02-11 15:48:13 -08:00
Andrew Kane
868e1f99a4 Added todo [skip ci] 2022-02-11 15:14:09 -08:00
Andrew Kane
354271178f Added todo [skip ci] 2022-02-11 14:31:42 -08:00
Andrew Kane
fe9b218903 Reduced memory for unlogged tables [skip ci] 2022-02-11 13:57:53 -08:00
Andrew Kane
3cdd1c16fb Improved code [skip ci] 2022-02-11 13:55:39 -08:00
Andrew Kane
4f7a5c92da Use maxlen for check [skip ci] 2022-02-11 13:54:03 -08:00
Andrew Kane
9399575e02 Include samples size and centers size in check 2022-02-11 12:43:01 -08:00
Andrew Kane
e7539fe548 Added new centers size 2022-02-11 11:58:58 -08:00
Andrew Kane
61860ee6d9 Updated changelog [skip ci] 2022-02-11 11:49:25 -08:00
Andrew Kane
eb99730d87 Removed unnecessary cast [skip ci] 2022-02-11 03:43:31 -08:00
Andrew Kane
e5da5cc532 Add one to error message to ceil [skip ci] 2022-02-11 03:41:54 -08:00
Andrew Kane
f59b1184fc Calculate all allocation sizes 2022-02-11 03:33:43 -08:00
Andrew Kane
cab103a2e7 Fixed index creation exceeding maintenance_work_mem and fixed error when lists > 1600 2022-02-11 03:10:19 -08:00
Andrew Kane
0094b70a36 Updated changelog [skip ci] 2022-02-11 03:02:59 -08:00
Andrew Kane
46ed56d80f Use float instead of double for k-means to save memory 2022-02-11 01:56:28 -08:00
Andrew Kane
d390cc9fa6 Increased recall queries in test 2022-02-11 00:45:39 -08:00
Andrew Kane
02ae98791c Improved recall test 2022-02-11 00:43:01 -08:00
Andrew Kane
b64c7d6228 Improved test [skip ci] 2022-02-11 00:15:37 -08:00
Andrew Kane
76f117c81c Added test for array of vectors [skip ci] 2022-02-07 15:25:37 -08:00
Andrew Kane
5414b2bc9e Added link to pgvector-ruby [skip ci] 2022-02-07 00:52:46 -08:00
Andrew Kane
6ba583b587 Improved test style [skip ci] 2022-02-06 20:24:50 -08:00
Andrew Kane
da88217fd8 Fixed test style 2022-02-06 20:19:20 -08:00
Andrew Kane
d5da22f24b Fixed tests 2022-02-06 20:14:21 -08:00
Andrew Kane
062e220936 Test probes 2022-02-06 20:08:53 -08:00
Andrew Kane
3d2b867a58 Added recall test 2022-02-06 19:59:46 -08:00
Andrew Kane
5aeabc3648 Added comments on index handler [skip ci] 2022-02-06 15:26:18 -08:00
Andrew Kane
687ec6d8d7 Added amadjustmembers 2022-02-06 15:21:59 -08:00
Andrew Kane
51dc18d5b7 Added compiler error for unsupported versions of Postgres 2022-02-06 15:15:46 -08:00
Andrew Kane
cb108ebfd2 Removed reindexing section [skip ci] 2022-02-06 12:45:49 -08:00
Andrew Kane
12bc4eb036 Version bump to 0.2.4 [skip ci] 2022-02-06 12:14:43 -08:00
Andrew Kane
fd0e472316 Added test for vacuum 2022-02-06 12:06:27 -08:00
Andrew Kane
9b11b05cd7 Added comment for amcanreturn [skip ci] 2022-02-06 11:28:03 -08:00
Andrew Kane
ab079c1911 Added support for parallel vacuum 2022-02-06 11:25:38 -08:00
Andrew Kane
f4eaa3de1f Fixed issue with index not reusing space 2022-02-06 11:23:10 -08:00
Andrew Kane
e47675d6ae Updated changelog [skip ci] 2022-01-30 20:31:09 -08:00
Andrew Kane
20167ddc33 Updated readme [skip ci] 2022-01-30 20:06:59 -08:00
Andrew Kane
e14fa82626 Version bump to 0.2.3 [skip ci] 2022-01-30 19:59:47 -08:00
Andrew Kane
2ae4de83fe Improved error message for spherical k-means with one dimension 2022-01-30 11:57:52 -08:00
Andrew Kane
5d2c4883dc Fixed undeclared error 2022-01-29 23:28:23 -08:00
Andrew Kane
fc1de9806a Added tuples_done and tuples_total 2022-01-29 23:15:46 -08:00
Andrew Kane
e375dd33f3 Added FAQ to readme [skip ci] 2022-01-29 22:17:38 -08:00
Andrew Kane
b643ef637a Added indexing progress to readme [skip ci] 2022-01-29 20:25:06 -08:00
Andrew Kane
0ee5bfda65 Added comment [skip ci] 2022-01-29 20:12:00 -08:00
Andrew Kane
c5bec2a534 Improved interrupt handling during index creation 2022-01-29 19:50:03 -08:00
Andrew Kane
40f65dc8ba Updated changelog [skip ci] 2022-01-29 19:23:44 -08:00
Andrew Kane
9ec174137f Added build phase progress for Postgres 12+ 2022-01-29 19:19:16 -08:00
Andrew Kane
423e478a85 Fixed Docker link [skip ci] 2022-01-29 00:46:48 -08:00
Andrew Kane
8b301197fe Updated Homebrew tap [skip ci] 2022-01-24 16:55:28 -08:00
Andrew Kane
c02feaa9ad Updated CI 2022-01-24 16:39:01 -08:00
Andrew Kane
729374a9b8 Updated urls [skip ci] 2022-01-24 16:21:51 -08:00
Andrew Kane
a8245d6cda Updated urls [skip ci] 2022-01-24 16:16:54 -08:00
Andrew Kane
5220a6ec7e Updated DigitalOcean link [skip ci] 2022-01-23 19:10:13 -08:00
Andrew Kane
ddd873f369 Version bump to 0.2.2 [skip ci] 2022-01-15 14:23:32 -05:00
Andrew Kane
8735c4782a Fixed compilation on Mac ARM - #22 2022-01-14 01:18:33 -05:00
Andrew Kane
17ec3f3852 Moved test 2022-01-02 19:24:50 -05:00
Andrew Kane
8df12a62e7 Updated changelog [skip ci] 2022-01-02 19:20:00 -05:00
Andrew Kane
647c9002be Version bump to 0.2.1 [skip ci] 2022-01-02 19:11:37 -05:00
Andrew Kane
3e6345f02a Moved function 2022-01-02 19:09:37 -05:00
Andrew Kane
2b3484ddcd Fixed CI 2022-01-02 19:03:15 -05:00
Andrew Kane
1a6debf281 Fixed operator is not unique error - fixes #20 2022-01-02 18:47:03 -05:00
Andrew Kane
bff4d7ea68 Updated link [skip ci] 2021-10-13 14:29:09 -07:00
Andrew Kane
d5b979e4fa Added guidance on lists [skip ci] 2021-10-13 14:27:01 -07:00
Andrew Kane
d52426b3fa Improved examples [skip ci] 2021-10-13 14:06:55 -07:00
Andrew Kane
b125b2debb Added section on performance [skip ci] 2021-10-13 13:56:03 -07:00
Andrew Kane
3d7186a17c Added Docker to Makefile [skip ci] 2021-10-13 13:53:29 -07:00
Andrew Kane
58ba2139ce Added instructions for reindexing - #17 [skip ci] 2021-10-13 13:48:54 -07:00
Andrew Kane
fd93c9db86 Version bump to 0.2.0 [skip ci] 2021-10-03 20:32:45 -07:00
Andrew Kane
8dde26920c Added support for Postgres 14 2021-09-30 16:28:25 -07:00
Andrew Kane
9153a545a7 Added link to pgvector-cpp [skip ci] 2021-09-10 15:22:44 -07:00
Andrew Kane
9c20f04a11 Version bump to 0.1.8 [skip ci] 2021-09-07 14:10:58 -07:00
Andrew Kane
7c12d100ce Fixed build for Postgres < 13 2021-06-20 22:06:14 -07:00
Andrew Kane
d97d9a6561 Added cast for vector to real[] 2021-06-20 21:53:06 -07:00
Andrew Kane
f15ea301c5 Added link to Node library [skip ci] 2021-06-18 15:01:32 -07:00
Andrew Kane
cfa23f2c7a Added note on partitioning [skip ci] 2021-06-17 14:20:43 -07:00
Andrew Kane
b5b7035edc Restored section on partial indexes [skip ci] 2021-06-17 14:05:48 -07:00
Andrew Kane
478d62cdbd Added link to Go library [skip ci] 2021-06-16 17:33:49 -07:00
Andrew Kane
c7df9ef3a1 Updated changelog [skip ci] 2021-06-13 12:58:10 -07:00
Andrew Kane
ea1b094d8e Version bump to 0.1.7 [skip ci] 2021-06-13 12:52:55 -07:00
Andrew Kane
63d2965e8e Added link to pgvector-python [skip ci] 2021-06-12 06:49:44 -07:00
Andrew Kane
e5e7a6ec15 Fixed message [skip ci] 2021-06-11 03:36:06 -07:00
Andrew Kane
ede572d80b Fixed identation [skip ci] 2021-06-11 03:35:17 -07:00
Andrew Kane
154e4334fb Added cast for numeric[] 2021-06-11 03:32:47 -07:00
Andrew Kane
77d54333f6 Updated link [skip ci] 2021-06-09 22:01:38 -07:00
Andrew Kane
c95732340a Version bump to 0.1.6 [skip ci] 2021-06-09 20:48:50 -07:00
Andrew Kane
2af9bdefbb Added link to pgvector.rs [skip ci] 2021-06-09 19:42:40 -07:00
Andrew Kane
bd36231871 Less space [skip ci] 2021-06-09 04:03:06 -07:00
Andrew Kane
523788d964 Improved code 2021-06-09 03:59:00 -07:00
Andrew Kane
1eed57310a Use DBL_MAX for cost 2021-06-09 03:31:41 -07:00
Andrew Kane
57e6a01214 Fixed segmentation fault with COUNT - fixes #9 2021-06-09 03:13:22 -07:00
Andrew Kane
b713cbc3b6 Added instructions for Azure [skip ci] 2021-06-08 23:36:47 -07:00
Andrew Kane
ebeac156c0 Require unused to be 0 2021-05-26 13:40:38 -07:00
Andrew Kane
60bc93f938 Version bump to 0.1.5 [skip ci] 2021-05-25 17:31:02 -07:00
Andrew Kane
87a1b3365e Simplified logic 2021-05-24 18:58:11 -07:00
Andrew Kane
e9a762223b Moved line [skip ci] 2021-05-24 18:48:49 -07:00
Andrew Kane
db83f4faa8 Reduced memory usage during index build - #7 2021-05-24 18:40:34 -07:00
Andrew Kane
2cf71ec6bd Moved test [skip ci] 2021-05-15 00:29:52 -07:00
Andrew Kane
a7510430e3 Added test for max dimensions 2021-05-15 00:28:02 -07:00
Andrew Kane
ea8f06e8f4 Updated readme [skip ci] 2021-05-14 22:05:07 -07:00
49 changed files with 1194 additions and 218 deletions

View File

@@ -1,6 +1,6 @@
root = true
[*.{c,h}]
[*.{c,h,pl,pm}]
indent_style = tab
indent_size = tab
tab_width = 4

View File

@@ -2,23 +2,23 @@ name: build
on: [push, pull_request]
jobs:
build:
if: "!contains(github.event.head_commit.message, '[skip ci]')"
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
postgres: [13, 12, 11, 10, 9.6]
postgres: [15, 14, 13, 12, 11, 10]
include:
- os: macos-latest
postgres: 13
postgres: 14
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: ankane/setup-postgres@v1
with:
postgres-version: ${{ matrix.postgres }}
dev-files: true
- if: ${{ startsWith(matrix.os, 'ubuntu') }}
run: sudo apt-get update && sudo apt-get install postgresql-server-dev-${{ matrix.postgres }} libipc-run-perl
run: sudo apt-get update && sudo apt-get install libipc-run-perl
- run: make
- if: ${{ startsWith(matrix.os, 'ubuntu') }}
run: |
@@ -34,6 +34,6 @@ jobs:
- if: ${{ startsWith(matrix.os, 'macos') }}
run: |
brew install cpanm && cpanm IPC::Run
wget -q https://github.com/postgres/postgres/archive/refs/tags/REL_13_2.tar.gz
tar xf REL_13_2.tar.gz
make prove_installcheck PROVE=prove PERL5LIB=postgres-REL_13_2/src/test/perl
wget -q https://github.com/postgres/postgres/archive/refs/tags/REL_14_5.tar.gz
tar xf REL_14_5.tar.gz
make prove_installcheck PROVE=prove PROVE_FLAGS="-I ./postgres-REL_14_5/src/test/perl" PERL5LIB="/Users/runner/perl5/lib/perl5"

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@
regression.*
*.o
*.so
*.bc

View File

@@ -1,3 +1,71 @@
## 0.3.2 (2022-11-22)
- Fixed `invalid memory alloc request size` error
## 0.3.1 (2022-11-02)
If upgrading from 0.2.7 or 0.3.0, [recreate](https://github.com/pgvector/pgvector#031) all `ivfflat` indexes after upgrading to ensure all data is indexed.
- Fixed issue with inserts silently corrupting `ivfflat` indexes (introduced in 0.2.7)
- Fixed segmentation fault with index creation when lists > 6500
## 0.3.0 (2022-10-15)
- Added support for Postgres 15
- Dropped support for Postgres 9.6
## 0.2.7 (2022-07-31)
- Fixed `unexpected data beyond EOF` error
## 0.2.6 (2022-05-22)
- Improved performance of index creation for Postgres < 12
## 0.2.5 (2022-02-11)
- Reduced memory usage during index creation
- Fixed index creation exceeding `maintenance_work_mem`
- Fixed error with index creation when lists > 1600
## 0.2.4 (2022-02-06)
- Added support for parallel vacuum
- Fixed issue with index not reusing space
## 0.2.3 (2022-01-30)
- Added indexing progress for Postgres 12+
- Improved interrupt handling during index creation
## 0.2.2 (2022-01-15)
- Fixed compilation error on Mac ARM
## 0.2.1 (2022-01-02)
- Fixed `operator is not unique` error
## 0.2.0 (2021-10-03)
- Added support for Postgres 14
## 0.1.8 (2021-09-07)
- Added cast for `vector` to `real[]`
## 0.1.7 (2021-06-13)
- Added cast for `numeric[]` to `vector`
## 0.1.6 (2021-06-09)
- Fixed segmentation fault with `COUNT`
## 0.1.5 (2021-05-25)
- Reduced memory usage during index creation
## 0.1.4 (2021-05-09)
- Fixed kmeans for inner product

View File

@@ -1,9 +1,9 @@
FROM postgres
FROM postgres:15
COPY . /tmp/pgvector
RUN apt-get update && \
apt-get install -y --no-install-recommends build-essential postgresql-server-dev-13 && \
apt-get install -y --no-install-recommends build-essential postgresql-server-dev-15 && \
cd /tmp/pgvector && \
make clean && \
make OPTFLAGS="" && \
@@ -11,6 +11,6 @@ RUN apt-get update && \
mkdir /usr/share/doc/pgvector && \
cp LICENSE README.md /usr/share/doc/pgvector && \
rm -r /tmp/pgvector && \
apt-get remove -y build-essential postgresql-server-dev-13 && \
apt-get remove -y build-essential postgresql-server-dev-15 && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/*

View File

@@ -2,7 +2,7 @@
"name": "vector",
"abstract": "Open-source vector similarity search for Postgres",
"description": "Supports L2 distance, inner product, and cosine distance",
"version": "0.1.4",
"version": "0.3.2",
"maintainer": [
"Andrew Kane <andrew@ankane.org>"
],
@@ -12,7 +12,7 @@
"prereqs": {
"runtime": {
"requires": {
"PostgreSQL": "9.6.0"
"PostgreSQL": "10.0.0"
}
}
},
@@ -20,18 +20,18 @@
"vector": {
"file": "sql/vector.sql",
"docfile": "README.md",
"version": "0.1.4",
"version": "0.3.2",
"abstract": "Open-source vector similarity search for Postgres"
}
},
"resources": {
"homepage": "https://github.com/ankane/pgvector",
"homepage": "https://github.com/pgvector/pgvector",
"bugtracker": {
"web": "https://github.com/ankane/pgvector/issues"
"web": "https://github.com/pgvector/pgvector/issues"
},
"repository": {
"url": "https://github.com/ankane/pgvector.git",
"web": "https://github.com/ankane/pgvector",
"url": "https://github.com/pgvector/pgvector.git",
"web": "https://github.com/pgvector/pgvector",
"type": "git"
}
},

View File

@@ -1,5 +1,5 @@
EXTENSION = vector
EXTVERSION = 0.1.4
EXTVERSION = 0.3.2
MODULE_big = vector
DATA = $(wildcard sql/*--*.sql)
@@ -11,10 +11,17 @@ REGRESS_OPTS = --inputdir=test
OPTFLAGS = -march=native
# Mac ARM doesn't support -march=native
ifeq ($(shell uname -s), Darwin)
ifeq ($(shell uname -p), arm)
OPTFLAGS =
endif
endif
# For auto-vectorization:
# - 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
PG_CFLAGS = $(OPTFLAGS) -ftree-vectorize -fassociative-math -fno-signed-zeros -fno-trapping-math
PG_CFLAGS += $(OPTFLAGS) -ftree-vectorize -fassociative-math -fno-signed-zeros -fno-trapping-math
# Debug GCC auto-vectorization
# PG_CFLAGS += -fopt-info-vec
@@ -33,6 +40,9 @@ PG_CONFIG ?= pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
# for Postgres 15
PROVE_FLAGS += -I ./test/perl
prove_installcheck:
rm -rf $(CURDIR)/tmp_check
cd $(srcdir) && TESTDIR='$(CURDIR)' PATH="$(bindir):$$PATH" PGPORT='6$(DEF_PGPORT)' PG_REGRESS='$(top_builddir)/src/test/regress/pg_regress' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),test/t/*.pl)
@@ -42,3 +52,8 @@ prove_installcheck:
dist:
mkdir -p dist
git archive --format zip --prefix=$(EXTENSION)-$(EXTVERSION)/ --output dist/$(EXTENSION)-$(EXTVERSION).zip master
.PHONY: docker
docker:
docker build --pull --no-cache -t ankane/pgvector:latest .

154
README.md
View File

@@ -3,21 +3,21 @@
Open-source vector similarity search for Postgres
```sql
CREATE TABLE table (column vector(3));
CREATE INDEX ON table USING ivfflat (column);
SELECT * FROM table ORDER BY column <-> '[1,2,3]' LIMIT 5;
CREATE TABLE items (embedding vector(3));
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops);
SELECT * FROM items ORDER BY embedding <-> '[1,2,3]' LIMIT 5;
```
Supports L2 distance, inner product, and cosine distance
[![Build Status](https://github.com/ankane/pgvector/workflows/build/badge.svg?branch=master)](https://github.com/ankane/pgvector/actions)
[![Build Status](https://github.com/pgvector/pgvector/workflows/build/badge.svg?branch=master)](https://github.com/pgvector/pgvector/actions)
## Installation
Compile and install the extension (supports Postgres 9.6+)
Compile and install the extension (supports Postgres 10+)
```sh
git clone --branch v0.1.4 https://github.com/ankane/pgvector.git
git clone --branch v0.3.2 https://github.com/pgvector/pgvector.git
cd pgvector
make
make install # may need sudo
@@ -33,22 +33,22 @@ You can also install it with [Docker](#docker), [Homebrew](#homebrew), or [PGXN]
## Getting Started
Create a vector column with 3 dimensions (replace `table` and `column` with non-reserved names)
Create a vector column with 3 dimensions
```sql
CREATE TABLE table (column vector(3));
CREATE TABLE items (embedding vector(3));
```
Insert values
```sql
INSERT INTO table VALUES ('[1,2,3]'), ('[4,5,6]');
INSERT INTO items VALUES ('[1,2,3]'), ('[4,5,6]');
```
Get the nearest neighbor by L2 distance
```sql
SELECT * FROM table ORDER BY column <-> '[3,1,2]' LIMIT 1;
SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 1;
```
Also supports inner product (`<#>`) and cosine distance (`<=>`)
@@ -62,31 +62,33 @@ Speed up queries with an approximate index. Add an index for each distance funct
L2 distance
```sql
CREATE INDEX ON table USING ivfflat (column);
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops);
```
Inner product
```sql
CREATE INDEX ON table USING ivfflat (column vector_ip_ops);
CREATE INDEX ON items USING ivfflat (embedding vector_ip_ops);
```
Cosine distance
```sql
CREATE INDEX ON table USING ivfflat (column vector_cosine_ops);
CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops);
```
Indexes should be created after the table has data for optimal clustering. Also, unlike typical indexes which only affect performance, you may see different results for queries after adding an approximate index.
Indexes should be created after the table has some data for optimal clustering. Also, unlike typical indexes which only affect performance, you may see different results for queries after adding an approximate index.
### Index Options
Specify the number of inverted lists (100 by default)
```sql
CREATE INDEX ON table USING ivfflat (column) WITH (lists = 100);
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);
```
A [good place to start](https://github.com/facebookresearch/faiss/issues/112) is `4 * sqrt(rows)`
### Query Options
Specify the number of probes (1 by default)
@@ -106,6 +108,58 @@ SELECT ...
COMMIT;
```
### Indexing Progress
Check [indexing progress](https://www.postgresql.org/docs/current/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING) with Postgres 12+
```sql
SELECT phase, tuples_done, tuples_total FROM pg_stat_progress_create_index;
```
The phases are:
1. `initializing`
2. `sampling table`
3. `performing k-means`
4. `sorting tuples`
5. `loading tuples`
Note: `tuples_done` and `tuples_total` are only populated during the `loading tuples` phase
### Partial Indexes
Consider [partial indexes](https://www.postgresql.org/docs/current/indexes-partial.html) for queries with a `WHERE` clause
```sql
SELECT * FROM items WHERE category_id = 123 ORDER BY embedding <-> '[3,1,2]' LIMIT 5;
```
can be indexed with:
```sql
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WHERE (category_id = 123);
```
To index many different values of `category_id`, consider [partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html) on `category_id`.
```sql
CREATE TABLE items (embedding vector(3), category_id int) PARTITION BY LIST(category_id);
```
## Performance
To speed up queries without an index, increase `max_parallel_workers_per_gather`.
```sql
SET max_parallel_workers_per_gather = 4;
```
To speed up queries with an index, increase the number of inverted lists (at the expense of recall).
```sql
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 1000);
```
## Reference
### Vector Type
@@ -136,13 +190,38 @@ vector_norm(vector) | Euclidean norm
Libraries that use pgvector:
- [pgvector-python](https://github.com/pgvector/pgvector-python) (Python)
- [Neighbor](https://github.com/ankane/neighbor) (Ruby)
- [pgvector-ruby](https://github.com/pgvector/pgvector-ruby) (Ruby)
- [pgvector-node](https://github.com/pgvector/pgvector-node) (Node.js)
- [pgvector-go](https://github.com/pgvector/pgvector-go) (Go)
- [pgvector-php](https://github.com/pgvector/pgvector-php) (PHP)
- [pgvector-rust](https://github.com/pgvector/pgvector-rust) (Rust)
- [pgvector-cpp](https://github.com/pgvector/pgvector-cpp) (C++)
- [pgvector-elixir](https://github.com/pgvector/pgvector-elixir) (Elixir)
## Frequently Asked Questions
#### How many vectors can be stored in a single table?
A non-partitioned table has a limit of 32 TB by default in Postgres. A partitioned table can have thousands of partitions of that size.
#### Is replication supported?
Yes, pgvector uses the write-ahead log (WAL), which allows for replication and point-in-time recovery.
#### What if my data has more than 1024 dimensions?
Two things you can try are:
1. use dimensionality reduction
2. compile Postgres with a larger block size (`./configure --with-blocksize=32`) and edit the limit in `src/vector.h`
## Additional Installation Methods
### Docker
Get the [Docker image](https://hub.docker.com/repository/docker/ankane/pgvector) with:
Get the [Docker image](https://hub.docker.com/r/ankane/pgvector) with:
```sh
docker pull ankane/pgvector
@@ -153,17 +232,17 @@ This adds pgvector to the [Postgres image](https://hub.docker.com/_/postgres).
You can also build the image manually
```sh
git clone --branch v0.1.4 https://github.com/ankane/pgvector.git
git clone --branch v0.3.2 https://github.com/pgvector/pgvector.git
cd pgvector
docker build -t pgvector .
```
### Homebrew
On Mac with Homebrew Postgres, you can use:
With Homebrew Postgres, you can use:
```sh
brew install ankane/brew/pgvector
brew install pgvector/brew/pgvector
```
### PGXN
@@ -180,16 +259,33 @@ Some Postgres providers only support specific extensions. To request a new exten
- Amazon RDS - follow the instructions on [this page](https://aws.amazon.com/rds/postgresql/faqs/)
- Google Cloud SQL - follow the instructions on [this page](https://cloud.google.com/sql/docs/postgres/extensions#requesting-support-for-a-new-extension)
- DigitalOcean Managed Databases - follow the instructions on [this page](https://docs.digitalocean.com/products/databases/postgresql/resources/supported-extensions/#supported-extensions)
- DigitalOcean Managed Databases - vote or comment on [this page](https://ideas.digitalocean.com/app-framework-services/p/pgvector-extension-for-postgresql)
- Azure Database for PostgreSQL - follow the instructions on [this page](https://docs.microsoft.com/en-us/azure/postgresql/concepts-extensions#next-steps)
## Upgrading
Compile and install the latest version and run:
Install the latest version and run:
```sql
ALTER EXTENSION vector UPDATE;
```
## Upgrade Notes
### 0.3.1
If upgrading from 0.2.7 or 0.3.0, recreate all `ivfflat` indexes after upgrading to ensure all data is indexed.
```sql
-- Postgres 12+
REINDEX INDEX CONCURRENTLY index_name;
-- Postgres < 12
CREATE INDEX CONCURRENTLY temp_name ON table USING ivfflat (column opclass);
DROP INDEX CONCURRENTLY index_name;
ALTER INDEX temp_name RENAME TO index_name;
```
## Thanks
Thanks to:
@@ -202,21 +298,21 @@ Thanks to:
## History
View the [changelog](https://github.com/ankane/pgvector/blob/master/CHANGELOG.md)
View the [changelog](https://github.com/pgvector/pgvector/blob/master/CHANGELOG.md)
## Contributing
Everyone is encouraged to help improve this project. Here are a few ways you can help:
- [Report bugs](https://github.com/ankane/pgvector/issues)
- Fix bugs and [submit pull requests](https://github.com/ankane/pgvector/pulls)
- [Report bugs](https://github.com/pgvector/pgvector/issues)
- Fix bugs and [submit pull requests](https://github.com/pgvector/pgvector/pulls)
- Write, clarify, or fix documentation
- Suggest or add new features
To get started with development:
```sh
git clone https://github.com/ankane/pgvector.git
git clone https://github.com/pgvector/pgvector.git
cd pgvector
make
make install
@@ -232,10 +328,16 @@ make prove_installcheck # TAP tests
To run single tests:
```sh
make installcheck REGRESS=vector # regression test
make installcheck REGRESS=functions # regression test
make prove_installcheck PROVE_TESTS=test/t/001_wal.pl # TAP test
```
To enable benchmarking:
```sh
make clean && PG_CFLAGS=-DIVFFLAT_BENCH make && make install
```
Resources for contributors
- [Extension Building Infrastructure](https://www.postgresql.org/docs/current/extend-pgxs.html)

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "ALTER EXTENSION vector UPDATE TO '0.1.7'" to load this file. \quit
CREATE FUNCTION array_to_vector(numeric[], integer, boolean) RETURNS vector
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE CAST (numeric[] AS vector)
WITH FUNCTION array_to_vector(numeric[], integer, boolean) AS IMPLICIT;

View File

@@ -0,0 +1,8 @@
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "ALTER EXTENSION vector UPDATE TO '0.1.8'" to load this file. \quit
CREATE FUNCTION vector_to_float4(vector, integer, boolean) RETURNS real[]
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE CAST (vector AS real[])
WITH FUNCTION vector_to_float4(vector, integer, boolean) AS IMPLICIT;

View File

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

View File

@@ -0,0 +1,19 @@
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "ALTER EXTENSION vector UPDATE TO '0.2.1'" to load this file. \quit
DROP CAST (integer[] AS vector);
DROP CAST (real[] AS vector);
DROP CAST (double precision[] AS vector);
DROP CAST (numeric[] AS vector);
CREATE CAST (integer[] AS vector)
WITH FUNCTION array_to_vector(integer[], integer, boolean) AS ASSIGNMENT;
CREATE CAST (real[] AS vector)
WITH FUNCTION array_to_vector(real[], integer, boolean) AS ASSIGNMENT;
CREATE CAST (double precision[] AS vector)
WITH FUNCTION array_to_vector(double precision[], integer, boolean) AS ASSIGNMENT;
CREATE CAST (numeric[] AS vector)
WITH FUNCTION array_to_vector(numeric[], integer, boolean) AS ASSIGNMENT;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -97,19 +97,31 @@ CREATE FUNCTION array_to_vector(real[], integer, boolean) RETURNS vector
CREATE FUNCTION array_to_vector(double precision[], integer, boolean) RETURNS vector
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION array_to_vector(numeric[], integer, boolean) RETURNS vector
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION vector_to_float4(vector, integer, boolean) RETURNS real[]
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
-- casts
CREATE CAST (vector AS vector)
WITH FUNCTION vector(vector, integer, boolean) AS IMPLICIT;
CREATE CAST (vector AS real[])
WITH FUNCTION vector_to_float4(vector, integer, boolean) AS IMPLICIT;
CREATE CAST (integer[] AS vector)
WITH FUNCTION array_to_vector(integer[], integer, boolean) AS IMPLICIT;
WITH FUNCTION array_to_vector(integer[], integer, boolean) AS ASSIGNMENT;
CREATE CAST (real[] AS vector)
WITH FUNCTION array_to_vector(real[], integer, boolean) AS IMPLICIT;
WITH FUNCTION array_to_vector(real[], integer, boolean) AS ASSIGNMENT;
CREATE CAST (double precision[] AS vector)
WITH FUNCTION array_to_vector(double precision[], integer, boolean) AS IMPLICIT;
WITH FUNCTION array_to_vector(double precision[], integer, boolean) AS ASSIGNMENT;
CREATE CAST (numeric[] AS vector)
WITH FUNCTION array_to_vector(numeric[], integer, boolean) AS ASSIGNMENT;
-- operators

View File

@@ -7,8 +7,19 @@
#include "miscadmin.h"
#include "storage/bufmgr.h"
#if PG_VERSION_NUM >= 140000
#include "utils/backend_progress.h"
#elif PG_VERSION_NUM >= 120000
#include "pgstat.h"
#endif
#if PG_VERSION_NUM >= 120000
#include "access/tableam.h"
#include "commands/progress.h"
#else
#define PROGRESS_CREATEIDX_SUBPHASE 0
#define PROGRESS_CREATEIDX_TUPLES_TOTAL 0
#define PROGRESS_CREATEIDX_TUPLES_DONE 0
#endif
#if PG_VERSION_NUM >= 110000
@@ -25,6 +36,12 @@
#define CALLBACK_ITEM_POINTER HeapTuple hup
#endif
#if PG_VERSION_NUM >= 120000
#define UpdateProgress(index, val) pgstat_progress_update_param(index, val)
#else
#define UpdateProgress(index, val) ((void)val)
#endif
/*
* Callback for sampling
*/
@@ -63,7 +80,11 @@ SampleCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
if (buildstate->rowstoskip <= 0)
{
#if PG_VERSION_NUM >= 150000
int k = (int) (targsamples * sampler_random_fract(&buildstate->rstate.randstate));
#else
int k = (int) (targsamples * sampler_random_fract(buildstate->rstate.randstate));
#endif
Assert(k >= 0 && k < targsamples);
VectorArraySet(samples, k, DatumGetVector(value));
@@ -82,6 +103,8 @@ SampleRows(IvfflatBuildState * buildstate)
int targsamples = buildstate->samples->maxlen;
BlockNumber totalblocks = RelationGetNumberOfBlocks(buildstate->heap);
UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_SAMPLE);
buildstate->rowstoskip = -1;
BlockSampler_Init(&buildstate->bs, totalblocks, targsamples, random());
@@ -93,13 +116,13 @@ SampleRows(IvfflatBuildState * buildstate)
#if PG_VERSION_NUM >= 120000
table_index_build_range_scan(buildstate->heap, buildstate->index, buildstate->indexInfo,
false, true, true, targblock, 1, SampleCallback, (void *) buildstate, NULL);
false, true, false, targblock, 1, SampleCallback, (void *) buildstate, NULL);
#elif PG_VERSION_NUM >= 110000
IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
true, true, targblock, 1, SampleCallback, (void *) buildstate, NULL);
false, true, targblock, 1, SampleCallback, (void *) buildstate, NULL);
#else
IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
true, true, targblock, 1, SampleCallback, (void *) buildstate);
false, true, targblock, 1, SampleCallback, (void *) buildstate);
#endif
}
}
@@ -146,16 +169,20 @@ BuildCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
}
}
#ifdef IVFFLAT_KMEANS_DEBUG
buildstate->inertia += minDistance;
buildstate->listSums[closestCenter] += minDistance;
buildstate->listCounts[closestCenter]++;
#endif
/* Create a virtual tuple */
ExecClearTuple(slot);
slot->tts_values[0] = Int32GetDatum(closestCenter);
slot->tts_isnull[0] = false;
slot->tts_values[1] = Int32GetDatum(ItemPointerGetBlockNumberNoCheck(tid));
slot->tts_values[1] = PointerGetDatum(tid);
slot->tts_isnull[1] = false;
slot->tts_values[2] = Int32GetDatum(ItemPointerGetOffsetNumberNoCheck(tid));
slot->tts_values[2] = value;
slot->tts_isnull[2] = false;
slot->tts_values[3] = value;
slot->tts_isnull[3] = false;
ExecStoreVirtualTuple(slot);
/*
@@ -165,6 +192,8 @@ BuildCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
* need not save it.
*/
tuplesort_puttupleslot(buildstate->sortstate, slot);
buildstate->indtuples++;
}
/*
@@ -175,8 +204,6 @@ GetNextTuple(Tuplesortstate *sortstate, TupleDesc tupdesc, TupleTableSlot *slot,
{
Datum value;
bool isnull;
int tupblk;
int tupoff;
#if PG_VERSION_NUM >= 100000
if (tuplesort_gettupleslot(sortstate, true, false, slot, NULL))
@@ -185,13 +212,11 @@ GetNextTuple(Tuplesortstate *sortstate, TupleDesc tupdesc, TupleTableSlot *slot,
#endif
{
*list = DatumGetInt32(slot_getattr(slot, 1, &isnull));
tupblk = DatumGetInt32(slot_getattr(slot, 2, &isnull));
tupoff = DatumGetInt32(slot_getattr(slot, 3, &isnull));
value = slot_getattr(slot, 4, &isnull);
value = slot_getattr(slot, 3, &isnull);
/* Form the index tuple */
*itup = index_form_tuple(tupdesc, &value, &isnull);
ItemPointerSet(&(*itup)->t_tid, tupblk, tupoff);
(*itup)->t_tid = *((ItemPointer) DatumGetPointer(slot_getattr(slot, 2, &isnull)));
}
else
*list = -1;
@@ -208,10 +233,11 @@ InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum)
GenericXLogState *state;
int list;
IndexTuple itup = NULL; /* silence compiler warning */
BlockNumber startPage = InvalidBlockNumber;
BlockNumber insertPage = InvalidBlockNumber;
BlockNumber startPage;
BlockNumber insertPage;
Size itemsz;
int i;
int64 inserted = 0;
#if PG_VERSION_NUM >= 120000
TupleTableSlot *slot = MakeSingleTupleTableSlot(buildstate->tupdesc, &TTSOpsMinimalTuple);
@@ -220,12 +246,20 @@ InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum)
#endif
TupleDesc tupdesc = RelationGetDescr(index);
UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_LOAD);
UpdateProgress(PROGRESS_CREATEIDX_TUPLES_TOTAL, buildstate->indtuples);
GetNextTuple(buildstate->sortstate, tupdesc, slot, &itup, &list);
for (i = 0; i < buildstate->centers->length; i++)
{
/* Can take a while, so ensure we can interrupt */
/* Needs to be called when no buffer locks are held */
CHECK_FOR_INTERRUPTS();
buf = IvfflatNewBuffer(index, forkNum);
IvfflatInitPage(index, &buf, &page, &state);
IvfflatInitRegisterPage(index, &buf, &page, &state);
startPage = BufferGetBlockNumber(buf);
@@ -241,7 +275,9 @@ InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum)
if (PageAddItem(page, (Item) itup, itemsz, InvalidOffsetNumber, false, false) == InvalidOffsetNumber)
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
buildstate->indtuples += 1;
pfree(itup);
UpdateProgress(PROGRESS_CREATEIDX_TUPLES_DONE, ++inserted);
GetNextTuple(buildstate->sortstate, tupdesc, slot, &itup, &list);
}
@@ -251,7 +287,7 @@ InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum)
IvfflatCommitBuffer(buf, state);
/* Set the start and insert pages */
IvfflatUpdateList(index, state, buildstate->listInfo[i], insertPage, startPage, forkNum);
IvfflatUpdateList(index, state, buildstate->listInfo[i], insertPage, InvalidBlockNumber, startPage, forkNum);
}
}
@@ -281,19 +317,24 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
buildstate->kmeansnormprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC);
buildstate->collation = index->rd_indcollation[0];
/* Require more than one dimension for spherical k-means */
/* Lists check for backwards compatibility */
/* TODO Remove lists check in 0.3.0 */
if (buildstate->kmeansnormprocinfo != NULL && buildstate->dimensions == 1 && buildstate->lists > 1)
elog(ERROR, "dimensions must be greater than one for this opclass");
/* Create tuple description for sorting */
#if PG_VERSION_NUM >= 120000
buildstate->tupdesc = CreateTemplateTupleDesc(4);
buildstate->tupdesc = CreateTemplateTupleDesc(3);
#else
buildstate->tupdesc = CreateTemplateTupleDesc(4, false);
buildstate->tupdesc = CreateTemplateTupleDesc(3, false);
#endif
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 1, "list", INT4OID, -1, 0);
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 2, "blkno", INT4OID, -1, 0);
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 3, "offset", INT4OID, -1, 0);
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0);
#if PG_VERSION_NUM >= 110000
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 4, "vector", RelationGetDescr(index)->attrs[0].atttypid, -1, 0);
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 3, "vector", RelationGetDescr(index)->attrs[0].atttypid, -1, 0);
#else
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 4, "vector", RelationGetDescr(index)->attrs[0]->atttypid, -1, 0);
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 3, "vector", RelationGetDescr(index)->attrs[0]->atttypid, -1, 0);
#endif
#if PG_VERSION_NUM >= 120000
@@ -307,6 +348,12 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
/* Reuse for each tuple */
buildstate->normvec = InitVector(buildstate->dimensions);
#ifdef IVFFLAT_KMEANS_DEBUG
buildstate->inertia = 0;
buildstate->listSums = palloc0(sizeof(double) * buildstate->lists);
buildstate->listCounts = palloc0(sizeof(int) * buildstate->lists);
#endif
}
/*
@@ -318,6 +365,11 @@ FreeBuildState(IvfflatBuildState * buildstate)
pfree(buildstate->centers);
pfree(buildstate->listInfo);
pfree(buildstate->normvec);
#ifdef IVFFLAT_KMEANS_DEBUG
pfree(buildstate->listSums);
pfree(buildstate->listCounts);
#endif
}
/*
@@ -334,13 +386,19 @@ ComputeCenters(IvfflatBuildState * buildstate)
if (numSamples < 10000)
numSamples = 10000;
/* Sample samples */
/* Skip samples for unlogged table */
if (buildstate->heap == NULL)
numSamples = 1;
/* Sample rows */
/* TODO Ensure within maintenance_work_mem */
buildstate->samples = VectorArrayInit(numSamples, buildstate->dimensions);
if (buildstate->heap != NULL)
SampleRows(buildstate);
/* Calculate centers */
IvfflatKmeans(buildstate->index, buildstate->samples, buildstate->centers);
UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_KMEANS);
IvfflatBench("k-means", IvfflatKmeans(buildstate->index, buildstate->samples, buildstate->centers));
/* Free samples before we allocate more memory */
pfree(buildstate->samples);
@@ -358,7 +416,7 @@ CreateMetaPage(Relation index, int dimensions, int lists, ForkNumber forkNum)
IvfflatMetaPage metap;
buf = IvfflatNewBuffer(index, forkNum);
IvfflatInitPage(index, &buf, &page, &state);
IvfflatInitRegisterPage(index, &buf, &page, &state);
/* Set metapage data */
metap = IvfflatPageGetMeta(page);
@@ -391,7 +449,7 @@ CreateListPages(Relation index, VectorArray centers, int dimensions,
list = palloc(itemsz);
buf = IvfflatNewBuffer(index, forkNum);
IvfflatInitPage(index, &buf, &page, &state);
IvfflatInitRegisterPage(index, &buf, &page, &state);
for (i = 0; i < lists; i++)
{
@@ -419,6 +477,51 @@ CreateListPages(Relation index, VectorArray centers, int dimensions,
pfree(list);
}
/*
* Print k-means metrics
*/
#ifdef IVFFLAT_KMEANS_DEBUG
static void
PrintKmeansMetrics(IvfflatBuildState * buildstate)
{
elog(INFO, "inertia: %.3e", buildstate->inertia);
/* Calculate Davies-Bouldin index */
if (buildstate->lists > 1)
{
double db = 0.0;
/* Calculate average distance */
for (int i = 0; i < buildstate->lists; i++)
{
if (buildstate->listCounts[i] > 0)
buildstate->listSums[i] /= buildstate->listCounts[i];
}
for (int i = 0; i < buildstate->lists; i++)
{
double max = 0.0;
double distance;
for (int j = 0; j < buildstate->lists; j++)
{
if (j == i)
continue;
distance = DatumGetFloat8(FunctionCall2Coll(buildstate->procinfo, buildstate->collation, PointerGetDatum(VectorArrayGet(buildstate->centers, i)), PointerGetDatum(VectorArrayGet(buildstate->centers, j))));
distance = (buildstate->listSums[i] + buildstate->listSums[j]) / distance;
if (distance > max)
max = distance;
}
db += max;
}
db /= buildstate->lists;
elog(INFO, "davies-bouldin: %.3f", db);
}
}
#endif
/*
* Create entry pages
*/
@@ -430,6 +533,8 @@ CreateEntryPages(IvfflatBuildState * buildstate, ForkNumber forkNum)
Oid sortCollations[] = {InvalidOid};
bool nullsFirstFlags[] = {false};
UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_SORT);
#if PG_VERSION_NUM >= 110000
buildstate->sortstate = tuplesort_begin_heap(buildstate->tupdesc, 1, attNums, sortOperators, sortCollations, nullsFirstFlags, maintenance_work_mem, NULL, false);
#else
@@ -451,8 +556,14 @@ CreateEntryPages(IvfflatBuildState * buildstate, ForkNumber forkNum)
#endif
}
/* Sort and insert */
/* Sort */
tuplesort_performsort(buildstate->sortstate);
#ifdef IVFFLAT_KMEANS_DEBUG
PrintKmeansMetrics(buildstate);
#endif
/* Insert */
InsertTuples(buildstate->index, buildstate, forkNum);
tuplesort_end(buildstate->sortstate);
}
@@ -471,7 +582,7 @@ BuildIndex(Relation heap, Relation index, IndexInfo *indexInfo,
/* Create pages */
CreateMetaPage(index, buildstate->dimensions, buildstate->lists, forkNum);
CreateListPages(index, buildstate->centers, buildstate->dimensions, buildstate->lists, forkNum, &buildstate->listInfo);
CreateEntryPages(buildstate, forkNum);
IvfflatBench("CreateEntryPages", CreateEntryPages(buildstate, forkNum));
FreeBuildState(buildstate);
}

View File

@@ -1,11 +1,17 @@
#include "postgres.h"
#include <float.h>
#include "access/amapi.h"
#include "commands/vacuum.h"
#include "ivfflat.h"
#include "utils/guc.h"
#include "utils/selfuncs.h"
#if PG_VERSION_NUM >= 120000
#include "commands/progress.h"
#endif
int ivfflat_probes;
static relopt_kind ivfflat_relopt_kind;
@@ -28,6 +34,31 @@ _PG_init(void)
1, 1, IVFFLAT_MAX_LISTS, PGC_USERSET, 0, NULL, NULL, NULL);
}
/*
* Get the name of index build phase
*/
#if PG_VERSION_NUM >= 120000
static char *
ivfflatbuildphasename(int64 phasenum)
{
switch (phasenum)
{
case PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE:
return "initializing";
case PROGRESS_IVFFLAT_PHASE_SAMPLE:
return "sampling table";
case PROGRESS_IVFFLAT_PHASE_KMEANS:
return "performing k-means";
case PROGRESS_IVFFLAT_PHASE_SORT:
return "sorting tuples";
case PROGRESS_IVFFLAT_PHASE_LOAD:
return "loading tuples";
default:
return NULL;
}
}
#endif
/*
* Estimate the cost of an index scan
*/
@@ -44,18 +75,29 @@ ivfflatcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
int lists;
double ratio;
Relation indexRel;
#if PG_VERSION_NUM < 120000
List *qinfos;
qinfos = deconstruct_indexquals(path);
#endif
/* Never use index without order */
if (path->indexorderbys == NULL)
{
*indexStartupCost = DBL_MAX;
*indexTotalCost = DBL_MAX;
*indexSelectivity = 0;
*indexCorrelation = 0;
#if PG_VERSION_NUM >= 100000
*indexPages = 0;
#endif
return;
}
MemSet(&costs, 0, sizeof(costs));
#if PG_VERSION_NUM >= 120000
genericcostestimate(root, path, loop_count, &costs);
#else
qinfos = deconstruct_indexquals(path);
genericcostestimate(root, path, loop_count, qinfos, &costs);
#endif
@@ -117,6 +159,11 @@ ivfflatvalidate(Oid opclassoid)
return true;
}
/*
* Define index handler
*
* See https://www.postgresql.org/docs/current/index-api.html
*/
PG_FUNCTION_INFO_V1(ivfflathandler);
Datum
ivfflathandler(PG_FUNCTION_ARGS)
@@ -147,23 +194,27 @@ ivfflathandler(PG_FUNCTION_ARGS)
#endif
#if PG_VERSION_NUM >= 130000
amroutine->amusemaintenanceworkmem = false; /* not used during VACUUM */
amroutine->amparallelvacuumoptions = VACUUM_OPTION_NO_PARALLEL; /* TODO support parallel */
amroutine->amparallelvacuumoptions = VACUUM_OPTION_PARALLEL_BULKDEL;
#endif
amroutine->amkeytype = InvalidOid;
/* Interface functions */
amroutine->ambuild = ivfflatbuild;
amroutine->ambuildempty = ivfflatbuildempty;
amroutine->aminsert = ivfflatinsert;
amroutine->ambulkdelete = ivfflatbulkdelete;
amroutine->amvacuumcleanup = ivfflatvacuumcleanup;
amroutine->amcanreturn = NULL;
amroutine->amcanreturn = NULL; /* tuple not included in heapsort */
amroutine->amcostestimate = ivfflatcostestimate;
amroutine->amoptions = ivfflatoptions;
amroutine->amproperty = NULL; /* TODO AMPROP_DISTANCE_ORDERABLE */
#if PG_VERSION_NUM >= 120000
amroutine->ambuildphasename = NULL;
amroutine->ambuildphasename = ivfflatbuildphasename;
#endif
amroutine->amvalidate = ivfflatvalidate;
#if PG_VERSION_NUM >= 140000
amroutine->amadjustmembers = NULL;
#endif
amroutine->ambeginscan = ivfflatbeginscan;
amroutine->amrescan = ivfflatrescan;
amroutine->amgettuple = ivfflatgettuple;
@@ -171,6 +222,8 @@ ivfflathandler(PG_FUNCTION_ARGS)
amroutine->amendscan = ivfflatendscan;
amroutine->ammarkpos = NULL;
amroutine->amrestrpos = NULL;
/* Interface functions to support parallel index scans */
#if PG_VERSION_NUM >= 100000
amroutine->amestimateparallelscan = NULL;
amroutine->aminitparallelscan = NULL;

View File

@@ -10,6 +10,14 @@
#include "utils/tuplesort.h"
#include "vector.h"
#ifdef IVFFLAT_BENCH
#include "portability/instr_time.h"
#endif
#if PG_VERSION_NUM < 100000
#error "Requires PostgreSQL 10+"
#endif
/* Support functions */
#define IVFFLAT_DISTANCE_PROC 1
#define IVFFLAT_NORM_PROC 2
@@ -27,14 +35,31 @@
#define IVFFLAT_DEFAULT_LISTS 100
#define IVFFLAT_MAX_LISTS 32768
/* Build phases */
/* PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE is 1 */
#define PROGRESS_IVFFLAT_PHASE_SAMPLE 2
#define PROGRESS_IVFFLAT_PHASE_KMEANS 3
#define PROGRESS_IVFFLAT_PHASE_SORT 4
#define PROGRESS_IVFFLAT_PHASE_LOAD 5
#define IVFFLAT_LIST_SIZE(_dim) (offsetof(IvfflatListData, center) + VECTOR_SIZE(_dim))
#define IvfflatPageGetOpaque(page) ((IvfflatPageOpaque) PageGetSpecialPointer(page))
#define IvfflatPageGetMeta(page) ((IvfflatMetaPageData *) PageGetContents(page))
#if PG_VERSION_NUM < 100000
#define ItemPointerGetBlockNumberNoCheck ItemPointerGetBlockNumber
#define ItemPointerGetOffsetNumberNoCheck ItemPointerGetOffsetNumber
#ifdef IVFFLAT_BENCH
#define IvfflatBench(name, code) \
do { \
instr_time start; \
instr_time duration; \
INSTR_TIME_SET_CURRENT(start); \
(code); \
INSTR_TIME_SET_CURRENT(duration); \
INSTR_TIME_SUBTRACT(duration, start); \
elog(INFO, "%s: %.3f ms", name, INSTR_TIME_GET_MILLISEC(duration)); \
} while (0)
#else
#define IvfflatBench(name, code) (code)
#endif
/* Variables */
@@ -90,6 +115,12 @@ typedef struct IvfflatBuildState
ListInfo *listInfo;
Vector *normvec;
#ifdef IVFFLAT_KMEANS_DEBUG
double inertia;
double *listSums;
int *listCounts;
#endif
/* Sampling */
BlockSamplerData bs;
ReservoirStateData rstate;
@@ -131,6 +162,7 @@ typedef IvfflatListData * IvfflatList;
typedef struct IvfflatScanList
{
pairingheap_node ph_node;
BlockNumber startPage;
double distance;
} IvfflatScanList;
@@ -152,6 +184,8 @@ typedef struct IvfflatScanOpaqueData
FmgrInfo *normprocinfo;
Oid collation;
/* Lists */
pairingheap *listQueue;
IvfflatScanList lists[FLEXIBLE_ARRAY_MEMBER]; /* must come last */
} IvfflatScanOpaqueData;
@@ -170,16 +204,20 @@ void IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers);
FmgrInfo *IvfflatOptionalProcInfo(Relation rel, uint16 procnum);
bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result);
int IvfflatGetLists(Relation index);
void IvfflatUpdateList(Relation index, GenericXLogState *state, ListInfo listInfo, BlockNumber insertPage, BlockNumber startPage, ForkNumber forkNum);
void IvfflatUpdateList(Relation index, GenericXLogState *state, ListInfo listInfo, BlockNumber insertPage, BlockNumber originalInsertPage, BlockNumber startPage, ForkNumber forkNum);
void IvfflatCommitBuffer(Buffer buf, GenericXLogState *state);
void IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum);
Buffer IvfflatNewBuffer(Relation index, ForkNumber forkNum);
void IvfflatInitPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state);
void IvfflatInitPage(Buffer buf, Page page);
void IvfflatInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state);
/* Index access methods */
IndexBuildResult *ivfflatbuild(Relation heap, Relation index, IndexInfo *indexInfo);
void ivfflatbuildempty(Relation index);
bool ivfflatinsert(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heap, IndexUniqueCheck checkUnique
#if PG_VERSION_NUM >= 140000
,bool indexUnchanged
#endif
#if PG_VERSION_NUM >= 100000
,IndexInfo *indexInfo
#endif

View File

@@ -53,18 +53,6 @@ FindInsertPage(Relation rel, Datum *values, BlockNumber *insertPage, ListInfo *
}
}
/*
* Prepare to insert an index tuple
*/
static void
LoadInsertPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, BlockNumber insertPage)
{
*buf = ReadBuffer(index, insertPage);
LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
*state = GenericXLogStart(index);
*page = GenericXLogRegisterBuffer(*state, *buf, 0);
}
/*
* Insert a tuple into the index
*/
@@ -77,20 +65,28 @@ InsertTuple(Relation rel, IndexTuple itup, Relation heapRel, Datum *values)
Size itemsz;
BlockNumber insertPage = InvalidBlockNumber;
ListInfo listInfo;
bool newPage = false;
BlockNumber originalInsertPage;
/* Find the insert page - sets the page and list info */
FindInsertPage(rel, values, &insertPage, &listInfo);
Assert(BlockNumberIsValid(insertPage));
originalInsertPage = insertPage;
itemsz = MAXALIGN(IndexTupleSize(itup));
Assert(itemsz <= BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(IvfflatPageOpaqueData)));
LoadInsertPage(rel, &buf, &page, &state, insertPage);
/* Find a page to insert the item */
while (PageGetFreeSpace(page) < itemsz)
for (;;)
{
buf = ReadBuffer(rel, insertPage);
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
state = GenericXLogStart(rel);
page = GenericXLogRegisterBuffer(state, buf, 0);
if (PageGetFreeSpace(page) >= itemsz)
break;
insertPage = IvfflatPageGetOpaque(page)->nextblkno;
if (BlockNumberIsValid(insertPage))
@@ -98,16 +94,45 @@ InsertTuple(Relation rel, IndexTuple itup, Relation heapRel, Datum *values)
/* Move to next page */
GenericXLogAbort(state);
UnlockReleaseBuffer(buf);
LoadInsertPage(rel, &buf, &page, &state, insertPage);
}
else
{
/* Add a new page */
IvfflatAppendPage(rel, &buf, &page, &state, MAIN_FORKNUM);
Buffer metabuf;
Buffer newbuf;
Page newpage;
insertPage = BufferGetBlockNumber(buf);
newPage = true;
/*
* From ReadBufferExtended: Caller is responsible for ensuring
* that only one backend tries to extend a relation at the same
* time!
*/
metabuf = ReadBuffer(rel, IVFFLAT_METAPAGE_BLKNO);
LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE);
/* Add a new page */
newbuf = IvfflatNewBuffer(rel, MAIN_FORKNUM);
newpage = GenericXLogRegisterBuffer(state, newbuf, GENERIC_XLOG_FULL_IMAGE);
/* Init new page */
IvfflatInitPage(newbuf, newpage);
/* Update insert page */
insertPage = BufferGetBlockNumber(newbuf);
/* Update previous buffer */
IvfflatPageGetOpaque(page)->nextblkno = insertPage;
/* Commit */
MarkBufferDirty(newbuf);
MarkBufferDirty(buf);
GenericXLogFinish(state);
/* Unlock extend relation lock as early as possible */
UnlockReleaseBuffer(metabuf);
/* Unlock rest */
UnlockReleaseBuffer(newbuf);
UnlockReleaseBuffer(buf);
}
}
@@ -118,8 +143,8 @@ InsertTuple(Relation rel, IndexTuple itup, Relation heapRel, Datum *values)
IvfflatCommitBuffer(buf, state);
/* Update the insert page */
if (newPage)
IvfflatUpdateList(rel, state, listInfo, insertPage, InvalidBlockNumber, MAIN_FORKNUM);
if (insertPage != originalInsertPage)
IvfflatUpdateList(rel, state, listInfo, insertPage, originalInsertPage, InvalidBlockNumber, MAIN_FORKNUM);
}
/*
@@ -128,6 +153,9 @@ InsertTuple(Relation rel, IndexTuple itup, Relation heapRel, Datum *values)
bool
ivfflatinsert(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid,
Relation heap, IndexUniqueCheck checkUnique
#if PG_VERSION_NUM >= 140000
,bool indexUnchanged
#endif
#if PG_VERSION_NUM >= 100000
,IndexInfo *indexInfo
#endif

View File

@@ -5,23 +5,33 @@
#include "ivfflat.h"
#include "miscadmin.h"
#if PG_VERSION_NUM >= 150000
#include "common/pg_prng.h"
#endif
#if PG_VERSION_NUM >= 150000
#define RandomDouble() pg_prng_double(&pg_global_prng_state)
#else
#define RandomDouble() (((double) random()) / MAX_RANDOM_VALUE)
#endif
/*
* Initialize with kmeans++
*
* https://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf
*/
static void
InitCenters(Relation index, VectorArray samples, VectorArray centers, double *lowerBound)
InitCenters(Relation index, VectorArray samples, VectorArray centers, float *lowerBound)
{
FmgrInfo *procinfo;
Oid collation;
int i;
int j;
int64 j;
double distance;
double sum;
double choice;
Vector *vec;
double *weight = palloc(samples->length * sizeof(double));
float *weight = palloc(samples->length * sizeof(float));
int numCenters = centers->maxlen;
int numSamples = samples->length;
@@ -66,7 +76,7 @@ InitCenters(Relation index, VectorArray samples, VectorArray centers, double *lo
break;
/* Choose new center using weighted probability distribution. */
choice = sum * (((double) random()) / MAX_RANDOM_VALUE);
choice = sum * RandomDouble();
for (j = 0; j < numSamples - 1; j++)
{
choice -= weight[j];
@@ -121,15 +131,18 @@ QuickCenters(Relation index, VectorArray samples, VectorArray centers)
FmgrInfo *normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC);
/* Copy existing vectors while avoiding duplicates */
qsort(samples->items, samples->length, VECTOR_SIZE(samples->dim), CompareVectors);
for (i = 0; i < samples->length; i++)
if (samples->length > 0)
{
vec = VectorArrayGet(samples, i);
if (i == 0 || CompareVectors(vec, VectorArrayGet(samples, i - 1)) != 0)
qsort(samples->items, samples->length, VECTOR_SIZE(samples->dim), CompareVectors);
for (i = 0; i < samples->length; i++)
{
VectorArraySet(centers, centers->length, vec);
centers->length++;
vec = VectorArrayGet(samples, i);
if (i == 0 || CompareVectors(vec, VectorArrayGet(samples, i - 1)) != 0)
{
VectorArraySet(centers, centers->length, vec);
centers->length++;
}
}
}
@@ -142,7 +155,7 @@ QuickCenters(Relation index, VectorArray samples, VectorArray centers)
vec->dim = dimensions;
for (j = 0; j < dimensions; j++)
vec->x[j] = ((double) random()) / MAX_RANDOM_VALUE;
vec->x[j] = RandomDouble();
/* Normalize if needed (only needed for random centers) */
if (normprocinfo != NULL)
@@ -169,19 +182,19 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
Vector *vec;
Vector *newCenter;
int iteration;
int j;
int k;
int64 j;
int64 k;
int dimensions = centers->dim;
int numCenters = centers->maxlen;
int numSamples = samples->length;
VectorArray newCenters;
int *centerCounts;
int *closestCenters;
double *lowerBound;
double *upperBound;
double *s;
double *halfcdist;
double *newcdist;
float *lowerBound;
float *upperBound;
float *s;
float *halfcdist;
float *newcdist;
int changes;
double minDistance;
int closestCenter;
@@ -191,19 +204,47 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
double dxcx;
double dxc;
/* Calculate allocation sizes */
Size samplesSize = VECTOR_ARRAY_SIZE(samples->maxlen, samples->dim);
Size centersSize = VECTOR_ARRAY_SIZE(centers->maxlen, centers->dim);
Size newCentersSize = VECTOR_ARRAY_SIZE(numCenters, dimensions);
Size centerCountsSize = sizeof(int) * numCenters;
Size closestCentersSize = sizeof(int) * numSamples;
Size lowerBoundSize = sizeof(float) * numSamples * numCenters;
Size upperBoundSize = sizeof(float) * numSamples;
Size sSize = sizeof(float) * numCenters;
Size halfcdistSize = sizeof(float) * numCenters * numCenters;
Size newcdistSize = sizeof(float) * numCenters;
/* Calculate total size */
Size totalSize = samplesSize + centersSize + newCentersSize + centerCountsSize + closestCentersSize + lowerBoundSize + upperBoundSize + sSize + halfcdistSize + newcdistSize;
/* Check memory requirements */
/* Add one to error message to ceil */
if (totalSize / 1024 > maintenance_work_mem)
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 */
if (numCenters * numCenters > INT_MAX)
elog(ERROR, "Indexing overflow detected. Please report a bug.");
/* Set support functions */
procinfo = index_getprocinfo(index, 1, IVFFLAT_KMEANS_DISTANCE_PROC);
normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC);
collation = index->rd_indcollation[0];
/* Allocate space */
centerCounts = palloc(sizeof(int) * numCenters);
closestCenters = palloc(sizeof(int) * numSamples);
lowerBound = palloc(sizeof(double) * numSamples * numCenters);
upperBound = palloc(sizeof(double) * numSamples);
s = palloc(sizeof(double) * numCenters);
halfcdist = palloc(sizeof(double) * numCenters * numCenters);
newcdist = palloc(sizeof(double) * numCenters);
/* Use float instead of double to save memory */
centerCounts = palloc(centerCountsSize);
closestCenters = palloc(closestCentersSize);
lowerBound = palloc_extended(lowerBoundSize, MCXT_ALLOC_HUGE);
upperBound = palloc(upperBoundSize);
s = palloc(sSize);
halfcdist = palloc_extended(halfcdistSize, MCXT_ALLOC_HUGE);
newcdist = palloc(newcdistSize);
newCenters = VectorArrayInit(numCenters, dimensions);
for (j = 0; j < numCenters; j++)
@@ -222,8 +263,6 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
minDistance = DBL_MAX;
closestCenter = -1;
vec = VectorArrayGet(samples, j);
/* Find closest center */
for (k = 0; k < numCenters; k++)
{
@@ -376,7 +415,7 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
{
/* TODO Handle empty centers properly */
for (k = 0; k < dimensions; k++)
vec->x[k] = ((double) random()) / MAX_RANDOM_VALUE;
vec->x[k] = RandomDouble();
}
/* Normalize if needed */

View File

@@ -1,5 +1,7 @@
#include "postgres.h"
#include <float.h>
#include "access/relscan.h"
#include "ivfflat.h"
#include "miscadmin.h"
@@ -17,14 +19,12 @@
* Compare list distances
*/
static int
CompareLists(const void *a, const void *b)
CompareLists(const pairingheap_node *a, const pairingheap_node *b, void *arg)
{
double diff = (((IvfflatScanList *) a)->distance - ((IvfflatScanList *) b)->distance);
if (diff > 0)
if (((const IvfflatScanList *) a)->distance > ((const IvfflatScanList *) b)->distance)
return 1;
if (diff < 0)
if (((const IvfflatScanList *) a)->distance < ((const IvfflatScanList *) b)->distance)
return -1;
return 0;
@@ -45,6 +45,8 @@ GetScanLists(IndexScanDesc scan, Datum value)
int listCount = 0;
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
double distance;
IvfflatScanList *scanlist;
double maxDistance = DBL_MAX;
/* Search all list pages */
while (BlockNumberIsValid(nextblkno))
@@ -62,21 +64,39 @@ GetScanLists(IndexScanDesc scan, Datum value)
/* Use procinfo from the index instead of scan key for performance */
distance = DatumGetFloat8(FunctionCall2Coll(so->procinfo, so->collation, PointerGetDatum(&list->center), value));
so->lists[listCount].startPage = list->startPage;
so->lists[listCount].distance = distance;
listCount++;
if (listCount < so->probes)
{
scanlist = &so->lists[listCount];
scanlist->startPage = list->startPage;
scanlist->distance = distance;
listCount++;
/* Add to heap */
pairingheap_add(so->listQueue, &scanlist->ph_node);
/* Calculate max distance */
if (listCount == so->probes)
maxDistance = ((IvfflatScanList *) pairingheap_first(so->listQueue))->distance;
}
else if (distance < maxDistance)
{
/* Remove */
scanlist = (IvfflatScanList *) pairingheap_remove_first(so->listQueue);
/* Reuse */
scanlist->startPage = list->startPage;
scanlist->distance = distance;
pairingheap_add(so->listQueue, &scanlist->ph_node);
/* Update max distance */
maxDistance = ((IvfflatScanList *) pairingheap_first(so->listQueue))->distance;
}
}
nextblkno = IvfflatPageGetOpaque(cpage)->nextblkno;
UnlockReleaseBuffer(cbuf);
}
/* Sort by distance */
qsort(so->lists, listCount, sizeof(IvfflatScanList), CompareLists);
if (so->probes > listCount)
so->probes = listCount;
}
/*
@@ -94,7 +114,6 @@ GetScanItems(IndexScanDesc scan, Datum value)
OffsetNumber maxoffno;
Datum datum;
bool isnull;
int i;
TupleDesc tupdesc = RelationGetDescr(scan->indexRelation);
#if PG_VERSION_NUM >= 120000
@@ -111,9 +130,9 @@ GetScanItems(IndexScanDesc scan, Datum value)
BufferAccessStrategy bas = GetAccessStrategy(BAS_BULKREAD);
/* Search closest probes lists */
for (i = 0; i < so->probes; i++)
while (!pairingheap_is_empty(so->listQueue))
{
searchPage = so->lists[i].startPage;
searchPage = ((IvfflatScanList *) pairingheap_remove_first(so->listQueue))->startPage;
/* Search all entry pages for list */
while (BlockNumberIsValid(searchPage))
@@ -137,12 +156,10 @@ GetScanItems(IndexScanDesc scan, Datum value)
ExecClearTuple(slot);
slot->tts_values[0] = FunctionCall2Coll(so->procinfo, so->collation, datum, value);
slot->tts_isnull[0] = false;
slot->tts_values[1] = Int32GetDatum((int) ItemPointerGetBlockNumberNoCheck(&itup->t_tid));
slot->tts_values[1] = PointerGetDatum(&itup->t_tid);
slot->tts_isnull[1] = false;
slot->tts_values[2] = Int32GetDatum((int) ItemPointerGetOffsetNumberNoCheck(&itup->t_tid));
slot->tts_values[2] = Int32GetDatum((int) searchPage);
slot->tts_isnull[2] = false;
slot->tts_values[3] = Int32GetDatum((int) searchPage);
slot->tts_isnull[3] = false;
ExecStoreVirtualTuple(slot);
tuplesort_puttupleslot(so->sortstate, slot);
@@ -153,6 +170,8 @@ GetScanItems(IndexScanDesc scan, Datum value)
UnlockReleaseBuffer(buf);
}
}
tuplesort_performsort(so->sortstate);
}
/*
@@ -168,13 +187,18 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
Oid sortOperators[] = {Float8LessOperator};
Oid sortCollations[] = {InvalidOid};
bool nullsFirstFlags[] = {false};
int probes = ivfflat_probes;
scan = RelationGetIndexScan(index, nkeys, norderbys);
lists = IvfflatGetLists(scan->indexRelation);
so = (IvfflatScanOpaque) palloc(offsetof(IvfflatScanOpaqueData, lists) + lists * sizeof(IvfflatScanList));
if (probes > lists)
probes = lists;
so = (IvfflatScanOpaque) palloc(offsetof(IvfflatScanOpaqueData, lists) + probes * sizeof(IvfflatScanList));
so->buf = InvalidBuffer;
so->first = true;
so->probes = probes;
/* Set support functions */
so->procinfo = index_getprocinfo(index, 1, IVFFLAT_DISTANCE_PROC);
@@ -183,14 +207,13 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
/* Create tuple description for sorting */
#if PG_VERSION_NUM >= 120000
so->tupdesc = CreateTemplateTupleDesc(4);
so->tupdesc = CreateTemplateTupleDesc(3);
#else
so->tupdesc = CreateTemplateTupleDesc(4, false);
so->tupdesc = CreateTemplateTupleDesc(3, false);
#endif
TupleDescInitEntry(so->tupdesc, (AttrNumber) 1, "distance", FLOAT8OID, -1, 0);
TupleDescInitEntry(so->tupdesc, (AttrNumber) 2, "blkno", INT4OID, -1, 0);
TupleDescInitEntry(so->tupdesc, (AttrNumber) 3, "offset", INT4OID, -1, 0);
TupleDescInitEntry(so->tupdesc, (AttrNumber) 4, "indexblkno", INT4OID, -1, 0);
TupleDescInitEntry(so->tupdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0);
TupleDescInitEntry(so->tupdesc, (AttrNumber) 3, "indexblkno", INT4OID, -1, 0);
/* Prep sort */
#if PG_VERSION_NUM >= 110000
@@ -205,6 +228,8 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
so->slot = MakeSingleTupleTableSlot(so->tupdesc);
#endif
so->listQueue = pairingheap_allocate(CompareLists, scan);
scan->opaque = so;
return scan;
@@ -224,7 +249,7 @@ ivfflatrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int
#endif
so->first = true;
so->probes = ivfflat_probes;
pairingheap_reset(so->listQueue);
if (keys && scan->numberOfKeys > 0)
memmove(scan->keyData, keys, scan->numberOfKeys * sizeof(ScanKeyData));
@@ -251,6 +276,10 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
{
Datum value;
/* Safety check */
if (scan->orderByData == NULL)
elog(ERROR, "cannot scan ivfflat index without order");
/* No items will match if null */
if (scan->orderByData->sk_flags & SK_ISNULL)
return false;
@@ -264,9 +293,8 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
return false;
}
GetScanLists(scan, value);
GetScanItems(scan, value);
tuplesort_performsort(so->sortstate);
IvfflatBench("GetScanLists", GetScanLists(scan, value));
IvfflatBench("GetScanItems", GetScanItems(scan, value));
so->first = false;
/* Clean up if we allocated a new value */
@@ -280,14 +308,13 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
if (tuplesort_gettupleslot(so->sortstate, true, so->slot, NULL))
#endif
{
BlockNumber blkno = DatumGetInt32(slot_getattr(so->slot, 2, &so->isnull));
OffsetNumber offset = DatumGetInt32(slot_getattr(so->slot, 3, &so->isnull));
BlockNumber indexblkno = DatumGetInt32(slot_getattr(so->slot, 4, &so->isnull));
ItemPointer tid = (ItemPointer) DatumGetPointer(slot_getattr(so->slot, 2, &so->isnull));
BlockNumber indexblkno = DatumGetInt32(slot_getattr(so->slot, 3, &so->isnull));
#if PG_VERSION_NUM >= 120000
ItemPointerSet(&scan->xs_heaptid, blkno, offset);
scan->xs_heaptid = *tid;
#else
ItemPointerSet(&scan->xs_ctup.t_self, blkno, offset);
scan->xs_ctup.t_self = *tid;
#endif
if (BufferIsValid(so->buf))
@@ -320,6 +347,7 @@ ivfflatendscan(IndexScanDesc scan)
if (BufferIsValid(so->buf))
ReleaseBuffer(so->buf);
pairingheap_free(so->listQueue);
tuplesort_end(so->sortstate);
pfree(so);

View File

@@ -10,7 +10,7 @@
VectorArray
VectorArrayInit(int maxlen, int dimensions)
{
VectorArray res = palloc0(VECTOR_ARRAY_SIZE(maxlen, dimensions));
VectorArray res = palloc_extended(VECTOR_ARRAY_SIZE(maxlen, dimensions), MCXT_ALLOC_ZERO | MCXT_ALLOC_HUGE);
res->length = 0;
res->maxlen = maxlen;
@@ -107,13 +107,22 @@ IvfflatNewBuffer(Relation index, ForkNumber forkNum)
* Init page
*/
void
IvfflatInitPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state)
IvfflatInitPage(Buffer buf, Page page)
{
PageInit(page, BufferGetPageSize(buf), sizeof(IvfflatPageOpaqueData));
IvfflatPageGetOpaque(page)->nextblkno = InvalidBlockNumber;
IvfflatPageGetOpaque(page)->page_id = IVFFLAT_PAGE_ID;
}
/*
* Init and register page
*/
void
IvfflatInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state)
{
*state = GenericXLogStart(index);
*page = GenericXLogRegisterBuffer(*state, *buf, GENERIC_XLOG_FULL_IMAGE);
PageInit(*page, BufferGetPageSize(*buf), sizeof(IvfflatPageOpaqueData));
IvfflatPageGetOpaque(*page)->nextblkno = InvalidBlockNumber;
IvfflatPageGetOpaque(*page)->page_id = IVFFLAT_PAGE_ID;
IvfflatInitPage(*buf, *page);
}
/*
@@ -135,17 +144,27 @@ IvfflatCommitBuffer(Buffer buf, GenericXLogState *state)
void
IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum)
{
Buffer prevbuf = *buf;
/* Get new buffer */
*buf = IvfflatNewBuffer(index, forkNum);
Buffer newbuf = IvfflatNewBuffer(index, forkNum);
Page newpage = GenericXLogRegisterBuffer(*state, newbuf, GENERIC_XLOG_FULL_IMAGE);
/* Update and commit previous buffer */
IvfflatPageGetOpaque(*page)->nextblkno = BufferGetBlockNumber(*buf);
IvfflatCommitBuffer(prevbuf, *state);
/* Update the previous buffer */
IvfflatPageGetOpaque(*page)->nextblkno = BufferGetBlockNumber(newbuf);
/* Init new page */
IvfflatInitPage(index, buf, page, state);
IvfflatInitPage(newbuf, newpage);
/* Commit */
MarkBufferDirty(*buf);
MarkBufferDirty(newbuf);
GenericXLogFinish(*state);
/* Unlock */
UnlockReleaseBuffer(*buf);
*state = GenericXLogStart(index);
*page = GenericXLogRegisterBuffer(*state, newbuf, GENERIC_XLOG_FULL_IMAGE);
*buf = newbuf;
}
/*
@@ -153,11 +172,13 @@ IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **st
*/
void
IvfflatUpdateList(Relation index, GenericXLogState *state, ListInfo listInfo,
BlockNumber insertPage, BlockNumber startPage, ForkNumber forkNum)
BlockNumber insertPage, BlockNumber originalInsertPage,
BlockNumber startPage, ForkNumber forkNum)
{
Buffer buf;
Page page;
IvfflatList list;
bool changed = false;
buf = ReadBufferExtended(index, forkNum, listInfo.blkno, RBM_NORMAL, NULL);
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
@@ -165,12 +186,29 @@ IvfflatUpdateList(Relation index, GenericXLogState *state, ListInfo listInfo,
page = GenericXLogRegisterBuffer(state, buf, 0);
list = (IvfflatList) PageGetItem(page, PageGetItemId(page, listInfo.offno));
if (BlockNumberIsValid(insertPage))
list->insertPage = insertPage;
if (BlockNumberIsValid(insertPage) && insertPage != list->insertPage)
{
/* Skip update if insert page is lower than original insert page */
/* This is needed to prevent insert from overwriting vacuum */
if (!BlockNumberIsValid(originalInsertPage) || insertPage >= originalInsertPage)
{
list->insertPage = insertPage;
changed = true;
}
}
if (BlockNumberIsValid(startPage))
if (BlockNumberIsValid(startPage) && startPage != list->startPage)
{
list->startPage = startPage;
changed = true;
}
/* Could only commit if changed, but extra complexity isn't needed */
IvfflatCommitBuffer(buf, state);
/* Only commit if changed */
if (changed)
IvfflatCommitBuffer(buf, state);
else
{
GenericXLogAbort(state);
UnlockReleaseBuffer(buf);
}
}

View File

@@ -21,7 +21,7 @@ ivfflatbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
ItemPointer htup;
OffsetNumber deletable[MaxOffsetNumber];
int ndeletable;
OffsetNumber startPages[MaxOffsetNumber];
BlockNumber startPages[MaxOffsetNumber];
BlockNumber nextblkno = IVFFLAT_HEAD_BLKNO;
BlockNumber searchPage;
BlockNumber insertPage;
@@ -98,6 +98,11 @@ ivfflatbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
stats->num_index_tuples++;
}
/* Set to first free page */
/* Must be set before searchPage is updated */
if (!BlockNumberIsValid(insertPage) && ndeletable > 0)
insertPage = searchPage;
searchPage = IvfflatPageGetOpaque(page)->nextblkno;
if (ndeletable > 0)
@@ -106,10 +111,6 @@ ivfflatbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
PageIndexMultiDelete(page, deletable, ndeletable);
MarkBufferDirty(buf);
GenericXLogFinish(state);
/* Set to first free page */
if (!BlockNumberIsValid(insertPage))
insertPage = searchPage;
}
else
GenericXLogAbort(state);
@@ -123,10 +124,10 @@ ivfflatbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
* We don't add or delete items from lists pages, so offset won't
* change.
*/
if (!BlockNumberIsValid(insertPage))
if (BlockNumberIsValid(insertPage))
{
listInfo.offno = coffno;
IvfflatUpdateList(index, state, listInfo, insertPage, InvalidBlockNumber, MAIN_FORKNUM);
IvfflatUpdateList(index, state, listInfo, insertPage, InvalidBlockNumber, InvalidBlockNumber, MAIN_FORKNUM);
}
}
}

View File

@@ -10,11 +10,16 @@
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/numeric.h"
#if PG_VERSION_NUM >= 120000
#include "utils/float.h"
#endif
#if PG_VERSION_NUM < 130000
#define TYPALIGN_INT 'i'
#endif
PG_MODULE_MAGIC;
/*
@@ -244,14 +249,20 @@ vector_recv(PG_FUNCTION_ARGS)
int32 typmod = PG_GETARG_INT32(2);
Vector *result;
int16 dim;
int16 unused;
int i;
dim = pq_getmsgint(buf, sizeof(int16));
pq_getmsgint(buf, sizeof(int16)); /* unused */
unused = pq_getmsgint(buf, sizeof(int16));
CheckDim(dim);
CheckExpectedDim(typmod, dim);
if (unused != 0)
ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION),
errmsg("expected unused to be 0, not %d", unused)));
result = InitVector(dim);
for (i = 0; i < dim; i++)
result->x[i] = pq_getmsgfloat4(buf);
@@ -339,6 +350,8 @@ array_to_vector(PG_FUNCTION_ARGS)
result->x[i] = DatumGetFloat8(elemsp[i]);
else if (ARR_ELEMTYPE(array) == FLOAT4OID)
result->x[i] = DatumGetFloat4(elemsp[i]);
else if (ARR_ELEMTYPE(array) == NUMERICOID)
result->x[i] = DatumGetFloat4(DirectFunctionCall1(numeric_float4, NumericGetDatum(elemsp[i])));
else
ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION),
@@ -350,6 +363,29 @@ array_to_vector(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(result);
}
/*
* Convert vector to float4[]
*/
PG_FUNCTION_INFO_V1(vector_to_float4);
Datum
vector_to_float4(PG_FUNCTION_ARGS)
{
Vector *vec = PG_GETARG_VECTOR_P(0);
Datum *d;
ArrayType *result;
int i;
d = (Datum *) palloc(sizeof(Datum) * vec->dim);
for (i = 0; i < vec->dim; i++)
d[i] = Float4GetDatum(vec->x[i]);
/* Use TYPALIGN_INT for float4 */
result = construct_array(d, vec->dim, FLOAT4OID, sizeof(float4), true, TYPALIGN_INT);
PG_RETURN_POINTER(result);
}
/*
* Get the L2 distance between vectors
*/

View File

@@ -6,6 +6,12 @@ SELECT ARRAY[1,2,3]::vector;
[1,2,3]
(1 row)
SELECT ARRAY[1.0,2.0,3.0]::vector;
array
---------
[1,2,3]
(1 row)
SELECT ARRAY[1,2,3]::float4[]::vector;
array
---------
@@ -28,3 +34,18 @@ SELECT '{-Infinity}'::real[]::vector;
ERROR: infinite value not allowed in vector
SELECT '{}'::real[]::vector;
ERROR: vector must have at least 1 dimension
SELECT '[1,2,3]'::vector::real[];
float4
---------
{1,2,3}
(1 row)
SELECT array_agg(n)::vector FROM generate_series(1, 1025) n;
ERROR: vector cannot have more than 1024 dimensions
-- ensure no error
SELECT ARRAY[1,2,3] = ARRAY[1,2,3];
?column?
----------
t
(1 row)

View File

@@ -53,3 +53,12 @@ LINE 1: SELECT '[1,]'::vector;
^
SELECT '[1,2,3]'::vector(2);
ERROR: expected 2 dimensions, not 3
SELECT unnest('{"[1,2,3]", "[4,5,6]"}'::vector[]);
unnest
---------
[1,2,3]
[4,5,6]
(2 rows)
SELECT '{"[1,2,3]"}'::vector(2)[];
ERROR: expected 2 dimensions, not 3

View File

@@ -19,4 +19,10 @@ SELECT * FROM t ORDER BY val <-> (SELECT NULL::vector);
-----
(0 rows)
SELECT COUNT(*) FROM t;
count
-------
5
(1 row)
DROP TABLE t;

View File

@@ -0,0 +1,8 @@
use PostgreSQL::Test::Cluster;
sub get_new_node
{
return PostgreSQL::Test::Cluster->new(@_);
}
1;

3
test/perl/TestLib.pm Normal file
View File

@@ -0,0 +1,3 @@
use PostgreSQL::Test::Utils;
1;

View File

@@ -2,6 +2,7 @@ SET client_min_messages = warning;
CREATE EXTENSION IF NOT EXISTS vector;
SELECT ARRAY[1,2,3]::vector;
SELECT ARRAY[1.0,2.0,3.0]::vector;
SELECT ARRAY[1,2,3]::float4[]::vector;
SELECT ARRAY[1,2,3]::float8[]::vector;
SELECT '{NULL}'::real[]::vector;
@@ -9,3 +10,8 @@ SELECT '{NaN}'::real[]::vector;
SELECT '{Infinity}'::real[]::vector;
SELECT '{-Infinity}'::real[]::vector;
SELECT '{}'::real[]::vector;
SELECT '[1,2,3]'::vector::real[];
SELECT array_agg(n)::vector FROM generate_series(1, 1025) n;
-- ensure no error
SELECT ARRAY[1,2,3] = ARRAY[1,2,3];

View File

@@ -13,3 +13,6 @@ SELECT '1,2,3'::vector;
SELECT '[]'::vector;
SELECT '[1,]'::vector;
SELECT '[1,2,3]'::vector(2);
SELECT unnest('{"[1,2,3]", "[4,5,6]"}'::vector[]);
SELECT '{"[1,2,3]"}'::vector(2)[];

View File

@@ -10,5 +10,6 @@ INSERT INTO t (val) VALUES ('[1,2,4]');
SELECT * FROM t ORDER BY val <-> '[3,3,3]';
SELECT * FROM t ORDER BY val <-> (SELECT NULL::vector);
SELECT COUNT(*) FROM t;
DROP TABLE t;

View File

@@ -7,6 +7,8 @@ use PostgresNode;
use TestLib;
use Test::More tests => 31;
my $dim = 32;
my $node_primary;
my $node_replica;
@@ -30,13 +32,16 @@ sub test_index_replay
$node_primary->poll_query_until('postgres', $caughtup_query)
or die "Timed out while waiting for replica 1 to catch up";
my $r1 = rand();
my $r2 = rand();
my $r3 = rand();
my @r = ();
for (1 .. $dim) {
push(@r, rand());
}
my $sql = join(",", @r);
my $queries = qq(SET enable_seqscan=off;
SELECT * FROM tst ORDER BY v <-> '[$r1,$r2,$r3]' LIMIT 10;
);
my $queries = qq(
SET enable_seqscan = off;
SELECT * FROM tst ORDER BY v <-> '[$sql]' LIMIT 10;
);
# Run test queries and compare their result
my $primary_result = $node_primary->safe_psql("postgres", $queries);
@@ -46,9 +51,18 @@ SELECT * FROM tst ORDER BY v <-> '[$r1,$r2,$r3]' LIMIT 10;
return;
}
# Use ARRAY[random(), random(), random(), ...] over
# SELECT array_agg(random()) FROM generate_series(1, $dim)
# to generate different values for each row
my $array_sql = join(",", ('random()') x $dim);
# Initialize primary node
$node_primary = get_new_node('primary');
$node_primary->init(allows_streaming => 1);
if ($dim > 32) {
# TODO use wal_keep_segments for Postgres < 13
$node_primary->append_conf('postgresql.conf', qq(wal_keep_size = 1GB));
}
$node_primary->start;
my $backup_name = 'my_backup';
@@ -63,12 +77,11 @@ $node_replica->start;
# Create ivfflat index on primary
$node_primary->safe_psql("postgres", "CREATE EXTENSION vector;");
$node_primary->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector(3));");
$node_primary->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector($dim));");
$node_primary->safe_psql("postgres",
"INSERT INTO tst SELECT i%10, ARRAY[random(), random(), random()] FROM generate_series(1,100000) i;"
"INSERT INTO tst SELECT i % 10, ARRAY[$array_sql] FROM generate_series(1, 100000) i;"
);
$node_primary->safe_psql("postgres",
"CREATE INDEX ON tst USING ivfflat (v);");
$node_primary->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v);");
# Test that queries give same result
test_index_replay('initial');
@@ -82,7 +95,7 @@ for my $i (1 .. 10)
test_index_replay("vacuum $i");
my ($start, $end) = (100001 + ($i - 1) * 10000, 100000 + $i * 10000);
$node_primary->safe_psql("postgres",
"INSERT INTO tst SELECT i%10, ARRAY[random(), random(), random()] FROM generate_series($start,$end) i;"
"INSERT INTO tst SELECT i % 10, ARRAY[$array_sql] FROM generate_series($start, $end) i;"
);
test_index_replay("insert $i");
}

32
test/t/002_vacuum.pl Normal file
View File

@@ -0,0 +1,32 @@
use strict;
use warnings;
use PostgresNode;
use TestLib;
use Test::More tests => 1;
# Initialize node
my $node = get_new_node('node');
$node->init;
$node->start;
# Create table and index
$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 % 10, ARRAY[i % 1000, i % 333, i % 55] FROM generate_series(1, 100000) i;"
);
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v);");
# Get size
my $size = $node->safe_psql("postgres", "SELECT pg_total_relation_size('tst_v_idx');");
# Delete all, vacuum, and insert same data
$node->safe_psql("postgres", "DELETE FROM tst;");
$node->safe_psql("postgres", "VACUUM tst;");
$node->safe_psql("postgres",
"INSERT INTO tst SELECT i % 10, ARRAY[i % 1000, i % 333, i % 55] FROM generate_series(1, 100000) i;"
);
# Check size
my $new_size = $node->safe_psql("postgres", "SELECT pg_total_relation_size('tst_v_idx');");
is($size, $new_size, "size does not change");

88
test/t/003_recall.pl Normal file
View File

@@ -0,0 +1,88 @@
use strict;
use warnings;
use PostgresNode;
use TestLib;
use Test::More tests => 9;
my $node;
my @queries = ();
my @expected;
my $limit = 20;
sub test_recall
{
my ($probes, $min, $operator) = @_;
my $correct = 0;
my $total = 0;
for my $i (0 .. $#queries) {
my $actual = $node->safe_psql("postgres", qq(
SET enable_seqscan = off;
SET ivfflat.probes = $probes;
SELECT i FROM tst ORDER BY v $operator '$queries[$i]' LIMIT $limit;
));
my @actual_ids = split("\n", $actual);
my %actual_set = map { $_ => 1 } @actual_ids;
my @expected_ids = split("\n", $expected[$i]);
foreach (@expected_ids) {
if (exists($actual_set{$_})) {
$correct++;
}
$total++;
}
}
cmp_ok($correct / $total, ">=", $min, $operator);
}
# Initialize node
$node = get_new_node('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 = ("<->", "<#>", "<=>");
foreach (@operators) {
my $operator = $_;
# Get exact results
@expected = ();
foreach (@queries) {
my $res = $node->safe_psql("postgres", "SELECT i FROM tst ORDER BY v $operator '$_' LIMIT $limit;");
push(@expected, $res);
}
# Add index
my $opclass;
if ($operator == "<->") {
$opclass = "vector_l2_ops";
} elsif ($operator == "<#>") {
$opclass = "vector_ip_ops";
} else {
$opclass = "vector_cosine_ops";
}
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v $opclass);");
# Test approximate results
test_recall(1, 0.75, $operator);
test_recall(10, 0.95, $operator);
test_recall(100, 1.0, $operator);
}

36
test/t/004_centers.pl Normal file
View File

@@ -0,0 +1,36 @@
use strict;
use warnings;
use PostgresNode;
use TestLib;
use Test::More tests => 3;
# Initialize node
my $node = get_new_node('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, '[1,2,3]' FROM generate_series(1, 10) i;"
);
sub test_centers
{
my ($lists, $min) = @_;
my ($ret, $stdout, $stderr) = $node->psql("postgres", "CREATE INDEX ON tst USING ivfflat (v) WITH (lists = $lists);");
is($ret, 0, $stderr);
}
# Test no error for duplicate centers
test_centers(5);
test_centers(10);
$node->safe_psql("postgres",
"INSERT INTO tst SELECT i, '[4,5,6]' FROM generate_series(1, 10) i;"
);
# Test no error for duplicate centers
test_centers(10);

View File

@@ -0,0 +1,45 @@
use strict;
use warnings;
use PostgresNode;
use TestLib;
use Test::More tests => 60;
# Initialize node
my $node = get_new_node('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(3));");
$node->safe_psql("postgres",
"INSERT INTO tst SELECT i, ARRAY[random(), random(), random()] FROM generate_series(1, 100000) i;"
);
# Check each index type
my @operators = ("<->", "<#>", "<=>");
foreach (@operators) {
my $operator = $_;
# Add index
my $opclass;
if ($operator == "<->") {
$opclass = "vector_l2_ops";
} elsif ($operator == "<#>") {
$opclass = "vector_ip_ops";
} else {
$opclass = "vector_cosine_ops";
}
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v $opclass);");
# Test 100% recall
for (1..20) {
my $i = int(rand() * 100000);
my $query = $node->safe_psql("postgres", "SELECT v FROM tst WHERE i = $i;");
my $res = $node->safe_psql("postgres", qq(
SET enable_seqscan = off;
SELECT v FROM tst ORDER BY v <-> '$query' LIMIT 1;
));
is($res, $query);
}
}

31
test/t/006_lists.pl Normal file
View File

@@ -0,0 +1,31 @@
use strict;
use warnings;
use PostgresNode;
use TestLib;
use Test::More tests => 3;
# Initialize node
my $node = get_new_node('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, 100000) i;"
);
$node->safe_psql("postgres", "CREATE INDEX lists50 ON tst USING ivfflat (v) WITH (lists = 50);");
$node->safe_psql("postgres", "CREATE INDEX lists100 ON tst USING ivfflat (v) WITH (lists = 100);");
# Test prefers more lists
my $res = $node->safe_psql("postgres", "EXPLAIN SELECT v FROM tst ORDER BY v <-> '[0.5,0.5,0.5]' LIMIT 10;");
like($res, qr/lists100/);
unlike($res, qr/lists50/);
# Test errors with too much memory
my ($ret, $stdout, $stderr) = $node->psql("postgres",
"CREATE INDEX lists10000 ON tst USING ivfflat (v) WITH (lists = 10000);"
);
like($stderr, qr/memory required is/);

45
test/t/007_inserts.pl Normal file
View File

@@ -0,0 +1,45 @@
use strict;
use warnings;
use PostgresNode;
use TestLib;
use Test::More tests => 5;
my $dim = 768;
my $array_sql = join(",", ('random()') x $dim);
# Initialize node
my $node = get_new_node('node');
$node->init;
$node->start;
# Create table and index
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
$node->safe_psql("postgres", "CREATE TABLE tst (v vector($dim));");
$node->safe_psql("postgres",
"INSERT INTO tst SELECT ARRAY[$array_sql] FROM generate_series(1, 10000) i;"
);
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v);");
$node->pgbench(
"--no-vacuum --client=5 --transactions=100",
0,
[qr{actually processed}],
[qr{^$}],
"concurrent INSERTs",
{
"007_inserts" => "INSERT INTO tst SELECT ARRAY[$array_sql] FROM generate_series(1, 10) i;"
}
);
my $expected = 10000 + 5 * 100 * 10;
my $count = $node->safe_psql("postgres", "SELECT COUNT(*) FROM tst;");
is($count, $expected);
$count = $node->safe_psql("postgres", qq(
SET enable_seqscan = off;
SET ivfflat.probes = 100;
SELECT COUNT(*) FROM (SELECT v FROM tst ORDER BY v <-> (SELECT v FROM tst LIMIT 1)) t;
));
is($count, $expected);

View File

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