ANN Indexes
A vector<n> column is searchable the moment it has rows: with no index, KNN
runs as an exact scan — correct at any size, linear in table size. An ANN
index buys latency at large row counts, and nothing else changes about the
answer.
A distance you see is the true distance. An ANN or quantized index only ever selects candidates; the candidates are re-ranked at full f32 precision before they are returned. An index changes which rows you get, never the distance reported for a row you got. Quantization is a storage and recall trade — never a reported-value trade.
The type model, the distance operators and their opclasses are in Core Concepts. This page is the index itself.
Declaring one
pgvector CREATE INDEX, one column per index. The table needs a
single-column primary key — integer or string. A VARCHAR key builds a
surrogate dictionary index and KNN returns the real string ids, which is what
lets the Qdrant / Pinecone surfaces be ANN-indexed at all; a composite key
is refused with an explicit error and the column serves exact scan.
-- HNSW: high recall, larger build
CREATE INDEX ON docs USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- IVF-Flat: fast build, recall tunable by probe count
CREATE INDEX ON docs USING ivfflat (embedding vector_l2_ops)
WITH (lists = 100);
-- quantized tiers (smaller resident footprint)
CREATE INDEX ON docs USING hnsw (embedding vector_l2_ops) WITH (quantization = 'f16'); -- or 'i8'
CREATE INDEX ON docs USING ivfflat (embedding vector_l2_ops) WITH (lists = 100, quantization = 'sq8');
-- register now, build in the background
CREATE INDEX ON docs USING hnsw (embedding vector_l2_ops) WITH (async);
SHOW VECTOR INDEXES;
-- table | column | kind | metric | dim | state | built_at_version | row_count | errorThe five kinds the f32 serve path offers are hnsw (the default),
hnsw_sq_f16, hnsw_sq_i8, ivfflat and ivf_sq8. The
quantization storage parameter is the pgvector-native spelling; the kind
name is also accepted directly after USING.
An index moves building → ready, or failed with its error kept. There is
no automatic retry — deliberately, to avoid rebuild storms — so re-run
CREATE INDEX to retry. Unknown WITH keys, unknown opclasses and invalid
(method, quantization) pairs are rejected at DDL, never silently defaulted.
Which kind to ask for
Measured against a brute-force exact-KNN oracle: recall@10 at the serve defaults, on clustered corpora that stand in for real embeddings.
| Collection | Ask for | Why |
|---|---|---|
| under ~20 000 vectors | hnsw (the default) | every kind measures ≥ 0.99 here, and hnsw builds fastest with no quantization step. Don’t override the default for a small collection. |
| ~50 000 and above, at embedding dimensions | hnsw_sq_f16 | measured recall@10 holds ≥ 0.987 across 20k → 200k × 768, where an unquantized graph over the same rows falls to 0.434. |
Two things worth knowing before reaching for a quantized kind:
i8is not a cheaperf16. They tie on realistic clustered data and diverge badly on uniformly random vectors — 0.578 against 0.788 at 20 000 × 768. Preferf16unless you have measured your own corpus.hnsw_sq_i8on an inner-product column is refused at DDL. Inner product ranks by vector magnitude, and 8-bit quantization does not preserve magnitude across vectors, so the graph builds fragmented — 22 % of vectors unreachable at 768 dimensions, even after a full serial rebuild. The error names the fix:hnsw_sq_f16(same memory tier, clean on this metric) orhnsw(full precision).
ivf_sq8 is roughly a quarter the code footprint of ivfflat and scores
identically to it in every measured cell. IVF recall depends on how many lists
a query probes — raise ivfflat.probes if your vectors are not well
clustered.
The default is hnsw and has not changed. Quantization is a precision trade,
so any change of default is announced as a behaviour change, never applied
silently.
Beyond a single index, the engine serves large collections as segments — which is what keeps recall stable and build memory bounded as a collection grows into the millions. See Vector at Scale.
Every build proves its own completeness
The build verifies reachability, not recall:
- Every HNSW build walks the finished graph, re-inserts anything unreachable, and declares the exact residual as a scan tail that search covers. A tail past 2 % of the corpus fails the build, so the repair pass can never quietly degrade into a brute-force scan that happens to answer correctly.
- Every IVF build verifies partition completeness — every stored vector in exactly one posting list, nothing missing, nothing twice, nothing out of range. Exact, and O(n).
A build that cannot prove every stored vector reachable fails, leaving the
index failed with its error rather than serving a quietly lossy one. That
contract exists because the alternative was live: a defect that left roughly
1 in 100–200 stored vectors inserted, acknowledged, present in the index and
absent from the graph — invisible to recall measurements, and caught only by
instrumenting the real adjacency.
Tuning and serving
SET hnsw.ef_search = 512; -- HNSW beam width (server default 384)
SET ivfflat.probes = 40; -- IVF probe count (server default ~sqrt(lists))
SET dodil.vector_consistency = eventual; -- default: strong
SET dodil.vector_exact_scan = on; -- permit exact scans on large tablesHigher ef_search / probes raise recall at some latency cost; the defaults
are chosen for high recall out of the box.
When the index serves, and when it doesn’t. Under the default strong
consistency a KNN query is served from the index only when that is safe — the
index is ready and current with no un-compacted writes pending. Otherwise
the engine falls back to an exact scan and schedules a background rebuild, so
correctness never depends on index freshness. eventual always uses the
index and accepts a snapshot that may trail your latest writes.
Residency. Indexes are served from memory under a budget, so pin one you cannot afford to serve cold. Releasing is a latency event, not a data event: a released index does not refuse queries — the next query reloads it lazily, or serves exact until it is back.
LOAD VECTOR INDEX docs (embedding); -- eagerly load + pin (idempotent)
RELEASE VECTOR INDEX docs (embedding); -- evict from RAM
LOAD VECTOR INDEX docs; -- table-wide: pins every ready indexSee Reservation & Hot Cache for how that budget is set.
See also
- Vector at Scale — the guarantees that hold as a collection grows into the millions
- Core Concepts — the
vector<n>type model, metrics, operators and opclasses - Wire Compatibility — the Qdrant and Pinecone adapters
- SQL Compatibility — the KNN operators in the dialect