mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-22 03:57:34 +08:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5af768752f | ||
|
|
f03bcc4fd8 | ||
|
|
74fc4b9aaa | ||
|
|
6870ba96ba | ||
|
|
2575cfdaa8 | ||
|
|
9ff5ceacca | ||
|
|
9a58ee609a | ||
|
|
66a31bef36 | ||
|
|
163ecdf063 | ||
|
|
c9c6b96eed | ||
|
|
48e9a6830b | ||
|
|
f22b47ca46 | ||
|
|
2a057c055c | ||
|
|
b89b58b893 | ||
|
|
2e77650e1d | ||
|
|
0a68b040fb | ||
|
|
434dc904c1 | ||
|
|
92a671eeb1 | ||
|
|
94eeb83abe | ||
|
|
6866939090 | ||
|
|
ba2fb6ad8c | ||
|
|
51fe8aff02 | ||
|
|
df3dc151d5 | ||
|
|
6687430589 | ||
|
|
f128a455d1 |
@@ -1,6 +1,6 @@
|
||||
root = true
|
||||
|
||||
[*.{c,h,pl}]
|
||||
[*.{c,h,pl,pm}]
|
||||
indent_style = tab
|
||||
indent_size = tab
|
||||
tab_width = 4
|
||||
|
||||
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -7,12 +7,12 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
postgres: [14, 13, 12, 11, 10, 9.6]
|
||||
postgres: [15, 14, 13, 12, 11, 10]
|
||||
include:
|
||||
- os: macos-latest
|
||||
postgres: 14
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ankane/setup-postgres@v1
|
||||
with:
|
||||
postgres-version: ${{ matrix.postgres }}
|
||||
@@ -33,6 +33,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_14_1.tar.gz
|
||||
tar xf REL_14_1.tar.gz
|
||||
make prove_installcheck PROVE=prove PERL5LIB=postgres-REL_14_1/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,3 +1,12 @@
|
||||
## 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
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
FROM postgres:14
|
||||
FROM postgres:15
|
||||
|
||||
COPY . /tmp/pgvector
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential postgresql-server-dev-14 && \
|
||||
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-14 && \
|
||||
apt-get remove -y build-essential postgresql-server-dev-15 && \
|
||||
apt-get autoremove -y && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@@ -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.2.6",
|
||||
"version": "0.3.0",
|
||||
"maintainer": [
|
||||
"Andrew Kane <andrew@ankane.org>"
|
||||
],
|
||||
@@ -12,7 +12,7 @@
|
||||
"prereqs": {
|
||||
"runtime": {
|
||||
"requires": {
|
||||
"PostgreSQL": "9.6.0"
|
||||
"PostgreSQL": "10.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -20,7 +20,7 @@
|
||||
"vector": {
|
||||
"file": "sql/vector.sql",
|
||||
"docfile": "README.md",
|
||||
"version": "0.2.6",
|
||||
"version": "0.3.0",
|
||||
"abstract": "Open-source vector similarity search for Postgres"
|
||||
}
|
||||
},
|
||||
|
||||
5
Makefile
5
Makefile
@@ -1,5 +1,5 @@
|
||||
EXTENSION = vector
|
||||
EXTVERSION = 0.2.6
|
||||
EXTVERSION = 0.3.0
|
||||
|
||||
MODULE_big = vector
|
||||
DATA = $(wildcard sql/*--*.sql)
|
||||
@@ -40,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)
|
||||
|
||||
48
README.md
48
README.md
@@ -3,9 +3,9 @@
|
||||
Open-source vector similarity search for Postgres
|
||||
|
||||
```sql
|
||||
CREATE TABLE table (column vector(3));
|
||||
CREATE INDEX ON table USING ivfflat (column vector_l2_ops);
|
||||
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
|
||||
@@ -14,10 +14,10 @@ Supports L2 distance, inner product, and cosine distance
|
||||
|
||||
## Installation
|
||||
|
||||
Compile and install the extension (supports Postgres 9.6+)
|
||||
Compile and install the extension (supports Postgres 10+)
|
||||
|
||||
```sh
|
||||
git clone --branch v0.2.6 https://github.com/pgvector/pgvector.git
|
||||
git clone --branch v0.3.0 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,19 +62,19 @@ Speed up queries with an approximate index. Add an index for each distance funct
|
||||
L2 distance
|
||||
|
||||
```sql
|
||||
CREATE INDEX ON table USING ivfflat (column vector_l2_ops);
|
||||
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 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.
|
||||
@@ -84,7 +84,7 @@ Indexes should be created after the table has some data for optimal clustering.
|
||||
Specify the number of inverted lists (100 by default)
|
||||
|
||||
```sql
|
||||
CREATE INDEX ON table USING ivfflat (column opclass) 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)`
|
||||
@@ -131,10 +131,20 @@ Note: `tuples_done` and `tuples_total` are only populated during the `loading tu
|
||||
Consider [partial indexes](https://www.postgresql.org/docs/current/indexes-partial.html) for queries with a `WHERE` clause
|
||||
|
||||
```sql
|
||||
CREATE INDEX ON table USING ivfflat (column opclass) WHERE (other_column = 123);
|
||||
SELECT * FROM items WHERE category_id = 123 ORDER BY embedding <-> '[3,1,2]' LIMIT 5;
|
||||
```
|
||||
|
||||
To index many different values of `other_column`, consider [partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html) on `other_column`.
|
||||
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
|
||||
|
||||
@@ -147,7 +157,7 @@ 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 table USING ivfflat (column opclass) WITH (lists = 1000);
|
||||
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 1000);
|
||||
```
|
||||
|
||||
## Reference
|
||||
@@ -185,8 +195,10 @@ Libraries that use pgvector:
|
||||
- [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
|
||||
|
||||
@@ -220,14 +232,14 @@ This adds pgvector to the [Postgres image](https://hub.docker.com/_/postgres).
|
||||
You can also build the image manually
|
||||
|
||||
```sh
|
||||
git clone --branch v0.2.6 https://github.com/pgvector/pgvector.git
|
||||
git clone --branch v0.3.0 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 pgvector/brew/pgvector
|
||||
|
||||
2
sql/vector--0.2.6--0.2.7.sql
Normal file
2
sql/vector--0.2.6--0.2.7.sql
Normal 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
|
||||
2
sql/vector--0.2.7--0.3.0.sql
Normal file
2
sql/vector--0.2.7--0.3.0.sql
Normal 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
|
||||
@@ -80,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));
|
||||
@@ -255,7 +259,7 @@ InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum)
|
||||
CHECK_FOR_INTERRUPTS();
|
||||
|
||||
buf = IvfflatNewBuffer(index, forkNum);
|
||||
IvfflatInitPage(index, &buf, &page, &state);
|
||||
IvfflatInitRegisterPage(index, &buf, &page, &state);
|
||||
|
||||
startPage = BufferGetBlockNumber(buf);
|
||||
|
||||
@@ -412,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);
|
||||
@@ -445,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++)
|
||||
{
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
#include "portability/instr_time.h"
|
||||
#endif
|
||||
|
||||
#if PG_VERSION_NUM < 90600
|
||||
#error "Requires PostgreSQL 9.6+"
|
||||
#if PG_VERSION_NUM < 100000
|
||||
#error "Requires PostgreSQL 10+"
|
||||
#endif
|
||||
|
||||
/* Support functions */
|
||||
@@ -208,7 +208,8 @@ void IvfflatUpdateList(Relation index, GenericXLogState *state, ListInfo listIn
|
||||
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);
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -87,11 +75,18 @@ InsertTuple(Relation rel, IndexTuple itup, Relation heapRel, Datum *values)
|
||||
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))
|
||||
@@ -99,15 +94,29 @@ 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 newbuf = IvfflatNewBuffer(rel, MAIN_FORKNUM);
|
||||
Page newpage = GenericXLogRegisterBuffer(state, buf, GENERIC_XLOG_FULL_IMAGE);
|
||||
|
||||
insertPage = BufferGetBlockNumber(buf);
|
||||
insertPage = BufferGetBlockNumber(newbuf);
|
||||
|
||||
/* Update previous buffer */
|
||||
IvfflatPageGetOpaque(page)->nextblkno = insertPage;
|
||||
|
||||
/* Init page */
|
||||
IvfflatInitPage(newbuf, newpage);
|
||||
|
||||
/* Commit */
|
||||
MarkBufferDirty(buf);
|
||||
MarkBufferDirty(newbuf);
|
||||
GenericXLogFinish(state);
|
||||
|
||||
/* Unlock */
|
||||
UnlockReleaseBuffer(buf);
|
||||
UnlockReleaseBuffer(newbuf);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,16 @@
|
||||
#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++
|
||||
*
|
||||
@@ -66,7 +76,7 @@ InitCenters(Relation index, VectorArray samples, VectorArray centers, float *low
|
||||
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];
|
||||
@@ -145,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)
|
||||
@@ -403,7 +413,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 */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
8
test/perl/PostgresNode.pm
Normal file
8
test/perl/PostgresNode.pm
Normal 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
3
test/perl/TestLib.pm
Normal file
@@ -0,0 +1,3 @@
|
||||
use PostgreSQL::Test::Utils;
|
||||
|
||||
1;
|
||||
@@ -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,15 @@ 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;
|
||||
SELECT * FROM tst ORDER BY v <-> '[$sql]' LIMIT 10;
|
||||
);
|
||||
|
||||
# Run test queries and compare their result
|
||||
@@ -50,6 +54,10 @@ sub test_index_replay
|
||||
# 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';
|
||||
|
||||
@@ -64,9 +72,9 @@ $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, (SELECT array_agg(random()) FROM generate_series(1, $dim)) FROM generate_series(1, 100000) i;"
|
||||
);
|
||||
$node_primary->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v);");
|
||||
|
||||
@@ -82,7 +90,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, (SELECT array_agg(random()) FROM generate_series(1, $dim)) FROM generate_series($start, $end) i;"
|
||||
);
|
||||
test_index_replay("insert $i");
|
||||
}
|
||||
|
||||
33
test/t/007_inserts.pl
Normal file
33
test/t/007_inserts.pl
Normal file
@@ -0,0 +1,33 @@
|
||||
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 and index
|
||||
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
||||
$node->safe_psql("postgres", "CREATE TABLE tst (v vector(768));");
|
||||
$node->safe_psql("postgres",
|
||||
"INSERT INTO tst SELECT (SELECT array_agg(random()) FROM generate_series(1, 768)) 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_concurrent" => q(
|
||||
BEGIN;
|
||||
INSERT INTO tst SELECT (SELECT array_agg(random()) FROM generate_series(1, 768)) FROM generate_series(1, 10) i;
|
||||
COMMIT;
|
||||
),
|
||||
}
|
||||
);
|
||||
@@ -1,4 +1,4 @@
|
||||
comment = 'vector data type and ivfflat access method'
|
||||
default_version = '0.2.6'
|
||||
default_version = '0.3.0'
|
||||
module_pathname = '$libdir/vector'
|
||||
relocatable = true
|
||||
|
||||
Reference in New Issue
Block a user