Compare commits

..

4 Commits

Author SHA1 Message Date
Andrew Kane
76418fc093 Improved test [skip ci] 2024-01-23 15:13:14 -08:00
Andrew Kane
34d5a8cf3f Improved types 2024-01-23 15:08:50 -08:00
Andrew Kane
f9ae736c57 Updated comment [skip ci] 2024-01-23 15:01:48 -08:00
Andrew Kane
76c6dbb0a0 Added hamming_distance function 2024-01-23 15:01:11 -08:00
17 changed files with 229 additions and 142 deletions

View File

@@ -57,9 +57,9 @@ jobs:
- run: | - run: |
brew install cpanm brew install cpanm
cpanm --notest IPC::Run cpanm --notest IPC::Run
wget -q https://github.com/postgres/postgres/archive/refs/tags/REL_14_10.tar.gz wget -q https://github.com/postgres/postgres/archive/refs/tags/REL_14_5.tar.gz
tar xf REL_14_10.tar.gz tar xf REL_14_5.tar.gz
- run: make prove_installcheck PROVE_FLAGS="-I ./postgres-REL_14_10/src/test/perl" PERL5LIB="/Users/runner/perl5/lib/perl5" - run: make prove_installcheck PROVE_FLAGS="-I ./postgres-REL_14_5/src/test/perl" PERL5LIB="/Users/runner/perl5/lib/perl5"
- run: make clean && /usr/local/opt/llvm@15/bin/scan-build --status-bugs make PG_CFLAGS="-DUSE_ASSERT_CHECKING" - run: make clean && /usr/local/opt/llvm@15/bin/scan-build --status-bugs make PG_CFLAGS="-DUSE_ASSERT_CHECKING"
windows: windows:
runs-on: windows-latest runs-on: windows-latest

View File

@@ -1,17 +1,16 @@
## 0.6.0 (unreleased) ## 0.6.0 (unreleased)
If upgrading with Postgres 12 or Docker, see [these notes](https://github.com/pgvector/pgvector#060-unreleased). If upgrading with Postgres < 13, see [this note](https://github.com/pgvector/pgvector#060).
- Changed storage for vector from `extended` to `external` - Changed storage for vector from `extended` to `external`
- Improved performance of HNSW - Improved performance of HNSW
- Added support for parallel index builds for HNSW - Added support for parallel index builds for HNSW
- Added `hamming_distance` function
- Added validation for GUC parameters - Added validation for GUC parameters
- Reduced memory usage for HNSW index builds - Reduced memory usage for HNSW index builds
- Reduced WAL generation for HNSW index builds - Reduced WAL generation for HNSW index builds
- Fixed error with logical replication - Fixed error with logical replication
- Fixed `invalid memory alloc request size` error with HNSW index builds - Fixed `invalid memory alloc request size` error with HNSW index build
- Moved Docker image to `pgvector` org
- Added Docker tags for each supported version of Postgres
- Dropped support for Postgres 11 - Dropped support for Postgres 11
## 0.5.1 (2023-10-10) ## 0.5.1 (2023-10-10)
@@ -60,7 +59,7 @@ If upgrading with Postgres 12 or Docker, see [these notes](https://github.com/pg
## 0.4.0 (2023-01-11) ## 0.4.0 (2023-01-11)
If upgrading with Postgres < 13, see [this note](https://github.com/pgvector/pgvector/blob/v0.4.0/README.md#040). If upgrading with Postgres < 13, see [this note](https://github.com/pgvector/pgvector#040).
- Changed text representation for vector elements to match `real` - Changed text representation for vector elements to match `real`
- Changed storage for vector from `plain` to `extended` - Changed storage for vector from `plain` to `extended`
@@ -77,7 +76,7 @@ If upgrading with Postgres < 13, see [this note](https://github.com/pgvector/pgv
## 0.3.1 (2022-11-02) ## 0.3.1 (2022-11-02)
If upgrading from 0.2.7 or 0.3.0, [recreate](https://github.com/pgvector/pgvector/blob/v0.3.1/README.md#031) all `ivfflat` indexes after upgrading to ensure all data is indexed. 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 issue with inserts silently corrupting `ivfflat` indexes (introduced in 0.2.7)
- Fixed segmentation fault with index creation when lists > 6500 - Fixed segmentation fault with index creation when lists > 6500

View File

@@ -65,17 +65,13 @@ dist:
mkdir -p dist mkdir -p dist
git archive --format zip --prefix=$(EXTENSION)-$(EXTVERSION)/ --output dist/$(EXTENSION)-$(EXTVERSION).zip master git archive --format zip --prefix=$(EXTENSION)-$(EXTVERSION)/ --output dist/$(EXTENSION)-$(EXTVERSION).zip master
# for Docker
PG_MAJOR ?= 16
.PHONY: docker .PHONY: docker
docker: docker:
docker build --pull --no-cache --build-arg PG_MAJOR=$(PG_MAJOR) -t pgvector/pgvector:pg$(PG_MAJOR) . docker build --pull --no-cache --platform linux/amd64 -t ankane/pgvector:latest .
docker build --build-arg PG_MAJOR=$(PG_MAJOR) -t pgvector/pgvector:$(EXTVERSION)-pg$(PG_MAJOR) .
.PHONY: docker-release .PHONY: docker-release
docker-release: docker-release:
docker buildx build --push --pull --no-cache --platform linux/amd64,linux/arm64 --build-arg PG_MAJOR=$(PG_MAJOR) -t pgvector/pgvector:pg$(PG_MAJOR) . docker buildx build --push --pull --no-cache --platform linux/amd64,linux/arm64 -t ankane/pgvector:latest .
docker buildx build --push --platform linux/amd64,linux/arm64 --build-arg PG_MAJOR=$(PG_MAJOR) -t pgvector/pgvector:$(EXTVERSION)-pg$(PG_MAJOR) . docker buildx build --push --platform linux/amd64,linux/arm64 -t ankane/pgvector:v$(EXTVERSION) .

View File

@@ -14,8 +14,6 @@ Plus [ACID](https://en.wikipedia.org/wiki/ACID) compliance, point-in-time recove
## Installation ## Installation
### Linux and Mac
Compile and install the extension (supports Postgres 11+) Compile and install the extension (supports Postgres 11+)
```sh ```sh
@@ -30,28 +28,6 @@ See the [installation notes](#installation-notes) if you run into issues
You can also install it with [Docker](#docker), [Homebrew](#homebrew), [PGXN](#pgxn), [APT](#apt), [Yum](#yum), or [conda-forge](#conda-forge), and it comes preinstalled with [Postgres.app](#postgresapp) and many [hosted providers](#hosted-postgres). There are also instructions for [GitHub Actions](https://github.com/pgvector/setup-pgvector). You can also install it with [Docker](#docker), [Homebrew](#homebrew), [PGXN](#pgxn), [APT](#apt), [Yum](#yum), or [conda-forge](#conda-forge), and it comes preinstalled with [Postgres.app](#postgresapp) and many [hosted providers](#hosted-postgres). There are also instructions for [GitHub Actions](https://github.com/pgvector/setup-pgvector).
### Windows
Ensure [C++ support in Visual Studio](https://learn.microsoft.com/en-us/cpp/build/building-on-the-command-line?view=msvc-170#download-and-install-the-tools) is installed, and run:
```cmd
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
```
Note: The exact path will vary depending on your Visual Studio version and edition
Then use `nmake` to build:
```cmd
set "PGROOT=C:\Program Files\PostgreSQL\16"
git clone --branch v0.5.1 https://github.com/pgvector/pgvector.git
cd pgvector
nmake /F Makefile.win
nmake /F Makefile.win install
```
You can also install it with [Docker](#docker) or [conda-forge](#conda-forge).
## Getting Started ## Getting Started
Enable the extension (do this once in each database where you want to use it) Enable the extension (do this once in each database where you want to use it)
@@ -264,14 +240,6 @@ HINT: Increase maintenance_work_mem to speed up builds.
Note: Do not set `maintenance_work_mem` so high that it exhausts the memory on the server Note: Do not set `maintenance_work_mem` so high that it exhausts the memory on the server
Starting with 0.6.0 [unreleased], you can also speed up index creation by increasing the number of parallel workers (2 by default)
```sql
SET max_parallel_maintenance_workers = 7; -- plus leader
```
For a large number of workers, you may also need to increase `max_parallel_workers` (8 by default)
### Indexing Progress ### Indexing Progress
Check [indexing progress](https://www.postgresql.org/docs/current/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING) with Postgres 12+ Check [indexing progress](https://www.postgresql.org/docs/current/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING) with Postgres 12+
@@ -647,6 +615,26 @@ Note: Replace `16` with your Postgres server version
If compilation fails and the output includes `warning: no such sysroot directory` on Mac, reinstall Xcode Command Line Tools. If compilation fails and the output includes `warning: no such sysroot directory` on Mac, reinstall Xcode Command Line Tools.
### Windows
Support for Windows is currently experimental. Ensure [C++ support in Visual Studio](https://learn.microsoft.com/en-us/cpp/build/building-on-the-command-line?view=msvc-170#download-and-install-the-tools) is installed, and run:
```cmd
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
```
Note: The exact path will vary depending on your Visual Studio version and edition
Then use `nmake` to build:
```cmd
set "PGROOT=C:\Program Files\PostgreSQL\16"
git clone --branch v0.5.1 https://github.com/pgvector/pgvector.git
cd pgvector
nmake /F Makefile.win
nmake /F Makefile.win install
```
## Additional Installation Methods ## Additional Installation Methods
### Docker ### Docker
@@ -743,9 +731,7 @@ SELECT extversion FROM pg_extension WHERE extname = 'vector';
### 0.6.0 [unreleased] ### 0.6.0 [unreleased]
#### Postgres 12 If upgrading with Postgres < 13, remove this line from `sql/vector--0.5.1--0.6.0.sql`:
If upgrading with Postgres 12, remove this line from `sql/vector--0.5.1--0.6.0.sql`:
```sql ```sql
ALTER TYPE vector SET (STORAGE = external); ALTER TYPE vector SET (STORAGE = external);
@@ -753,20 +739,28 @@ ALTER TYPE vector SET (STORAGE = external);
Then run `make install` and `ALTER EXTENSION vector UPDATE;`. Then run `make install` and `ALTER EXTENSION vector UPDATE;`.
#### Docker ### 0.4.0
The Docker image is now published in the `pgvector` org, and there are tags for each supported version of Postgres (rather than a `latest` tag). If upgrading with Postgres < 13, remove this line from `sql/vector--0.3.2--0.4.0.sql`:
```sh ```sql
docker pull pgvector/pgvector:pg16 ALTER TYPE vector SET (STORAGE = extended);
# or
docker pull pgvector/pgvector:0.6.0-pg16
``` ```
Also, if youve increased `maintenance_work_mem`, make sure `--shm-size` is at least that size to avoid an error with parallel HNSW index builds. Then run `make install` and `ALTER EXTENSION vector UPDATE;`.
```sh ### 0.3.1
docker run --shm-size=1g ...
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
@@ -812,8 +806,8 @@ make prove_installcheck # TAP tests
To run single tests: To run single tests:
```sh ```sh
make installcheck REGRESS=functions # regression test make installcheck REGRESS=functions # regression test
make prove_installcheck PROVE_TESTS=test/t/001_ivfflat_wal.pl # TAP test make prove_installcheck PROVE_TESTS=test/t/001_wal.pl # TAP test
``` ```
To enable benchmarking: To enable benchmarking:

View File

@@ -3,3 +3,6 @@
-- remove this single line for Postgres < 13 -- remove this single line for Postgres < 13
ALTER TYPE vector SET (STORAGE = external); ALTER TYPE vector SET (STORAGE = external);
CREATE FUNCTION hamming_distance(bytea, bytea) RETURNS float8
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

View File

@@ -290,3 +290,8 @@ CREATE OPERATOR CLASS vector_cosine_ops
OPERATOR 1 <=> (vector, vector) FOR ORDER BY float_ops, OPERATOR 1 <=> (vector, vector) FOR ORDER BY float_ops,
FUNCTION 1 vector_negative_inner_product(vector, vector), FUNCTION 1 vector_negative_inner_product(vector, vector),
FUNCTION 2 vector_norm(vector); FUNCTION 2 vector_norm(vector);
-- bytea functions
CREATE FUNCTION hamming_distance(bytea, bytea) RETURNS float8
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

View File

@@ -64,8 +64,6 @@
#define HNSW_ELEMENT_TUPLE_SIZE(size) MAXALIGN(offsetof(HnswElementTupleData, data) + (size)) #define HNSW_ELEMENT_TUPLE_SIZE(size) MAXALIGN(offsetof(HnswElementTupleData, data) + (size))
#define HNSW_NEIGHBOR_TUPLE_SIZE(level, m) MAXALIGN(offsetof(HnswNeighborTupleData, indextids) + ((level) + 2) * (m) * sizeof(ItemPointerData)) #define HNSW_NEIGHBOR_TUPLE_SIZE(level, m) MAXALIGN(offsetof(HnswNeighborTupleData, indextids) + ((level) + 2) * (m) * sizeof(ItemPointerData))
#define HNSW_NEIGHBOR_ARRAY_SIZE(lm) (offsetof(HnswNeighborArray, items) + sizeof(HnswCandidate) * (lm))
#define HnswPageGetOpaque(page) ((HnswPageOpaque) PageGetSpecialPointer(page)) #define HnswPageGetOpaque(page) ((HnswPageOpaque) PageGetSpecialPointer(page))
#define HnswPageGetMeta(page) ((HnswMetaPageData *) PageGetContents(page)) #define HnswPageGetMeta(page) ((HnswMetaPageData *) PageGetContents(page))
@@ -354,6 +352,7 @@ typedef struct HnswVacuumState
/* Variables */ /* Variables */
struct tidhash_hash *deleted; struct tidhash_hash *deleted;
BufferAccessStrategy bas; BufferAccessStrategy bas;
HnswNeighborTuple ntup;
HnswElementData highestPoint; HnswElementData highestPoint;
/* Memory */ /* Memory */
@@ -380,7 +379,7 @@ void HnswUpdateMetaPage(Relation index, int updateEntry, HnswElement entryPoint
void HnswSetNeighborTuple(char *base, HnswNeighborTuple ntup, HnswElement e, int m); void HnswSetNeighborTuple(char *base, HnswNeighborTuple ntup, HnswElement e, int m);
void HnswAddHeapTid(HnswElement element, ItemPointer heaptid); void HnswAddHeapTid(HnswElement element, ItemPointer heaptid);
void HnswInitNeighbors(char *base, HnswElement element, int m, HnswAllocator * alloc); void HnswInitNeighbors(char *base, HnswElement element, int m, HnswAllocator * alloc);
bool HnswInsertTupleOnDisk(Relation index, Datum value, Datum *values, bool *isnull, ItemPointer heap_tid, bool building); bool HnswInsertTupleOnDisk(Relation index, Datum value, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel, bool building);
void HnswUpdateNeighborsOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting, bool building); void HnswUpdateNeighborsOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting, bool building);
void HnswLoadElementFromTuple(HnswElement element, HnswElementTuple etup, bool loadHeaptids, bool loadVec); void HnswLoadElementFromTuple(HnswElement element, HnswElementTuple etup, bool loadHeaptids, bool loadVec);
void HnswLoadElement(HnswElement element, float *distance, Datum *q, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec); void HnswLoadElement(HnswElement element, float *distance, Datum *q, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec);

View File

@@ -65,6 +65,8 @@
#define CALLBACK_ITEM_POINTER HeapTuple hup #define CALLBACK_ITEM_POINTER HeapTuple hup
#endif #endif
#define UpdateProgress(index, val) pgstat_progress_update_param(index, val)
#if PG_VERSION_NUM >= 140000 #if PG_VERSION_NUM >= 140000
#include "utils/backend_status.h" #include "utils/backend_status.h"
#include "utils/wait_event.h" #include "utils/wait_event.h"
@@ -141,10 +143,10 @@ HnswBuildAppendPage(Relation index, Buffer *buf, Page *page, ForkNumber forkNum)
} }
/* /*
* Create graph pages * Create element pages
*/ */
static void static void
CreateGraphPages(HnswBuildState * buildstate) CreateElementPages(HnswBuildState * buildstate)
{ {
Relation index = buildstate->index; Relation index = buildstate->index;
ForkNumber forkNum = buildstate->forkNum; ForkNumber forkNum = buildstate->forkNum;
@@ -244,24 +246,27 @@ CreateGraphPages(HnswBuildState * buildstate)
} }
/* /*
* Write neighbor tuples * Create neighbor pages
*/ */
static void static void
WriteNeighborTuples(HnswBuildState * buildstate) CreateNeighborPages(HnswBuildState * buildstate)
{ {
Relation index = buildstate->index; Relation index = buildstate->index;
ForkNumber forkNum = buildstate->forkNum; ForkNumber forkNum = buildstate->forkNum;
int m = buildstate->m; int m = buildstate->m;
HnswElementPtr iter = buildstate->graph->head; HnswElementPtr iter = buildstate->graph->head;
char *base = buildstate->hnswarea; char *base = buildstate->hnswarea;
HnswNeighborTuple ntup;
/* Allocate once */
ntup = palloc0(BLCKSZ);
while (!HnswPtrIsNull(base, iter)) while (!HnswPtrIsNull(base, iter))
{ {
HnswElement e = HnswPtrAccess(base, iter); HnswElement e = HnswPtrAccess(base, iter);
Buffer buf; Buffer buf;
Page page; Page page;
ItemId itemid; Size ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(e->level, m);
HnswNeighborTuple ntup;
/* Update iterator */ /* Update iterator */
iter = e->next; iter = e->next;
@@ -273,19 +278,18 @@ WriteNeighborTuples(HnswBuildState * buildstate)
buf = ReadBufferExtended(index, forkNum, e->neighborPage, RBM_NORMAL, NULL); buf = ReadBufferExtended(index, forkNum, e->neighborPage, RBM_NORMAL, NULL);
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
page = BufferGetPage(buf); page = BufferGetPage(buf);
itemid = PageGetItemId(page, e->neighborOffno);
ntup = (HnswNeighborTuple) PageGetItem(page, itemid);
/* Check expected size */
Assert(ItemIdGetLength(itemid) == HNSW_NEIGHBOR_TUPLE_SIZE(e->level, m));
/* Update page in-place */
HnswSetNeighborTuple(base, ntup, e, m); HnswSetNeighborTuple(base, ntup, e, m);
if (!PageIndexTupleOverwrite(page, e->neighborOffno, (Item) ntup, ntupSize))
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
/* Commit */ /* Commit */
MarkBufferDirty(buf); MarkBufferDirty(buf);
UnlockReleaseBuffer(buf); UnlockReleaseBuffer(buf);
} }
pfree(ntup);
} }
/* /*
@@ -299,8 +303,8 @@ FlushPages(HnswBuildState * buildstate)
#endif #endif
CreateMetaPage(buildstate); CreateMetaPage(buildstate);
CreateGraphPages(buildstate); CreateElementPages(buildstate);
WriteNeighborTuples(buildstate); CreateNeighborPages(buildstate);
buildstate->graph->flushed = true; buildstate->graph->flushed = true;
MemoryContextReset(buildstate->graphCtx); MemoryContextReset(buildstate->graphCtx);
@@ -494,7 +498,7 @@ InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heaptid, Hn
{ {
LWLockRelease(flushLock); LWLockRelease(flushLock);
return HnswInsertTupleOnDisk(index, value, values, isnull, heaptid, true); return HnswInsertTupleOnDisk(index, value, values, isnull, heaptid, buildstate->heap, true);
} }
/* /*
@@ -526,7 +530,7 @@ InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heaptid, Hn
LWLockRelease(flushLock); LWLockRelease(flushLock);
return HnswInsertTupleOnDisk(index, value, values, isnull, heaptid, true); return HnswInsertTupleOnDisk(index, value, values, isnull, heaptid, buildstate->heap, true);
} }
/* Ok, we can proceed to allocate the element */ /* Ok, we can proceed to allocate the element */
@@ -583,7 +587,7 @@ BuildCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
{ {
/* Update progress */ /* Update progress */
SpinLockAcquire(&graph->lock); SpinLockAcquire(&graph->lock);
pgstat_progress_update_param(PROGRESS_CREATEIDX_TUPLES_DONE, ++graph->indtuples); UpdateProgress(PROGRESS_CREATEIDX_TUPLES_DONE, ++graph->indtuples);
SpinLockRelease(&graph->lock); SpinLockRelease(&graph->lock);
} }
@@ -1044,7 +1048,7 @@ BuildGraph(HnswBuildState * buildstate, ForkNumber forkNum)
{ {
int parallel_workers = 0; int parallel_workers = 0;
pgstat_progress_update_param(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_HNSW_PHASE_LOAD); UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_HNSW_PHASE_LOAD);
/* Calculate parallel workers */ /* Calculate parallel workers */
if (buildstate->heap != NULL) if (buildstate->heap != NULL)

View File

@@ -570,7 +570,7 @@ UpdateGraphOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement
* Insert a tuple into the index * Insert a tuple into the index
*/ */
bool bool
HnswInsertTupleOnDisk(Relation index, Datum value, Datum *values, bool *isnull, ItemPointer heap_tid, bool building) HnswInsertTupleOnDisk(Relation index, Datum value, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel, bool building)
{ {
HnswElement entryPoint; HnswElement entryPoint;
HnswElement element; HnswElement element;
@@ -625,7 +625,7 @@ HnswInsertTupleOnDisk(Relation index, Datum value, Datum *values, bool *isnull,
* Insert a tuple into the index * Insert a tuple into the index
*/ */
static void static void
HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid) HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel)
{ {
Datum value; Datum value;
FmgrInfo *normprocinfo; FmgrInfo *normprocinfo;
@@ -642,7 +642,7 @@ HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_ti
return; return;
} }
HnswInsertTupleOnDisk(index, value, values, isnull, heap_tid, false); HnswInsertTupleOnDisk(index, value, values, isnull, heap_tid, heapRel, false);
} }
/* /*
@@ -671,7 +671,7 @@ hnswinsert(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid,
oldCtx = MemoryContextSwitchTo(insertCtx); oldCtx = MemoryContextSwitchTo(insertCtx);
/* Insert tuple */ /* Insert tuple */
HnswInsertTuple(index, values, isnull, heap_tid); HnswInsertTuple(index, values, isnull, heap_tid, heap);
/* Delete memory context */ /* Delete memory context */
MemoryContextSwitchTo(oldCtx); MemoryContextSwitchTo(oldCtx);

View File

@@ -202,7 +202,6 @@ hnswgettuple(IndexScanDesc scan, ScanDirection dir)
MemoryContextSwitchTo(oldCtx); MemoryContextSwitchTo(oldCtx);
scan->xs_heaptid = *heaptid; scan->xs_heaptid = *heaptid;
scan->xs_recheck = false;
scan->xs_recheckorderby = false; scan->xs_recheckorderby = false;
return true; return true;
} }

View File

@@ -7,7 +7,6 @@
#include "lib/pairingheap.h" #include "lib/pairingheap.h"
#include "storage/bufmgr.h" #include "storage/bufmgr.h"
#include "utils/datum.h" #include "utils/datum.h"
#include "utils/memdebug.h"
#include "utils/rel.h" #include "utils/rel.h"
#include "vector.h" #include "vector.h"
@@ -203,19 +202,6 @@ HnswInitPage(Buffer buf, Page page)
HnswPageGetOpaque(page)->page_id = HNSW_PAGE_ID; HnswPageGetOpaque(page)->page_id = HNSW_PAGE_ID;
} }
/*
* Allocate a neighbor array
*/
static HnswNeighborArray *
HnswInitNeighborArray(int lm, HnswAllocator * allocator)
{
HnswNeighborArray *a = HnswAlloc(allocator, HNSW_NEIGHBOR_ARRAY_SIZE(lm));
a->length = 0;
a->closerSet = false;
return a;
}
/* /*
* Allocate neighbors * Allocate neighbors
*/ */
@@ -223,12 +209,22 @@ void
HnswInitNeighbors(char *base, HnswElement element, int m, HnswAllocator * allocator) HnswInitNeighbors(char *base, HnswElement element, int m, HnswAllocator * allocator)
{ {
int level = element->level; int level = element->level;
HnswNeighborArrayPtr *neighborList = (HnswNeighborArrayPtr *) HnswAlloc(allocator, sizeof(HnswNeighborArrayPtr) * (level + 1)); HnswNeighborArrayPtr *neighborList = (HnswNeighborArrayPtr *) HnswAlloc(allocator, sizeof(HnswNeighborArrayPtr) * (level + 1));
HnswPtrStore(base, element->neighbors, neighborList); HnswPtrStore(base, element->neighbors, neighborList);
for (int lc = 0; lc <= level; lc++) for (int lc = 0; lc <= level; lc++)
HnswPtrStore(base, neighborList[lc], HnswInitNeighborArray(HnswGetLayerM(m, lc), allocator)); {
HnswNeighborArray *a;
int lm = HnswGetLayerM(m, lc);
HnswPtrStore(base, neighborList[lc], (HnswNeighborArray *) HnswAlloc(allocator, offsetof(HnswNeighborArray, items) + sizeof(HnswCandidate) * lm));
a = HnswGetNeighbors(base, element, lc);
a->length = 0;
a->closerSet = false;
}
} }
/* /*
@@ -316,10 +312,7 @@ HnswGetMetaPageInfo(Relation index, int *m, HnswElement * entryPoint)
if (entryPoint != NULL) if (entryPoint != NULL)
{ {
if (BlockNumberIsValid(metap->entryBlkno)) if (BlockNumberIsValid(metap->entryBlkno))
{
*entryPoint = HnswInitElementFromBlock(metap->entryBlkno, metap->entryOffno); *entryPoint = HnswInitElementFromBlock(metap->entryBlkno, metap->entryOffno);
(*entryPoint)->level = metap->entryLevel;
}
else else
*entryPoint = NULL; *entryPoint = NULL;
} }
@@ -738,7 +731,7 @@ HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, F
/* Create local memory for neighborhood if needed */ /* Create local memory for neighborhood if needed */
if (index == NULL) if (index == NULL)
{ {
neighborhoodSize = HNSW_NEIGHBOR_ARRAY_SIZE(HnswGetLayerM(m, lc)); neighborhoodSize = offsetof(HnswNeighborArray, items) + sizeof(HnswCandidate) * HnswGetLayerM(m, lc);
neighborhoodData = palloc(neighborhoodSize); neighborhoodData = palloc(neighborhoodSize);
} }
@@ -914,10 +907,40 @@ CompareCandidateDistancesOffset(const void *a, const void *b)
* Calculate the distance between elements * Calculate the distance between elements
*/ */
static float static float
HnswGetDistance(char *base, HnswElement a, HnswElement b, FmgrInfo *procinfo, Oid collation) HnswGetDistance(char *base, HnswElement a, HnswElement b, int lc, FmgrInfo *procinfo, Oid collation)
{ {
Datum aValue = HnswGetValue(base, a); Datum aValue;
Datum bValue = HnswGetValue(base, b); Datum bValue;
/* Look for cached distance */
if (!HnswPtrIsNull(base, a->neighbors))
{
HnswNeighborArray *neighbors = HnswGetNeighbors(base, a, lc);
for (int i = 0; i < neighbors->length; i++)
{
HnswElement element = HnswPtrAccess(base, neighbors->items[i].element);
if (element == b)
return neighbors->items[i].distance;
}
}
if (!HnswPtrIsNull(base, b->neighbors))
{
HnswNeighborArray *neighbors = HnswGetNeighbors(base, b, lc);
for (int i = 0; i < neighbors->length; i++)
{
HnswElement element = HnswPtrAccess(base, neighbors->items[i].element);
if (element == a)
return neighbors->items[i].distance;
}
}
aValue = HnswGetValue(base, a);
bValue = HnswGetValue(base, b);
return DatumGetFloat8(FunctionCall2Coll(procinfo, collation, aValue, bValue)); return DatumGetFloat8(FunctionCall2Coll(procinfo, collation, aValue, bValue));
} }
@@ -926,7 +949,7 @@ HnswGetDistance(char *base, HnswElement a, HnswElement b, FmgrInfo *procinfo, Oi
* Check if an element is closer to q than any element from R * Check if an element is closer to q than any element from R
*/ */
static bool static bool
CheckElementCloser(char *base, HnswCandidate * e, List *r, FmgrInfo *procinfo, Oid collation) CheckElementCloser(char *base, HnswCandidate * e, List *r, int lc, FmgrInfo *procinfo, Oid collation)
{ {
HnswElement eElement = HnswPtrAccess(base, e->element); HnswElement eElement = HnswPtrAccess(base, e->element);
ListCell *lc2; ListCell *lc2;
@@ -935,7 +958,7 @@ CheckElementCloser(char *base, HnswCandidate * e, List *r, FmgrInfo *procinfo, O
{ {
HnswCandidate *ri = lfirst(lc2); HnswCandidate *ri = lfirst(lc2);
HnswElement riElement = HnswPtrAccess(base, ri->element); HnswElement riElement = HnswPtrAccess(base, ri->element);
float distance = HnswGetDistance(base, eElement, riElement, procinfo, collation); float distance = HnswGetDistance(base, eElement, riElement, lc, procinfo, collation);
if (distance <= e->distance) if (distance <= e->distance)
return false; return false;
@@ -981,20 +1004,16 @@ SelectNeighbors(char *base, List *c, int lm, int lc, FmgrInfo *procinfo, Oid col
/* Use previous state of r and wd to skip work when possible */ /* Use previous state of r and wd to skip work when possible */
if (mustCalculate) if (mustCalculate)
e->closer = CheckElementCloser(base, e, r, procinfo, collation); e->closer = CheckElementCloser(base, e, r, lc, procinfo, collation);
else if (list_length(added) > 0) else if (list_length(added) > 0)
{ {
/* Keep Valgrind happy for in-memory, parallel builds */
if (base != NULL)
VALGRIND_MAKE_MEM_DEFINED(&e->closer, 1);
/* /*
* If the current candidate was closer, we only need to compare it * If the current candidate was closer, we only need to compare it
* with the other candidates that we have added. * with the other candidates that we have added.
*/ */
if (e->closer) if (e->closer)
{ {
e->closer = CheckElementCloser(base, e, added, procinfo, collation); e->closer = CheckElementCloser(base, e, added, lc, procinfo, collation);
if (!e->closer) if (!e->closer)
removedAny = true; removedAny = true;
@@ -1007,7 +1026,7 @@ SelectNeighbors(char *base, List *c, int lm, int lc, FmgrInfo *procinfo, Oid col
*/ */
if (removedAny) if (removedAny)
{ {
e->closer = CheckElementCloser(base, e, r, procinfo, collation); e->closer = CheckElementCloser(base, e, r, lc, procinfo, collation);
if (e->closer) if (e->closer)
added = lappend(added, e); added = lappend(added, e);
} }
@@ -1015,15 +1034,11 @@ SelectNeighbors(char *base, List *c, int lm, int lc, FmgrInfo *procinfo, Oid col
} }
else if (e == newCandidate) else if (e == newCandidate)
{ {
e->closer = CheckElementCloser(base, e, r, procinfo, collation); e->closer = CheckElementCloser(base, e, r, lc, procinfo, collation);
if (e->closer) if (e->closer)
added = lappend(added, e); added = lappend(added, e);
} }
/* Keep Valgrind happy for in-memory, parallel builds */
if (base != NULL)
VALGRIND_MAKE_MEM_DEFINED(&e->closer, 1);
if (e->closer) if (e->closer)
r = lappend(r, e); r = lappend(r, e);
else else

View File

@@ -198,8 +198,8 @@ RepairGraphElement(HnswVacuumState * vacuumstate, HnswElement element, HnswEleme
FmgrInfo *procinfo = vacuumstate->procinfo; FmgrInfo *procinfo = vacuumstate->procinfo;
Oid collation = vacuumstate->collation; Oid collation = vacuumstate->collation;
BufferAccessStrategy bas = vacuumstate->bas; BufferAccessStrategy bas = vacuumstate->bas;
ItemId itemid; HnswNeighborTuple ntup = vacuumstate->ntup;
HnswNeighborTuple ntup; Size ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(element->level, m);
char *base = NULL; char *base = NULL;
/* Skip if element is entry point */ /* Skip if element is entry point */
@@ -213,23 +213,20 @@ RepairGraphElement(HnswVacuumState * vacuumstate, HnswElement element, HnswEleme
/* Find neighbors for element, skipping itself */ /* Find neighbors for element, skipping itself */
HnswFindElementNeighbors(base, element, entryPoint, index, procinfo, collation, m, efConstruction, true); HnswFindElementNeighbors(base, element, entryPoint, index, procinfo, collation, m, efConstruction, true);
/* Update neighbor tuple */
/* Do this before getting page to minimize locking */
HnswSetNeighborTuple(base, ntup, element, m);
/* Get neighbor page */ /* Get neighbor page */
buf = ReadBufferExtended(index, MAIN_FORKNUM, element->neighborPage, RBM_NORMAL, bas); buf = ReadBufferExtended(index, MAIN_FORKNUM, element->neighborPage, RBM_NORMAL, bas);
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
state = GenericXLogStart(index); state = GenericXLogStart(index);
page = GenericXLogRegisterBuffer(state, buf, 0); page = GenericXLogRegisterBuffer(state, buf, 0);
/* Get neighbor tuple */ /* Overwrite tuple */
itemid = PageGetItemId(page, element->neighborOffno); if (!PageIndexTupleOverwrite(page, element->neighborOffno, (Item) ntup, ntupSize))
ntup = (HnswNeighborTuple) PageGetItem(page, itemid);
/* Check expected size */
if (ItemIdGetLength(itemid) != HNSW_NEIGHBOR_TUPLE_SIZE(element->level, m))
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index)); elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
/* Update page in-place */
HnswSetNeighborTuple(base, ntup, element, m);
/* Commit */ /* Commit */
GenericXLogFinish(state); GenericXLogFinish(state);
UnlockReleaseBuffer(buf); UnlockReleaseBuffer(buf);
@@ -591,6 +588,7 @@ InitVacuumState(HnswVacuumState * vacuumstate, IndexVacuumInfo *info, IndexBulkD
vacuumstate->bas = GetAccessStrategy(BAS_BULKREAD); vacuumstate->bas = GetAccessStrategy(BAS_BULKREAD);
vacuumstate->procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC); vacuumstate->procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC);
vacuumstate->collation = index->rd_indcollation[0]; vacuumstate->collation = index->rd_indcollation[0];
vacuumstate->ntup = palloc0(BLCKSZ);
vacuumstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext, vacuumstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
"Hnsw vacuum temporary context", "Hnsw vacuum temporary context",
ALLOCSET_DEFAULT_SIZES); ALLOCSET_DEFAULT_SIZES);
@@ -610,6 +608,7 @@ FreeVacuumState(HnswVacuumState * vacuumstate)
{ {
tidhash_destroy(vacuumstate->deleted); tidhash_destroy(vacuumstate->deleted);
FreeAccessStrategy(vacuumstate->bas); FreeAccessStrategy(vacuumstate->bas);
pfree(vacuumstate->ntup);
MemoryContextDelete(vacuumstate->tmpCtx); MemoryContextDelete(vacuumstate->tmpCtx);
} }

View File

@@ -29,6 +29,8 @@
#define CALLBACK_ITEM_POINTER HeapTuple hup #define CALLBACK_ITEM_POINTER HeapTuple hup
#endif #endif
#define UpdateProgress(index, val) pgstat_progress_update_param(index, val)
#if PG_VERSION_NUM >= 140000 #if PG_VERSION_NUM >= 140000
#include "utils/backend_status.h" #include "utils/backend_status.h"
#include "utils/wait_event.h" #include "utils/wait_event.h"
@@ -260,9 +262,9 @@ InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum)
TupleTableSlot *slot = MakeSingleTupleTableSlot(buildstate->tupdesc, &TTSOpsMinimalTuple); TupleTableSlot *slot = MakeSingleTupleTableSlot(buildstate->tupdesc, &TTSOpsMinimalTuple);
TupleDesc tupdesc = RelationGetDescr(index); TupleDesc tupdesc = RelationGetDescr(index);
pgstat_progress_update_param(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_LOAD); UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_LOAD);
pgstat_progress_update_param(PROGRESS_CREATEIDX_TUPLES_TOTAL, buildstate->indtuples); UpdateProgress(PROGRESS_CREATEIDX_TUPLES_TOTAL, buildstate->indtuples);
GetNextTuple(buildstate->sortstate, tupdesc, slot, &itup, &list); GetNextTuple(buildstate->sortstate, tupdesc, slot, &itup, &list);
@@ -298,7 +300,7 @@ InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum)
pfree(itup); pfree(itup);
pgstat_progress_update_param(PROGRESS_CREATEIDX_TUPLES_DONE, ++inserted); UpdateProgress(PROGRESS_CREATEIDX_TUPLES_DONE, ++inserted);
GetNextTuple(buildstate->sortstate, tupdesc, slot, &itup, &list); GetNextTuple(buildstate->sortstate, tupdesc, slot, &itup, &list);
} }
@@ -398,7 +400,7 @@ ComputeCenters(IvfflatBuildState * buildstate)
{ {
int numSamples; int numSamples;
pgstat_progress_update_param(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_KMEANS); UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_KMEANS);
/* Target 50 samples per list, with at least 10000 samples */ /* Target 50 samples per list, with at least 10000 samples */
/* The number of samples has a large effect on index build time */ /* The number of samples has a large effect on index build time */
@@ -473,7 +475,7 @@ CreateListPages(Relation index, VectorArray centers, int dimensions,
IvfflatList list; IvfflatList list;
listSize = MAXALIGN(IVFFLAT_LIST_SIZE(dimensions)); listSize = MAXALIGN(IVFFLAT_LIST_SIZE(dimensions));
list = palloc0(listSize); list = palloc(listSize);
buf = IvfflatNewBuffer(index, forkNum); buf = IvfflatNewBuffer(index, forkNum);
IvfflatInitRegisterPage(index, &buf, &page, &state); IvfflatInitRegisterPage(index, &buf, &page, &state);
@@ -919,7 +921,7 @@ AssignTuples(IvfflatBuildState * buildstate)
Oid sortCollations[] = {InvalidOid}; Oid sortCollations[] = {InvalidOid};
bool nullsFirstFlags[] = {false}; bool nullsFirstFlags[] = {false};
pgstat_progress_update_param(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_ASSIGN); UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_ASSIGN);
/* Calculate parallel workers */ /* Calculate parallel workers */
if (buildstate->heap != NULL) if (buildstate->heap != NULL)

View File

@@ -310,7 +310,6 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
ItemPointer heaptid = (ItemPointer) DatumGetPointer(slot_getattr(so->slot, 2, &so->isnull)); ItemPointer heaptid = (ItemPointer) DatumGetPointer(slot_getattr(so->slot, 2, &so->isnull));
scan->xs_heaptid = *heaptid; scan->xs_heaptid = *heaptid;
scan->xs_recheck = false;
scan->xs_recheckorderby = false; scan->xs_recheckorderby = false;
return true; return true;
} }

View File

@@ -10,6 +10,7 @@
#include "lib/stringinfo.h" #include "lib/stringinfo.h"
#include "libpq/pqformat.h" #include "libpq/pqformat.h"
#include "port.h" /* for strtof() */ #include "port.h" /* for strtof() */
#include "port/pg_bitutils.h"
#include "utils/array.h" #include "utils/array.h"
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/float.h" #include "utils/float.h"
@@ -1134,3 +1135,43 @@ vector_avg(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(result); PG_RETURN_POINTER(result);
} }
/*
* Ensure same number of bytes
*/
static inline void
CheckByteLengths(uint32 aLen, uint32 bLen)
{
if (aLen != bLen)
ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION),
errmsg("different byte lengths %u and %u", aLen, bLen)));
}
/*
* Get the Hamming distance between two binary strings
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(hamming_distance);
Datum
hamming_distance(PG_FUNCTION_ARGS)
{
bytea *a = PG_GETARG_BYTEA_PP(0);
bytea *b = PG_GETARG_BYTEA_PP(1);
char *ax = VARDATA_ANY(a);
char *bx = VARDATA_ANY(b);
uint32 aLen = VARSIZE_ANY_EXHDR(a);
uint32 bLen = VARSIZE_ANY_EXHDR(b);
uint64 distance = 0;
CheckByteLengths(aLen, bLen);
for (uint32 i = 0; i < aLen; i++)
{
unsigned char diff = (unsigned char) (ax[i] ^ bx[i]);
distance += pg_number_of_ones[diff];
}
/* TODO Decide on return type */
PG_RETURN_FLOAT8((double) distance);
}

View File

@@ -158,6 +158,32 @@ SELECT l1_distance('[3e38]', '[-3e38]');
Infinity Infinity
(1 row) (1 row)
SELECT hamming_distance('\xFFFF', '\xFFFF');
hamming_distance
------------------
0
(1 row)
SELECT hamming_distance('\xFFFF', '\xFFFE');
hamming_distance
------------------
1
(1 row)
SELECT hamming_distance('\xFFFF', '\xFFFC');
hamming_distance
------------------
2
(1 row)
SELECT hamming_distance('\xFFFF', '\x0000');
hamming_distance
------------------
16
(1 row)
SELECT hamming_distance('\xFFFF', '\x00');
ERROR: different byte lengths 2 and 1
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]']) v; SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]']) v;
avg avg
----------- -----------

View File

@@ -37,6 +37,12 @@ SELECT l1_distance('[0,0]', '[0,1]');
SELECT l1_distance('[1,2]', '[3]'); SELECT l1_distance('[1,2]', '[3]');
SELECT l1_distance('[3e38]', '[-3e38]'); SELECT l1_distance('[3e38]', '[-3e38]');
SELECT hamming_distance('\xFFFF', '\xFFFF');
SELECT hamming_distance('\xFFFF', '\xFFFE');
SELECT hamming_distance('\xFFFF', '\xFFFC');
SELECT hamming_distance('\xFFFF', '\x0000');
SELECT hamming_distance('\xFFFF', '\x00');
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]']) v; SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]']) v;
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]', NULL]) v; SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]', NULL]) v;
SELECT avg(v) FROM unnest(ARRAY[]::vector[]) v; SELECT avg(v) FROM unnest(ARRAY[]::vector[]) v;