mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-22 12:07:34 +08:00
Compare commits
9 Commits
hnsw-fast-
...
hnsw-less-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c347b7f3e | ||
|
|
3e628986a1 | ||
|
|
858a89575b | ||
|
|
d81c1c9de0 | ||
|
|
4988d04338 | ||
|
|
c640def56c | ||
|
|
87fe23d9ec | ||
|
|
041f939bde | ||
|
|
5d7bf9509d |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -62,7 +62,7 @@ jobs:
|
|||||||
wget -q https://github.com/postgres/postgres/archive/refs/tags/REL_14_5.tar.gz
|
wget -q https://github.com/postgres/postgres/archive/refs/tags/REL_14_5.tar.gz
|
||||||
tar xf REL_14_5.tar.gz
|
tar xf REL_14_5.tar.gz
|
||||||
- run: make prove_installcheck PROVE_FLAGS="-I ./postgres-REL_14_5/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
|
||||||
windows:
|
windows:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
if: ${{ !startsWith(github.ref_name, 'mac') }}
|
if: ${{ !startsWith(github.ref_name, 'mac') }}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
## 0.5.2 (unreleased)
|
## 0.5.2 (unreleased)
|
||||||
|
|
||||||
- Improved performance of HNSW
|
- Improved performance of HNSW
|
||||||
- Added support for parallel index builds for HNSW
|
- Added support for on-disk parallel index builds for HNSW
|
||||||
- Reduced memory usage for HNSW index builds
|
|
||||||
- Reduced WAL generation for HNSW index builds
|
- Reduced WAL generation for HNSW index builds
|
||||||
- Fixed `invalid memory alloc request size` error with HNSW index build
|
- Fixed `invalid memory alloc request size` error with HNSW index build
|
||||||
|
|
||||||
|
|||||||
181
README.md
181
README.md
@@ -161,97 +161,8 @@ You can add an index to use approximate nearest neighbor search, which trades so
|
|||||||
|
|
||||||
Supported index types are:
|
Supported index types are:
|
||||||
|
|
||||||
- [HNSW](#hnsw) - added in 0.5.0
|
|
||||||
- [IVFFlat](#ivfflat)
|
- [IVFFlat](#ivfflat)
|
||||||
|
- [HNSW](#hnsw) - added in 0.5.0
|
||||||
## HNSW
|
|
||||||
|
|
||||||
An HNSW index creates a multilayer graph. It has better query performance than IVFFlat (in terms of speed-recall tradeoff), but has slower build times and uses more memory. Also, an index can be created without any data in the table since there isn’t a training step like IVFFlat.
|
|
||||||
|
|
||||||
Add an index for each distance function you want to use.
|
|
||||||
|
|
||||||
L2 distance
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops);
|
|
||||||
```
|
|
||||||
|
|
||||||
Inner product
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE INDEX ON items USING hnsw (embedding vector_ip_ops);
|
|
||||||
```
|
|
||||||
|
|
||||||
Cosine distance
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);
|
|
||||||
```
|
|
||||||
|
|
||||||
Vectors with up to 2,000 dimensions can be indexed.
|
|
||||||
|
|
||||||
### Index Options
|
|
||||||
|
|
||||||
Specify HNSW parameters
|
|
||||||
|
|
||||||
- `m` - the max number of connections per layer (16 by default)
|
|
||||||
- `ef_construction` - the size of the dynamic candidate list for constructing the graph (64 by default)
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops) WITH (m = 16, ef_construction = 64);
|
|
||||||
```
|
|
||||||
|
|
||||||
A higher value of `ef_construction` provides better recall at the cost of index build time / insert speed.
|
|
||||||
|
|
||||||
### Query Options
|
|
||||||
|
|
||||||
Specify the size of the dynamic candidate list for search (40 by default)
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SET hnsw.ef_search = 100;
|
|
||||||
```
|
|
||||||
|
|
||||||
A higher value provides better recall at the cost of speed.
|
|
||||||
|
|
||||||
Use `SET LOCAL` inside a transaction to set it for a single query
|
|
||||||
|
|
||||||
```sql
|
|
||||||
BEGIN;
|
|
||||||
SET LOCAL hnsw.ef_search = 100;
|
|
||||||
SELECT ...
|
|
||||||
COMMIT;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Index Build Time
|
|
||||||
|
|
||||||
Indexes build significantly faster when the graph fits into `maintenance_work_mem`
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SET maintenance_work_mem = '8GB';
|
|
||||||
```
|
|
||||||
|
|
||||||
A notice is shown when the graph no longer fits
|
|
||||||
|
|
||||||
```text
|
|
||||||
NOTICE: hnsw graph no longer fits into maintenance_work_mem after 100000 tuples
|
|
||||||
DETAIL: Building will take significantly more time.
|
|
||||||
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
|
|
||||||
|
|
||||||
### Indexing Progress
|
|
||||||
|
|
||||||
Check [indexing progress](https://www.postgresql.org/docs/current/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING) with Postgres 12+
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;
|
|
||||||
```
|
|
||||||
|
|
||||||
The phases for HNSW are:
|
|
||||||
|
|
||||||
1. `initializing`
|
|
||||||
2. `loading tuples`
|
|
||||||
|
|
||||||
## IVFFlat
|
## IVFFlat
|
||||||
|
|
||||||
@@ -321,6 +232,77 @@ The phases for IVFFlat are:
|
|||||||
|
|
||||||
Note: `%` is only populated during the `loading tuples` phase
|
Note: `%` is only populated during the `loading tuples` phase
|
||||||
|
|
||||||
|
## HNSW
|
||||||
|
|
||||||
|
An HNSW index creates a multilayer graph. It has slower build times and uses more memory than IVFFlat, but has better query performance (in terms of speed-recall tradeoff). There’s no training step like IVFFlat, so the index can be created without any data in the table.
|
||||||
|
|
||||||
|
Add an index for each distance function you want to use.
|
||||||
|
|
||||||
|
L2 distance
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops);
|
||||||
|
```
|
||||||
|
|
||||||
|
Inner product
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE INDEX ON items USING hnsw (embedding vector_ip_ops);
|
||||||
|
```
|
||||||
|
|
||||||
|
Cosine distance
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);
|
||||||
|
```
|
||||||
|
|
||||||
|
Vectors with up to 2,000 dimensions can be indexed.
|
||||||
|
|
||||||
|
### Index Options
|
||||||
|
|
||||||
|
Specify HNSW parameters
|
||||||
|
|
||||||
|
- `m` - the max number of connections per layer (16 by default)
|
||||||
|
- `ef_construction` - the size of the dynamic candidate list for constructing the graph (64 by default)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops) WITH (m = 16, ef_construction = 64);
|
||||||
|
```
|
||||||
|
|
||||||
|
A higher value of `ef_construction` provides better recall at the cost of index build time / insert speed.
|
||||||
|
|
||||||
|
### Query Options
|
||||||
|
|
||||||
|
Specify the size of the dynamic candidate list for search (40 by default)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SET hnsw.ef_search = 100;
|
||||||
|
```
|
||||||
|
|
||||||
|
A higher value provides better recall at the cost of speed.
|
||||||
|
|
||||||
|
Use `SET LOCAL` inside a transaction to set it for a single query
|
||||||
|
|
||||||
|
```sql
|
||||||
|
BEGIN;
|
||||||
|
SET LOCAL hnsw.ef_search = 100;
|
||||||
|
SELECT ...
|
||||||
|
COMMIT;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Indexing Progress
|
||||||
|
|
||||||
|
Check [indexing progress](https://www.postgresql.org/docs/current/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING) with Postgres 12+
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;
|
||||||
|
```
|
||||||
|
|
||||||
|
The phases for HNSW are:
|
||||||
|
|
||||||
|
1. `initializing`
|
||||||
|
2. `loading tuples`
|
||||||
|
|
||||||
## Filtering
|
## Filtering
|
||||||
|
|
||||||
There are a few ways to index nearest neighbor queries with a `WHERE` clause
|
There are a few ways to index nearest neighbor queries with a `WHERE` clause
|
||||||
@@ -338,7 +320,8 @@ CREATE INDEX ON items (category_id);
|
|||||||
Or a [partial index](https://www.postgresql.org/docs/current/indexes-partial.html) on the vector column for approximate search
|
Or a [partial index](https://www.postgresql.org/docs/current/indexes-partial.html) on the vector column for approximate search
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops) WHERE (category_id = 123);
|
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100)
|
||||||
|
WHERE (category_id = 123);
|
||||||
```
|
```
|
||||||
|
|
||||||
Use [partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html) for approximate search on many different values of the `WHERE` columns
|
Use [partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html) for approximate search on many different values of the `WHERE` columns
|
||||||
@@ -588,10 +571,10 @@ If compilation fails with `fatal error: postgres.h: No such file or directory`,
|
|||||||
For Ubuntu and Debian, use:
|
For Ubuntu and Debian, use:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo apt install postgresql-server-dev-16
|
sudo apt install postgresql-server-dev-15
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: Replace `16` with your Postgres server version
|
Note: Replace `15` with your Postgres server version
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
@@ -606,7 +589,7 @@ Note: The exact path will vary depending on your Visual Studio version and editi
|
|||||||
Then use `nmake` to build:
|
Then use `nmake` to build:
|
||||||
|
|
||||||
```cmd
|
```cmd
|
||||||
set "PGROOT=C:\Program Files\PostgreSQL\16"
|
set "PGROOT=C:\Program Files\PostgreSQL\15"
|
||||||
git clone --branch v0.5.1 https://github.com/pgvector/pgvector.git
|
git clone --branch v0.5.1 https://github.com/pgvector/pgvector.git
|
||||||
cd pgvector
|
cd pgvector
|
||||||
nmake /F Makefile.win
|
nmake /F Makefile.win
|
||||||
@@ -656,22 +639,22 @@ pgxn install vector
|
|||||||
Debian and Ubuntu packages are available from the [PostgreSQL APT Repository](https://wiki.postgresql.org/wiki/Apt). Follow the [setup instructions](https://wiki.postgresql.org/wiki/Apt#Quickstart) and run:
|
Debian and Ubuntu packages are available from the [PostgreSQL APT Repository](https://wiki.postgresql.org/wiki/Apt). Follow the [setup instructions](https://wiki.postgresql.org/wiki/Apt#Quickstart) and run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo apt install postgresql-16-pgvector
|
sudo apt install postgresql-15-pgvector
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: Replace `16` with your Postgres server version
|
Note: Replace `15` with your Postgres server version
|
||||||
|
|
||||||
### Yum
|
### Yum
|
||||||
|
|
||||||
RPM packages are available from the [PostgreSQL Yum Repository](https://yum.postgresql.org/). Follow the [setup instructions](https://www.postgresql.org/download/linux/redhat/) for your distribution and run:
|
RPM packages are available from the [PostgreSQL Yum Repository](https://yum.postgresql.org/). Follow the [setup instructions](https://www.postgresql.org/download/linux/redhat/) for your distribution and run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo yum install pgvector_16
|
sudo yum install pgvector_15
|
||||||
# or
|
# or
|
||||||
sudo dnf install pgvector_16
|
sudo dnf install pgvector_15
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: Replace `16` with your Postgres server version
|
Note: Replace `15` with your Postgres server version
|
||||||
|
|
||||||
### conda-forge
|
### conda-forge
|
||||||
|
|
||||||
|
|||||||
37
src/hnsw.c
37
src/hnsw.c
@@ -14,45 +14,15 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
int hnsw_ef_search;
|
int hnsw_ef_search;
|
||||||
int hnsw_lock_tranche_id;
|
bool hnsw_enable_parallel_build;
|
||||||
static relopt_kind hnsw_relopt_kind;
|
static relopt_kind hnsw_relopt_kind;
|
||||||
|
|
||||||
/*
|
|
||||||
* Assign a tranche ID for our LWLocks. This only needs to be done by one
|
|
||||||
* backend, as the tranche ID is remembered in shared memory.
|
|
||||||
*
|
|
||||||
* This shared memory area is very small, so we just allocate it from the
|
|
||||||
* "slop" that PostgreSQL reserves for small allocations like this. If
|
|
||||||
* this grows bigger, we should use a shmem_request_hook and
|
|
||||||
* RequestAddinShmemSpace() to pre-reserve space for this.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
HnswInitLockTranche(void)
|
|
||||||
{
|
|
||||||
int *tranche_ids;
|
|
||||||
bool found;
|
|
||||||
|
|
||||||
LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
|
|
||||||
tranche_ids = ShmemInitStruct("hnsw LWLock ids",
|
|
||||||
sizeof(int) * 1,
|
|
||||||
&found);
|
|
||||||
if (!found)
|
|
||||||
tranche_ids[0] = LWLockNewTrancheId();
|
|
||||||
hnsw_lock_tranche_id = tranche_ids[0];
|
|
||||||
LWLockRelease(AddinShmemInitLock);
|
|
||||||
|
|
||||||
/* Per-backend registration of the tranche ID */
|
|
||||||
LWLockRegisterTranche(hnsw_lock_tranche_id, "HnswBuild");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Initialize index options and variables
|
* Initialize index options and variables
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
HnswInit(void)
|
HnswInit(void)
|
||||||
{
|
{
|
||||||
HnswInitLockTranche();
|
|
||||||
|
|
||||||
hnsw_relopt_kind = add_reloption_kind();
|
hnsw_relopt_kind = add_reloption_kind();
|
||||||
add_int_reloption(hnsw_relopt_kind, "m", "Max number of connections",
|
add_int_reloption(hnsw_relopt_kind, "m", "Max number of connections",
|
||||||
HNSW_DEFAULT_M, HNSW_MIN_M, HNSW_MAX_M
|
HNSW_DEFAULT_M, HNSW_MIN_M, HNSW_MAX_M
|
||||||
@@ -70,6 +40,11 @@ HnswInit(void)
|
|||||||
DefineCustomIntVariable("hnsw.ef_search", "Sets the size of the dynamic candidate list for search",
|
DefineCustomIntVariable("hnsw.ef_search", "Sets the size of the dynamic candidate list for search",
|
||||||
"Valid range is 1..1000.", &hnsw_ef_search,
|
"Valid range is 1..1000.", &hnsw_ef_search,
|
||||||
HNSW_DEFAULT_EF_SEARCH, HNSW_MIN_EF_SEARCH, HNSW_MAX_EF_SEARCH, PGC_USERSET, 0, NULL, NULL, NULL);
|
HNSW_DEFAULT_EF_SEARCH, HNSW_MIN_EF_SEARCH, HNSW_MAX_EF_SEARCH, PGC_USERSET, 0, NULL, NULL, NULL);
|
||||||
|
|
||||||
|
/* Behind a variable for now since can be slower than building in memory */
|
||||||
|
DefineCustomBoolVariable("hnsw.enable_parallel_build", "Enables or disables building indexes in parallel",
|
||||||
|
NULL, &hnsw_enable_parallel_build,
|
||||||
|
false, PGC_USERSET, 0, NULL, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
136
src/hnsw.h
136
src/hnsw.h
@@ -8,7 +8,6 @@
|
|||||||
#include "access/reloptions.h"
|
#include "access/reloptions.h"
|
||||||
#include "nodes/execnodes.h"
|
#include "nodes/execnodes.h"
|
||||||
#include "port.h" /* for random() */
|
#include "port.h" /* for random() */
|
||||||
#include "utils/relptr.h"
|
|
||||||
#include "utils/sampling.h"
|
#include "utils/sampling.h"
|
||||||
#include "vector.h"
|
#include "vector.h"
|
||||||
|
|
||||||
@@ -73,10 +72,8 @@
|
|||||||
|
|
||||||
#if PG_VERSION_NUM >= 150000
|
#if PG_VERSION_NUM >= 150000
|
||||||
#define RandomDouble() pg_prng_double(&pg_global_prng_state)
|
#define RandomDouble() pg_prng_double(&pg_global_prng_state)
|
||||||
#define SeedRandom(seed) pg_prng_seed(&pg_global_prng_state, seed)
|
|
||||||
#else
|
#else
|
||||||
#define RandomDouble() (((double) random()) / MAX_RANDOM_VALUE)
|
#define RandomDouble() (((double) random()) / MAX_RANDOM_VALUE)
|
||||||
#define SeedRandom(seed) srandom(seed)
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 130000
|
#if PG_VERSION_NUM < 130000
|
||||||
@@ -94,64 +91,33 @@
|
|||||||
#define HnswGetMl(m) (1 / log(m))
|
#define HnswGetMl(m) (1 / log(m))
|
||||||
|
|
||||||
/* Ensure fits on page and in uint8 */
|
/* Ensure fits on page and in uint8 */
|
||||||
#define HnswGetMaxLevel(m) Min(((BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(HnswPageOpaqueData)) - offsetof(HnswNeighborTupleData, indextids) - sizeof(ItemIdData)) / (sizeof(ItemPointerData)) / (m)) - 2, 255)
|
#define HnswGetMaxLevel(m) Min(((BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(HnswPageOpaqueData)) - offsetof(HnswNeighborTupleData, indextids) - sizeof(ItemIdData)) / (sizeof(ItemPointerData)) / m) - 2, 255)
|
||||||
|
|
||||||
#define HnswGetValue(base, element) PointerGetDatum(HnswPtrAccess(base, (element)->value))
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 140005
|
|
||||||
#define relptr_offset(rp) ((rp).relptr_off - 1)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Pointer macros */
|
|
||||||
#define HnswPtrAccess(base, hp) ((base) == NULL ? (hp).ptr : relptr_access(base, (hp).relptr))
|
|
||||||
#define HnswPtrStore(base, hp, value) ((base) == NULL ? (void) ((hp).ptr = (value)) : (void) relptr_store(base, (hp).relptr, value))
|
|
||||||
#define HnswPtrIsNull(base, hp) ((base) == NULL ? (hp).ptr == NULL : relptr_is_null((hp).relptr))
|
|
||||||
#define HnswPtrEqual(base, hp1, hp2) ((base) == NULL ? (hp1).ptr == (hp2).ptr : relptr_offset((hp1).relptr) == relptr_offset((hp2).relptr))
|
|
||||||
|
|
||||||
/* For code paths dedicated to each type */
|
|
||||||
#define HnswPtrPointer(hp) (hp).ptr
|
|
||||||
#define HnswPtrOffset(hp) relptr_offset((hp).relptr)
|
|
||||||
|
|
||||||
/* Variables */
|
/* Variables */
|
||||||
extern int hnsw_ef_search;
|
extern int hnsw_ef_search;
|
||||||
extern int hnsw_lock_tranche_id;
|
extern bool hnsw_enable_parallel_build;
|
||||||
|
|
||||||
typedef struct HnswElementData HnswElementData;
|
|
||||||
typedef struct HnswNeighborArray HnswNeighborArray;
|
typedef struct HnswNeighborArray HnswNeighborArray;
|
||||||
|
|
||||||
#define HnswPtrDeclare(type, relptrtype, ptrtype) \
|
|
||||||
relptr_declare(type, relptrtype); \
|
|
||||||
typedef union { type *ptr; relptrtype relptr; } ptrtype;
|
|
||||||
|
|
||||||
/* Pointers that can be absolute or relative */
|
|
||||||
/* Use char for DatumPtr so works with Pointer */
|
|
||||||
HnswPtrDeclare(HnswElementData, HnswElementRelptr, HnswElementPtr);
|
|
||||||
HnswPtrDeclare(HnswNeighborArray, HnswNeighborArrayRelptr, HnswNeighborArrayPtr);
|
|
||||||
HnswPtrDeclare(HnswNeighborArrayPtr, HnswNeighborsRelptr, HnswNeighborsPtr);
|
|
||||||
HnswPtrDeclare(char, DatumRelptr, DatumPtr);
|
|
||||||
|
|
||||||
typedef struct HnswElementData
|
typedef struct HnswElementData
|
||||||
{
|
{
|
||||||
HnswElementPtr next;
|
List *heaptids;
|
||||||
ItemPointerData heaptids[HNSW_HEAPTIDS];
|
|
||||||
uint8 heaptidsLength;
|
|
||||||
uint8 level;
|
uint8 level;
|
||||||
uint8 deleted;
|
uint8 deleted;
|
||||||
uint32 hash;
|
uint32 hash;
|
||||||
HnswNeighborsPtr neighbors;
|
HnswNeighborArray *neighbors;
|
||||||
BlockNumber blkno;
|
BlockNumber blkno;
|
||||||
OffsetNumber offno;
|
OffsetNumber offno;
|
||||||
OffsetNumber neighborOffno;
|
OffsetNumber neighborOffno;
|
||||||
BlockNumber neighborPage;
|
BlockNumber neighborPage;
|
||||||
DatumPtr value;
|
Datum value;
|
||||||
LWLock lock;
|
|
||||||
} HnswElementData;
|
} HnswElementData;
|
||||||
|
|
||||||
typedef HnswElementData * HnswElement;
|
typedef HnswElementData * HnswElement;
|
||||||
|
|
||||||
typedef struct HnswCandidate
|
typedef struct HnswCandidate
|
||||||
{
|
{
|
||||||
HnswElementPtr element;
|
HnswElement element;
|
||||||
float distance;
|
float distance;
|
||||||
bool closer;
|
bool closer;
|
||||||
} HnswCandidate;
|
} HnswCandidate;
|
||||||
@@ -160,7 +126,7 @@ typedef struct HnswNeighborArray
|
|||||||
{
|
{
|
||||||
int length;
|
int length;
|
||||||
bool closerSet;
|
bool closerSet;
|
||||||
HnswCandidate items[FLEXIBLE_ARRAY_MEMBER];
|
HnswCandidate *items;
|
||||||
} HnswNeighborArray;
|
} HnswNeighborArray;
|
||||||
|
|
||||||
typedef struct HnswPairingHeapNode
|
typedef struct HnswPairingHeapNode
|
||||||
@@ -177,26 +143,11 @@ typedef struct HnswOptions
|
|||||||
int efConstruction; /* size of dynamic candidate list */
|
int efConstruction; /* size of dynamic candidate list */
|
||||||
} HnswOptions;
|
} HnswOptions;
|
||||||
|
|
||||||
typedef struct HnswGraph
|
typedef struct HnswSpool
|
||||||
{
|
{
|
||||||
/* Graph state */
|
Relation heap;
|
||||||
slock_t lock;
|
Relation index;
|
||||||
HnswElementPtr head;
|
} HnswSpool;
|
||||||
double indtuples;
|
|
||||||
|
|
||||||
/* Entry state */
|
|
||||||
LWLock entryLock;
|
|
||||||
HnswElementPtr entryPoint;
|
|
||||||
|
|
||||||
/* Allocations state */
|
|
||||||
LWLock allocatorLock;
|
|
||||||
long memoryUsed;
|
|
||||||
long memoryTotal;
|
|
||||||
|
|
||||||
/* Flushed state */
|
|
||||||
LWLock flushLock;
|
|
||||||
bool flushed;
|
|
||||||
} HnswGraph;
|
|
||||||
|
|
||||||
typedef struct HnswShared
|
typedef struct HnswShared
|
||||||
{
|
{
|
||||||
@@ -204,6 +155,7 @@ typedef struct HnswShared
|
|||||||
Oid heaprelid;
|
Oid heaprelid;
|
||||||
Oid indexrelid;
|
Oid indexrelid;
|
||||||
bool isconcurrent;
|
bool isconcurrent;
|
||||||
|
int scantuplesortstates;
|
||||||
|
|
||||||
/* Worker progress */
|
/* Worker progress */
|
||||||
ConditionVariable workersdonecv;
|
ConditionVariable workersdonecv;
|
||||||
@@ -214,7 +166,7 @@ typedef struct HnswShared
|
|||||||
/* Mutable state */
|
/* Mutable state */
|
||||||
int nparticipantsdone;
|
int nparticipantsdone;
|
||||||
double reltuples;
|
double reltuples;
|
||||||
HnswGraph graphData;
|
double indtuples;
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 120000
|
#if PG_VERSION_NUM < 120000
|
||||||
ParallelHeapScanDescData heapdesc; /* must come last */
|
ParallelHeapScanDescData heapdesc; /* must come last */
|
||||||
@@ -232,15 +184,8 @@ typedef struct HnswLeader
|
|||||||
int nparticipanttuplesorts;
|
int nparticipanttuplesorts;
|
||||||
HnswShared *hnswshared;
|
HnswShared *hnswshared;
|
||||||
Snapshot snapshot;
|
Snapshot snapshot;
|
||||||
char *hnswarea;
|
|
||||||
} HnswLeader;
|
} HnswLeader;
|
||||||
|
|
||||||
typedef struct HnswAllocator
|
|
||||||
{
|
|
||||||
void *(*alloc) (Size size, void *state);
|
|
||||||
void *state;
|
|
||||||
} HnswAllocator;
|
|
||||||
|
|
||||||
typedef struct HnswBuildState
|
typedef struct HnswBuildState
|
||||||
{
|
{
|
||||||
/* Info */
|
/* Info */
|
||||||
@@ -264,21 +209,20 @@ typedef struct HnswBuildState
|
|||||||
Oid collation;
|
Oid collation;
|
||||||
|
|
||||||
/* Variables */
|
/* Variables */
|
||||||
HnswGraph graphData;
|
List *elements;
|
||||||
HnswGraph *graph;
|
HnswElement entryPoint;
|
||||||
double ml;
|
double ml;
|
||||||
int maxLevel;
|
int maxLevel;
|
||||||
|
long memoryLeft;
|
||||||
|
bool flushed;
|
||||||
Vector *normvec;
|
Vector *normvec;
|
||||||
|
|
||||||
/* Memory */
|
/* Memory */
|
||||||
MemoryContext graphCtx;
|
|
||||||
MemoryContext tmpCtx;
|
MemoryContext tmpCtx;
|
||||||
HnswAllocator allocator;
|
|
||||||
|
|
||||||
/* Parallel builds */
|
/* Parallel builds */
|
||||||
HnswLeader *hnswleader;
|
HnswLeader *hnswleader;
|
||||||
HnswShared *hnswshared;
|
HnswShared *hnswshared;
|
||||||
char *hnswarea;
|
|
||||||
} HnswBuildState;
|
} HnswBuildState;
|
||||||
|
|
||||||
typedef struct HnswMetaPageData
|
typedef struct HnswMetaPageData
|
||||||
@@ -377,24 +321,25 @@ bool HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * re
|
|||||||
Buffer HnswNewBuffer(Relation index, ForkNumber forkNum);
|
Buffer HnswNewBuffer(Relation index, ForkNumber forkNum);
|
||||||
void HnswInitPage(Buffer buf, Page page);
|
void HnswInitPage(Buffer buf, Page page);
|
||||||
void HnswInit(void);
|
void HnswInit(void);
|
||||||
List *HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *procinfo, Oid collation, int m, bool inserting, HnswElement skipElement);
|
List *HnswSearchLayer(Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *procinfo, Oid collation, int m, bool inserting, HnswElement skipElement);
|
||||||
HnswElement HnswGetEntryPoint(Relation index);
|
HnswElement HnswGetEntryPoint(Relation index);
|
||||||
void HnswGetMetaPageInfo(Relation index, int *m, HnswElement * entryPoint);
|
void HnswGetMetaPageInfo(Relation index, int *m, HnswElement * entryPoint);
|
||||||
void *HnswAlloc(HnswAllocator * allocator, Size size);
|
HnswElement HnswInitElement(ItemPointer tid, int m, double ml, int maxLevel);
|
||||||
HnswElement HnswInitElement(char *base, ItemPointer tid, int m, double ml, int maxLevel, HnswAllocator * alloc);
|
void HnswFreeElement(HnswElement element);
|
||||||
HnswElement HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno);
|
HnswElement HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno);
|
||||||
void HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint, Relation index, FmgrInfo *procinfo, Oid collation, int m, int efConstruction, bool existing);
|
void HnswInsertElement(HnswElement element, HnswElement entryPoint, Relation index, FmgrInfo *procinfo, Oid collation, int m, int efConstruction, bool existing);
|
||||||
HnswCandidate *HnswEntryCandidate(char *base, HnswElement em, Datum q, Relation rel, FmgrInfo *procinfo, Oid collation, bool loadVec);
|
HnswElement HnswFindDuplicate(HnswElement e);
|
||||||
|
HnswCandidate *HnswEntryCandidate(HnswElement em, Datum q, Relation rel, FmgrInfo *procinfo, Oid collation, bool loadVec);
|
||||||
void HnswUpdateMetaPage(Relation index, int updateEntry, HnswElement entryPoint, BlockNumber insertPage, ForkNumber forkNum, bool building);
|
void HnswUpdateMetaPage(Relation index, int updateEntry, HnswElement entryPoint, BlockNumber insertPage, ForkNumber forkNum, bool building);
|
||||||
void HnswSetNeighborTuple(char *base, HnswNeighborTuple ntup, HnswElement e, int m);
|
void HnswSetNeighborTuple(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(HnswElement element, int m);
|
||||||
bool HnswInsertTupleOnDisk(Relation index, Datum value, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel, bool building);
|
bool HnswInsertTuple(Relation index, 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 HnswUpdateNeighborPages(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);
|
||||||
void HnswSetElementTuple(char *base, HnswElementTuple etup, HnswElement element);
|
void HnswSetElementTuple(HnswElementTuple etup, HnswElement element);
|
||||||
void HnswUpdateConnection(char *base, HnswElement element, HnswCandidate * hc, int m, int lc, int *updateIdx, Relation index, FmgrInfo *procinfo, Oid collation);
|
void HnswUpdateConnection(HnswElement element, HnswCandidate * hc, int m, int lc, int *updateIdx, Relation index, FmgrInfo *procinfo, Oid collation);
|
||||||
void HnswLoadNeighbors(HnswElement element, Relation index, int m);
|
void HnswLoadNeighbors(HnswElement element, Relation index, int m);
|
||||||
PGDLLEXPORT void HnswParallelBuildMain(dsm_segment *seg, shm_toc *toc);
|
PGDLLEXPORT void HnswParallelBuildMain(dsm_segment *seg, shm_toc *toc);
|
||||||
|
|
||||||
@@ -414,16 +359,6 @@ void hnswrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys,
|
|||||||
bool hnswgettuple(IndexScanDesc scan, ScanDirection dir);
|
bool hnswgettuple(IndexScanDesc scan, ScanDirection dir);
|
||||||
void hnswendscan(IndexScanDesc scan);
|
void hnswendscan(IndexScanDesc scan);
|
||||||
|
|
||||||
static inline HnswNeighborArray *
|
|
||||||
HnswGetNeighbors(char *base, HnswElement element, int lc)
|
|
||||||
{
|
|
||||||
HnswNeighborArrayPtr *neighborList = HnswPtrAccess(base, element->neighbors);
|
|
||||||
|
|
||||||
Assert(element->level >= lc);
|
|
||||||
|
|
||||||
return HnswPtrAccess(base, neighborList[lc]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hash tables */
|
/* Hash tables */
|
||||||
typedef struct TidHashEntry
|
typedef struct TidHashEntry
|
||||||
{
|
{
|
||||||
@@ -451,17 +386,4 @@ typedef struct PointerHashEntry
|
|||||||
#define SH_DECLARE
|
#define SH_DECLARE
|
||||||
#include "lib/simplehash.h"
|
#include "lib/simplehash.h"
|
||||||
|
|
||||||
typedef struct OffsetHashEntry
|
|
||||||
{
|
|
||||||
Size offset;
|
|
||||||
char status;
|
|
||||||
} OffsetHashEntry;
|
|
||||||
|
|
||||||
#define SH_PREFIX offsethash
|
|
||||||
#define SH_ELEMENT_TYPE OffsetHashEntry
|
|
||||||
#define SH_KEY_TYPE Size
|
|
||||||
#define SH_SCOPE extern
|
|
||||||
#define SH_DECLARE
|
|
||||||
#include "lib/simplehash.h"
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
642
src/hnswbuild.c
642
src/hnswbuild.c
@@ -1,39 +1,3 @@
|
|||||||
/*
|
|
||||||
* The HNSW build happens in two phases:
|
|
||||||
*
|
|
||||||
* 1. In-memory phase
|
|
||||||
*
|
|
||||||
* In this first phase, the graph is held completely in memory. When the graph
|
|
||||||
* is fully built, or we run out of memory reserved for the build (determined
|
|
||||||
* by maintenance_work_mem), we materialize the graph to disk (see
|
|
||||||
* FlushPages()), and switch to the on-disk phase.
|
|
||||||
*
|
|
||||||
* In a parallel build, a large contiguous chunk of shared memory is allocated
|
|
||||||
* to hold the graph. Each worker process has its own HnswBuildState struct in
|
|
||||||
* private memory, which contains information that doesn't change throughout
|
|
||||||
* the build, and pointers to the shared structs in shared memory. The shared
|
|
||||||
* memory area is mapped to a different address in each worker process, and
|
|
||||||
* 'HnswBuildState.hnswarea' points to the beginning of the shared area in the
|
|
||||||
* worker process's address space. All pointers used in the graph are
|
|
||||||
* "relative pointers", stored as an offset from 'hnswarea'.
|
|
||||||
*
|
|
||||||
* Each element is protected by an LWLock. It must be held when reading or
|
|
||||||
* modifying the element's neighbors or 'heaptids'.
|
|
||||||
*
|
|
||||||
* In a non-parallel build, the graph is held in backend-private memory. All
|
|
||||||
* the elements are allocated in a dedicated memory context, 'graphCtx', and
|
|
||||||
* the pointers used in the graph are regular pointers.
|
|
||||||
*
|
|
||||||
* 2. On-disk phase
|
|
||||||
*
|
|
||||||
* In the on-disk phase, the index is built by inserting each vector to the
|
|
||||||
* index one by one, just like on INSERT. The only difference is that we don't
|
|
||||||
* WAL-log the individual inserts. If the graph fit completely in memory and
|
|
||||||
* was fully built in the in-memory phase, the on-disk phase is skipped.
|
|
||||||
*
|
|
||||||
* After we have finished building the graph, we perform one more scan through
|
|
||||||
* the index and write all the pages to the WAL.
|
|
||||||
*/
|
|
||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
@@ -90,12 +54,9 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define PARALLEL_KEY_HNSW_SHARED UINT64CONST(0xA000000000000001)
|
#define PARALLEL_KEY_HNSW_SHARED UINT64CONST(0xA000000000000001)
|
||||||
#define PARALLEL_KEY_HNSW_AREA UINT64CONST(0xA000000000000002)
|
#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xA000000000000002)
|
||||||
#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xA000000000000003)
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 130000
|
#define LIST_MAX_LENGTH ((1 << 26) - 1)
|
||||||
#define GENERATIONCHUNK_RAWSIZE (SIZEOF_SIZE_T + SIZEOF_VOID_P * 2)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create the metapage
|
* Create the metapage
|
||||||
@@ -127,7 +88,6 @@ CreateMetaPage(HnswBuildState * buildstate)
|
|||||||
((PageHeader) page)->pd_lower =
|
((PageHeader) page)->pd_lower =
|
||||||
((char *) metap + sizeof(HnswMetaPageData)) - (char *) page;
|
((char *) metap + sizeof(HnswMetaPageData)) - (char *) page;
|
||||||
|
|
||||||
MarkBufferDirty(buf);
|
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +104,6 @@ HnswBuildAppendPage(Relation index, Buffer *buf, Page *page, ForkNumber forkNum)
|
|||||||
HnswPageGetOpaque(*page)->nextblkno = BufferGetBlockNumber(newbuf);
|
HnswPageGetOpaque(*page)->nextblkno = BufferGetBlockNumber(newbuf);
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
MarkBufferDirty(*buf);
|
|
||||||
UnlockReleaseBuffer(*buf);
|
UnlockReleaseBuffer(*buf);
|
||||||
|
|
||||||
/* Can take a while, so ensure we can interrupt */
|
/* Can take a while, so ensure we can interrupt */
|
||||||
@@ -172,11 +131,9 @@ CreateElementPages(HnswBuildState * buildstate)
|
|||||||
HnswElementTuple etup;
|
HnswElementTuple etup;
|
||||||
HnswNeighborTuple ntup;
|
HnswNeighborTuple ntup;
|
||||||
BlockNumber insertPage;
|
BlockNumber insertPage;
|
||||||
HnswElement entryPoint;
|
|
||||||
Buffer buf;
|
Buffer buf;
|
||||||
Page page;
|
Page page;
|
||||||
HnswElementPtr iter = buildstate->graph->head;
|
ListCell *lc;
|
||||||
char *base = buildstate->hnswarea;
|
|
||||||
|
|
||||||
/* Calculate sizes */
|
/* Calculate sizes */
|
||||||
etupAllocSize = BLCKSZ;
|
etupAllocSize = BLCKSZ;
|
||||||
@@ -191,22 +148,18 @@ CreateElementPages(HnswBuildState * buildstate)
|
|||||||
page = BufferGetPage(buf);
|
page = BufferGetPage(buf);
|
||||||
HnswInitPage(buf, page);
|
HnswInitPage(buf, page);
|
||||||
|
|
||||||
while (!HnswPtrIsNull(base, iter))
|
foreach(lc, buildstate->elements)
|
||||||
{
|
{
|
||||||
HnswElement element = HnswPtrAccess(base, iter);
|
HnswElement element = lfirst(lc);
|
||||||
Size etupSize;
|
Size etupSize;
|
||||||
Size ntupSize;
|
Size ntupSize;
|
||||||
Size combinedSize;
|
Size combinedSize;
|
||||||
void *valuePtr = HnswPtrAccess(base, element->value);
|
|
||||||
|
|
||||||
/* Update iterator */
|
|
||||||
iter = element->next;
|
|
||||||
|
|
||||||
/* Zero memory for each element */
|
/* Zero memory for each element */
|
||||||
MemSet(etup, 0, etupAllocSize);
|
MemSet(etup, 0, etupAllocSize);
|
||||||
|
|
||||||
/* Calculate sizes */
|
/* Calculate sizes */
|
||||||
etupSize = HNSW_ELEMENT_TUPLE_SIZE(VARSIZE_ANY(valuePtr));
|
etupSize = HNSW_ELEMENT_TUPLE_SIZE(VARSIZE_ANY(DatumGetPointer(element->value)));
|
||||||
ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(element->level, buildstate->m);
|
ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(element->level, buildstate->m);
|
||||||
combinedSize = etupSize + ntupSize + sizeof(ItemIdData);
|
combinedSize = etupSize + ntupSize + sizeof(ItemIdData);
|
||||||
|
|
||||||
@@ -214,7 +167,7 @@ CreateElementPages(HnswBuildState * buildstate)
|
|||||||
if (etupSize > etupAllocSize)
|
if (etupSize > etupAllocSize)
|
||||||
elog(ERROR, "index tuple too large");
|
elog(ERROR, "index tuple too large");
|
||||||
|
|
||||||
HnswSetElementTuple(base, etup, element);
|
HnswSetElementTuple(etup, element);
|
||||||
|
|
||||||
/* Keep element and neighbors on the same page if possible */
|
/* Keep element and neighbors on the same page if possible */
|
||||||
if (PageGetFreeSpace(page) < etupSize || (combinedSize <= maxSize && PageGetFreeSpace(page) < combinedSize))
|
if (PageGetFreeSpace(page) < etupSize || (combinedSize <= maxSize && PageGetFreeSpace(page) < combinedSize))
|
||||||
@@ -252,11 +205,9 @@ CreateElementPages(HnswBuildState * buildstate)
|
|||||||
insertPage = BufferGetBlockNumber(buf);
|
insertPage = BufferGetBlockNumber(buf);
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
MarkBufferDirty(buf);
|
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
|
|
||||||
entryPoint = HnswPtrAccess(base, buildstate->graph->entryPoint);
|
HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_ALWAYS, buildstate->entryPoint, insertPage, forkNum, true);
|
||||||
HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_ALWAYS, entryPoint, insertPage, forkNum, true);
|
|
||||||
|
|
||||||
pfree(etup);
|
pfree(etup);
|
||||||
pfree(ntup);
|
pfree(ntup);
|
||||||
@@ -271,23 +222,19 @@ 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;
|
ListCell *lc;
|
||||||
char *base = buildstate->hnswarea;
|
|
||||||
HnswNeighborTuple ntup;
|
HnswNeighborTuple ntup;
|
||||||
|
|
||||||
/* Allocate once */
|
/* Allocate once */
|
||||||
ntup = palloc0(BLCKSZ);
|
ntup = palloc0(BLCKSZ);
|
||||||
|
|
||||||
while (!HnswPtrIsNull(base, iter))
|
foreach(lc, buildstate->elements)
|
||||||
{
|
{
|
||||||
HnswElement e = HnswPtrAccess(base, iter);
|
HnswElement e = lfirst(lc);
|
||||||
Buffer buf;
|
Buffer buf;
|
||||||
Page page;
|
Page page;
|
||||||
Size ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(e->level, m);
|
Size ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(e->level, m);
|
||||||
|
|
||||||
/* Update iterator */
|
|
||||||
iter = e->next;
|
|
||||||
|
|
||||||
/* Can take a while, so ensure we can interrupt */
|
/* Can take a while, so ensure we can interrupt */
|
||||||
/* Needs to be called when no buffer locks are held */
|
/* Needs to be called when no buffer locks are held */
|
||||||
CHECK_FOR_INTERRUPTS();
|
CHECK_FOR_INTERRUPTS();
|
||||||
@@ -296,203 +243,58 @@ CreateNeighborPages(HnswBuildState * buildstate)
|
|||||||
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
||||||
page = BufferGetPage(buf);
|
page = BufferGetPage(buf);
|
||||||
|
|
||||||
HnswSetNeighborTuple(base, ntup, e, m);
|
HnswSetNeighborTuple(ntup, e, m);
|
||||||
|
|
||||||
if (!PageIndexTupleOverwrite(page, e->neighborOffno, (Item) ntup, ntupSize))
|
if (!PageIndexTupleOverwrite(page, e->neighborOffno, (Item) ntup, ntupSize))
|
||||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
MarkBufferDirty(buf);
|
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
pfree(ntup);
|
pfree(ntup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Free elements
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
FreeElements(HnswBuildState * buildstate)
|
||||||
|
{
|
||||||
|
ListCell *lc;
|
||||||
|
|
||||||
|
foreach(lc, buildstate->elements)
|
||||||
|
HnswFreeElement(lfirst(lc));
|
||||||
|
|
||||||
|
list_free(buildstate->elements);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Flush pages
|
* Flush pages
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
FlushPages(HnswBuildState * buildstate)
|
FlushPages(HnswBuildState * buildstate)
|
||||||
{
|
{
|
||||||
#ifdef HNSW_MEMORY
|
|
||||||
elog(INFO, "memory: %zu MB", buildstate->graph->memoryUsed / (1024 * 1024));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
CreateMetaPage(buildstate);
|
CreateMetaPage(buildstate);
|
||||||
CreateElementPages(buildstate);
|
CreateElementPages(buildstate);
|
||||||
CreateNeighborPages(buildstate);
|
CreateNeighborPages(buildstate);
|
||||||
|
|
||||||
buildstate->graph->flushed = true;
|
buildstate->flushed = true;
|
||||||
MemoryContextReset(buildstate->graphCtx);
|
FreeElements(buildstate);
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Add a heap TID to an existing element
|
|
||||||
*/
|
|
||||||
static bool
|
|
||||||
AddDuplicateInMemory(HnswElement element, HnswElement dup)
|
|
||||||
{
|
|
||||||
LWLockAcquire(&dup->lock, LW_EXCLUSIVE);
|
|
||||||
|
|
||||||
if (dup->heaptidsLength == HNSW_HEAPTIDS)
|
|
||||||
{
|
|
||||||
LWLockRelease(&dup->lock);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
HnswAddHeapTid(dup, &element->heaptids[0]);
|
|
||||||
|
|
||||||
LWLockRelease(&dup->lock);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Find duplicate element
|
|
||||||
*/
|
|
||||||
static bool
|
|
||||||
FindDuplicateInMemory(char *base, HnswElement element)
|
|
||||||
{
|
|
||||||
HnswNeighborArray *neighbors = HnswGetNeighbors(base, element, 0);
|
|
||||||
Datum value = HnswGetValue(base, element);
|
|
||||||
|
|
||||||
for (int i = 0; i < neighbors->length; i++)
|
|
||||||
{
|
|
||||||
HnswCandidate *neighbor = &neighbors->items[i];
|
|
||||||
HnswElement neighborElement = HnswPtrAccess(base, neighbor->element);
|
|
||||||
Datum neighborValue = HnswGetValue(base, neighborElement);
|
|
||||||
|
|
||||||
/* Exit early since ordered by distance */
|
|
||||||
if (!datumIsEqual(value, neighborValue, false, -1))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
/* Check for space */
|
|
||||||
if (AddDuplicateInMemory(element, neighborElement))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Add to element list
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
AddElementInMemory(char *base, HnswGraph * graph, HnswElement element)
|
|
||||||
{
|
|
||||||
SpinLockAcquire(&graph->lock);
|
|
||||||
element->next = graph->head;
|
|
||||||
HnswPtrStore(base, graph->head, element);
|
|
||||||
SpinLockRelease(&graph->lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Update neighbors
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
UpdateNeighborsInMemory(char *base, FmgrInfo *procinfo, Oid collation, HnswElement e, int m)
|
|
||||||
{
|
|
||||||
for (int lc = e->level; lc >= 0; lc--)
|
|
||||||
{
|
|
||||||
int lm = HnswGetLayerM(m, lc);
|
|
||||||
HnswNeighborArray *neighbors = HnswGetNeighbors(base, e, lc);
|
|
||||||
|
|
||||||
for (int i = 0; i < neighbors->length; i++)
|
|
||||||
{
|
|
||||||
HnswCandidate *hc = &neighbors->items[i];
|
|
||||||
HnswElement neighborElement = HnswPtrAccess(base, hc->element);
|
|
||||||
|
|
||||||
/* Keep scan-build happy on Mac x86-64 */
|
|
||||||
Assert(neighborElement);
|
|
||||||
|
|
||||||
/* Use element for lock instead of hc since hc can be replaced */
|
|
||||||
LWLockAcquire(&neighborElement->lock, LW_EXCLUSIVE);
|
|
||||||
HnswUpdateConnection(base, e, hc, lm, lc, NULL, NULL, procinfo, collation);
|
|
||||||
LWLockRelease(&neighborElement->lock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Update graph in memory
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
UpdateGraphInMemory(FmgrInfo *procinfo, Oid collation, HnswElement element, int m, int efConstruction, HnswElement entryPoint, HnswBuildState * buildstate)
|
|
||||||
{
|
|
||||||
HnswGraph *graph = buildstate->graph;
|
|
||||||
char *base = buildstate->hnswarea;
|
|
||||||
|
|
||||||
/* Look for duplicate */
|
|
||||||
if (FindDuplicateInMemory(base, element))
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* Add element */
|
|
||||||
AddElementInMemory(base, graph, element);
|
|
||||||
|
|
||||||
/* Update neighbors */
|
|
||||||
UpdateNeighborsInMemory(base, procinfo, collation, element, m);
|
|
||||||
|
|
||||||
/* Update entry point if needed (already have lock) */
|
|
||||||
if (entryPoint == NULL || element->level > entryPoint->level)
|
|
||||||
HnswPtrStore(base, graph->entryPoint, element);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Insert tuple in memory
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
InsertTupleInMemory(HnswBuildState * buildstate, HnswElement element)
|
|
||||||
{
|
|
||||||
FmgrInfo *procinfo = buildstate->procinfo;
|
|
||||||
Oid collation = buildstate->collation;
|
|
||||||
HnswGraph *graph = buildstate->graph;
|
|
||||||
HnswElement entryPoint;
|
|
||||||
LWLock *entryLock = &graph->entryLock;
|
|
||||||
int efConstruction = buildstate->efConstruction;
|
|
||||||
int m = buildstate->m;
|
|
||||||
char *base = buildstate->hnswarea;
|
|
||||||
|
|
||||||
/* Get entry point */
|
|
||||||
LWLockAcquire(entryLock, LW_SHARED);
|
|
||||||
entryPoint = HnswPtrAccess(base, graph->entryPoint);
|
|
||||||
|
|
||||||
/* Prevent concurrent inserts when likely updating entry point */
|
|
||||||
if (entryPoint == NULL || element->level > entryPoint->level)
|
|
||||||
{
|
|
||||||
/* Release shared lock */
|
|
||||||
LWLockRelease(entryLock);
|
|
||||||
|
|
||||||
/* Get exclusive lock */
|
|
||||||
LWLockAcquire(entryLock, LW_EXCLUSIVE);
|
|
||||||
|
|
||||||
/* Get latest entry point after lock is acquired */
|
|
||||||
entryPoint = HnswPtrAccess(base, graph->entryPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Find neighbors for element */
|
|
||||||
HnswFindElementNeighbors(base, element, entryPoint, NULL, procinfo, collation, m, efConstruction, false);
|
|
||||||
|
|
||||||
/* Update graph in memory */
|
|
||||||
UpdateGraphInMemory(procinfo, collation, element, m, efConstruction, entryPoint, buildstate);
|
|
||||||
|
|
||||||
/* Release entry lock */
|
|
||||||
LWLockRelease(entryLock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Insert tuple
|
* Insert tuple
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool
|
||||||
InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heaptid, HnswBuildState * buildstate)
|
InsertTuple(Relation index, Datum *values, HnswElement element, HnswBuildState * buildstate, HnswElement * dup, MemoryContext outerCtx)
|
||||||
{
|
{
|
||||||
HnswGraph *graph = buildstate->graph;
|
FmgrInfo *procinfo = buildstate->procinfo;
|
||||||
HnswElement element;
|
Oid collation = buildstate->collation;
|
||||||
HnswAllocator *allocator = &buildstate->allocator;
|
HnswElement entryPoint = buildstate->entryPoint;
|
||||||
Size valueSize;
|
int efConstruction = buildstate->efConstruction;
|
||||||
Pointer valuePtr;
|
int m = buildstate->m;
|
||||||
LWLock *flushLock = &graph->flushLock;
|
MemoryContext oldCtx;
|
||||||
char *base = buildstate->hnswarea;
|
|
||||||
|
|
||||||
/* Detoast once for all calls */
|
/* Detoast once for all calls */
|
||||||
Datum value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
Datum value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
||||||
@@ -500,81 +302,56 @@ InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heaptid, Hn
|
|||||||
/* Normalize if needed */
|
/* Normalize if needed */
|
||||||
if (buildstate->normprocinfo != NULL)
|
if (buildstate->normprocinfo != NULL)
|
||||||
{
|
{
|
||||||
if (!HnswNormValue(buildstate->normprocinfo, buildstate->collation, &value, buildstate->normvec))
|
if (!HnswNormValue(buildstate->normprocinfo, collation, &value, buildstate->normvec))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get datum size */
|
/* Copy value to element so accessible outside of memory context */
|
||||||
valueSize = VARSIZE_ANY(DatumGetPointer(value));
|
oldCtx = MemoryContextSwitchTo(outerCtx);
|
||||||
|
element->value = datumCopy(value, false, -1);
|
||||||
|
MemoryContextSwitchTo(oldCtx);
|
||||||
|
|
||||||
/* Ensure graph not flushed when inserting */
|
/* Insert element in graph */
|
||||||
LWLockAcquire(flushLock, LW_SHARED);
|
HnswInsertElement(element, entryPoint, NULL, procinfo, collation, m, efConstruction, false);
|
||||||
|
|
||||||
/* Are we in the on-disk phase? */
|
/* Look for duplicate */
|
||||||
if (graph->flushed)
|
*dup = HnswFindDuplicate(element);
|
||||||
|
|
||||||
|
/* Update neighbors if needed */
|
||||||
|
if (*dup == NULL)
|
||||||
{
|
{
|
||||||
LWLockRelease(flushLock);
|
for (int lc = element->level; lc >= 0; lc--)
|
||||||
|
|
||||||
return HnswInsertTupleOnDisk(index, value, values, isnull, heaptid, buildstate->heap, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* In a parallel build, the HnswElement is allocated from the shared
|
|
||||||
* memory area, so we need to coordinate with other processes.
|
|
||||||
*/
|
|
||||||
LWLockAcquire(&graph->allocatorLock, LW_EXCLUSIVE);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Check that we have enough memory available for the new element now that
|
|
||||||
* we have the allocator lock, and flush pages if needed.
|
|
||||||
*/
|
|
||||||
if (graph->memoryUsed >= graph->memoryTotal)
|
|
||||||
{
|
|
||||||
LWLockRelease(&graph->allocatorLock);
|
|
||||||
|
|
||||||
LWLockRelease(flushLock);
|
|
||||||
LWLockAcquire(flushLock, LW_EXCLUSIVE);
|
|
||||||
|
|
||||||
if (!graph->flushed)
|
|
||||||
{
|
{
|
||||||
ereport(NOTICE,
|
int lm = HnswGetLayerM(m, lc);
|
||||||
(errmsg("hnsw graph no longer fits into maintenance_work_mem after " INT64_FORMAT " tuples", (int64) graph->indtuples),
|
HnswNeighborArray *neighbors = &element->neighbors[lc];
|
||||||
errdetail("Building will take significantly more time."),
|
|
||||||
errhint("Increase maintenance_work_mem to speed up builds.")));
|
|
||||||
|
|
||||||
FlushPages(buildstate);
|
for (int i = 0; i < neighbors->length; i++)
|
||||||
|
HnswUpdateConnection(element, &neighbors->items[i], lm, lc, NULL, NULL, procinfo, collation);
|
||||||
}
|
}
|
||||||
|
|
||||||
LWLockRelease(flushLock);
|
|
||||||
|
|
||||||
return HnswInsertTupleOnDisk(index, value, values, isnull, heaptid, buildstate->heap, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ok, we can proceed to allocate the element */
|
/* Update entry point if needed */
|
||||||
element = HnswInitElement(base, heaptid, buildstate->m, buildstate->ml, buildstate->maxLevel, allocator);
|
if (*dup == NULL && (entryPoint == NULL || element->level > entryPoint->level))
|
||||||
valuePtr = HnswAlloc(allocator, valueSize);
|
buildstate->entryPoint = element;
|
||||||
|
|
||||||
/*
|
UpdateProgress(PROGRESS_CREATEIDX_TUPLES_DONE, ++buildstate->indtuples);
|
||||||
* We have now allocated the space needed for the element, so we don't
|
|
||||||
* need the allocator lock anymore. Release it and initialize the rest of
|
|
||||||
* the element.
|
|
||||||
*/
|
|
||||||
LWLockRelease(&graph->allocatorLock);
|
|
||||||
|
|
||||||
/* Copy the datum */
|
return *dup == NULL;
|
||||||
memcpy(valuePtr, DatumGetPointer(value), valueSize);
|
}
|
||||||
HnswPtrStore(base, element->value, valuePtr);
|
|
||||||
|
|
||||||
/* Create a lock for the element */
|
/*
|
||||||
LWLockInitialize(&element->lock, hnsw_lock_tranche_id);
|
* Get the memory used by an element
|
||||||
|
*/
|
||||||
|
static long
|
||||||
|
HnswElementMemory(HnswElement e, int m)
|
||||||
|
{
|
||||||
|
long elementSize = sizeof(HnswElementData);
|
||||||
|
|
||||||
/* Insert tuple */
|
elementSize += sizeof(HnswNeighborArray) * (e->level + 1);
|
||||||
InsertTupleInMemory(buildstate, element);
|
elementSize += sizeof(HnswCandidate) * (m * (e->level + 2));
|
||||||
|
elementSize += sizeof(ItemPointerData);
|
||||||
/* Release flush lock */
|
elementSize += VARSIZE_ANY(DatumGetPointer(e->value));
|
||||||
LWLockRelease(flushLock);
|
return elementSize;
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -585,8 +362,10 @@ BuildCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
|
|||||||
bool *isnull, bool tupleIsAlive, void *state)
|
bool *isnull, bool tupleIsAlive, void *state)
|
||||||
{
|
{
|
||||||
HnswBuildState *buildstate = (HnswBuildState *) state;
|
HnswBuildState *buildstate = (HnswBuildState *) state;
|
||||||
HnswGraph *graph = buildstate->graph;
|
|
||||||
MemoryContext oldCtx;
|
MemoryContext oldCtx;
|
||||||
|
HnswElement element;
|
||||||
|
HnswElement dup = NULL;
|
||||||
|
bool inserted;
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 130000
|
#if PG_VERSION_NUM < 130000
|
||||||
ItemPointer tid = &hup->t_self;
|
ItemPointer tid = &hup->t_self;
|
||||||
@@ -596,80 +375,70 @@ BuildCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
|
|||||||
if (isnull[0])
|
if (isnull[0])
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* Use memory context */
|
if (buildstate->flushed || buildstate->memoryLeft <= 0 || list_length(buildstate->elements) == LIST_MAX_LENGTH)
|
||||||
|
{
|
||||||
|
if (!buildstate->flushed)
|
||||||
|
{
|
||||||
|
if (buildstate->memoryLeft <= 0)
|
||||||
|
ereport(NOTICE,
|
||||||
|
(errmsg("hnsw graph no longer fits into maintenance_work_mem after " INT64_FORMAT " tuples", (int64) buildstate->indtuples),
|
||||||
|
errdetail("Building will take significantly more time."),
|
||||||
|
errhint("Increase maintenance_work_mem to speed up builds.")));
|
||||||
|
|
||||||
|
FlushPages(buildstate);
|
||||||
|
}
|
||||||
|
|
||||||
|
oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
|
||||||
|
|
||||||
|
if (HnswInsertTuple(buildstate->index, values, isnull, tid, buildstate->heap, true))
|
||||||
|
{
|
||||||
|
if (buildstate->hnswshared)
|
||||||
|
{
|
||||||
|
HnswShared *hnswshared = buildstate->hnswshared;
|
||||||
|
|
||||||
|
SpinLockAcquire(&hnswshared->mutex);
|
||||||
|
UpdateProgress(PROGRESS_CREATEIDX_TUPLES_DONE, ++hnswshared->indtuples);
|
||||||
|
SpinLockRelease(&hnswshared->mutex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
UpdateProgress(PROGRESS_CREATEIDX_TUPLES_DONE, ++buildstate->indtuples);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset memory context */
|
||||||
|
MemoryContextSwitchTo(oldCtx);
|
||||||
|
MemoryContextReset(buildstate->tmpCtx);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate necessary memory outside of memory context */
|
||||||
|
element = HnswInitElement(tid, buildstate->m, buildstate->ml, buildstate->maxLevel);
|
||||||
|
|
||||||
|
/* Use memory context since detoast can allocate */
|
||||||
oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
|
oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
|
||||||
|
|
||||||
/* Insert tuple */
|
/* Insert tuple */
|
||||||
if (InsertTuple(index, values, isnull, tid, buildstate))
|
inserted = InsertTuple(index, values, element, buildstate, &dup, oldCtx);
|
||||||
{
|
|
||||||
/* Update progress */
|
|
||||||
SpinLockAcquire(&graph->lock);
|
|
||||||
UpdateProgress(PROGRESS_CREATEIDX_TUPLES_DONE, ++graph->indtuples);
|
|
||||||
SpinLockRelease(&graph->lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reset memory context */
|
/* Reset memory context */
|
||||||
MemoryContextSwitchTo(oldCtx);
|
MemoryContextSwitchTo(oldCtx);
|
||||||
MemoryContextReset(buildstate->tmpCtx);
|
MemoryContextReset(buildstate->tmpCtx);
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/* Add outside memory context */
|
||||||
* Initialize the graph
|
if (dup != NULL)
|
||||||
*/
|
{
|
||||||
static void
|
HnswAddHeapTid(dup, tid);
|
||||||
InitGraph(HnswGraph * graph, char *base, long memoryTotal)
|
buildstate->memoryLeft -= sizeof(ItemPointerData);
|
||||||
{
|
}
|
||||||
HnswPtrStore(base, graph->head, (HnswElement) NULL);
|
|
||||||
HnswPtrStore(base, graph->entryPoint, (HnswElement) NULL);
|
|
||||||
graph->memoryUsed = 0;
|
|
||||||
graph->memoryTotal = memoryTotal;
|
|
||||||
graph->flushed = false;
|
|
||||||
graph->indtuples = 0;
|
|
||||||
SpinLockInit(&graph->lock);
|
|
||||||
LWLockInitialize(&graph->entryLock, hnsw_lock_tranche_id);
|
|
||||||
LWLockInitialize(&graph->allocatorLock, hnsw_lock_tranche_id);
|
|
||||||
LWLockInitialize(&graph->flushLock, hnsw_lock_tranche_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/* Add to buildstate or free */
|
||||||
* Initialize an allocator
|
if (inserted)
|
||||||
*/
|
{
|
||||||
static void
|
buildstate->elements = lappend(buildstate->elements, element);
|
||||||
InitAllocator(HnswAllocator * allocator, void *(*alloc) (Size size, void *state), void *state)
|
buildstate->memoryLeft -= HnswElementMemory(element, buildstate->m);
|
||||||
{
|
}
|
||||||
allocator->alloc = alloc;
|
else
|
||||||
allocator->state = state;
|
HnswFreeElement(element);
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Memory context allocator
|
|
||||||
*/
|
|
||||||
static void *
|
|
||||||
HnswMemoryContextAlloc(Size size, void *state)
|
|
||||||
{
|
|
||||||
HnswBuildState *buildstate = (HnswBuildState *) state;
|
|
||||||
void *chunk = MemoryContextAlloc(buildstate->graphCtx, size);
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 130000
|
|
||||||
buildstate->graphData.memoryUsed = MemoryContextMemAllocated(buildstate->graphCtx, false);
|
|
||||||
#else
|
|
||||||
buildstate->graphData.memoryUsed += MAXALIGN(size);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return chunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Shared memory allocator
|
|
||||||
*/
|
|
||||||
static void *
|
|
||||||
HnswSharedMemoryAlloc(Size size, void *state)
|
|
||||||
{
|
|
||||||
HnswBuildState *buildstate = (HnswBuildState *) state;
|
|
||||||
void *chunk = buildstate->hnswarea + buildstate->graph->memoryUsed;
|
|
||||||
|
|
||||||
buildstate->graph->memoryUsed += MAXALIGN(size);
|
|
||||||
return chunk;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -705,29 +474,22 @@ InitBuildState(HnswBuildState * buildstate, Relation heap, Relation index, Index
|
|||||||
buildstate->normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC);
|
buildstate->normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC);
|
||||||
buildstate->collation = index->rd_indcollation[0];
|
buildstate->collation = index->rd_indcollation[0];
|
||||||
|
|
||||||
InitGraph(&buildstate->graphData, NULL, maintenance_work_mem * 1024L);
|
buildstate->elements = NIL;
|
||||||
buildstate->graph = &buildstate->graphData;
|
buildstate->entryPoint = NULL;
|
||||||
buildstate->ml = HnswGetMl(buildstate->m);
|
buildstate->ml = HnswGetMl(buildstate->m);
|
||||||
buildstate->maxLevel = HnswGetMaxLevel(buildstate->m);
|
buildstate->maxLevel = HnswGetMaxLevel(buildstate->m);
|
||||||
|
buildstate->memoryLeft = maintenance_work_mem * 1024L;
|
||||||
|
buildstate->flushed = false;
|
||||||
|
|
||||||
/* Reuse for each tuple */
|
/* Reuse for each tuple */
|
||||||
buildstate->normvec = InitVector(buildstate->dimensions);
|
buildstate->normvec = InitVector(buildstate->dimensions);
|
||||||
|
|
||||||
buildstate->graphCtx = GenerationContextCreate(CurrentMemoryContext,
|
|
||||||
"Hnsw build graph context",
|
|
||||||
#if PG_VERSION_NUM >= 150000
|
|
||||||
1024 * 1024, 1024 * 1024,
|
|
||||||
#endif
|
|
||||||
1024 * 1024);
|
|
||||||
buildstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
|
buildstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
|
||||||
"Hnsw build temporary context",
|
"Hnsw build temporary context",
|
||||||
ALLOCSET_DEFAULT_SIZES);
|
ALLOCSET_DEFAULT_SIZES);
|
||||||
|
|
||||||
InitAllocator(&buildstate->allocator, &HnswMemoryContextAlloc, buildstate);
|
|
||||||
|
|
||||||
buildstate->hnswleader = NULL;
|
buildstate->hnswleader = NULL;
|
||||||
buildstate->hnswshared = NULL;
|
buildstate->hnswshared = NULL;
|
||||||
buildstate->hnswarea = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -737,7 +499,6 @@ static void
|
|||||||
FreeBuildState(HnswBuildState * buildstate)
|
FreeBuildState(HnswBuildState * buildstate)
|
||||||
{
|
{
|
||||||
pfree(buildstate->normvec);
|
pfree(buildstate->normvec);
|
||||||
MemoryContextDelete(buildstate->graphCtx);
|
|
||||||
MemoryContextDelete(buildstate->tmpCtx);
|
MemoryContextDelete(buildstate->tmpCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -757,8 +518,7 @@ ParallelHeapScan(HnswBuildState * buildstate)
|
|||||||
SpinLockAcquire(&hnswshared->mutex);
|
SpinLockAcquire(&hnswshared->mutex);
|
||||||
if (hnswshared->nparticipantsdone == nparticipanttuplesorts)
|
if (hnswshared->nparticipantsdone == nparticipanttuplesorts)
|
||||||
{
|
{
|
||||||
buildstate->graph = &hnswshared->graphData;
|
buildstate->indtuples = hnswshared->indtuples;
|
||||||
buildstate->hnswarea = buildstate->hnswleader->hnswarea;
|
|
||||||
reltuples = hnswshared->reltuples;
|
reltuples = hnswshared->reltuples;
|
||||||
SpinLockRelease(&hnswshared->mutex);
|
SpinLockRelease(&hnswshared->mutex);
|
||||||
break;
|
break;
|
||||||
@@ -778,7 +538,7 @@ ParallelHeapScan(HnswBuildState * buildstate)
|
|||||||
* Perform a worker's portion of a parallel insert
|
* Perform a worker's portion of a parallel insert
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
HnswParallelScanAndInsert(Relation heapRel, Relation indexRel, HnswShared * hnswshared, char *hnswarea, bool progress)
|
HnswParallelScanAndInsert(HnswSpool * hnswspool, HnswShared * hnswshared, bool progress)
|
||||||
{
|
{
|
||||||
HnswBuildState buildstate;
|
HnswBuildState buildstate;
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
@@ -790,21 +550,22 @@ HnswParallelScanAndInsert(Relation heapRel, Relation indexRel, HnswShared * hnsw
|
|||||||
IndexInfo *indexInfo;
|
IndexInfo *indexInfo;
|
||||||
|
|
||||||
/* Join parallel scan */
|
/* Join parallel scan */
|
||||||
indexInfo = BuildIndexInfo(indexRel);
|
indexInfo = BuildIndexInfo(hnswspool->index);
|
||||||
indexInfo->ii_Concurrent = hnswshared->isconcurrent;
|
indexInfo->ii_Concurrent = hnswshared->isconcurrent;
|
||||||
InitBuildState(&buildstate, heapRel, indexRel, indexInfo, MAIN_FORKNUM);
|
InitBuildState(&buildstate, hnswspool->heap, hnswspool->index, indexInfo, MAIN_FORKNUM);
|
||||||
buildstate.graph = &hnswshared->graphData;
|
/* TODO Support in-memory builds */
|
||||||
buildstate.hnswarea = hnswarea;
|
buildstate.memoryLeft = 0;
|
||||||
InitAllocator(&buildstate.allocator, &HnswSharedMemoryAlloc, &buildstate);
|
buildstate.flushed = true;
|
||||||
|
buildstate.hnswshared = hnswshared;
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
scan = table_beginscan_parallel(heapRel,
|
scan = table_beginscan_parallel(hnswspool->heap,
|
||||||
ParallelTableScanFromHnswShared(hnswshared));
|
ParallelTableScanFromHnswShared(hnswshared));
|
||||||
reltuples = table_index_build_scan(heapRel, indexRel, indexInfo,
|
reltuples = table_index_build_scan(hnswspool->heap, hnswspool->index, indexInfo,
|
||||||
true, progress, BuildCallback,
|
true, progress, BuildCallback,
|
||||||
(void *) &buildstate, scan);
|
(void *) &buildstate, scan);
|
||||||
#else
|
#else
|
||||||
scan = heap_beginscan_parallel(heapRel, &hnswshared->heapdesc);
|
scan = heap_beginscan_parallel(hnswspool->heap, &hnswshared->heapdesc);
|
||||||
reltuples = IndexBuildHeapScan(heapRel, indexRel, indexInfo,
|
reltuples = IndexBuildHeapScan(hnswspool->heap, hnswspool->index, indexInfo,
|
||||||
true, BuildCallback,
|
true, BuildCallback,
|
||||||
(void *) &buildstate, scan);
|
(void *) &buildstate, scan);
|
||||||
#endif
|
#endif
|
||||||
@@ -834,8 +595,8 @@ void
|
|||||||
HnswParallelBuildMain(dsm_segment *seg, shm_toc *toc)
|
HnswParallelBuildMain(dsm_segment *seg, shm_toc *toc)
|
||||||
{
|
{
|
||||||
char *sharedquery;
|
char *sharedquery;
|
||||||
|
HnswSpool *hnswspool;
|
||||||
HnswShared *hnswshared;
|
HnswShared *hnswshared;
|
||||||
char *hnswarea;
|
|
||||||
Relation heapRel;
|
Relation heapRel;
|
||||||
Relation indexRel;
|
Relation indexRel;
|
||||||
LOCKMODE heapLockmode;
|
LOCKMODE heapLockmode;
|
||||||
@@ -871,10 +632,13 @@ HnswParallelBuildMain(dsm_segment *seg, shm_toc *toc)
|
|||||||
#endif
|
#endif
|
||||||
indexRel = index_open(hnswshared->indexrelid, indexLockmode);
|
indexRel = index_open(hnswshared->indexrelid, indexLockmode);
|
||||||
|
|
||||||
hnswarea = shm_toc_lookup(toc, PARALLEL_KEY_HNSW_AREA, false);
|
/* Initialize worker's own spool */
|
||||||
|
hnswspool = (HnswSpool *) palloc0(sizeof(HnswSpool));
|
||||||
|
hnswspool->heap = heapRel;
|
||||||
|
hnswspool->index = indexRel;
|
||||||
|
|
||||||
/* Perform inserts */
|
/* Perform inserts */
|
||||||
HnswParallelScanAndInsert(heapRel, indexRel, hnswshared, hnswarea, false);
|
HnswParallelScanAndInsert(hnswspool, hnswshared, false);
|
||||||
|
|
||||||
/* Close relations within worker */
|
/* Close relations within worker */
|
||||||
index_close(indexRel, indexLockmode);
|
index_close(indexRel, indexLockmode);
|
||||||
@@ -929,9 +693,15 @@ static void
|
|||||||
HnswLeaderParticipateAsWorker(HnswBuildState * buildstate)
|
HnswLeaderParticipateAsWorker(HnswBuildState * buildstate)
|
||||||
{
|
{
|
||||||
HnswLeader *hnswleader = buildstate->hnswleader;
|
HnswLeader *hnswleader = buildstate->hnswleader;
|
||||||
|
HnswSpool *leaderworker;
|
||||||
|
|
||||||
|
/* Allocate memory and initialize private spool */
|
||||||
|
leaderworker = (HnswSpool *) palloc0(sizeof(HnswSpool));
|
||||||
|
leaderworker->heap = buildstate->heap;
|
||||||
|
leaderworker->index = buildstate->index;
|
||||||
|
|
||||||
/* Perform work common to all participants */
|
/* Perform work common to all participants */
|
||||||
HnswParallelScanAndInsert(buildstate->heap, buildstate->index, hnswleader->hnswshared, hnswleader->hnswarea, true);
|
HnswParallelScanAndInsert(leaderworker, hnswleader->hnswshared, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -941,12 +711,10 @@ static void
|
|||||||
HnswBeginParallel(HnswBuildState * buildstate, bool isconcurrent, int request)
|
HnswBeginParallel(HnswBuildState * buildstate, bool isconcurrent, int request)
|
||||||
{
|
{
|
||||||
ParallelContext *pcxt;
|
ParallelContext *pcxt;
|
||||||
|
int scantuplesortstates;
|
||||||
Snapshot snapshot;
|
Snapshot snapshot;
|
||||||
Size esthnswshared;
|
Size esthnswshared;
|
||||||
Size esthnswarea;
|
|
||||||
Size estother;
|
|
||||||
HnswShared *hnswshared;
|
HnswShared *hnswshared;
|
||||||
char *hnswarea;
|
|
||||||
HnswLeader *hnswleader = (HnswLeader *) palloc0(sizeof(HnswLeader));
|
HnswLeader *hnswleader = (HnswLeader *) palloc0(sizeof(HnswLeader));
|
||||||
bool leaderparticipates = true;
|
bool leaderparticipates = true;
|
||||||
int querylen;
|
int querylen;
|
||||||
@@ -964,6 +732,8 @@ HnswBeginParallel(HnswBuildState * buildstate, bool isconcurrent, int request)
|
|||||||
pcxt = CreateParallelContext("vector", "HnswParallelBuildMain", request, true);
|
pcxt = CreateParallelContext("vector", "HnswParallelBuildMain", request, true);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
scantuplesortstates = leaderparticipates ? request + 1 : request;
|
||||||
|
|
||||||
/* Get snapshot for table scan */
|
/* Get snapshot for table scan */
|
||||||
if (!isconcurrent)
|
if (!isconcurrent)
|
||||||
snapshot = SnapshotAny;
|
snapshot = SnapshotAny;
|
||||||
@@ -973,17 +743,7 @@ HnswBeginParallel(HnswBuildState * buildstate, bool isconcurrent, int request)
|
|||||||
/* Estimate size of workspaces */
|
/* Estimate size of workspaces */
|
||||||
esthnswshared = ParallelEstimateShared(buildstate->heap, snapshot);
|
esthnswshared = ParallelEstimateShared(buildstate->heap, snapshot);
|
||||||
shm_toc_estimate_chunk(&pcxt->estimator, esthnswshared);
|
shm_toc_estimate_chunk(&pcxt->estimator, esthnswshared);
|
||||||
|
shm_toc_estimate_keys(&pcxt->estimator, 1);
|
||||||
/* Leave space for other objects in shared memory */
|
|
||||||
/* Docker has a default limit of 64 MB for shm_size */
|
|
||||||
/* which happens to be the default value of maintenance_work_mem */
|
|
||||||
esthnswarea = maintenance_work_mem * 1024L;
|
|
||||||
estother = 2 * 1024 * 1024;
|
|
||||||
if (esthnswarea > estother)
|
|
||||||
esthnswarea -= estother;
|
|
||||||
|
|
||||||
shm_toc_estimate_chunk(&pcxt->estimator, esthnswarea);
|
|
||||||
shm_toc_estimate_keys(&pcxt->estimator, 2);
|
|
||||||
|
|
||||||
/* Finally, estimate PARALLEL_KEY_QUERY_TEXT space */
|
/* Finally, estimate PARALLEL_KEY_QUERY_TEXT space */
|
||||||
if (debug_query_string)
|
if (debug_query_string)
|
||||||
@@ -1014,11 +774,13 @@ HnswBeginParallel(HnswBuildState * buildstate, bool isconcurrent, int request)
|
|||||||
hnswshared->heaprelid = RelationGetRelid(buildstate->heap);
|
hnswshared->heaprelid = RelationGetRelid(buildstate->heap);
|
||||||
hnswshared->indexrelid = RelationGetRelid(buildstate->index);
|
hnswshared->indexrelid = RelationGetRelid(buildstate->index);
|
||||||
hnswshared->isconcurrent = isconcurrent;
|
hnswshared->isconcurrent = isconcurrent;
|
||||||
|
hnswshared->scantuplesortstates = scantuplesortstates;
|
||||||
ConditionVariableInit(&hnswshared->workersdonecv);
|
ConditionVariableInit(&hnswshared->workersdonecv);
|
||||||
SpinLockInit(&hnswshared->mutex);
|
SpinLockInit(&hnswshared->mutex);
|
||||||
/* Initialize mutable state */
|
/* Initialize mutable state */
|
||||||
hnswshared->nparticipantsdone = 0;
|
hnswshared->nparticipantsdone = 0;
|
||||||
hnswshared->reltuples = 0;
|
hnswshared->reltuples = 0;
|
||||||
|
hnswshared->indtuples = 0;
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
table_parallelscan_initialize(buildstate->heap,
|
table_parallelscan_initialize(buildstate->heap,
|
||||||
ParallelTableScanFromHnswShared(hnswshared),
|
ParallelTableScanFromHnswShared(hnswshared),
|
||||||
@@ -1027,12 +789,7 @@ HnswBeginParallel(HnswBuildState * buildstate, bool isconcurrent, int request)
|
|||||||
heap_parallelscan_initialize(&hnswshared->heapdesc, buildstate->heap, snapshot);
|
heap_parallelscan_initialize(&hnswshared->heapdesc, buildstate->heap, snapshot);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
hnswarea = (char *) shm_toc_allocate(pcxt->toc, esthnswarea);
|
|
||||||
/* Report less than allocated so never fails */
|
|
||||||
InitGraph(&hnswshared->graphData, hnswarea, esthnswarea - 1024 * 1024);
|
|
||||||
|
|
||||||
shm_toc_insert(pcxt->toc, PARALLEL_KEY_HNSW_SHARED, hnswshared);
|
shm_toc_insert(pcxt->toc, PARALLEL_KEY_HNSW_SHARED, hnswshared);
|
||||||
shm_toc_insert(pcxt->toc, PARALLEL_KEY_HNSW_AREA, hnswarea);
|
|
||||||
|
|
||||||
/* Store query string for workers */
|
/* Store query string for workers */
|
||||||
if (debug_query_string)
|
if (debug_query_string)
|
||||||
@@ -1052,7 +809,6 @@ HnswBeginParallel(HnswBuildState * buildstate, bool isconcurrent, int request)
|
|||||||
hnswleader->nparticipanttuplesorts++;
|
hnswleader->nparticipanttuplesorts++;
|
||||||
hnswleader->hnswshared = hnswshared;
|
hnswleader->hnswshared = hnswshared;
|
||||||
hnswleader->snapshot = snapshot;
|
hnswleader->snapshot = snapshot;
|
||||||
hnswleader->hnswarea = hnswarea;
|
|
||||||
|
|
||||||
/* If no workers were successfully launched, back out (do serial build) */
|
/* If no workers were successfully launched, back out (do serial build) */
|
||||||
if (pcxt->nworkers_launched == 0)
|
if (pcxt->nworkers_launched == 0)
|
||||||
@@ -1075,27 +831,6 @@ HnswBeginParallel(HnswBuildState * buildstate, bool isconcurrent, int request)
|
|||||||
WaitForParallelWorkersToAttach(pcxt);
|
WaitForParallelWorkersToAttach(pcxt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Compute parallel workers
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
ComputeParallelWorkers(Relation heap, Relation index)
|
|
||||||
{
|
|
||||||
int parallel_workers;
|
|
||||||
|
|
||||||
/* Make sure it's safe to use parallel workers */
|
|
||||||
parallel_workers = plan_create_index_workers(RelationGetRelid(heap), RelationGetRelid(index));
|
|
||||||
if (parallel_workers == 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
/* Use parallel_workers storage parameter on table if set */
|
|
||||||
parallel_workers = RelationGetParallelWorkers(heap, -1);
|
|
||||||
if (parallel_workers != -1)
|
|
||||||
return Min(parallel_workers, max_parallel_maintenance_workers);
|
|
||||||
|
|
||||||
return max_parallel_maintenance_workers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Build graph
|
* Build graph
|
||||||
*/
|
*/
|
||||||
@@ -1107,35 +842,30 @@ BuildGraph(HnswBuildState * buildstate, ForkNumber forkNum)
|
|||||||
UpdateProgress(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 (hnsw_enable_parallel_build)
|
||||||
parallel_workers = ComputeParallelWorkers(buildstate->heap, buildstate->index);
|
parallel_workers = plan_create_index_workers(RelationGetRelid(buildstate->heap), RelationGetRelid(buildstate->index));
|
||||||
|
|
||||||
/* Attempt to launch parallel worker scan when required */
|
/* Attempt to launch parallel worker scan when required */
|
||||||
if (parallel_workers > 0)
|
if (parallel_workers > 0)
|
||||||
HnswBeginParallel(buildstate, buildstate->indexInfo->ii_Concurrent, parallel_workers);
|
|
||||||
|
|
||||||
/* Add tuples to graph */
|
|
||||||
if (buildstate->heap != NULL)
|
|
||||||
{
|
{
|
||||||
if (buildstate->hnswleader)
|
/* TODO Support in-memory builds */
|
||||||
buildstate->reltuples = ParallelHeapScan(buildstate);
|
FlushPages(buildstate);
|
||||||
else
|
HnswBeginParallel(buildstate, buildstate->indexInfo->ii_Concurrent, parallel_workers);
|
||||||
{
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
buildstate->reltuples = table_index_build_scan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
|
||||||
true, true, BuildCallback, (void *) buildstate, NULL);
|
|
||||||
#else
|
|
||||||
buildstate->reltuples = IndexBuildHeapScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
|
||||||
true, BuildCallback, (void *) buildstate, NULL);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
buildstate->indtuples = buildstate->graph->indtuples;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Flush pages */
|
/* Add tuples to sort */
|
||||||
if (!buildstate->graph->flushed)
|
if (buildstate->hnswleader)
|
||||||
FlushPages(buildstate);
|
buildstate->reltuples = ParallelHeapScan(buildstate);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#if PG_VERSION_NUM >= 120000
|
||||||
|
buildstate->reltuples = table_index_build_scan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||||
|
true, true, BuildCallback, (void *) buildstate, NULL);
|
||||||
|
#else
|
||||||
|
buildstate->reltuples = IndexBuildHeapScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||||
|
true, BuildCallback, (void *) buildstate, NULL);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/* End parallel build */
|
/* End parallel build */
|
||||||
if (buildstate->hnswleader)
|
if (buildstate->hnswleader)
|
||||||
@@ -1165,13 +895,13 @@ static void
|
|||||||
BuildIndex(Relation heap, Relation index, IndexInfo *indexInfo,
|
BuildIndex(Relation heap, Relation index, IndexInfo *indexInfo,
|
||||||
HnswBuildState * buildstate, ForkNumber forkNum)
|
HnswBuildState * buildstate, ForkNumber forkNum)
|
||||||
{
|
{
|
||||||
#ifdef HNSW_MEMORY
|
|
||||||
SeedRandom(42);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
InitBuildState(buildstate, heap, index, indexInfo, forkNum);
|
InitBuildState(buildstate, heap, index, indexInfo, forkNum);
|
||||||
|
|
||||||
BuildGraph(buildstate, forkNum);
|
if (buildstate->heap != NULL)
|
||||||
|
BuildGraph(buildstate, forkNum);
|
||||||
|
|
||||||
|
if (!buildstate->flushed)
|
||||||
|
FlushPages(buildstate);
|
||||||
|
|
||||||
if (RelationNeedsWAL(index))
|
if (RelationNeedsWAL(index))
|
||||||
log_newpage_range(index, forkNum, 0, RelationGetNumberOfBlocks(index), true);
|
log_newpage_range(index, forkNum, 0, RelationGetNumberOfBlocks(index), true);
|
||||||
|
|||||||
157
src/hnswinsert.c
157
src/hnswinsert.c
@@ -5,7 +5,6 @@
|
|||||||
#include "hnsw.h"
|
#include "hnsw.h"
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
#include "storage/lmgr.h"
|
#include "storage/lmgr.h"
|
||||||
#include "utils/datum.h"
|
|
||||||
#include "utils/memutils.h"
|
#include "utils/memutils.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -116,7 +115,7 @@ HnswInsertAppendPage(Relation index, Buffer *nbuf, Page *npage, GenericXLogState
|
|||||||
* Add to element and neighbor pages
|
* Add to element and neighbor pages
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
AddElementOnDisk(Relation index, HnswElement e, int m, BlockNumber insertPage, BlockNumber *updatedInsertPage, bool building)
|
WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPage, BlockNumber *updatedInsertPage, bool building)
|
||||||
{
|
{
|
||||||
Buffer buf;
|
Buffer buf;
|
||||||
Page page;
|
Page page;
|
||||||
@@ -134,10 +133,9 @@ AddElementOnDisk(Relation index, HnswElement e, int m, BlockNumber insertPage, B
|
|||||||
OffsetNumber freeOffno = InvalidOffsetNumber;
|
OffsetNumber freeOffno = InvalidOffsetNumber;
|
||||||
OffsetNumber freeNeighborOffno = InvalidOffsetNumber;
|
OffsetNumber freeNeighborOffno = InvalidOffsetNumber;
|
||||||
BlockNumber newInsertPage = InvalidBlockNumber;
|
BlockNumber newInsertPage = InvalidBlockNumber;
|
||||||
char *base = NULL;
|
|
||||||
|
|
||||||
/* Calculate sizes */
|
/* Calculate sizes */
|
||||||
etupSize = HNSW_ELEMENT_TUPLE_SIZE(VARSIZE_ANY(HnswPtrAccess(base, e->value)));
|
etupSize = HNSW_ELEMENT_TUPLE_SIZE(VARSIZE_ANY(DatumGetPointer(e->value)));
|
||||||
ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(e->level, m);
|
ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(e->level, m);
|
||||||
combinedSize = etupSize + ntupSize + sizeof(ItemIdData);
|
combinedSize = etupSize + ntupSize + sizeof(ItemIdData);
|
||||||
maxSize = HNSW_MAX_SIZE;
|
maxSize = HNSW_MAX_SIZE;
|
||||||
@@ -145,11 +143,11 @@ AddElementOnDisk(Relation index, HnswElement e, int m, BlockNumber insertPage, B
|
|||||||
|
|
||||||
/* Prepare element tuple */
|
/* Prepare element tuple */
|
||||||
etup = palloc0(etupSize);
|
etup = palloc0(etupSize);
|
||||||
HnswSetElementTuple(base, etup, e);
|
HnswSetElementTuple(etup, e);
|
||||||
|
|
||||||
/* Prepare neighbor tuple */
|
/* Prepare neighbor tuple */
|
||||||
ntup = palloc0(ntupSize);
|
ntup = palloc0(ntupSize);
|
||||||
HnswSetNeighborTuple(base, ntup, e, m);
|
HnswSetNeighborTuple(ntup, e, m);
|
||||||
|
|
||||||
/* Find a page (or two if needed) to insert the tuples */
|
/* Find a page (or two if needed) to insert the tuples */
|
||||||
for (;;)
|
for (;;)
|
||||||
@@ -221,9 +219,7 @@ AddElementOnDisk(Relation index, HnswElement e, int m, BlockNumber insertPage, B
|
|||||||
HnswInsertAppendPage(index, &newbuf, &newpage, state, page, building);
|
HnswInsertAppendPage(index, &newbuf, &newpage, state, page, building);
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
if (building)
|
if (!building)
|
||||||
MarkBufferDirty(buf);
|
|
||||||
else
|
|
||||||
GenericXLogFinish(state);
|
GenericXLogFinish(state);
|
||||||
|
|
||||||
/* Unlock previous buffer */
|
/* Unlock previous buffer */
|
||||||
@@ -298,13 +294,7 @@ AddElementOnDisk(Relation index, HnswElement e, int m, BlockNumber insertPage, B
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
if (building)
|
if (!building)
|
||||||
{
|
|
||||||
MarkBufferDirty(buf);
|
|
||||||
if (nbuf != buf)
|
|
||||||
MarkBufferDirty(nbuf);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
GenericXLogFinish(state);
|
GenericXLogFinish(state);
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
if (nbuf != buf)
|
if (nbuf != buf)
|
||||||
@@ -339,14 +329,12 @@ ConnectionExists(HnswElement e, HnswNeighborTuple ntup, int startIdx, int lm)
|
|||||||
* Update neighbors
|
* Update neighbors
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
HnswUpdateNeighborsOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting, bool building)
|
HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting, bool building)
|
||||||
{
|
{
|
||||||
char *base = NULL;
|
|
||||||
|
|
||||||
for (int lc = e->level; lc >= 0; lc--)
|
for (int lc = e->level; lc >= 0; lc--)
|
||||||
{
|
{
|
||||||
int lm = HnswGetLayerM(m, lc);
|
int lm = HnswGetLayerM(m, lc);
|
||||||
HnswNeighborArray *neighbors = HnswGetNeighbors(base, e, lc);
|
HnswNeighborArray *neighbors = &e->neighbors[lc];
|
||||||
|
|
||||||
for (int i = 0; i < neighbors->length; i++)
|
for (int i = 0; i < neighbors->length; i++)
|
||||||
{
|
{
|
||||||
@@ -359,12 +347,11 @@ HnswUpdateNeighborsOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, Hns
|
|||||||
Size ntupSize;
|
Size ntupSize;
|
||||||
int idx = -1;
|
int idx = -1;
|
||||||
int startIdx;
|
int startIdx;
|
||||||
HnswElement neighborElement = HnswPtrAccess(base, hc->element);
|
OffsetNumber offno = hc->element->neighborOffno;
|
||||||
OffsetNumber offno = neighborElement->neighborOffno;
|
|
||||||
|
|
||||||
/* Get latest neighbors since they may have changed */
|
/* Get latest neighbors since they may have changed */
|
||||||
/* Do not lock yet since selecting neighbors can take time */
|
/* Do not lock yet since selecting neighbors can take time */
|
||||||
HnswLoadNeighbors(neighborElement, index, m);
|
HnswLoadNeighbors(hc->element, index, m);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Could improve performance for vacuuming by checking neighbors
|
* Could improve performance for vacuuming by checking neighbors
|
||||||
@@ -374,14 +361,14 @@ HnswUpdateNeighborsOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, Hns
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* Select neighbors */
|
/* Select neighbors */
|
||||||
HnswUpdateConnection(NULL, e, hc, lm, lc, &idx, index, procinfo, collation);
|
HnswUpdateConnection(e, hc, lm, lc, &idx, index, procinfo, collation);
|
||||||
|
|
||||||
/* New element was not selected as a neighbor */
|
/* New element was not selected as a neighbor */
|
||||||
if (idx == -1)
|
if (idx == -1)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Register page */
|
/* Register page */
|
||||||
buf = ReadBuffer(index, neighborElement->neighborPage);
|
buf = ReadBuffer(index, hc->element->neighborPage);
|
||||||
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
||||||
if (building)
|
if (building)
|
||||||
{
|
{
|
||||||
@@ -400,7 +387,7 @@ HnswUpdateNeighborsOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, Hns
|
|||||||
ntupSize = ItemIdGetLength(itemid);
|
ntupSize = ItemIdGetLength(itemid);
|
||||||
|
|
||||||
/* Calculate index for update */
|
/* Calculate index for update */
|
||||||
startIdx = (neighborElement->level - lc) * m;
|
startIdx = (hc->element->level - lc) * m;
|
||||||
|
|
||||||
/* Check for existing connection */
|
/* Check for existing connection */
|
||||||
if (checkExisting && ConnectionExists(e, ntup, startIdx, lm))
|
if (checkExisting && ConnectionExists(e, ntup, startIdx, lm))
|
||||||
@@ -434,9 +421,7 @@ HnswUpdateNeighborsOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, Hns
|
|||||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
if (building)
|
if (!building)
|
||||||
MarkBufferDirty(buf);
|
|
||||||
else
|
|
||||||
GenericXLogFinish(state);
|
GenericXLogFinish(state);
|
||||||
}
|
}
|
||||||
else if (!building)
|
else if (!building)
|
||||||
@@ -451,7 +436,7 @@ HnswUpdateNeighborsOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, Hns
|
|||||||
* Add a heap TID to an existing element
|
* Add a heap TID to an existing element
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool
|
||||||
AddDuplicateOnDisk(Relation index, HnswElement element, HnswElement dup, bool building)
|
HnswAddDuplicate(Relation index, HnswElement element, HnswElement dup, bool building)
|
||||||
{
|
{
|
||||||
Buffer buf;
|
Buffer buf;
|
||||||
Page page;
|
Page page;
|
||||||
@@ -495,16 +480,14 @@ AddDuplicateOnDisk(Relation index, HnswElement element, HnswElement dup, bool bu
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Add heap TID */
|
/* Add heap TID */
|
||||||
etup->heaptids[i] = element->heaptids[0];
|
etup->heaptids[i] = *((ItemPointer) linitial(element->heaptids));
|
||||||
|
|
||||||
/* Overwrite tuple */
|
/* Overwrite tuple */
|
||||||
if (!PageIndexTupleOverwrite(page, dup->offno, (Item) etup, etupSize))
|
if (!PageIndexTupleOverwrite(page, dup->offno, (Item) etup, etupSize))
|
||||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
if (building)
|
if (!building)
|
||||||
MarkBufferDirty(buf);
|
|
||||||
else
|
|
||||||
GenericXLogFinish(state);
|
GenericXLogFinish(state);
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
|
|
||||||
@@ -512,55 +495,31 @@ AddDuplicateOnDisk(Relation index, HnswElement element, HnswElement dup, bool bu
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Find duplicate element
|
* Write changes to disk
|
||||||
*/
|
|
||||||
static bool
|
|
||||||
FindDuplicateOnDisk(Relation index, HnswElement element, bool building)
|
|
||||||
{
|
|
||||||
char *base = NULL;
|
|
||||||
HnswNeighborArray *neighbors = HnswGetNeighbors(base, element, 0);
|
|
||||||
Datum value = HnswGetValue(base, element);
|
|
||||||
|
|
||||||
for (int i = 0; i < neighbors->length; i++)
|
|
||||||
{
|
|
||||||
HnswCandidate *neighbor = &neighbors->items[i];
|
|
||||||
HnswElement neighborElement = HnswPtrAccess(base, neighbor->element);
|
|
||||||
Datum neighborValue = HnswGetValue(base, neighborElement);
|
|
||||||
|
|
||||||
/* Exit early since ordered by distance */
|
|
||||||
if (!datumIsEqual(value, neighborValue, false, -1))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (AddDuplicateOnDisk(index, element, neighborElement, building))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Update graph on disk
|
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
UpdateGraphOnDisk(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement element, int m, int efConstruction, HnswElement entryPoint, bool building)
|
WriteElement(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement element, int m, int efConstruction, HnswElement dup, HnswElement entryPoint, bool building)
|
||||||
{
|
{
|
||||||
BlockNumber newInsertPage = InvalidBlockNumber;
|
BlockNumber newInsertPage = InvalidBlockNumber;
|
||||||
|
|
||||||
/* Look for duplicate */
|
/* Try to add to existing page */
|
||||||
if (FindDuplicateOnDisk(index, element, building))
|
if (dup != NULL)
|
||||||
return;
|
{
|
||||||
|
if (HnswAddDuplicate(index, element, dup, building))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* Add element */
|
/* Write element and neighbor tuples */
|
||||||
AddElementOnDisk(index, element, m, GetInsertPage(index), &newInsertPage, building);
|
WriteNewElementPages(index, element, m, GetInsertPage(index), &newInsertPage, building);
|
||||||
|
|
||||||
/* Update insert page if needed */
|
/* Update insert page if needed */
|
||||||
if (BlockNumberIsValid(newInsertPage))
|
if (BlockNumberIsValid(newInsertPage))
|
||||||
HnswUpdateMetaPage(index, 0, NULL, newInsertPage, MAIN_FORKNUM, building);
|
HnswUpdateMetaPage(index, 0, NULL, newInsertPage, MAIN_FORKNUM, building);
|
||||||
|
|
||||||
/* Update neighbors */
|
/* Update neighbors */
|
||||||
HnswUpdateNeighborsOnDisk(index, procinfo, collation, element, m, false, building);
|
HnswUpdateNeighborPages(index, procinfo, collation, element, m, false, building);
|
||||||
|
|
||||||
/* Update entry point if needed */
|
/* Update metapage if needed */
|
||||||
if (entryPoint == NULL || element->level > entryPoint->level)
|
if (entryPoint == NULL || element->level > entryPoint->level)
|
||||||
HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_GREATER, element, InvalidBlockNumber, MAIN_FORKNUM, building);
|
HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_GREATER, element, InvalidBlockNumber, MAIN_FORKNUM, building);
|
||||||
}
|
}
|
||||||
@@ -569,16 +528,29 @@ 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, Relation heapRel, bool building)
|
HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel, bool building)
|
||||||
{
|
{
|
||||||
|
Datum value;
|
||||||
|
FmgrInfo *normprocinfo;
|
||||||
HnswElement entryPoint;
|
HnswElement entryPoint;
|
||||||
HnswElement element;
|
HnswElement element;
|
||||||
int m;
|
int m;
|
||||||
int efConstruction = HnswGetEfConstruction(index);
|
int efConstruction = HnswGetEfConstruction(index);
|
||||||
FmgrInfo *procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC);
|
FmgrInfo *procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC);
|
||||||
Oid collation = index->rd_indcollation[0];
|
Oid collation = index->rd_indcollation[0];
|
||||||
|
HnswElement dup;
|
||||||
LOCKMODE lockmode = ShareLock;
|
LOCKMODE lockmode = ShareLock;
|
||||||
char *base = NULL;
|
|
||||||
|
/* Detoast once for all calls */
|
||||||
|
value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
||||||
|
|
||||||
|
/* Normalize if needed */
|
||||||
|
normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC);
|
||||||
|
if (normprocinfo != NULL)
|
||||||
|
{
|
||||||
|
if (!HnswNormValue(normprocinfo, collation, &value, NULL))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get a shared lock. This allows vacuum to ensure no in-flight inserts
|
* Get a shared lock. This allows vacuum to ensure no in-flight inserts
|
||||||
@@ -591,8 +563,8 @@ HnswInsertTupleOnDisk(Relation index, Datum value, Datum *values, bool *isnull,
|
|||||||
HnswGetMetaPageInfo(index, &m, &entryPoint);
|
HnswGetMetaPageInfo(index, &m, &entryPoint);
|
||||||
|
|
||||||
/* Create an element */
|
/* Create an element */
|
||||||
element = HnswInitElement(base, heap_tid, m, HnswGetMl(m), HnswGetMaxLevel(m), NULL);
|
element = HnswInitElement(heap_tid, m, HnswGetMl(m), HnswGetMaxLevel(m));
|
||||||
HnswPtrStore(base, element->value, DatumGetPointer(value));
|
element->value = value;
|
||||||
|
|
||||||
/* Prevent concurrent inserts when likely updating entry point */
|
/* Prevent concurrent inserts when likely updating entry point */
|
||||||
if (entryPoint == NULL || element->level > entryPoint->level)
|
if (entryPoint == NULL || element->level > entryPoint->level)
|
||||||
@@ -608,11 +580,14 @@ HnswInsertTupleOnDisk(Relation index, Datum value, Datum *values, bool *isnull,
|
|||||||
entryPoint = HnswGetEntryPoint(index);
|
entryPoint = HnswGetEntryPoint(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Find neighbors for element */
|
/* Insert element in graph */
|
||||||
HnswFindElementNeighbors(base, element, entryPoint, index, procinfo, collation, m, efConstruction, false);
|
HnswInsertElement(element, entryPoint, index, procinfo, collation, m, efConstruction, false);
|
||||||
|
|
||||||
/* Update graph on disk */
|
/* Look for duplicate */
|
||||||
UpdateGraphOnDisk(index, procinfo, collation, element, m, efConstruction, entryPoint, building);
|
dup = HnswFindDuplicate(element);
|
||||||
|
|
||||||
|
/* Write to disk */
|
||||||
|
WriteElement(index, procinfo, collation, element, m, efConstruction, dup, entryPoint, building);
|
||||||
|
|
||||||
/* Release lock */
|
/* Release lock */
|
||||||
UnlockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
UnlockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
||||||
@@ -620,30 +595,6 @@ HnswInsertTupleOnDisk(Relation index, Datum value, Datum *values, bool *isnull,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Insert a tuple into the index
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel)
|
|
||||||
{
|
|
||||||
Datum value;
|
|
||||||
FmgrInfo *normprocinfo;
|
|
||||||
Oid collation = index->rd_indcollation[0];
|
|
||||||
|
|
||||||
/* Detoast once for all calls */
|
|
||||||
value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
|
||||||
|
|
||||||
/* Normalize if needed */
|
|
||||||
normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC);
|
|
||||||
if (normprocinfo != NULL)
|
|
||||||
{
|
|
||||||
if (!HnswNormValue(normprocinfo, collation, &value, NULL))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
HnswInsertTupleOnDisk(index, value, values, isnull, heap_tid, heapRel, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Insert a tuple into the index
|
* Insert a tuple into the index
|
||||||
*/
|
*/
|
||||||
@@ -670,7 +621,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, heap);
|
HnswInsertTuple(index, values, isnull, heap_tid, heap, false);
|
||||||
|
|
||||||
/* Delete memory context */
|
/* Delete memory context */
|
||||||
MemoryContextSwitchTo(oldCtx);
|
MemoryContextSwitchTo(oldCtx);
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ GetScanItems(IndexScanDesc scan, Datum q)
|
|||||||
List *w;
|
List *w;
|
||||||
int m;
|
int m;
|
||||||
HnswElement entryPoint;
|
HnswElement entryPoint;
|
||||||
char *base = NULL;
|
|
||||||
|
|
||||||
/* Get m and entry point */
|
/* Get m and entry point */
|
||||||
HnswGetMetaPageInfo(index, &m, &entryPoint);
|
HnswGetMetaPageInfo(index, &m, &entryPoint);
|
||||||
@@ -29,15 +28,15 @@ GetScanItems(IndexScanDesc scan, Datum q)
|
|||||||
if (entryPoint == NULL)
|
if (entryPoint == NULL)
|
||||||
return NIL;
|
return NIL;
|
||||||
|
|
||||||
ep = list_make1(HnswEntryCandidate(base, entryPoint, q, index, procinfo, collation, false));
|
ep = list_make1(HnswEntryCandidate(entryPoint, q, index, procinfo, collation, false));
|
||||||
|
|
||||||
for (int lc = entryPoint->level; lc >= 1; lc--)
|
for (int lc = entryPoint->level; lc >= 1; lc--)
|
||||||
{
|
{
|
||||||
w = HnswSearchLayer(base, q, ep, 1, lc, index, procinfo, collation, m, false, NULL);
|
w = HnswSearchLayer(q, ep, 1, lc, index, procinfo, collation, m, false, NULL);
|
||||||
ep = w;
|
ep = w;
|
||||||
}
|
}
|
||||||
|
|
||||||
return HnswSearchLayer(base, q, ep, hnsw_ef_search, 0, index, procinfo, collation, m, false, NULL);
|
return HnswSearchLayer(q, ep, hnsw_ef_search, 0, index, procinfo, collation, m, false, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -185,19 +184,19 @@ hnswgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
|
|
||||||
while (list_length(so->w) > 0)
|
while (list_length(so->w) > 0)
|
||||||
{
|
{
|
||||||
char *base = NULL;
|
|
||||||
HnswCandidate *hc = llast(so->w);
|
HnswCandidate *hc = llast(so->w);
|
||||||
HnswElement element = HnswPtrAccess(base, hc->element);
|
|
||||||
ItemPointer heaptid;
|
ItemPointer heaptid;
|
||||||
|
|
||||||
/* Move to next element if no valid heap TIDs */
|
/* Move to next element if no valid heap TIDs */
|
||||||
if (element->heaptidsLength == 0)
|
if (list_length(hc->element->heaptids) == 0)
|
||||||
{
|
{
|
||||||
so->w = list_delete_last(so->w);
|
so->w = list_delete_last(so->w);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
heaptid = &element->heaptids[--element->heaptidsLength];
|
heaptid = llast(hc->element->heaptids);
|
||||||
|
|
||||||
|
hc->element->heaptids = list_delete_last(hc->element->heaptids);
|
||||||
|
|
||||||
MemoryContextSwitchTo(oldCtx);
|
MemoryContextSwitchTo(oldCtx);
|
||||||
|
|
||||||
|
|||||||
448
src/hnswutils.c
448
src/hnswutils.c
@@ -84,40 +84,9 @@ hash_pointer(uintptr_t ptr)
|
|||||||
#define SH_DEFINE
|
#define SH_DEFINE
|
||||||
#include "lib/simplehash.h"
|
#include "lib/simplehash.h"
|
||||||
|
|
||||||
/* Needed to include simplehash.h again */
|
|
||||||
#if PG_VERSION_NUM < 120000
|
|
||||||
#undef SH_EQUAL
|
|
||||||
#undef sh_log2
|
|
||||||
#undef sh_pow2
|
|
||||||
#define sh_log2 offsethash_sh_log2
|
|
||||||
#define sh_pow2 offsethash_sh_pow2
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Offset hash table */
|
|
||||||
static uint32
|
|
||||||
hash_offset(Size offset)
|
|
||||||
{
|
|
||||||
#if SIZEOF_SIZE_T == 8
|
|
||||||
return murmurhash64((uint64) offset);
|
|
||||||
#else
|
|
||||||
return murmurhash32((uint32) offset);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#define SH_PREFIX offsethash
|
|
||||||
#define SH_ELEMENT_TYPE OffsetHashEntry
|
|
||||||
#define SH_KEY_TYPE Size
|
|
||||||
#define SH_KEY offset
|
|
||||||
#define SH_HASH_KEY(tb, key) hash_offset(key)
|
|
||||||
#define SH_EQUAL(tb, a, b) (a == b)
|
|
||||||
#define SH_SCOPE extern
|
|
||||||
#define SH_DEFINE
|
|
||||||
#include "lib/simplehash.h"
|
|
||||||
|
|
||||||
typedef union
|
typedef union
|
||||||
{
|
{
|
||||||
pointerhash_hash *pointers;
|
pointerhash_hash *pointers;
|
||||||
offsethash_hash *offsets;
|
|
||||||
tidhash_hash *tids;
|
tidhash_hash *tids;
|
||||||
} visited_hash;
|
} visited_hash;
|
||||||
|
|
||||||
@@ -219,46 +188,42 @@ HnswInitPage(Buffer buf, Page page)
|
|||||||
* Allocate neighbors
|
* Allocate neighbors
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
HnswInitNeighbors(char *base, HnswElement element, int m, HnswAllocator * allocator)
|
HnswInitNeighbors(HnswElement element, int m)
|
||||||
{
|
{
|
||||||
int level = element->level;
|
int level = element->level;
|
||||||
|
|
||||||
HnswNeighborArrayPtr *neighborList = (HnswNeighborArrayPtr *) HnswAlloc(allocator, sizeof(HnswNeighborArrayPtr) * (level + 1));
|
element->neighbors = palloc(sizeof(HnswNeighborArray) * (level + 1));
|
||||||
|
|
||||||
HnswPtrStore(base, element->neighbors, neighborList);
|
|
||||||
|
|
||||||
for (int lc = 0; lc <= level; lc++)
|
for (int lc = 0; lc <= level; lc++)
|
||||||
{
|
{
|
||||||
HnswNeighborArray *a;
|
HnswNeighborArray *a;
|
||||||
int lm = HnswGetLayerM(m, lc);
|
int lm = HnswGetLayerM(m, lc);
|
||||||
|
|
||||||
HnswPtrStore(base, neighborList[lc], (HnswNeighborArray *) HnswAlloc(allocator, offsetof(HnswNeighborArray, items) + sizeof(HnswCandidate) * lm));
|
a = &element->neighbors[lc];
|
||||||
|
|
||||||
a = HnswGetNeighbors(base, element, lc);
|
|
||||||
a->length = 0;
|
a->length = 0;
|
||||||
|
a->items = palloc(sizeof(HnswCandidate) * lm);
|
||||||
a->closerSet = false;
|
a->closerSet = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Allocate memory from the allocator
|
* Free neighbors
|
||||||
*/
|
*/
|
||||||
void *
|
static void
|
||||||
HnswAlloc(HnswAllocator * allocator, Size size)
|
HnswFreeNeighbors(HnswElement element)
|
||||||
{
|
{
|
||||||
if (allocator)
|
for (int lc = 0; lc <= element->level; lc++)
|
||||||
return (*(allocator)->alloc) (size, (allocator)->state);
|
pfree(element->neighbors[lc].items);
|
||||||
|
pfree(element->neighbors);
|
||||||
return palloc(size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Allocate an element
|
* Allocate an element
|
||||||
*/
|
*/
|
||||||
HnswElement
|
HnswElement
|
||||||
HnswInitElement(char *base, ItemPointer heaptid, int m, double ml, int maxLevel, HnswAllocator * allocator)
|
HnswInitElement(ItemPointer heaptid, int m, double ml, int maxLevel)
|
||||||
{
|
{
|
||||||
HnswElement element = HnswAlloc(allocator, sizeof(HnswElementData));
|
HnswElement element = palloc(sizeof(HnswElementData));
|
||||||
|
|
||||||
int level = (int) (-log(RandomDouble()) * ml);
|
int level = (int) (-log(RandomDouble()) * ml);
|
||||||
|
|
||||||
@@ -266,26 +231,42 @@ HnswInitElement(char *base, ItemPointer heaptid, int m, double ml, int maxLevel,
|
|||||||
if (level > maxLevel)
|
if (level > maxLevel)
|
||||||
level = maxLevel;
|
level = maxLevel;
|
||||||
|
|
||||||
element->heaptidsLength = 0;
|
element->heaptids = NIL;
|
||||||
HnswAddHeapTid(element, heaptid);
|
HnswAddHeapTid(element, heaptid);
|
||||||
|
|
||||||
element->level = level;
|
element->level = level;
|
||||||
element->deleted = 0;
|
element->deleted = 0;
|
||||||
|
|
||||||
HnswInitNeighbors(base, element, m, allocator);
|
HnswInitNeighbors(element, m);
|
||||||
|
|
||||||
HnswPtrStore(base, element->value, (Pointer) NULL);
|
element->value = PointerGetDatum(NULL);
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Free an element
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
HnswFreeElement(HnswElement element)
|
||||||
|
{
|
||||||
|
HnswFreeNeighbors(element);
|
||||||
|
list_free_deep(element->heaptids);
|
||||||
|
if (DatumGetPointer(element->value))
|
||||||
|
pfree(DatumGetPointer(element->value));
|
||||||
|
pfree(element);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add a heap TID to an element
|
* Add a heap TID to an element
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
HnswAddHeapTid(HnswElement element, ItemPointer heaptid)
|
HnswAddHeapTid(HnswElement element, ItemPointer heaptid)
|
||||||
{
|
{
|
||||||
element->heaptids[element->heaptidsLength++] = *heaptid;
|
ItemPointer copy = palloc(sizeof(ItemPointerData));
|
||||||
|
|
||||||
|
ItemPointerCopy(heaptid, copy);
|
||||||
|
element->heaptids = lappend(element->heaptids, copy);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -295,12 +276,11 @@ HnswElement
|
|||||||
HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno)
|
HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno)
|
||||||
{
|
{
|
||||||
HnswElement element = palloc(sizeof(HnswElementData));
|
HnswElement element = palloc(sizeof(HnswElementData));
|
||||||
char *base = NULL;
|
|
||||||
|
|
||||||
element->blkno = blkno;
|
element->blkno = blkno;
|
||||||
element->offno = offno;
|
element->offno = offno;
|
||||||
HnswPtrStore(base, element->neighbors, (HnswNeighborArrayPtr *) NULL);
|
element->neighbors = NULL;
|
||||||
HnswPtrStore(base, element->value, (Pointer) NULL);
|
element->value = PointerGetDatum(NULL);
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,9 +379,7 @@ HnswUpdateMetaPage(Relation index, int updateEntry, HnswElement entryPoint, Bloc
|
|||||||
|
|
||||||
HnswUpdateMetaPageInfo(page, updateEntry, entryPoint, insertPage);
|
HnswUpdateMetaPageInfo(page, updateEntry, entryPoint, insertPage);
|
||||||
|
|
||||||
if (building)
|
if (!building)
|
||||||
MarkBufferDirty(buf);
|
|
||||||
else
|
|
||||||
GenericXLogFinish(state);
|
GenericXLogFinish(state);
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
}
|
}
|
||||||
@@ -410,28 +388,26 @@ HnswUpdateMetaPage(Relation index, int updateEntry, HnswElement entryPoint, Bloc
|
|||||||
* Set element tuple, except for neighbor info
|
* Set element tuple, except for neighbor info
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
HnswSetElementTuple(char *base, HnswElementTuple etup, HnswElement element)
|
HnswSetElementTuple(HnswElementTuple etup, HnswElement element)
|
||||||
{
|
{
|
||||||
Pointer valuePtr = HnswPtrAccess(base, element->value);
|
|
||||||
|
|
||||||
etup->type = HNSW_ELEMENT_TUPLE_TYPE;
|
etup->type = HNSW_ELEMENT_TUPLE_TYPE;
|
||||||
etup->level = element->level;
|
etup->level = element->level;
|
||||||
etup->deleted = 0;
|
etup->deleted = 0;
|
||||||
for (int i = 0; i < HNSW_HEAPTIDS; i++)
|
for (int i = 0; i < HNSW_HEAPTIDS; i++)
|
||||||
{
|
{
|
||||||
if (i < element->heaptidsLength)
|
if (i < list_length(element->heaptids))
|
||||||
etup->heaptids[i] = element->heaptids[i];
|
etup->heaptids[i] = *((ItemPointer) list_nth(element->heaptids, i));
|
||||||
else
|
else
|
||||||
ItemPointerSetInvalid(&etup->heaptids[i]);
|
ItemPointerSetInvalid(&etup->heaptids[i]);
|
||||||
}
|
}
|
||||||
memcpy(&etup->data, valuePtr, VARSIZE_ANY(valuePtr));
|
memcpy(&etup->data, DatumGetPointer(element->value), VARSIZE_ANY(DatumGetPointer(element->value)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set neighbor tuple
|
* Set neighbor tuple
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
HnswSetNeighborTuple(char *base, HnswNeighborTuple ntup, HnswElement e, int m)
|
HnswSetNeighborTuple(HnswNeighborTuple ntup, HnswElement e, int m)
|
||||||
{
|
{
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
|
|
||||||
@@ -439,7 +415,7 @@ HnswSetNeighborTuple(char *base, HnswNeighborTuple ntup, HnswElement e, int m)
|
|||||||
|
|
||||||
for (int lc = e->level; lc >= 0; lc--)
|
for (int lc = e->level; lc >= 0; lc--)
|
||||||
{
|
{
|
||||||
HnswNeighborArray *neighbors = HnswGetNeighbors(base, e, lc);
|
HnswNeighborArray *neighbors = &e->neighbors[lc];
|
||||||
int lm = HnswGetLayerM(m, lc);
|
int lm = HnswGetLayerM(m, lc);
|
||||||
|
|
||||||
for (int i = 0; i < lm; i++)
|
for (int i = 0; i < lm; i++)
|
||||||
@@ -449,9 +425,8 @@ HnswSetNeighborTuple(char *base, HnswNeighborTuple ntup, HnswElement e, int m)
|
|||||||
if (i < neighbors->length)
|
if (i < neighbors->length)
|
||||||
{
|
{
|
||||||
HnswCandidate *hc = &neighbors->items[i];
|
HnswCandidate *hc = &neighbors->items[i];
|
||||||
HnswElement hce = HnswPtrAccess(base, hc->element);
|
|
||||||
|
|
||||||
ItemPointerSet(indextid, hce->blkno, hce->offno);
|
ItemPointerSet(indextid, hc->element->blkno, hc->element->offno);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
ItemPointerSetInvalid(indextid);
|
ItemPointerSetInvalid(indextid);
|
||||||
@@ -467,14 +442,12 @@ HnswSetNeighborTuple(char *base, HnswNeighborTuple ntup, HnswElement e, int m)
|
|||||||
static void
|
static void
|
||||||
LoadNeighborsFromPage(HnswElement element, Relation index, Page page, int m)
|
LoadNeighborsFromPage(HnswElement element, Relation index, Page page, int m)
|
||||||
{
|
{
|
||||||
char *base = NULL;
|
|
||||||
|
|
||||||
HnswNeighborTuple ntup = (HnswNeighborTuple) PageGetItem(page, PageGetItemId(page, element->neighborOffno));
|
HnswNeighborTuple ntup = (HnswNeighborTuple) PageGetItem(page, PageGetItemId(page, element->neighborOffno));
|
||||||
int neighborCount = (element->level + 2) * m;
|
int neighborCount = (element->level + 2) * m;
|
||||||
|
|
||||||
Assert(HnswIsNeighborTuple(ntup));
|
Assert(HnswIsNeighborTuple(ntup));
|
||||||
|
|
||||||
HnswInitNeighbors(base, element, m, NULL);
|
HnswInitNeighbors(element, m);
|
||||||
|
|
||||||
/* Ensure expected neighbors */
|
/* Ensure expected neighbors */
|
||||||
if (ntup->count != neighborCount)
|
if (ntup->count != neighborCount)
|
||||||
@@ -500,9 +473,9 @@ LoadNeighborsFromPage(HnswElement element, Relation index, Page page, int m)
|
|||||||
if (level < 0)
|
if (level < 0)
|
||||||
level = 0;
|
level = 0;
|
||||||
|
|
||||||
neighbors = HnswGetNeighbors(base, element, level);
|
neighbors = &element->neighbors[level];
|
||||||
hc = &neighbors->items[neighbors->length++];
|
hc = &neighbors->items[neighbors->length++];
|
||||||
HnswPtrStore(base, hc->element, e);
|
hc->element = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -534,7 +507,7 @@ HnswLoadElementFromTuple(HnswElement element, HnswElementTuple etup, bool loadHe
|
|||||||
element->deleted = etup->deleted;
|
element->deleted = etup->deleted;
|
||||||
element->neighborPage = ItemPointerGetBlockNumber(&etup->neighbortid);
|
element->neighborPage = ItemPointerGetBlockNumber(&etup->neighbortid);
|
||||||
element->neighborOffno = ItemPointerGetOffsetNumber(&etup->neighbortid);
|
element->neighborOffno = ItemPointerGetOffsetNumber(&etup->neighbortid);
|
||||||
element->heaptidsLength = 0;
|
element->heaptids = NIL;
|
||||||
|
|
||||||
if (loadHeaptids)
|
if (loadHeaptids)
|
||||||
{
|
{
|
||||||
@@ -549,12 +522,7 @@ HnswLoadElementFromTuple(HnswElement element, HnswElementTuple etup, bool loadHe
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (loadVec)
|
if (loadVec)
|
||||||
{
|
element->value = datumCopy(PointerGetDatum(&etup->data), false, -1);
|
||||||
char *base = NULL;
|
|
||||||
Datum value = datumCopy(PointerGetDatum(&etup->data), false, -1);
|
|
||||||
|
|
||||||
HnswPtrStore(base, element->value, DatumGetPointer(value));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -590,27 +558,24 @@ HnswLoadElement(HnswElement element, float *distance, Datum *q, Relation index,
|
|||||||
* Get the distance for a candidate
|
* Get the distance for a candidate
|
||||||
*/
|
*/
|
||||||
static float
|
static float
|
||||||
GetCandidateDistance(char *base, HnswCandidate * hc, Datum q, FmgrInfo *procinfo, Oid collation)
|
GetCandidateDistance(HnswCandidate * hc, Datum q, FmgrInfo *procinfo, Oid collation)
|
||||||
{
|
{
|
||||||
HnswElement hce = HnswPtrAccess(base, hc->element);
|
return DatumGetFloat8(FunctionCall2Coll(procinfo, collation, q, hc->element->value));
|
||||||
Datum value = HnswGetValue(base, hce);
|
|
||||||
|
|
||||||
return DatumGetFloat8(FunctionCall2Coll(procinfo, collation, q, value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create a candidate for the entry point
|
* Create a candidate for the entry point
|
||||||
*/
|
*/
|
||||||
HnswCandidate *
|
HnswCandidate *
|
||||||
HnswEntryCandidate(char *base, HnswElement entryPoint, Datum q, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec)
|
HnswEntryCandidate(HnswElement entryPoint, Datum q, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec)
|
||||||
{
|
{
|
||||||
HnswCandidate *hc = palloc(sizeof(HnswCandidate));
|
HnswCandidate *hc = palloc(sizeof(HnswCandidate));
|
||||||
|
|
||||||
HnswPtrStore(base, hc->element, entryPoint);
|
hc->element = entryPoint;
|
||||||
if (index == NULL)
|
if (index == NULL)
|
||||||
hc->distance = GetCandidateDistance(base, hc, q, procinfo, collation);
|
hc->distance = GetCandidateDistance(hc, q, procinfo, collation);
|
||||||
else
|
else
|
||||||
HnswLoadElement(entryPoint, &hc->distance, &q, index, procinfo, collation, loadVec);
|
HnswLoadElement(hc->element, &hc->distance, &q, index, procinfo, collation, loadVec);
|
||||||
return hc;
|
return hc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -656,97 +621,48 @@ CreatePairingHeapNode(HnswCandidate * c)
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Init visited
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
InitVisited(char *base, visited_hash * v, Relation index, int ef, int m)
|
|
||||||
{
|
|
||||||
if (index != NULL)
|
|
||||||
v->tids = tidhash_create(CurrentMemoryContext, ef * m * 2, NULL);
|
|
||||||
else if (base != NULL)
|
|
||||||
v->offsets = offsethash_create(CurrentMemoryContext, ef * m * 2, NULL);
|
|
||||||
else
|
|
||||||
v->pointers = pointerhash_create(CurrentMemoryContext, ef * m * 2, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add to visited
|
* Add to visited
|
||||||
*/
|
*/
|
||||||
static inline void
|
static inline void
|
||||||
AddToVisited(char *base, visited_hash * v, HnswCandidate * hc, Relation index, bool *found)
|
AddToVisited(visited_hash v, HnswCandidate * hc, Relation index, bool *found)
|
||||||
{
|
{
|
||||||
if (index != NULL)
|
if (index == NULL)
|
||||||
{
|
|
||||||
HnswElement element = HnswPtrAccess(base, hc->element);
|
|
||||||
ItemPointerData indextid;
|
|
||||||
|
|
||||||
ItemPointerSet(&indextid, element->blkno, element->offno);
|
|
||||||
tidhash_insert(v->tids, indextid, found);
|
|
||||||
}
|
|
||||||
else if (base != NULL)
|
|
||||||
{
|
{
|
||||||
#if PG_VERSION_NUM >= 130000
|
#if PG_VERSION_NUM >= 130000
|
||||||
HnswElement element = HnswPtrAccess(base, hc->element);
|
pointerhash_insert_hash(v.pointers, (uintptr_t) hc->element, hc->element->hash, found);
|
||||||
|
|
||||||
offsethash_insert_hash(v->offsets, HnswPtrOffset(hc->element), element->hash, found);
|
|
||||||
#else
|
#else
|
||||||
offsethash_insert(v->offsets, HnswPtrOffset(hc->element), found);
|
pointerhash_insert(v.pointers, (uintptr_t) hc->element, found);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
#if PG_VERSION_NUM >= 130000
|
ItemPointerData indextid;
|
||||||
HnswElement element = HnswPtrAccess(base, hc->element);
|
|
||||||
|
|
||||||
pointerhash_insert_hash(v->pointers, (uintptr_t) HnswPtrPointer(hc->element), element->hash, found);
|
ItemPointerSet(&indextid, hc->element->blkno, hc->element->offno);
|
||||||
#else
|
tidhash_insert(v.tids, indextid, found);
|
||||||
pointerhash_insert(v->pointers, (uintptr_t) HnswPtrPointer(hc->element), found);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Count element towards ef
|
|
||||||
*/
|
|
||||||
static inline bool
|
|
||||||
CountElement(char *base, HnswElement skipElement, HnswCandidate * hc)
|
|
||||||
{
|
|
||||||
HnswElement e;
|
|
||||||
|
|
||||||
if (skipElement == NULL)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
/* Ensure does not access heaptidsLength during in-memory build */
|
|
||||||
pg_memory_barrier();
|
|
||||||
|
|
||||||
e = HnswPtrAccess(base, hc->element);
|
|
||||||
return e->heaptidsLength != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Algorithm 2 from paper
|
* Algorithm 2 from paper
|
||||||
*/
|
*/
|
||||||
List *
|
List *
|
||||||
HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *procinfo, Oid collation, int m, bool inserting, HnswElement skipElement)
|
HnswSearchLayer(Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *procinfo, Oid collation, int m, bool inserting, HnswElement skipElement)
|
||||||
{
|
{
|
||||||
|
ListCell *lc2;
|
||||||
|
|
||||||
List *w = NIL;
|
List *w = NIL;
|
||||||
pairingheap *C = pairingheap_allocate(CompareNearestCandidates, NULL);
|
pairingheap *C = pairingheap_allocate(CompareNearestCandidates, NULL);
|
||||||
pairingheap *W = pairingheap_allocate(CompareFurthestCandidates, NULL);
|
pairingheap *W = pairingheap_allocate(CompareFurthestCandidates, NULL);
|
||||||
int wlen = 0;
|
int wlen = 0;
|
||||||
visited_hash v;
|
visited_hash v;
|
||||||
ListCell *lc2;
|
|
||||||
HnswNeighborArray *neighborhoodData = NULL;
|
|
||||||
Size neighborhoodSize;
|
|
||||||
|
|
||||||
InitVisited(base, &v, index, ef, m);
|
/* Create hash table */
|
||||||
|
|
||||||
/* Create local memory for neighborhood if needed */
|
|
||||||
if (index == NULL)
|
if (index == NULL)
|
||||||
{
|
v.pointers = pointerhash_create(CurrentMemoryContext, ef * m * 2, NULL);
|
||||||
neighborhoodSize = offsetof(HnswNeighborArray, items) + sizeof(HnswCandidate) * HnswGetLayerM(m, lc);
|
else
|
||||||
neighborhoodData = palloc(neighborhoodSize);
|
v.tids = tidhash_create(CurrentMemoryContext, ef * m * 2, NULL);
|
||||||
}
|
|
||||||
|
|
||||||
/* Add entry points to v, C, and W */
|
/* Add entry points to v, C, and W */
|
||||||
foreach(lc2, ep)
|
foreach(lc2, ep)
|
||||||
@@ -754,7 +670,7 @@ HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, F
|
|||||||
HnswCandidate *hc = (HnswCandidate *) lfirst(lc2);
|
HnswCandidate *hc = (HnswCandidate *) lfirst(lc2);
|
||||||
bool found;
|
bool found;
|
||||||
|
|
||||||
AddToVisited(base, &v, hc, index, &found);
|
AddToVisited(v, hc, index, &found);
|
||||||
|
|
||||||
pairingheap_add(C, &(CreatePairingHeapNode(hc)->ph_node));
|
pairingheap_add(C, &(CreatePairingHeapNode(hc)->ph_node));
|
||||||
pairingheap_add(W, &(CreatePairingHeapNode(hc)->ph_node));
|
pairingheap_add(W, &(CreatePairingHeapNode(hc)->ph_node));
|
||||||
@@ -764,7 +680,7 @@ HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, F
|
|||||||
* would be ideal to do this for inserts as well, but this could
|
* would be ideal to do this for inserts as well, but this could
|
||||||
* affect insert performance.
|
* affect insert performance.
|
||||||
*/
|
*/
|
||||||
if (CountElement(base, skipElement, hc))
|
if (skipElement == NULL || list_length(hc->element->heaptids) != 0)
|
||||||
wlen++;
|
wlen++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -773,51 +689,38 @@ HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, F
|
|||||||
HnswNeighborArray *neighborhood;
|
HnswNeighborArray *neighborhood;
|
||||||
HnswCandidate *c = ((HnswPairingHeapNode *) pairingheap_remove_first(C))->inner;
|
HnswCandidate *c = ((HnswPairingHeapNode *) pairingheap_remove_first(C))->inner;
|
||||||
HnswCandidate *f = ((HnswPairingHeapNode *) pairingheap_first(W))->inner;
|
HnswCandidate *f = ((HnswPairingHeapNode *) pairingheap_first(W))->inner;
|
||||||
HnswElement cElement;
|
|
||||||
|
|
||||||
if (c->distance > f->distance)
|
if (c->distance > f->distance)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
cElement = HnswPtrAccess(base, c->element);
|
if (c->element->neighbors == NULL)
|
||||||
|
HnswLoadNeighbors(c->element, index, m);
|
||||||
if (HnswPtrIsNull(base, cElement->neighbors))
|
|
||||||
HnswLoadNeighbors(cElement, index, m);
|
|
||||||
|
|
||||||
/* Get the neighborhood at layer lc */
|
/* Get the neighborhood at layer lc */
|
||||||
neighborhood = HnswGetNeighbors(base, cElement, lc);
|
neighborhood = &c->element->neighbors[lc];
|
||||||
|
|
||||||
/* Copy neighborhood to local memory if needed */
|
|
||||||
if (index == NULL)
|
|
||||||
{
|
|
||||||
LWLockAcquire(&cElement->lock, LW_SHARED);
|
|
||||||
memcpy(neighborhoodData, neighborhood, neighborhoodSize);
|
|
||||||
LWLockRelease(&cElement->lock);
|
|
||||||
neighborhood = neighborhoodData;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < neighborhood->length; i++)
|
for (int i = 0; i < neighborhood->length; i++)
|
||||||
{
|
{
|
||||||
HnswCandidate *e = &neighborhood->items[i];
|
HnswCandidate *e = &neighborhood->items[i];
|
||||||
bool visited;
|
bool visited;
|
||||||
|
|
||||||
AddToVisited(base, &v, e, index, &visited);
|
AddToVisited(v, e, index, &visited);
|
||||||
|
|
||||||
if (!visited)
|
if (!visited)
|
||||||
{
|
{
|
||||||
float eDistance;
|
float eDistance;
|
||||||
HnswElement eElement = HnswPtrAccess(base, e->element);
|
|
||||||
|
|
||||||
f = ((HnswPairingHeapNode *) pairingheap_first(W))->inner;
|
f = ((HnswPairingHeapNode *) pairingheap_first(W))->inner;
|
||||||
|
|
||||||
if (index == NULL)
|
if (index == NULL)
|
||||||
eDistance = GetCandidateDistance(base, e, q, procinfo, collation);
|
eDistance = GetCandidateDistance(e, q, procinfo, collation);
|
||||||
else
|
else
|
||||||
HnswLoadElement(eElement, &eDistance, &q, index, procinfo, collation, inserting);
|
HnswLoadElement(e->element, &eDistance, &q, index, procinfo, collation, inserting);
|
||||||
|
|
||||||
Assert(!eElement->deleted);
|
Assert(!e->element->deleted);
|
||||||
|
|
||||||
/* Make robust to issues */
|
/* Make robust to issues */
|
||||||
if (eElement->level < lc)
|
if (e->element->level < lc)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (eDistance < f->distance || wlen < ef)
|
if (eDistance < f->distance || wlen < ef)
|
||||||
@@ -825,7 +728,7 @@ HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, F
|
|||||||
/* Copy e */
|
/* Copy e */
|
||||||
HnswCandidate *ec = palloc(sizeof(HnswCandidate));
|
HnswCandidate *ec = palloc(sizeof(HnswCandidate));
|
||||||
|
|
||||||
HnswPtrStore(base, ec->element, eElement);
|
ec->element = e->element;
|
||||||
ec->distance = eDistance;
|
ec->distance = eDistance;
|
||||||
|
|
||||||
pairingheap_add(C, &(CreatePairingHeapNode(ec)->ph_node));
|
pairingheap_add(C, &(CreatePairingHeapNode(ec)->ph_node));
|
||||||
@@ -836,7 +739,7 @@ HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, F
|
|||||||
* vacuuming. It would be ideal to do this for inserts as
|
* vacuuming. It would be ideal to do this for inserts as
|
||||||
* well, but this could affect insert performance.
|
* well, but this could affect insert performance.
|
||||||
*/
|
*/
|
||||||
if (CountElement(base, skipElement, e))
|
if (skipElement == NULL || list_length(e->element->heaptids) != 0)
|
||||||
{
|
{
|
||||||
wlen++;
|
wlen++;
|
||||||
|
|
||||||
@@ -861,7 +764,7 @@ HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, F
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Compare candidate distances with pointer tie-breaker
|
* Compare candidate distances
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
#if PG_VERSION_NUM >= 130000
|
#if PG_VERSION_NUM >= 130000
|
||||||
@@ -879,38 +782,10 @@ CompareCandidateDistances(const void *a, const void *b)
|
|||||||
if (hca->distance > hcb->distance)
|
if (hca->distance > hcb->distance)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (HnswPtrPointer(hca->element) < HnswPtrPointer(hcb->element))
|
if (hca->element < hcb->element)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
if (HnswPtrPointer(hca->element) > HnswPtrPointer(hcb->element))
|
if (hca->element > hcb->element)
|
||||||
return -1;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Compare candidate distances with offset tie-breaker
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
#if PG_VERSION_NUM >= 130000
|
|
||||||
CompareCandidateDistancesOffset(const ListCell *a, const ListCell *b)
|
|
||||||
#else
|
|
||||||
CompareCandidateDistancesOffset(const void *a, const void *b)
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
HnswCandidate *hca = lfirst((ListCell *) a);
|
|
||||||
HnswCandidate *hcb = lfirst((ListCell *) b);
|
|
||||||
|
|
||||||
if (hca->distance < hcb->distance)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (hca->distance > hcb->distance)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (HnswPtrOffset(hca->element) < HnswPtrOffset(hcb->element))
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (HnswPtrOffset(hca->element) > HnswPtrOffset(hcb->element))
|
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -920,58 +795,46 @@ 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, int lc, FmgrInfo *procinfo, Oid collation)
|
HnswGetDistance(HnswElement a, HnswElement b, int lc, FmgrInfo *procinfo, Oid collation)
|
||||||
{
|
{
|
||||||
Datum aValue;
|
|
||||||
Datum bValue;
|
|
||||||
|
|
||||||
/* Look for cached distance */
|
/* Look for cached distance */
|
||||||
if (!HnswPtrIsNull(base, a->neighbors))
|
if (a->neighbors != NULL)
|
||||||
{
|
{
|
||||||
HnswNeighborArray *neighbors = HnswGetNeighbors(base, a, lc);
|
Assert(a->level >= lc);
|
||||||
|
|
||||||
for (int i = 0; i < neighbors->length; i++)
|
for (int i = 0; i < a->neighbors[lc].length; i++)
|
||||||
{
|
{
|
||||||
HnswElement element = HnswPtrAccess(base, neighbors->items[i].element);
|
if (a->neighbors[lc].items[i].element == b)
|
||||||
|
return a->neighbors[lc].items[i].distance;
|
||||||
if (element == b)
|
|
||||||
return neighbors->items[i].distance;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!HnswPtrIsNull(base, b->neighbors))
|
if (b->neighbors != NULL)
|
||||||
{
|
{
|
||||||
HnswNeighborArray *neighbors = HnswGetNeighbors(base, b, lc);
|
Assert(b->level >= lc);
|
||||||
|
|
||||||
for (int i = 0; i < neighbors->length; i++)
|
for (int i = 0; i < b->neighbors[lc].length; i++)
|
||||||
{
|
{
|
||||||
HnswElement element = HnswPtrAccess(base, neighbors->items[i].element);
|
if (b->neighbors[lc].items[i].element == a)
|
||||||
|
return b->neighbors[lc].items[i].distance;
|
||||||
if (element == a)
|
|
||||||
return neighbors->items[i].distance;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
aValue = HnswGetValue(base, a);
|
return DatumGetFloat8(FunctionCall2Coll(procinfo, collation, a->value, b->value));
|
||||||
bValue = HnswGetValue(base, b);
|
|
||||||
|
|
||||||
return DatumGetFloat8(FunctionCall2Coll(procinfo, collation, aValue, bValue));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check if an element is closer to q than any element from R
|
* Check if an element is closer to q than any element from R
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool
|
||||||
CheckElementCloser(char *base, HnswCandidate * e, List *r, int lc, FmgrInfo *procinfo, Oid collation)
|
CheckElementCloser(HnswCandidate * e, List *r, int lc, FmgrInfo *procinfo, Oid collation)
|
||||||
{
|
{
|
||||||
HnswElement eElement = HnswPtrAccess(base, e->element);
|
|
||||||
ListCell *lc2;
|
ListCell *lc2;
|
||||||
|
|
||||||
foreach(lc2, r)
|
foreach(lc2, r)
|
||||||
{
|
{
|
||||||
HnswCandidate *ri = lfirst(lc2);
|
HnswCandidate *ri = lfirst(lc2);
|
||||||
HnswElement riElement = HnswPtrAccess(base, ri->element);
|
float distance = HnswGetDistance(e->element, ri->element, lc, procinfo, collation);
|
||||||
float distance = HnswGetDistance(base, eElement, riElement, lc, procinfo, collation);
|
|
||||||
|
|
||||||
if (distance <= e->distance)
|
if (distance <= e->distance)
|
||||||
return false;
|
return false;
|
||||||
@@ -984,13 +847,12 @@ CheckElementCloser(char *base, HnswCandidate * e, List *r, int lc, FmgrInfo *pro
|
|||||||
* Algorithm 4 from paper
|
* Algorithm 4 from paper
|
||||||
*/
|
*/
|
||||||
static List *
|
static List *
|
||||||
SelectNeighbors(char *base, List *c, int m, int lc, FmgrInfo *procinfo, Oid collation, HnswElement e2, HnswCandidate * newCandidate, HnswCandidate * *pruned, bool sortCandidates)
|
SelectNeighbors(List *c, int m, int lc, FmgrInfo *procinfo, Oid collation, HnswElement e2, HnswCandidate * newCandidate, HnswCandidate * *pruned, bool sortCandidates)
|
||||||
{
|
{
|
||||||
List *r = NIL;
|
List *r = NIL;
|
||||||
List *w = list_copy(c);
|
List *w = list_copy(c);
|
||||||
pairingheap *wd;
|
pairingheap *wd;
|
||||||
HnswNeighborArray *neighbors = HnswGetNeighbors(base, e2, lc);
|
bool mustCalculate = !e2->neighbors[lc].closerSet;
|
||||||
bool mustCalculate = !neighbors->closerSet;
|
|
||||||
List *added = NIL;
|
List *added = NIL;
|
||||||
bool removedAny = false;
|
bool removedAny = false;
|
||||||
|
|
||||||
@@ -1001,12 +863,7 @@ SelectNeighbors(char *base, List *c, int m, int lc, FmgrInfo *procinfo, Oid coll
|
|||||||
|
|
||||||
/* Ensure order of candidates is deterministic for closer caching */
|
/* Ensure order of candidates is deterministic for closer caching */
|
||||||
if (sortCandidates)
|
if (sortCandidates)
|
||||||
{
|
list_sort(w, CompareCandidateDistances);
|
||||||
if (base == NULL)
|
|
||||||
list_sort(w, CompareCandidateDistances);
|
|
||||||
else
|
|
||||||
list_sort(w, CompareCandidateDistancesOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (list_length(w) > 0 && list_length(r) < m)
|
while (list_length(w) > 0 && list_length(r) < m)
|
||||||
{
|
{
|
||||||
@@ -1017,7 +874,7 @@ SelectNeighbors(char *base, List *c, int m, int lc, FmgrInfo *procinfo, Oid coll
|
|||||||
|
|
||||||
/* 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, lc, procinfo, collation);
|
e->closer = CheckElementCloser(e, r, lc, procinfo, collation);
|
||||||
else if (list_length(added) > 0)
|
else if (list_length(added) > 0)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
@@ -1026,7 +883,7 @@ SelectNeighbors(char *base, List *c, int m, int lc, FmgrInfo *procinfo, Oid coll
|
|||||||
*/
|
*/
|
||||||
if (e->closer)
|
if (e->closer)
|
||||||
{
|
{
|
||||||
e->closer = CheckElementCloser(base, e, added, lc, procinfo, collation);
|
e->closer = CheckElementCloser(e, added, lc, procinfo, collation);
|
||||||
|
|
||||||
if (!e->closer)
|
if (!e->closer)
|
||||||
removedAny = true;
|
removedAny = true;
|
||||||
@@ -1039,7 +896,7 @@ SelectNeighbors(char *base, List *c, int m, int lc, FmgrInfo *procinfo, Oid coll
|
|||||||
*/
|
*/
|
||||||
if (removedAny)
|
if (removedAny)
|
||||||
{
|
{
|
||||||
e->closer = CheckElementCloser(base, e, r, lc, procinfo, collation);
|
e->closer = CheckElementCloser(e, r, lc, procinfo, collation);
|
||||||
if (e->closer)
|
if (e->closer)
|
||||||
added = lappend(added, e);
|
added = lappend(added, e);
|
||||||
}
|
}
|
||||||
@@ -1047,7 +904,7 @@ SelectNeighbors(char *base, List *c, int m, int lc, FmgrInfo *procinfo, Oid coll
|
|||||||
}
|
}
|
||||||
else if (e == newCandidate)
|
else if (e == newCandidate)
|
||||||
{
|
{
|
||||||
e->closer = CheckElementCloser(base, e, r, lc, procinfo, collation);
|
e->closer = CheckElementCloser(e, r, lc, procinfo, collation);
|
||||||
if (e->closer)
|
if (e->closer)
|
||||||
added = lappend(added, e);
|
added = lappend(added, e);
|
||||||
}
|
}
|
||||||
@@ -1059,7 +916,7 @@ SelectNeighbors(char *base, List *c, int m, int lc, FmgrInfo *procinfo, Oid coll
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Cached value can only be used in future if sorted deterministically */
|
/* Cached value can only be used in future if sorted deterministically */
|
||||||
neighbors->closerSet = sortCandidates;
|
e2->neighbors[lc].closerSet = sortCandidates;
|
||||||
|
|
||||||
/* Keep pruned connections */
|
/* Keep pruned connections */
|
||||||
while (!pairingheap_is_empty(wd) && list_length(r) < m)
|
while (!pairingheap_is_empty(wd) && list_length(r) < m)
|
||||||
@@ -1077,14 +934,38 @@ SelectNeighbors(char *base, List *c, int m, int lc, FmgrInfo *procinfo, Oid coll
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find duplicate element
|
||||||
|
*/
|
||||||
|
HnswElement
|
||||||
|
HnswFindDuplicate(HnswElement e)
|
||||||
|
{
|
||||||
|
HnswNeighborArray *neighbors = &e->neighbors[0];
|
||||||
|
|
||||||
|
for (int i = 0; i < neighbors->length; i++)
|
||||||
|
{
|
||||||
|
HnswCandidate *neighbor = &neighbors->items[i];
|
||||||
|
|
||||||
|
/* Exit early since ordered by distance */
|
||||||
|
if (!datumIsEqual(e->value, neighbor->element->value, false, -1))
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Check for space */
|
||||||
|
if (list_length(neighbor->element->heaptids) < HNSW_HEAPTIDS)
|
||||||
|
return neighbor->element;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add connections
|
* Add connections
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
AddConnections(char *base, HnswElement element, List *neighbors, int m, int lc)
|
AddConnections(HnswElement element, List *neighbors, int m, int lc)
|
||||||
{
|
{
|
||||||
ListCell *lc2;
|
ListCell *lc2;
|
||||||
HnswNeighborArray *a = HnswGetNeighbors(base, element, lc);
|
HnswNeighborArray *a = &element->neighbors[lc];
|
||||||
|
|
||||||
foreach(lc2, neighbors)
|
foreach(lc2, neighbors)
|
||||||
a->items[a->length++] = *((HnswCandidate *) lfirst(lc2));
|
a->items[a->length++] = *((HnswCandidate *) lfirst(lc2));
|
||||||
@@ -1094,13 +975,13 @@ AddConnections(char *base, HnswElement element, List *neighbors, int m, int lc)
|
|||||||
* Update connections
|
* Update connections
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
HnswUpdateConnection(char *base, HnswElement element, HnswCandidate * hc, int m, int lc, int *updateIdx, Relation index, FmgrInfo *procinfo, Oid collation)
|
HnswUpdateConnection(HnswElement element, HnswCandidate * hc, int m, int lc, int *updateIdx, Relation index, FmgrInfo *procinfo, Oid collation)
|
||||||
{
|
{
|
||||||
HnswElement hce = HnswPtrAccess(base, hc->element);
|
HnswNeighborArray *currentNeighbors = &hc->element->neighbors[lc];
|
||||||
HnswNeighborArray *currentNeighbors = HnswGetNeighbors(base, hce, lc);
|
|
||||||
HnswCandidate hc2;
|
HnswCandidate hc2;
|
||||||
|
|
||||||
HnswPtrStore(base, hc2.element, element);
|
hc2.element = element;
|
||||||
hc2.distance = hc->distance;
|
hc2.distance = hc->distance;
|
||||||
|
|
||||||
if (currentNeighbors->length < m)
|
if (currentNeighbors->length < m)
|
||||||
@@ -1119,20 +1000,19 @@ HnswUpdateConnection(char *base, HnswElement element, HnswCandidate * hc, int m,
|
|||||||
/* Load elements on insert */
|
/* Load elements on insert */
|
||||||
if (index != NULL)
|
if (index != NULL)
|
||||||
{
|
{
|
||||||
Datum q = HnswGetValue(base, hce);
|
Datum q = hc->element->value;
|
||||||
|
|
||||||
for (int i = 0; i < currentNeighbors->length; i++)
|
for (int i = 0; i < currentNeighbors->length; i++)
|
||||||
{
|
{
|
||||||
HnswCandidate *hc3 = ¤tNeighbors->items[i];
|
HnswCandidate *hc3 = ¤tNeighbors->items[i];
|
||||||
HnswElement hc3Element = HnswPtrAccess(base, hc3->element);
|
|
||||||
|
|
||||||
if (HnswPtrIsNull(base, hc3Element->value))
|
if (DatumGetPointer(hc3->element->value) == NULL)
|
||||||
HnswLoadElement(hc3Element, &hc3->distance, &q, index, procinfo, collation, true);
|
HnswLoadElement(hc3->element, &hc3->distance, &q, index, procinfo, collation, true);
|
||||||
else
|
else
|
||||||
hc3->distance = GetCandidateDistance(base, hc3, q, procinfo, collation);
|
hc3->distance = GetCandidateDistance(hc3, q, procinfo, collation);
|
||||||
|
|
||||||
/* Prune element if being deleted */
|
/* Prune element if being deleted */
|
||||||
if (hc3Element->heaptidsLength == 0)
|
if (list_length(hc3->element->heaptids) == 0)
|
||||||
{
|
{
|
||||||
pruned = ¤tNeighbors->items[i];
|
pruned = ¤tNeighbors->items[i];
|
||||||
break;
|
break;
|
||||||
@@ -1149,7 +1029,7 @@ HnswUpdateConnection(char *base, HnswElement element, HnswCandidate * hc, int m,
|
|||||||
c = lappend(c, ¤tNeighbors->items[i]);
|
c = lappend(c, ¤tNeighbors->items[i]);
|
||||||
c = lappend(c, &hc2);
|
c = lappend(c, &hc2);
|
||||||
|
|
||||||
SelectNeighbors(base, c, m, lc, procinfo, collation, hce, &hc2, &pruned, true);
|
SelectNeighbors(c, m, lc, procinfo, collation, hc->element, &hc2, &pruned, true);
|
||||||
|
|
||||||
/* Should not happen */
|
/* Should not happen */
|
||||||
if (pruned == NULL)
|
if (pruned == NULL)
|
||||||
@@ -1159,7 +1039,7 @@ HnswUpdateConnection(char *base, HnswElement element, HnswCandidate * hc, int m,
|
|||||||
/* Find and replace the pruned element */
|
/* Find and replace the pruned element */
|
||||||
for (int i = 0; i < currentNeighbors->length; i++)
|
for (int i = 0; i < currentNeighbors->length; i++)
|
||||||
{
|
{
|
||||||
if (HnswPtrEqual(base, currentNeighbors->items[i].element, pruned->element))
|
if (currentNeighbors->items[i].element == pruned->element)
|
||||||
{
|
{
|
||||||
currentNeighbors->items[i] = hc2;
|
currentNeighbors->items[i] = hc2;
|
||||||
|
|
||||||
@@ -1177,65 +1057,43 @@ HnswUpdateConnection(char *base, HnswElement element, HnswCandidate * hc, int m,
|
|||||||
* Remove elements being deleted or skipped
|
* Remove elements being deleted or skipped
|
||||||
*/
|
*/
|
||||||
static List *
|
static List *
|
||||||
RemoveElements(char *base, List *w, HnswElement skipElement)
|
RemoveElements(List *w, HnswElement skipElement)
|
||||||
{
|
{
|
||||||
ListCell *lc2;
|
ListCell *lc2;
|
||||||
List *w2 = NIL;
|
List *w2 = NIL;
|
||||||
|
|
||||||
/* Ensure does not access heaptidsLength during in-memory build */
|
|
||||||
pg_memory_barrier();
|
|
||||||
|
|
||||||
foreach(lc2, w)
|
foreach(lc2, w)
|
||||||
{
|
{
|
||||||
HnswCandidate *hc = (HnswCandidate *) lfirst(lc2);
|
HnswCandidate *hc = (HnswCandidate *) lfirst(lc2);
|
||||||
HnswElement hce = HnswPtrAccess(base, hc->element);
|
|
||||||
|
|
||||||
/* Skip self for vacuuming update */
|
/* Skip self for vacuuming update */
|
||||||
if (skipElement != NULL && hce->blkno == skipElement->blkno && hce->offno == skipElement->offno)
|
if (skipElement != NULL && hc->element->blkno == skipElement->blkno && hc->element->offno == skipElement->offno)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (hce->heaptidsLength != 0)
|
if (list_length(hc->element->heaptids) != 0)
|
||||||
w2 = lappend(w2, hc);
|
w2 = lappend(w2, hc);
|
||||||
}
|
}
|
||||||
|
|
||||||
return w2;
|
return w2;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 130000
|
|
||||||
/*
|
|
||||||
* Precompute hash
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
PrecomputeHash(char *base, HnswElement element)
|
|
||||||
{
|
|
||||||
HnswElementPtr ptr;
|
|
||||||
|
|
||||||
HnswPtrStore(base, ptr, element);
|
|
||||||
|
|
||||||
if (base == NULL)
|
|
||||||
element->hash = hash_pointer((uintptr_t) HnswPtrPointer(ptr));
|
|
||||||
else
|
|
||||||
element->hash = hash_offset(HnswPtrOffset(ptr));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Algorithm 1 from paper
|
* Algorithm 1 from paper
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint, Relation index, FmgrInfo *procinfo, Oid collation, int m, int efConstruction, bool existing)
|
HnswInsertElement(HnswElement element, HnswElement entryPoint, Relation index, FmgrInfo *procinfo, Oid collation, int m, int efConstruction, bool existing)
|
||||||
{
|
{
|
||||||
List *ep;
|
List *ep;
|
||||||
List *w;
|
List *w;
|
||||||
int level = element->level;
|
int level = element->level;
|
||||||
int entryLevel;
|
int entryLevel;
|
||||||
Datum q = HnswGetValue(base, element);
|
Datum q = element->value;
|
||||||
HnswElement skipElement = existing ? element : NULL;
|
HnswElement skipElement = existing ? element : NULL;
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 130000
|
#if PG_VERSION_NUM >= 130000
|
||||||
/* Precompute hash */
|
/* Precompute hash */
|
||||||
if (index == NULL)
|
if (index == NULL)
|
||||||
PrecomputeHash(base, element);
|
element->hash = hash_pointer((uintptr_t) element);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* No neighbors if no entry point */
|
/* No neighbors if no entry point */
|
||||||
@@ -1243,13 +1101,13 @@ HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
/* Get entry point and level */
|
/* Get entry point and level */
|
||||||
ep = list_make1(HnswEntryCandidate(base, entryPoint, q, index, procinfo, collation, true));
|
ep = list_make1(HnswEntryCandidate(entryPoint, q, index, procinfo, collation, true));
|
||||||
entryLevel = entryPoint->level;
|
entryLevel = entryPoint->level;
|
||||||
|
|
||||||
/* 1st phase: greedy search to insert level */
|
/* 1st phase: greedy search to insert level */
|
||||||
for (int lc = entryLevel; lc >= level + 1; lc--)
|
for (int lc = entryLevel; lc >= level + 1; lc--)
|
||||||
{
|
{
|
||||||
w = HnswSearchLayer(base, q, ep, 1, lc, index, procinfo, collation, m, true, skipElement);
|
w = HnswSearchLayer(q, ep, 1, lc, index, procinfo, collation, m, true, skipElement);
|
||||||
ep = w;
|
ep = w;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1267,12 +1125,12 @@ HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint
|
|||||||
List *neighbors;
|
List *neighbors;
|
||||||
List *lw;
|
List *lw;
|
||||||
|
|
||||||
w = HnswSearchLayer(base, q, ep, efConstruction, lc, index, procinfo, collation, m, true, skipElement);
|
w = HnswSearchLayer(q, ep, efConstruction, lc, index, procinfo, collation, m, true, skipElement);
|
||||||
|
|
||||||
/* Elements being deleted or skipped can help with search */
|
/* Elements being deleted or skipped can help with search */
|
||||||
/* but should be removed before selecting neighbors */
|
/* but should be removed before selecting neighbors */
|
||||||
if (index != NULL)
|
if (index != NULL)
|
||||||
lw = RemoveElements(base, w, skipElement);
|
lw = RemoveElements(w, skipElement);
|
||||||
else
|
else
|
||||||
lw = w;
|
lw = w;
|
||||||
|
|
||||||
@@ -1281,9 +1139,9 @@ HnswFindElementNeighbors(char *base, HnswElement element, HnswElement entryPoint
|
|||||||
* sortCandidates to true for in-memory builds to enable closer
|
* sortCandidates to true for in-memory builds to enable closer
|
||||||
* caching, but there does not seem to be a difference in performance.
|
* caching, but there does not seem to be a difference in performance.
|
||||||
*/
|
*/
|
||||||
neighbors = SelectNeighbors(base, lw, lm, lc, procinfo, collation, element, NULL, NULL, false);
|
neighbors = SelectNeighbors(lw, lm, lc, procinfo, collation, element, NULL, NULL, false);
|
||||||
|
|
||||||
AddConnections(base, element, neighbors, lm, lc);
|
AddConnections(element, neighbors, lm, lc);
|
||||||
|
|
||||||
ep = w;
|
ep = w;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,22 +199,21 @@ RepairGraphElement(HnswVacuumState * vacuumstate, HnswElement element, HnswEleme
|
|||||||
BufferAccessStrategy bas = vacuumstate->bas;
|
BufferAccessStrategy bas = vacuumstate->bas;
|
||||||
HnswNeighborTuple ntup = vacuumstate->ntup;
|
HnswNeighborTuple ntup = vacuumstate->ntup;
|
||||||
Size ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(element->level, m);
|
Size ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(element->level, m);
|
||||||
char *base = NULL;
|
|
||||||
|
|
||||||
/* Skip if element is entry point */
|
/* Skip if element is entry point */
|
||||||
if (entryPoint != NULL && element->blkno == entryPoint->blkno && element->offno == entryPoint->offno)
|
if (entryPoint != NULL && element->blkno == entryPoint->blkno && element->offno == entryPoint->offno)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* Init fields */
|
/* Init fields */
|
||||||
HnswInitNeighbors(base, element, m, NULL);
|
HnswInitNeighbors(element, m);
|
||||||
element->heaptidsLength = 0;
|
element->heaptids = NIL;
|
||||||
|
|
||||||
/* Find neighbors for element, skipping itself */
|
/* Add element to graph, skipping itself */
|
||||||
HnswFindElementNeighbors(base, element, entryPoint, index, procinfo, collation, m, efConstruction, true);
|
HnswInsertElement(element, entryPoint, index, procinfo, collation, m, efConstruction, true);
|
||||||
|
|
||||||
/* Update neighbor tuple */
|
/* Update neighbor tuple */
|
||||||
/* Do this before getting page to minimize locking */
|
/* Do this before getting page to minimize locking */
|
||||||
HnswSetNeighborTuple(base, ntup, element, m);
|
HnswSetNeighborTuple(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);
|
||||||
@@ -231,7 +230,7 @@ RepairGraphElement(HnswVacuumState * vacuumstate, HnswElement element, HnswEleme
|
|||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
|
|
||||||
/* Update neighbors */
|
/* Update neighbors */
|
||||||
HnswUpdateNeighborsOnDisk(index, procinfo, collation, element, m, true, false);
|
HnswUpdateNeighborPages(index, procinfo, collation, element, m, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -302,7 +301,7 @@ RepairGraphEntryPoint(HnswVacuumState * vacuumstate)
|
|||||||
{
|
{
|
||||||
/* Reset neighbors from previous update */
|
/* Reset neighbors from previous update */
|
||||||
if (highestPoint != NULL)
|
if (highestPoint != NULL)
|
||||||
HnswPtrStore((char *) NULL, highestPoint->neighbors, (HnswNeighborArrayPtr *) NULL);
|
highestPoint->neighbors = NULL;
|
||||||
|
|
||||||
RepairGraphElement(vacuumstate, entryPoint, highestPoint);
|
RepairGraphElement(vacuumstate, entryPoint, highestPoint);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -543,10 +543,10 @@ CreateListPages(Relation index, VectorArray centers, int dimensions,
|
|||||||
pfree(list);
|
pfree(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef IVFFLAT_KMEANS_DEBUG
|
|
||||||
/*
|
/*
|
||||||
* Print k-means metrics
|
* Print k-means metrics
|
||||||
*/
|
*/
|
||||||
|
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||||
static void
|
static void
|
||||||
PrintKmeansMetrics(IvfflatBuildState * buildstate)
|
PrintKmeansMetrics(IvfflatBuildState * buildstate)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,10 +6,6 @@
|
|||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
|
|
||||||
#ifdef IVFFLAT_MEMORY
|
|
||||||
#include "utils/memutils.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Initialize with kmeans++
|
* Initialize with kmeans++
|
||||||
*
|
*
|
||||||
@@ -155,23 +151,6 @@ QuickCenters(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef IVFFLAT_MEMORY
|
|
||||||
/*
|
|
||||||
* Show memory usage
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
ShowMemoryUsage(Size estimatedSize)
|
|
||||||
{
|
|
||||||
#if PG_VERSION_NUM >= 130000
|
|
||||||
elog(INFO, "total memory: %zu MB",
|
|
||||||
MemoryContextMemAllocated(CurrentMemoryContext, true) / (1024 * 1024));
|
|
||||||
#else
|
|
||||||
MemoryContextStats(CurrentMemoryContext);
|
|
||||||
#endif
|
|
||||||
elog(INFO, "estimated memory: %zu MB", estimatedSize / (1024 * 1024));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Use Elkan for performance. This requires distance function to satisfy triangle inequality.
|
* Use Elkan for performance. This requires distance function to satisfy triangle inequality.
|
||||||
*
|
*
|
||||||
@@ -252,10 +231,6 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
vec->dim = dimensions;
|
vec->dim = dimensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef IVFFLAT_MEMORY
|
|
||||||
ShowMemoryUsage(totalSize);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Pick initial centers */
|
/* Pick initial centers */
|
||||||
InitCenters(index, samples, centers, lowerBound);
|
InitCenters(index, samples, centers, lowerBound);
|
||||||
|
|
||||||
|
|||||||
@@ -95,10 +95,11 @@ for my $i (0 .. $#operators)
|
|||||||
|
|
||||||
$node->safe_psql("postgres", "DROP INDEX idx;");
|
$node->safe_psql("postgres", "DROP INDEX idx;");
|
||||||
|
|
||||||
# Build index in parallel in memory
|
# Build index in parallel
|
||||||
my ($ret, $stdout, $stderr) = $node->psql("postgres", qq(
|
my ($ret, $stdout, $stderr) = $node->psql("postgres", qq(
|
||||||
SET client_min_messages = DEBUG;
|
SET client_min_messages = DEBUG;
|
||||||
SET min_parallel_table_scan_size = 1;
|
SET min_parallel_table_scan_size = 1;
|
||||||
|
SET hnsw.enable_parallel_build = on;
|
||||||
CREATE INDEX idx ON tst USING hnsw (v $opclass);
|
CREATE INDEX idx ON tst USING hnsw (v $opclass);
|
||||||
));
|
));
|
||||||
is($ret, 0, $stderr);
|
is($ret, 0, $stderr);
|
||||||
@@ -108,21 +109,6 @@ for my $i (0 .. $#operators)
|
|||||||
test_recall($min, $operator);
|
test_recall($min, $operator);
|
||||||
|
|
||||||
$node->safe_psql("postgres", "DROP INDEX idx;");
|
$node->safe_psql("postgres", "DROP INDEX idx;");
|
||||||
|
|
||||||
# Build index in parallel on disk
|
|
||||||
# Set parallel_workers on table to use workers with low maintenance_work_mem
|
|
||||||
($ret, $stdout, $stderr) = $node->psql("postgres", qq(
|
|
||||||
ALTER TABLE tst SET (parallel_workers = 2);
|
|
||||||
SET client_min_messages = DEBUG;
|
|
||||||
SET maintenance_work_mem = '4MB';
|
|
||||||
CREATE INDEX idx ON tst USING hnsw (v $opclass);
|
|
||||||
ALTER TABLE tst RESET (parallel_workers);
|
|
||||||
));
|
|
||||||
is($ret, 0, $stderr);
|
|
||||||
like($stderr, qr/using \d+ parallel workers/);
|
|
||||||
like($stderr, qr/hnsw graph no longer fits into maintenance_work_mem/);
|
|
||||||
|
|
||||||
$node->safe_psql("postgres", "DROP INDEX idx;");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
done_testing();
|
done_testing();
|
||||||
|
|||||||
Reference in New Issue
Block a user