mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-22 12:07:34 +08:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56cb5f3503 | ||
|
|
ecbf46938f | ||
|
|
4ca264ba02 | ||
|
|
f5458414b8 | ||
|
|
e64ed39acb | ||
|
|
0d025be9d3 | ||
|
|
fed60dce78 | ||
|
|
a37f5eea4a | ||
|
|
4bdb27e85a | ||
|
|
38f869e0bd | ||
|
|
ad8acc00d4 | ||
|
|
01926a418e | ||
|
|
9658d3c1ad | ||
|
|
6b9c6516f4 | ||
|
|
88be03a3fa | ||
|
|
bf5b2c8d7e | ||
|
|
ac65ec2856 | ||
|
|
7bba0e2a01 | ||
|
|
c35e9f3b84 | ||
|
|
9549d93260 | ||
|
|
310809d0e5 | ||
|
|
ce72ca8620 | ||
|
|
4f2c937a1f | ||
|
|
ff400ce5f1 | ||
|
|
41d11c62d6 | ||
|
|
fa5e90585d | ||
|
|
71d2908be1 | ||
|
|
d0a1c1d0ed | ||
|
|
8063201cdf | ||
|
|
2f52127bee | ||
|
|
4778039ffa | ||
|
|
e3c9d70acc | ||
|
|
5971e28f8c | ||
|
|
2da27e2f01 | ||
|
|
7dd934913d | ||
|
|
9b46987e73 | ||
|
|
868e1f99a4 | ||
|
|
354271178f | ||
|
|
fe9b218903 | ||
|
|
3cdd1c16fb | ||
|
|
4f7a5c92da | ||
|
|
9399575e02 | ||
|
|
e7539fe548 | ||
|
|
61860ee6d9 | ||
|
|
eb99730d87 | ||
|
|
e5da5cc532 | ||
|
|
f59b1184fc | ||
|
|
cab103a2e7 | ||
|
|
0094b70a36 | ||
|
|
46ed56d80f | ||
|
|
d390cc9fa6 | ||
|
|
02ae98791c | ||
|
|
b64c7d6228 | ||
|
|
76f117c81c | ||
|
|
5414b2bc9e | ||
|
|
6ba583b587 | ||
|
|
da88217fd8 | ||
|
|
d5da22f24b | ||
|
|
062e220936 | ||
|
|
3d2b867a58 | ||
|
|
5aeabc3648 | ||
|
|
687ec6d8d7 | ||
|
|
51dc18d5b7 | ||
|
|
cb108ebfd2 |
@@ -1,6 +1,6 @@
|
|||||||
root = true
|
root = true
|
||||||
|
|
||||||
[*.{c,h}]
|
[*.{c,h,pl}]
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
indent_size = tab
|
indent_size = tab
|
||||||
tab_width = 4
|
tab_width = 4
|
||||||
|
|||||||
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,3 +1,13 @@
|
|||||||
|
## 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)
|
## 0.2.4 (2022-02-06)
|
||||||
|
|
||||||
- Added support for parallel vacuum
|
- Added support for parallel vacuum
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "vector",
|
"name": "vector",
|
||||||
"abstract": "Open-source vector similarity search for Postgres",
|
"abstract": "Open-source vector similarity search for Postgres",
|
||||||
"description": "Supports L2 distance, inner product, and cosine distance",
|
"description": "Supports L2 distance, inner product, and cosine distance",
|
||||||
"version": "0.2.4",
|
"version": "0.2.6",
|
||||||
"maintainer": [
|
"maintainer": [
|
||||||
"Andrew Kane <andrew@ankane.org>"
|
"Andrew Kane <andrew@ankane.org>"
|
||||||
],
|
],
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"vector": {
|
"vector": {
|
||||||
"file": "sql/vector.sql",
|
"file": "sql/vector.sql",
|
||||||
"docfile": "README.md",
|
"docfile": "README.md",
|
||||||
"version": "0.2.4",
|
"version": "0.2.6",
|
||||||
"abstract": "Open-source vector similarity search for Postgres"
|
"abstract": "Open-source vector similarity search for Postgres"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -1,5 +1,5 @@
|
|||||||
EXTENSION = vector
|
EXTENSION = vector
|
||||||
EXTVERSION = 0.2.4
|
EXTVERSION = 0.2.6
|
||||||
|
|
||||||
MODULE_big = vector
|
MODULE_big = vector
|
||||||
DATA = $(wildcard sql/*--*.sql)
|
DATA = $(wildcard sql/*--*.sql)
|
||||||
@@ -21,7 +21,7 @@ endif
|
|||||||
# For auto-vectorization:
|
# For auto-vectorization:
|
||||||
# - GCC (needs -ftree-vectorize OR -O3) - https://gcc.gnu.org/projects/tree-ssa/vectorization.html
|
# - GCC (needs -ftree-vectorize OR -O3) - https://gcc.gnu.org/projects/tree-ssa/vectorization.html
|
||||||
# - Clang (could use pragma instead) - https://llvm.org/docs/Vectorizers.html
|
# - Clang (could use pragma instead) - https://llvm.org/docs/Vectorizers.html
|
||||||
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
|
# Debug GCC auto-vectorization
|
||||||
# PG_CFLAGS += -fopt-info-vec
|
# PG_CFLAGS += -fopt-info-vec
|
||||||
|
|||||||
25
README.md
25
README.md
@@ -17,7 +17,7 @@ Supports L2 distance, inner product, and cosine distance
|
|||||||
Compile and install the extension (supports Postgres 9.6+)
|
Compile and install the extension (supports Postgres 9.6+)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone --branch v0.2.4 https://github.com/pgvector/pgvector.git
|
git clone --branch v0.2.6 https://github.com/pgvector/pgvector.git
|
||||||
cd pgvector
|
cd pgvector
|
||||||
make
|
make
|
||||||
make install # may need sudo
|
make install # may need sudo
|
||||||
@@ -77,19 +77,7 @@ Cosine distance
|
|||||||
CREATE INDEX ON table USING ivfflat (column vector_cosine_ops);
|
CREATE INDEX ON table USING ivfflat (column vector_cosine_ops);
|
||||||
```
|
```
|
||||||
|
|
||||||
Indexes should be created after the table has data for optimal clustering. If the distribution of data changes significantly, you can reindex without downtime:
|
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.
|
||||||
|
|
||||||
```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;
|
|
||||||
```
|
|
||||||
|
|
||||||
Also, unlike typical indexes which only affect performance, you may see different results for queries after adding an approximate index.
|
|
||||||
|
|
||||||
### Index Options
|
### Index Options
|
||||||
|
|
||||||
@@ -194,6 +182,7 @@ Libraries that use pgvector:
|
|||||||
|
|
||||||
- [pgvector-python](https://github.com/pgvector/pgvector-python) (Python)
|
- [pgvector-python](https://github.com/pgvector/pgvector-python) (Python)
|
||||||
- [Neighbor](https://github.com/ankane/neighbor) (Ruby)
|
- [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-node](https://github.com/pgvector/pgvector-node) (Node.js)
|
||||||
- [pgvector-go](https://github.com/pgvector/pgvector-go) (Go)
|
- [pgvector-go](https://github.com/pgvector/pgvector-go) (Go)
|
||||||
- [pgvector-rust](https://github.com/pgvector/pgvector-rust) (Rust)
|
- [pgvector-rust](https://github.com/pgvector/pgvector-rust) (Rust)
|
||||||
@@ -231,7 +220,7 @@ This adds pgvector to the [Postgres image](https://hub.docker.com/_/postgres).
|
|||||||
You can also build the image manually
|
You can also build the image manually
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone --branch v0.2.4 https://github.com/pgvector/pgvector.git
|
git clone --branch v0.2.6 https://github.com/pgvector/pgvector.git
|
||||||
cd pgvector
|
cd pgvector
|
||||||
docker build -t pgvector .
|
docker build -t pgvector .
|
||||||
```
|
```
|
||||||
@@ -315,6 +304,12 @@ make installcheck REGRESS=functions # regression test
|
|||||||
make prove_installcheck PROVE_TESTS=test/t/001_wal.pl # TAP 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
|
Resources for contributors
|
||||||
|
|
||||||
- [Extension Building Infrastructure](https://www.postgresql.org/docs/current/extend-pgxs.html)
|
- [Extension Building Infrastructure](https://www.postgresql.org/docs/current/extend-pgxs.html)
|
||||||
|
|||||||
2
sql/vector--0.2.4--0.2.5.sql
Normal file
2
sql/vector--0.2.4--0.2.5.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.5'" to load this file. \quit
|
||||||
2
sql/vector--0.2.5--0.2.6.sql
Normal file
2
sql/vector--0.2.5--0.2.6.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.6'" to load this file. \quit
|
||||||
123
src/ivfbuild.c
123
src/ivfbuild.c
@@ -36,16 +36,11 @@
|
|||||||
#define CALLBACK_ITEM_POINTER HeapTuple hup
|
#define CALLBACK_ITEM_POINTER HeapTuple hup
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
|
||||||
* Update build phase progress
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
UpdateProgress(int index, int64 val)
|
|
||||||
{
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
pgstat_progress_update_param(index, val);
|
#define UpdateProgress(index, val) pgstat_progress_update_param(index, val)
|
||||||
|
#else
|
||||||
|
#define UpdateProgress(index, val) ((void)val)
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Callback for sampling
|
* Callback for sampling
|
||||||
@@ -117,13 +112,13 @@ SampleRows(IvfflatBuildState * buildstate)
|
|||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
table_index_build_range_scan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
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
|
#elif PG_VERSION_NUM >= 110000
|
||||||
IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||||
true, true, targblock, 1, SampleCallback, (void *) buildstate, NULL);
|
false, true, targblock, 1, SampleCallback, (void *) buildstate, NULL);
|
||||||
#else
|
#else
|
||||||
IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||||
true, true, targblock, 1, SampleCallback, (void *) buildstate);
|
false, true, targblock, 1, SampleCallback, (void *) buildstate);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,16 +165,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 */
|
/* Create a virtual tuple */
|
||||||
ExecClearTuple(slot);
|
ExecClearTuple(slot);
|
||||||
slot->tts_values[0] = Int32GetDatum(closestCenter);
|
slot->tts_values[0] = Int32GetDatum(closestCenter);
|
||||||
slot->tts_isnull[0] = false;
|
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_isnull[1] = false;
|
||||||
slot->tts_values[2] = Int32GetDatum(ItemPointerGetOffsetNumberNoCheck(tid));
|
slot->tts_values[2] = value;
|
||||||
slot->tts_isnull[2] = false;
|
slot->tts_isnull[2] = false;
|
||||||
slot->tts_values[3] = value;
|
|
||||||
slot->tts_isnull[3] = false;
|
|
||||||
ExecStoreVirtualTuple(slot);
|
ExecStoreVirtualTuple(slot);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -201,8 +200,6 @@ GetNextTuple(Tuplesortstate *sortstate, TupleDesc tupdesc, TupleTableSlot *slot,
|
|||||||
{
|
{
|
||||||
Datum value;
|
Datum value;
|
||||||
bool isnull;
|
bool isnull;
|
||||||
int tupblk;
|
|
||||||
int tupoff;
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 100000
|
#if PG_VERSION_NUM >= 100000
|
||||||
if (tuplesort_gettupleslot(sortstate, true, false, slot, NULL))
|
if (tuplesort_gettupleslot(sortstate, true, false, slot, NULL))
|
||||||
@@ -211,13 +208,11 @@ GetNextTuple(Tuplesortstate *sortstate, TupleDesc tupdesc, TupleTableSlot *slot,
|
|||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
*list = DatumGetInt32(slot_getattr(slot, 1, &isnull));
|
*list = DatumGetInt32(slot_getattr(slot, 1, &isnull));
|
||||||
tupblk = DatumGetInt32(slot_getattr(slot, 2, &isnull));
|
value = slot_getattr(slot, 3, &isnull);
|
||||||
tupoff = DatumGetInt32(slot_getattr(slot, 3, &isnull));
|
|
||||||
value = slot_getattr(slot, 4, &isnull);
|
|
||||||
|
|
||||||
/* Form the index tuple */
|
/* Form the index tuple */
|
||||||
*itup = index_form_tuple(tupdesc, &value, &isnull);
|
*itup = index_form_tuple(tupdesc, &value, &isnull);
|
||||||
ItemPointerSet(&(*itup)->t_tid, tupblk, tupoff);
|
(*itup)->t_tid = *((ItemPointer) DatumGetPointer(slot_getattr(slot, 2, &isnull)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
*list = -1;
|
*list = -1;
|
||||||
@@ -326,17 +321,16 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
|
|||||||
|
|
||||||
/* Create tuple description for sorting */
|
/* Create tuple description for sorting */
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
buildstate->tupdesc = CreateTemplateTupleDesc(4);
|
buildstate->tupdesc = CreateTemplateTupleDesc(3);
|
||||||
#else
|
#else
|
||||||
buildstate->tupdesc = CreateTemplateTupleDesc(4, false);
|
buildstate->tupdesc = CreateTemplateTupleDesc(3, false);
|
||||||
#endif
|
#endif
|
||||||
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 1, "list", INT4OID, -1, 0);
|
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 1, "list", INT4OID, -1, 0);
|
||||||
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 2, "blkno", INT4OID, -1, 0);
|
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0);
|
||||||
TupleDescInitEntry(buildstate->tupdesc, (AttrNumber) 3, "offset", INT4OID, -1, 0);
|
|
||||||
#if PG_VERSION_NUM >= 110000
|
#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
|
#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
|
#endif
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
@@ -350,6 +344,12 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
|
|||||||
|
|
||||||
/* Reuse for each tuple */
|
/* Reuse for each tuple */
|
||||||
buildstate->normvec = InitVector(buildstate->dimensions);
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -361,6 +361,11 @@ FreeBuildState(IvfflatBuildState * buildstate)
|
|||||||
pfree(buildstate->centers);
|
pfree(buildstate->centers);
|
||||||
pfree(buildstate->listInfo);
|
pfree(buildstate->listInfo);
|
||||||
pfree(buildstate->normvec);
|
pfree(buildstate->normvec);
|
||||||
|
|
||||||
|
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||||
|
pfree(buildstate->listSums);
|
||||||
|
pfree(buildstate->listCounts);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -377,14 +382,19 @@ ComputeCenters(IvfflatBuildState * buildstate)
|
|||||||
if (numSamples < 10000)
|
if (numSamples < 10000)
|
||||||
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);
|
buildstate->samples = VectorArrayInit(numSamples, buildstate->dimensions);
|
||||||
if (buildstate->heap != NULL)
|
if (buildstate->heap != NULL)
|
||||||
SampleRows(buildstate);
|
SampleRows(buildstate);
|
||||||
|
|
||||||
/* Calculate centers */
|
/* Calculate centers */
|
||||||
UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_KMEANS);
|
UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_KMEANS);
|
||||||
IvfflatKmeans(buildstate->index, buildstate->samples, buildstate->centers);
|
IvfflatBench("k-means", IvfflatKmeans(buildstate->index, buildstate->samples, buildstate->centers));
|
||||||
|
|
||||||
/* Free samples before we allocate more memory */
|
/* Free samples before we allocate more memory */
|
||||||
pfree(buildstate->samples);
|
pfree(buildstate->samples);
|
||||||
@@ -463,6 +473,51 @@ CreateListPages(Relation index, VectorArray centers, int dimensions,
|
|||||||
pfree(list);
|
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
|
* Create entry pages
|
||||||
*/
|
*/
|
||||||
@@ -497,8 +552,14 @@ CreateEntryPages(IvfflatBuildState * buildstate, ForkNumber forkNum)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sort and insert */
|
/* Sort */
|
||||||
tuplesort_performsort(buildstate->sortstate);
|
tuplesort_performsort(buildstate->sortstate);
|
||||||
|
|
||||||
|
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||||
|
PrintKmeansMetrics(buildstate);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Insert */
|
||||||
InsertTuples(buildstate->index, buildstate, forkNum);
|
InsertTuples(buildstate->index, buildstate, forkNum);
|
||||||
tuplesort_end(buildstate->sortstate);
|
tuplesort_end(buildstate->sortstate);
|
||||||
}
|
}
|
||||||
@@ -517,7 +578,7 @@ BuildIndex(Relation heap, Relation index, IndexInfo *indexInfo,
|
|||||||
/* Create pages */
|
/* Create pages */
|
||||||
CreateMetaPage(index, buildstate->dimensions, buildstate->lists, forkNum);
|
CreateMetaPage(index, buildstate->dimensions, buildstate->lists, forkNum);
|
||||||
CreateListPages(index, buildstate->centers, buildstate->dimensions, buildstate->lists, forkNum, &buildstate->listInfo);
|
CreateListPages(index, buildstate->centers, buildstate->dimensions, buildstate->lists, forkNum, &buildstate->listInfo);
|
||||||
CreateEntryPages(buildstate, forkNum);
|
IvfflatBench("CreateEntryPages", CreateEntryPages(buildstate, forkNum));
|
||||||
|
|
||||||
FreeBuildState(buildstate);
|
FreeBuildState(buildstate);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,6 +159,11 @@ ivfflatvalidate(Oid opclassoid)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Define index handler
|
||||||
|
*
|
||||||
|
* See https://www.postgresql.org/docs/current/index-api.html
|
||||||
|
*/
|
||||||
PG_FUNCTION_INFO_V1(ivfflathandler);
|
PG_FUNCTION_INFO_V1(ivfflathandler);
|
||||||
Datum
|
Datum
|
||||||
ivfflathandler(PG_FUNCTION_ARGS)
|
ivfflathandler(PG_FUNCTION_ARGS)
|
||||||
@@ -193,6 +198,7 @@ ivfflathandler(PG_FUNCTION_ARGS)
|
|||||||
#endif
|
#endif
|
||||||
amroutine->amkeytype = InvalidOid;
|
amroutine->amkeytype = InvalidOid;
|
||||||
|
|
||||||
|
/* Interface functions */
|
||||||
amroutine->ambuild = ivfflatbuild;
|
amroutine->ambuild = ivfflatbuild;
|
||||||
amroutine->ambuildempty = ivfflatbuildempty;
|
amroutine->ambuildempty = ivfflatbuildempty;
|
||||||
amroutine->aminsert = ivfflatinsert;
|
amroutine->aminsert = ivfflatinsert;
|
||||||
@@ -206,6 +212,9 @@ ivfflathandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->ambuildphasename = ivfflatbuildphasename;
|
amroutine->ambuildphasename = ivfflatbuildphasename;
|
||||||
#endif
|
#endif
|
||||||
amroutine->amvalidate = ivfflatvalidate;
|
amroutine->amvalidate = ivfflatvalidate;
|
||||||
|
#if PG_VERSION_NUM >= 140000
|
||||||
|
amroutine->amadjustmembers = NULL;
|
||||||
|
#endif
|
||||||
amroutine->ambeginscan = ivfflatbeginscan;
|
amroutine->ambeginscan = ivfflatbeginscan;
|
||||||
amroutine->amrescan = ivfflatrescan;
|
amroutine->amrescan = ivfflatrescan;
|
||||||
amroutine->amgettuple = ivfflatgettuple;
|
amroutine->amgettuple = ivfflatgettuple;
|
||||||
@@ -213,6 +222,8 @@ ivfflathandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amendscan = ivfflatendscan;
|
amroutine->amendscan = ivfflatendscan;
|
||||||
amroutine->ammarkpos = NULL;
|
amroutine->ammarkpos = NULL;
|
||||||
amroutine->amrestrpos = NULL;
|
amroutine->amrestrpos = NULL;
|
||||||
|
|
||||||
|
/* Interface functions to support parallel index scans */
|
||||||
#if PG_VERSION_NUM >= 100000
|
#if PG_VERSION_NUM >= 100000
|
||||||
amroutine->amestimateparallelscan = NULL;
|
amroutine->amestimateparallelscan = NULL;
|
||||||
amroutine->aminitparallelscan = NULL;
|
amroutine->aminitparallelscan = NULL;
|
||||||
|
|||||||
@@ -10,6 +10,14 @@
|
|||||||
#include "utils/tuplesort.h"
|
#include "utils/tuplesort.h"
|
||||||
#include "vector.h"
|
#include "vector.h"
|
||||||
|
|
||||||
|
#ifdef IVFFLAT_BENCH
|
||||||
|
#include "portability/instr_time.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM < 90600
|
||||||
|
#error "Requires PostgreSQL 9.6+"
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Support functions */
|
/* Support functions */
|
||||||
#define IVFFLAT_DISTANCE_PROC 1
|
#define IVFFLAT_DISTANCE_PROC 1
|
||||||
#define IVFFLAT_NORM_PROC 2
|
#define IVFFLAT_NORM_PROC 2
|
||||||
@@ -39,9 +47,19 @@
|
|||||||
#define IvfflatPageGetOpaque(page) ((IvfflatPageOpaque) PageGetSpecialPointer(page))
|
#define IvfflatPageGetOpaque(page) ((IvfflatPageOpaque) PageGetSpecialPointer(page))
|
||||||
#define IvfflatPageGetMeta(page) ((IvfflatMetaPageData *) PageGetContents(page))
|
#define IvfflatPageGetMeta(page) ((IvfflatMetaPageData *) PageGetContents(page))
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 100000
|
#ifdef IVFFLAT_BENCH
|
||||||
#define ItemPointerGetBlockNumberNoCheck ItemPointerGetBlockNumber
|
#define IvfflatBench(name, code) \
|
||||||
#define ItemPointerGetOffsetNumberNoCheck ItemPointerGetOffsetNumber
|
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
|
#endif
|
||||||
|
|
||||||
/* Variables */
|
/* Variables */
|
||||||
@@ -97,6 +115,12 @@ typedef struct IvfflatBuildState
|
|||||||
ListInfo *listInfo;
|
ListInfo *listInfo;
|
||||||
Vector *normvec;
|
Vector *normvec;
|
||||||
|
|
||||||
|
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||||
|
double inertia;
|
||||||
|
double *listSums;
|
||||||
|
int *listCounts;
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Sampling */
|
/* Sampling */
|
||||||
BlockSamplerData bs;
|
BlockSamplerData bs;
|
||||||
ReservoirStateData rstate;
|
ReservoirStateData rstate;
|
||||||
@@ -138,6 +162,7 @@ typedef IvfflatListData * IvfflatList;
|
|||||||
|
|
||||||
typedef struct IvfflatScanList
|
typedef struct IvfflatScanList
|
||||||
{
|
{
|
||||||
|
pairingheap_node ph_node;
|
||||||
BlockNumber startPage;
|
BlockNumber startPage;
|
||||||
double distance;
|
double distance;
|
||||||
} IvfflatScanList;
|
} IvfflatScanList;
|
||||||
@@ -159,6 +184,8 @@ typedef struct IvfflatScanOpaqueData
|
|||||||
FmgrInfo *normprocinfo;
|
FmgrInfo *normprocinfo;
|
||||||
Oid collation;
|
Oid collation;
|
||||||
|
|
||||||
|
/* Lists */
|
||||||
|
pairingheap *listQueue;
|
||||||
IvfflatScanList lists[FLEXIBLE_ARRAY_MEMBER]; /* must come last */
|
IvfflatScanList lists[FLEXIBLE_ARRAY_MEMBER]; /* must come last */
|
||||||
} IvfflatScanOpaqueData;
|
} IvfflatScanOpaqueData;
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
* https://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf
|
* https://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
InitCenters(Relation index, VectorArray samples, VectorArray centers, double *lowerBound)
|
InitCenters(Relation index, VectorArray samples, VectorArray centers, float *lowerBound)
|
||||||
{
|
{
|
||||||
FmgrInfo *procinfo;
|
FmgrInfo *procinfo;
|
||||||
Oid collation;
|
Oid collation;
|
||||||
@@ -21,7 +21,7 @@ InitCenters(Relation index, VectorArray samples, VectorArray centers, double *lo
|
|||||||
double sum;
|
double sum;
|
||||||
double choice;
|
double choice;
|
||||||
Vector *vec;
|
Vector *vec;
|
||||||
double *weight = palloc(samples->length * sizeof(double));
|
float *weight = palloc(samples->length * sizeof(float));
|
||||||
int numCenters = centers->maxlen;
|
int numCenters = centers->maxlen;
|
||||||
int numSamples = samples->length;
|
int numSamples = samples->length;
|
||||||
|
|
||||||
@@ -121,15 +121,18 @@ QuickCenters(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
FmgrInfo *normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC);
|
FmgrInfo *normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC);
|
||||||
|
|
||||||
/* Copy existing vectors while avoiding duplicates */
|
/* Copy existing vectors while avoiding duplicates */
|
||||||
qsort(samples->items, samples->length, VECTOR_SIZE(samples->dim), CompareVectors);
|
if (samples->length > 0)
|
||||||
for (i = 0; i < samples->length; i++)
|
|
||||||
{
|
{
|
||||||
vec = VectorArrayGet(samples, i);
|
qsort(samples->items, samples->length, VECTOR_SIZE(samples->dim), CompareVectors);
|
||||||
|
for (i = 0; i < samples->length; i++)
|
||||||
if (i == 0 || CompareVectors(vec, VectorArrayGet(samples, i - 1)) != 0)
|
|
||||||
{
|
{
|
||||||
VectorArraySet(centers, centers->length, vec);
|
vec = VectorArrayGet(samples, i);
|
||||||
centers->length++;
|
|
||||||
|
if (i == 0 || CompareVectors(vec, VectorArrayGet(samples, i - 1)) != 0)
|
||||||
|
{
|
||||||
|
VectorArraySet(centers, centers->length, vec);
|
||||||
|
centers->length++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,11 +180,11 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
VectorArray newCenters;
|
VectorArray newCenters;
|
||||||
int *centerCounts;
|
int *centerCounts;
|
||||||
int *closestCenters;
|
int *closestCenters;
|
||||||
double *lowerBound;
|
float *lowerBound;
|
||||||
double *upperBound;
|
float *upperBound;
|
||||||
double *s;
|
float *s;
|
||||||
double *halfcdist;
|
float *halfcdist;
|
||||||
double *newcdist;
|
float *newcdist;
|
||||||
int changes;
|
int changes;
|
||||||
double minDistance;
|
double minDistance;
|
||||||
int closestCenter;
|
int closestCenter;
|
||||||
@@ -191,19 +194,43 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
double dxcx;
|
double dxcx;
|
||||||
double dxc;
|
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)));
|
||||||
|
|
||||||
/* Set support functions */
|
/* Set support functions */
|
||||||
procinfo = index_getprocinfo(index, 1, IVFFLAT_KMEANS_DISTANCE_PROC);
|
procinfo = index_getprocinfo(index, 1, IVFFLAT_KMEANS_DISTANCE_PROC);
|
||||||
normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC);
|
normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC);
|
||||||
collation = index->rd_indcollation[0];
|
collation = index->rd_indcollation[0];
|
||||||
|
|
||||||
/* Allocate space */
|
/* Allocate space */
|
||||||
centerCounts = palloc(sizeof(int) * numCenters);
|
/* Use float instead of double to save memory */
|
||||||
closestCenters = palloc(sizeof(int) * numSamples);
|
centerCounts = palloc(centerCountsSize);
|
||||||
lowerBound = palloc(sizeof(double) * numSamples * numCenters);
|
closestCenters = palloc(closestCentersSize);
|
||||||
upperBound = palloc(sizeof(double) * numSamples);
|
lowerBound = palloc_extended(lowerBoundSize, MCXT_ALLOC_HUGE);
|
||||||
s = palloc(sizeof(double) * numCenters);
|
upperBound = palloc(upperBoundSize);
|
||||||
halfcdist = palloc(sizeof(double) * numCenters * numCenters);
|
s = palloc(sSize);
|
||||||
newcdist = palloc(sizeof(double) * numCenters);
|
halfcdist = palloc(halfcdistSize);
|
||||||
|
newcdist = palloc(newcdistSize);
|
||||||
|
|
||||||
newCenters = VectorArrayInit(numCenters, dimensions);
|
newCenters = VectorArrayInit(numCenters, dimensions);
|
||||||
for (j = 0; j < numCenters; j++)
|
for (j = 0; j < numCenters; j++)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include <float.h>
|
||||||
|
|
||||||
#include "access/relscan.h"
|
#include "access/relscan.h"
|
||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
@@ -17,14 +19,12 @@
|
|||||||
* Compare list distances
|
* Compare list distances
|
||||||
*/
|
*/
|
||||||
static int
|
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 (((const IvfflatScanList *) a)->distance > ((const IvfflatScanList *) b)->distance)
|
||||||
|
|
||||||
if (diff > 0)
|
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
if (diff < 0)
|
if (((const IvfflatScanList *) a)->distance < ((const IvfflatScanList *) b)->distance)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -45,6 +45,8 @@ GetScanLists(IndexScanDesc scan, Datum value)
|
|||||||
int listCount = 0;
|
int listCount = 0;
|
||||||
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
IvfflatScanOpaque so = (IvfflatScanOpaque) scan->opaque;
|
||||||
double distance;
|
double distance;
|
||||||
|
IvfflatScanList *scanlist;
|
||||||
|
double maxDistance = DBL_MAX;
|
||||||
|
|
||||||
/* Search all list pages */
|
/* Search all list pages */
|
||||||
while (BlockNumberIsValid(nextblkno))
|
while (BlockNumberIsValid(nextblkno))
|
||||||
@@ -62,21 +64,39 @@ GetScanLists(IndexScanDesc scan, Datum value)
|
|||||||
/* Use procinfo from the index instead of scan key for performance */
|
/* Use procinfo from the index instead of scan key for performance */
|
||||||
distance = DatumGetFloat8(FunctionCall2Coll(so->procinfo, so->collation, PointerGetDatum(&list->center), value));
|
distance = DatumGetFloat8(FunctionCall2Coll(so->procinfo, so->collation, PointerGetDatum(&list->center), value));
|
||||||
|
|
||||||
so->lists[listCount].startPage = list->startPage;
|
if (listCount < so->probes)
|
||||||
so->lists[listCount].distance = distance;
|
{
|
||||||
listCount++;
|
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;
|
nextblkno = IvfflatPageGetOpaque(cpage)->nextblkno;
|
||||||
|
|
||||||
UnlockReleaseBuffer(cbuf);
|
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;
|
OffsetNumber maxoffno;
|
||||||
Datum datum;
|
Datum datum;
|
||||||
bool isnull;
|
bool isnull;
|
||||||
int i;
|
|
||||||
TupleDesc tupdesc = RelationGetDescr(scan->indexRelation);
|
TupleDesc tupdesc = RelationGetDescr(scan->indexRelation);
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
@@ -111,9 +130,9 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
|||||||
BufferAccessStrategy bas = GetAccessStrategy(BAS_BULKREAD);
|
BufferAccessStrategy bas = GetAccessStrategy(BAS_BULKREAD);
|
||||||
|
|
||||||
/* Search closest probes lists */
|
/* 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 */
|
/* Search all entry pages for list */
|
||||||
while (BlockNumberIsValid(searchPage))
|
while (BlockNumberIsValid(searchPage))
|
||||||
@@ -137,12 +156,10 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
|||||||
ExecClearTuple(slot);
|
ExecClearTuple(slot);
|
||||||
slot->tts_values[0] = FunctionCall2Coll(so->procinfo, so->collation, datum, value);
|
slot->tts_values[0] = FunctionCall2Coll(so->procinfo, so->collation, datum, value);
|
||||||
slot->tts_isnull[0] = false;
|
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_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_isnull[2] = false;
|
||||||
slot->tts_values[3] = Int32GetDatum((int) searchPage);
|
|
||||||
slot->tts_isnull[3] = false;
|
|
||||||
ExecStoreVirtualTuple(slot);
|
ExecStoreVirtualTuple(slot);
|
||||||
|
|
||||||
tuplesort_puttupleslot(so->sortstate, slot);
|
tuplesort_puttupleslot(so->sortstate, slot);
|
||||||
@@ -153,6 +170,8 @@ GetScanItems(IndexScanDesc scan, Datum value)
|
|||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tuplesort_performsort(so->sortstate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -168,13 +187,18 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
|||||||
Oid sortOperators[] = {Float8LessOperator};
|
Oid sortOperators[] = {Float8LessOperator};
|
||||||
Oid sortCollations[] = {InvalidOid};
|
Oid sortCollations[] = {InvalidOid};
|
||||||
bool nullsFirstFlags[] = {false};
|
bool nullsFirstFlags[] = {false};
|
||||||
|
int probes = ivfflat_probes;
|
||||||
|
|
||||||
scan = RelationGetIndexScan(index, nkeys, norderbys);
|
scan = RelationGetIndexScan(index, nkeys, norderbys);
|
||||||
lists = IvfflatGetLists(scan->indexRelation);
|
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->buf = InvalidBuffer;
|
||||||
so->first = true;
|
so->first = true;
|
||||||
|
so->probes = probes;
|
||||||
|
|
||||||
/* Set support functions */
|
/* Set support functions */
|
||||||
so->procinfo = index_getprocinfo(index, 1, IVFFLAT_DISTANCE_PROC);
|
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 */
|
/* Create tuple description for sorting */
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
so->tupdesc = CreateTemplateTupleDesc(4);
|
so->tupdesc = CreateTemplateTupleDesc(3);
|
||||||
#else
|
#else
|
||||||
so->tupdesc = CreateTemplateTupleDesc(4, false);
|
so->tupdesc = CreateTemplateTupleDesc(3, false);
|
||||||
#endif
|
#endif
|
||||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 1, "distance", FLOAT8OID, -1, 0);
|
TupleDescInitEntry(so->tupdesc, (AttrNumber) 1, "distance", FLOAT8OID, -1, 0);
|
||||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 2, "blkno", INT4OID, -1, 0);
|
TupleDescInitEntry(so->tupdesc, (AttrNumber) 2, "tid", TIDOID, -1, 0);
|
||||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 3, "offset", INT4OID, -1, 0);
|
TupleDescInitEntry(so->tupdesc, (AttrNumber) 3, "indexblkno", INT4OID, -1, 0);
|
||||||
TupleDescInitEntry(so->tupdesc, (AttrNumber) 4, "indexblkno", INT4OID, -1, 0);
|
|
||||||
|
|
||||||
/* Prep sort */
|
/* Prep sort */
|
||||||
#if PG_VERSION_NUM >= 110000
|
#if PG_VERSION_NUM >= 110000
|
||||||
@@ -205,6 +228,8 @@ ivfflatbeginscan(Relation index, int nkeys, int norderbys)
|
|||||||
so->slot = MakeSingleTupleTableSlot(so->tupdesc);
|
so->slot = MakeSingleTupleTableSlot(so->tupdesc);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
so->listQueue = pairingheap_allocate(CompareLists, scan);
|
||||||
|
|
||||||
scan->opaque = so;
|
scan->opaque = so;
|
||||||
|
|
||||||
return scan;
|
return scan;
|
||||||
@@ -224,7 +249,7 @@ ivfflatrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
so->first = true;
|
so->first = true;
|
||||||
so->probes = ivfflat_probes;
|
pairingheap_reset(so->listQueue);
|
||||||
|
|
||||||
if (keys && scan->numberOfKeys > 0)
|
if (keys && scan->numberOfKeys > 0)
|
||||||
memmove(scan->keyData, keys, scan->numberOfKeys * sizeof(ScanKeyData));
|
memmove(scan->keyData, keys, scan->numberOfKeys * sizeof(ScanKeyData));
|
||||||
@@ -268,9 +293,8 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
GetScanLists(scan, value);
|
IvfflatBench("GetScanLists", GetScanLists(scan, value));
|
||||||
GetScanItems(scan, value);
|
IvfflatBench("GetScanItems", GetScanItems(scan, value));
|
||||||
tuplesort_performsort(so->sortstate);
|
|
||||||
so->first = false;
|
so->first = false;
|
||||||
|
|
||||||
/* Clean up if we allocated a new value */
|
/* Clean up if we allocated a new value */
|
||||||
@@ -284,14 +308,13 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
if (tuplesort_gettupleslot(so->sortstate, true, so->slot, NULL))
|
if (tuplesort_gettupleslot(so->sortstate, true, so->slot, NULL))
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
BlockNumber blkno = DatumGetInt32(slot_getattr(so->slot, 2, &so->isnull));
|
ItemPointer tid = (ItemPointer) DatumGetPointer(slot_getattr(so->slot, 2, &so->isnull));
|
||||||
OffsetNumber offset = DatumGetInt32(slot_getattr(so->slot, 3, &so->isnull));
|
BlockNumber indexblkno = DatumGetInt32(slot_getattr(so->slot, 3, &so->isnull));
|
||||||
BlockNumber indexblkno = DatumGetInt32(slot_getattr(so->slot, 4, &so->isnull));
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
ItemPointerSet(&scan->xs_heaptid, blkno, offset);
|
scan->xs_heaptid = *tid;
|
||||||
#else
|
#else
|
||||||
ItemPointerSet(&scan->xs_ctup.t_self, blkno, offset);
|
scan->xs_ctup.t_self = *tid;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (BufferIsValid(so->buf))
|
if (BufferIsValid(so->buf))
|
||||||
@@ -324,6 +347,7 @@ ivfflatendscan(IndexScanDesc scan)
|
|||||||
if (BufferIsValid(so->buf))
|
if (BufferIsValid(so->buf))
|
||||||
ReleaseBuffer(so->buf);
|
ReleaseBuffer(so->buf);
|
||||||
|
|
||||||
|
pairingheap_free(so->listQueue);
|
||||||
tuplesort_end(so->sortstate);
|
tuplesort_end(so->sortstate);
|
||||||
|
|
||||||
pfree(so);
|
pfree(so);
|
||||||
|
|||||||
@@ -53,3 +53,12 @@ LINE 1: SELECT '[1,]'::vector;
|
|||||||
^
|
^
|
||||||
SELECT '[1,2,3]'::vector(2);
|
SELECT '[1,2,3]'::vector(2);
|
||||||
ERROR: expected 2 dimensions, not 3
|
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
|
||||||
|
|||||||
@@ -13,3 +13,6 @@ SELECT '1,2,3'::vector;
|
|||||||
SELECT '[]'::vector;
|
SELECT '[]'::vector;
|
||||||
SELECT '[1,]'::vector;
|
SELECT '[1,]'::vector;
|
||||||
SELECT '[1,2,3]'::vector(2);
|
SELECT '[1,2,3]'::vector(2);
|
||||||
|
|
||||||
|
SELECT unnest('{"[1,2,3]", "[4,5,6]"}'::vector[]);
|
||||||
|
SELECT '{"[1,2,3]"}'::vector(2)[];
|
||||||
|
|||||||
@@ -34,9 +34,10 @@ sub test_index_replay
|
|||||||
my $r2 = rand();
|
my $r2 = rand();
|
||||||
my $r3 = rand();
|
my $r3 = rand();
|
||||||
|
|
||||||
my $queries = qq(SET enable_seqscan=off;
|
my $queries = qq(
|
||||||
SELECT * FROM tst ORDER BY v <-> '[$r1,$r2,$r3]' LIMIT 10;
|
SET enable_seqscan = off;
|
||||||
);
|
SELECT * FROM tst ORDER BY v <-> '[$r1,$r2,$r3]' LIMIT 10;
|
||||||
|
);
|
||||||
|
|
||||||
# Run test queries and compare their result
|
# Run test queries and compare their result
|
||||||
my $primary_result = $node_primary->safe_psql("postgres", $queries);
|
my $primary_result = $node_primary->safe_psql("postgres", $queries);
|
||||||
@@ -65,10 +66,9 @@ $node_replica->start;
|
|||||||
$node_primary->safe_psql("postgres", "CREATE EXTENSION vector;");
|
$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(3));");
|
||||||
$node_primary->safe_psql("postgres",
|
$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[random(), random(), random()] FROM generate_series(1, 100000) i;"
|
||||||
);
|
);
|
||||||
$node_primary->safe_psql("postgres",
|
$node_primary->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v);");
|
||||||
"CREATE INDEX ON tst USING ivfflat (v);");
|
|
||||||
|
|
||||||
# Test that queries give same result
|
# Test that queries give same result
|
||||||
test_index_replay('initial');
|
test_index_replay('initial');
|
||||||
@@ -82,7 +82,7 @@ for my $i (1 .. 10)
|
|||||||
test_index_replay("vacuum $i");
|
test_index_replay("vacuum $i");
|
||||||
my ($start, $end) = (100001 + ($i - 1) * 10000, 100000 + $i * 10000);
|
my ($start, $end) = (100001 + ($i - 1) * 10000, 100000 + $i * 10000);
|
||||||
$node_primary->safe_psql("postgres",
|
$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[random(), random(), random()] FROM generate_series($start, $end) i;"
|
||||||
);
|
);
|
||||||
test_index_replay("insert $i");
|
test_index_replay("insert $i");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ $node->start;
|
|||||||
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
||||||
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector(3));");
|
$node->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector(3));");
|
||||||
$node->safe_psql("postgres",
|
$node->safe_psql("postgres",
|
||||||
"INSERT INTO tst SELECT i%10, ARRAY[i%1000, i%333, i%55] FROM generate_series(1,100000) i;"
|
"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);");
|
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v);");
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ my $size = $node->safe_psql("postgres", "SELECT pg_total_relation_size('tst_v_id
|
|||||||
$node->safe_psql("postgres", "DELETE FROM tst;");
|
$node->safe_psql("postgres", "DELETE FROM tst;");
|
||||||
$node->safe_psql("postgres", "VACUUM tst;");
|
$node->safe_psql("postgres", "VACUUM tst;");
|
||||||
$node->safe_psql("postgres",
|
$node->safe_psql("postgres",
|
||||||
"INSERT INTO tst SELECT i%10, ARRAY[i%1000, i%333, i%55] FROM generate_series(1,100000) i;"
|
"INSERT INTO tst SELECT i % 10, ARRAY[i % 1000, i % 333, i % 55] FROM generate_series(1, 100000) i;"
|
||||||
);
|
);
|
||||||
|
|
||||||
# Check size
|
# Check size
|
||||||
|
|||||||
88
test/t/003_recall.pl
Normal file
88
test/t/003_recall.pl
Normal 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
36
test/t/004_centers.pl
Normal 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);
|
||||||
45
test/t/005_query_recall.pl
Normal file
45
test/t/005_query_recall.pl
Normal 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
31
test/t/006_lists.pl
Normal 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/);
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
comment = 'vector data type and ivfflat access method'
|
comment = 'vector data type and ivfflat access method'
|
||||||
default_version = '0.2.4'
|
default_version = '0.2.6'
|
||||||
module_pathname = '$libdir/vector'
|
module_pathname = '$libdir/vector'
|
||||||
relocatable = true
|
relocatable = true
|
||||||
|
|||||||
Reference in New Issue
Block a user