pgvector 0.5 adds a graph index with an explicit recall tradeoff
HNSW adds a second approximate search method to pgvector. Its graph construction, memory cost and search settings differ from IVFFlat while retaining PostgreSQL storage and query semantics.
Adding an approximate vector index can change which rows a query returns. That is the first semantic difference to understand before treating pgvector 0.5.0 as an ordinary indexing upgrade. Its new HNSW index searches a multilayer graph to find nearby vectors without exhaustively comparing every candidate. Faster retrieval comes with a recall tradeoff that belongs in the application's understanding of the result.
The August 28 changelog records HNSW alongside parallel IVFFlat index builds and new vector operations. The documentation pinned to 0.5.0 supplies the relevant behavior and parameter defaults. Keeping that version boundary matters because the new index already has enough distinct choices to explain without importing capabilities from a different release.
I default to PostgreSQL for relational data and value its extension system. Keeping embeddings beside related rows is attractive because it preserves ordinary database access to both. The HNSW addition makes that arrangement more useful, but the approximate result remains a separate promise from the correctness of storing and retrieving the underlying rows.
Exact search remains the starting behavior
Without an approximate index, pgvector performs exact nearest-neighbor search. Its documentation describes that as providing perfect recall. A query orders rows by a distance operator and limits the number returned. The chosen distance defines what nearby means, independently of whether PostgreSQL later uses an approximate index to find candidates.
The version supports Euclidean distance, inner product and cosine distance for nearest-neighbor queries. Their operators are different. In particular, <#> returns the negative inner product because PostgreSQL's operator index scans use ascending order. Treating that value as an ordinary positive similarity score would reverse the meaning unless the application converts it appropriately.
This small example uses the cosine-distance form documented for this version. It shows the table and matching index definition as well as the query, so the operator-class choice is explicit.
CREATE EXTENSION vector;
CREATE TABLE items (
id bigserial PRIMARY KEY,
embedding vector(3)
);
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);
SELECT id
FROM items
ORDER BY embedding <=> '[1,2,3]'
LIMIT 5;
The example's three dimensions keep the SQL readable. The versioned documentation allows vectors with up to 16,000 dimensions in storage, while the supported index dimension limit is 2,000. Storage acceptance and index eligibility are distinct limits. A valid vector column therefore does not establish that every dimension count can use this index.
HNSW changes the index structure
An HNSW index creates a multilayer graph. The 0.5.0 documentation contrasts it with IVFFlat in terms of build time, memory and the speed-recall tradeoff. HNSW builds more slowly and uses more memory, while offering better query performance for that tradeoff. Those are the project's comparative statements, not measurements from a workload of mine.
HNSW has no training step like IVFFlat. It can therefore be created while the table is empty. That difference changes deployment ordering. A migration can establish the HNSW index definition before data arrives, whereas the IVFFlat guidance explicitly recommends building after the table contains data. The reason is the structure each algorithm constructs, rather than a general PostgreSQL requirement that every index needs training.
IVFFlat divides vectors into lists and searches a subset of lists near the query vector. Its tuning parameters include the number of lists selected during construction and the number of probes during querying. The documentation ties useful list choices to the number of rows and explains why too little training data can yield fewer results than expected.
The new graph method removes that particular training dependency. It still incurs construction work and retains a data structure whose size and connectivity need resources. Creating it on an empty table does not mean future inserts have no index-maintenance cost. The practical distinction is that a populated training set is not a prerequisite for constructing the index definition and graph incrementally.
Construction settings and query settings have different lifetimes
The HNSW construction options are m and ef_construction. Here, m limits connections per layer and defaults to 16. Meanwhile, ef_construction sets the dynamic candidate-list size used during graph construction and defaults to 64. These belong to index creation, so changing a search setting later does not mean that PostgreSQL has reconstructed the graph with different construction parameters.
The query setting is hnsw.ef_search, which defaults to 40. It controls the dynamic candidate-list size during search. The documentation says increasing it improves recall at the cost of speed. This provides a per-query resource choice within the same index, distinct from choosing a different distance operator or building another graph.
A transaction-local setting keeps that choice scoped to the intended operation. The versioned README documents SET LOCAL for this purpose.
BEGIN;
SET LOCAL hnsw.ef_search = 100;
SELECT id
FROM items
ORDER BY embedding <=> '[1,2,3]'
LIMIT 5;
COMMIT;
The value 100 is an example from the documentation, not a universal target. More search effort can improve the candidates found, but the statement remains an approximate search using the graph. An application requiring the exact nearest rows needs to preserve that stronger requirement in its choice of search method.
My preference is to make the recall decision explicit in the operation that needs it. A database query returning five identifiers looks familiar, yet the application may be depending on either an exact ordering or an acceptable approximate candidate set. Those requirements should not be conflated merely because both fit the same SQL shape.
Filtering is a separate retrieval problem
The 0.5.0 README discusses nearest-neighbor queries with a WHERE clause separately. It suggests an index on the filtering columns for exact search, a partial vector index for approximate search over a particular condition, and partitioning when many filter values need approximate searches. These are different ways of arranging which rows a search considers.
That distinction matters for data with categories or tenant-specific subsets. A nearby vector in the entire table is not automatically a nearby eligible vector under an application filter. The query must still express eligibility, and the index arrangement must fit the query's candidate population. The documentation's separate filtering section makes that design issue visible without claiming that one graph construction solves every filtered retrieval pattern.
The same README recommends EXPLAIN ANALYZE to inspect query performance. Here the useful point is the selected plan. Writing a distance-ordering query does not itself establish that PostgreSQL used the intended vector index, and a distance predicate alone is not the documented index-use shape. The instructions pair distance ordering with a limit when describing index-backed nearest-neighbor retrieval.
PostgreSQL retains responsibility for the stored data
The extension uses PostgreSQL's write-ahead log, and its versioned documentation states support for replication and point-in-time recovery. Those properties concern preserving and recovering the database state. They do not convert approximate candidate selection into exact search. A faithfully recovered HNSW index still implements the same approximate retrieval method.
Keeping vectors in PostgreSQL also permits ordinary joins with associated relational data. That reduces the need to move identifiers between a separate vector service and the system storing the corresponding records. It does not remove the application's need to choose a distance function or define the quality of acceptable candidates. Those are properties of the retrieval operation.
The vector representation also remains explicit. Each element is a single-precision floating-point value, and the documented storage formula is four bytes per dimension plus eight bytes. Elements must be finite, excluding NaN and infinities. These limits constrain accepted vector data independently of the index choice. Cosine similarity is another separate representation choice, computed as one minus cosine distance in the README. A client displaying a similarity score therefore needs that conversion even when the index and query correctly use the distance operator for ascending nearest-neighbor ordering.
IVFFlat gains a different improvement
The release also adds parallel index builds for IVFFlat. That is useful independently of HNSW because an installation can retain the list-based method and improve its construction workflow. The changelog does not announce parallel HNSW builds in this version, so the generic phrase parallel indexing would overstate what arrives here.
Other additions include an L1-distance function, element-wise vector multiplication and a vector sum aggregate. The L1 function is a new operation, while the documented index operator classes remain the Euclidean, inner-product and cosine choices. An added distance function does not by itself establish a new index access method for that distance.
The resulting release offers a concrete choice between two approximate structures. IVFFlat retains its lower build-time and memory requirements in the documented comparison. HNSW adds the graph option and a query-time candidate-list control without requiring initial training data. PostgreSQL applications can choose between those costs while keeping the vectors and their related relational records in the same database.