This is the multi-page printable view of this section. .
Usage
- 1: Indexing
- 2: Search
- 3: Monitoring
- 4: Quantization
- 5: `pgvector` compatibility
1 - Indexing
Indexing is the process of building a data structure that allows for efficient search. pgvecto.rs supports three indexing algorithms: brute force (flat), IVF, and HNSW. The default algorithm is HNSW.
To construct an index for vectors, first create a table named items with a column named embedding of type vector(n). Then, populate the table with generated data.
vector is the suitable data type for most situations. pgvecto.rs also supports other specific vector types, including:
- Half-Precision Vector
- 8-Bit Integer Vector since v0.3.0
- Sparse Vector since v0.3.0
- Binary Vector since v0.3.0
You can create a vector index for squared Euclidean distance with the following SQL.
The vector_l2_ops operator class calculates squared Euclidean distance. Refer to the documentation for a list of operator classes.
To switch the indexing algorithm from the default HNSW to IVF, execute the SQL command below. For additional examples of usage, refer to the documentation.
You can now perform a KNN search with the following SQL, leveraging the vector index for your query.
pgvecto.rs constructs the index asynchronously. When you insert new rows into the table, they will first be placed in an append-only file. The background thread will periodically merge the newly inserted row to the existing index. When a user performs any search prior to the merge process, it scans the append-only file to ensure accuracy and consistency.
Examples
Here are some examples of how to create indexes using various algorithms and settings. For a complete list of options, please consult the reference guide.
Suggestions
Selecting the appropriate indexing algorithm and settings is crucial for vector search performance. Below are recommendations tailored to various use cases.
Brute force (flat)
If you’re conducting a limited number of searches (between 1,000 and 10,000) or require precise results, the brute force algorithm is a viable option.
This algorithm sets the standard for other indexing methods. It retains vectors in their original form without compression and avoids additional overhead. While straightforward and user-friendly, it’s not ideal for processing large datasets.
In addition, you can use quantization to reduce memory usage. For a complete list of all options, please refer to the documentation. Detailed information about quantization can also be found there.
Inverted file index (IVF)
IVF mainly uses the idea of inverted indexing to store the vectors (id, vector) under each cluster center. When querying a vector, it finds the nearest several centers and searches for the vectors under these centers respectively.
Before you begin your search, it’s necessary to train nlist cluster centers. You also have the option to set nsample for K-Means clustering; a higher value yields more precise clusters but increases training time. For additional settings, please consult the reference guide.
Before searching, train nlist cluster centers using K-Means clustering with nsample points. A higher value yields more precise clustering but requires longer training time. For additional settings, please consult the reference guide.
Hierarchical navigable small world graph (HNSW)
HNSW merges the concepts of skip list with navigable small world (NSW) graphs, employing a hierarchical structure that features longer edges on higher layers for rapid searching and shorter edges on lower layers to enhance search precision.
Navigable small world
Navigable Small World operate on the premise that each node is accessible from any other through a minimal number of “hops.” Once an entry point is chosen—be it random, heuristic-based, or algorithmically determined—the surrounding nodes are examined to identify if they’re nearer than the current one. The search progresses to the nearest neighbor and continues until no closer nodes can be found.
When constructing the graph, the m parameter determines how many connections a new node will establish with its nearest neighbors. A higher m value results in a denser and more interconnected graph but also increases memory usage and slows down insertion times. During node insertion, the algorithm identifies the closest m nodes to create two-way links with them.
The search process navigates through an NSW graph, beginning at a pre-defined entry point. The algorithm proceeds by greedily moving to adjacent vertices closer to the query vector. The process stops once it finds no closer neighbors in the current vertex’s adjacency list.
A limitation of the NSW search is that it opts for the seemingly shortest path to the nearest node, disregarding the graph’s overall structure. This “greedy search” approach can result in getting stuck at a local optimum, an issue often referred to as “early stopping.”
Skip list
Skip lists are constructed using a probabilistic approach, where nodes become less likely to appear as you move up the layers, resulting in fewer nodes at higher levels. To search for a node, the algorithm starts at the top layer’s “head” and moves to the next node that is greater than or equal to the target. If the node isn’t found, it descends to a lower (more fine-grained) layer and continues this pattern until it locates either the desired node or its nearest neighbors.
HNSW
Constructing this multi-layered graph begins with a foundational layer that encapsulates the entire dataset. Moving up the hierarchy, each successive layer provides a streamlined summary of its predecessor, featuring fewer nodes and functioning as fast tracks for making broader leaps within the graph.
Smaller m) values are better for lower-dimensional data or when you require lower recall. Larger m values are useful for higher-dimensional data or when high recall is important.
The ef_construction parameter determines the dynamic candidate list size when adding new nodes; increasing this value may enhance recall but could extend index construction time.
The HNSW index is resource-intensive, requiring additional RAM and an adjustment to the maintenance_work_mem setting for larger datasets. If you’re seeking performance that’s markedly faster than IVF, with a high recall rate and scalability that matches dataset size, HNSW is an excellent option.
2 - Search
Get the nearest 5 neighbors to a vector
Operators
These operators are used for distance metrics:
| Name | Description |
|---|---|
| <-> | squared Euclidean distance |
| <#> | negative dot product |
| <=> | cosine distance |
For their definitions, see overview.
Filter
For a given category, get the nearest 10 neighbors to a vector
Query options
Set ivf scan lists to 1 in session:
Set hnsw search scope to 40 in transaction:
For all options, refer to search options.
Search modes
There are two search modes: vbase and basic.
vbase
As the default search mode, vbase is suitable for most scenarios.
For how it works, refer to the thesis VBASE: Unifying Online Vector Similarity Search and Relational Queries via Relaxed Monotonicity.
basic
basic is behaviorally consistent with vector algorithm libraries such as faiss.
It will be useful if you want to align them.
Enabling basic, you must respect these restrictions:
- Execute VACUUM after updates before searches.
- Search without filter
3 - Monitoring
We provide a view pg_vector_index_stat to monitor the progress of indexing.
| Column | Type | Description |
|---|---|---|
| tablerelid | oid | The oid of the table. |
| indexrelid | oid | The oid of the index. |
| tablename | name | The name of the table. |
| indexname | name | The name of the index. |
| idx_status | text | Its value is NORMAL or UPGRADE. Whether this index is normal or needs upgrade. |
| idx_indexing | bool | Not null if idx_status is NORMAL. Whether the background thread is indexing. |
| idx_tuples | int8 | Not null if idx_status is NORMAL. The number of tuples. |
| idx_sealed | int8[] | Not null if idx_status is NORMAL. The number of tuples in each sealed segment. |
| idx_growing | int8[] | Not null if idx_status is NORMAL. The number of tuples in each growing segment. |
| idx_write | int8 | Not null if idx_status is NORMAL. The number of tuples in write buffer. |
| idx_size | int8 | Not null if idx_status is NORMAL. The byte size for all the segments. |
| idx_options | text | Not null if idx_status is NORMAL. The configuration of the index. |
idx_options DetailsThe idx_options is a JSON string that includes vector, segment, optimization, and indexing options. Examples below:
If there are no more insertions and you wait for all indexing to be finished, you can check the field idx_indexing every 60 seconds util it’s true.
4 - Quantization
Quantization is a technique to reduce the memory consumption and computation complexity with the affordable decrease in precision. In the context of vector search, there are mainly two types of quantization: production quantization and scalar quantization.
Production Quantization
Production quantization is a method that trains several clustering models on the dataset and maps each vector to a list of cluster center indexes. It is commonly used address the problem of excessive memory usage with high-dimensional data.
In pgvecto.rs, you can enable the product quantization with:
More configurations can be found in indexing.
Scalar Quantization
Scalar quantization maps each value to low-bit format like from 32-bit float to 8-bit integer. Usually, this won’t affect the precision a lot but can reduce the memory usage and improve the query performance. pgvecto.rs supports uint8 scalar quantization with:
Suggestions
When quantization is required, it’s always recommended to try the scalar quantization first. It’s a good balance between precision and memory usage.
If the memory usage is still too high, you can try the product quantization. This method can reduce the memory usage significantly but may affect the precision. Since it’s highly related to the training dataset. You need to try different ratio and sample values to find the best one for your dataset.
Otherwise, you may also use some dimension reduction methods like PCA to reduce the vector dimension.
5 - `pgvector` compatibility
pgvecto.rs is natively compatible with pgvector at:
CREATE TABLEcommands, e.g.CREATE TABLE t (val vector(3))INSERT INTOcommands, e.g.INSERT INTO t (val) VALUES ('[0.6,0.6,0.6]')
pgvecto.rs can be configured to be compatible with pgvector at:
- Index options, which allows you to create index by
USING hnsw (val vector_ip_ops) - Query options, which can be set by
SET ivfflat.probes = 10
This feature is named pgvector compatibility.
It can be enabled by SET vectors.pgvector_compatibility = on.
Index Options & Query Options
For index ivfflat and hnsw only the following options are available.
- Default value for index options is different from pgvecto.rs original, which keeps the same as in
pgvector. - Default value for query options is the same as in
pgvecto.rs.
Index options for ivfflat:
| Key | Type | Range | Default | Description |
|---|---|---|---|---|
| nlist | integer | [1, 1_000_000] |
100 |
Number of cluster units. |
Query options for ivfflat:
| Option | Type | Range nn | Default | Description |
|---|---|---|---|---|
| ivfflat.probes | integer | [1, 1_000_000] |
10 |
Number of lists to scan. |
Default value of ivfflat.probes is 10 instead of 1 from pgvector.
Index options for hnsw:
| Key | Type | Range | Default | Description |
|---|---|---|---|---|
| m | integer | [4, 128] |
16 |
Maximum degree of the node. |
| ef_construction | integer | [10, 2000] |
64 |
Search scope in building. |
Query options for hnsw:
| Option | Type | Range | Default | Description |
|---|---|---|---|---|
| hnsw.ef_search | integer | [1, 65535] |
100 |
Search scope of HNSW. |
Default value for hnsw.ef_search is 100 instead of 40 from pgvector.
Examples
It’s easy to enable pgvector compatibility and start a vector query.
Multiply types of indexes are accepted: