Skip to main content

Vector Search

The inverted index also indexes vector embeddings for approximate nearest neighbor (ANN) search, using an IVF (Inverted File) index. This powers semantic search, recommendations and other similarity workloads over FLOAT vectors.

IVF partitions the vectors into nlist coarse clusters (found by k-means at build time). A query first identifies the clusters closest to the query vector, then computes distances only within those, so a search touches a small fraction of the vectors instead of scanning them all. That is what makes it approximate — it trades a little recall for a large speed-up, and the number of clusters scanned (nprobe, a query-time setting) tunes that trade-off. Optional quantization (quant) compresses each stored vector to shrink the index further, at some additional recall cost that can be recovered by reranking.

Creating a vector index

A vector column uses the ivf (...) operator class. The column must be a fixed-size FLOAT array (FLOAT[N]) — every row shares the same dimension N:

Query
CREATE TABLE vectors (id INTEGER, emb FLOAT[3]);
CREATE INDEX l2_index ON vectors USING inverted (id, emb ivf (metric = 'l2'));

The metric is required. Everything else is optional and defaults to an unquantized index sized automatically from the row count:

ParameterDescription
metricDistance metric: l2 (Euclidean), cosine, ip (inner product) or l1 (Manhattan)
nlistNumber of coarse clusters. Higher values narrow each cluster (faster, more precise probes) at the cost of build time. Mutually exclusive with nlist_factor
nlist_factorSizes nlist relative to the row count as round(nlist_factor * sqrt(rows)). Default 2.0. Mutually exclusive with nlist
quantVector compression: none (default), sq8, sq4, pq or rabitq — see Quantization below. Only valid with metric l2 or ip
pq_mNumber of subquantizers for quant = 'pq'. Must evenly divide the vector dimension N. Defaults to a value close to a 2-dimensional subvector
rabitq_bitsExtra magnitude bits per dimension for quant = 'rabitq', 19. Default 1 (sign-only)

There are two ways to query a vector index: k-nearest-neighbor search (the closest k vectors) and range search (every vector within a distance threshold).

k-nearest-neighbor (kNN)

Order by the distance to a query vector and LIMIT to the number of neighbors you want. Each distance operator computes a fixed metric — <-> is L2, <=> is cosine, <+> is L1 and <#> is inner product — so use the one matching the metric your index was built with; the optimizer then routes the query through the IVF index:

SELECT id FROM index_name ORDER BY emb <-> $query_vector LIMIT k;
Query
SELECT id FROM l2_index ORDER BY emb <-> [0, 0, 0]::FLOAT[3] LIMIT 2;
Result
 id----  3  2

The named distance functions l2_distance, cosine_distance, l1_distance and negative_inner_product are equivalent to the matching operator and can be used explicitly:

Query
SELECT id FROM cos_index ORDER BY cosine_distance(emb, [1, 0, 0]::FLOAT[3]) LIMIT 2;
Result
 id----  1  3

Instead of a fixed number of neighbors, return every vector within a distance threshold (a radius) by comparing the distance in a WHERE clause:

SELECT id FROM index_name WHERE emb <-> $query_vector < radius;
Query
SELECT id FROM l2_index WHERE emb <-> [0, 0, 0]::FLOAT[3] < 100 ORDER BY id;
Result
 id----  2  3

The two forms combine: add ORDER BY emb <-> $query_vector LIMIT k to a range query to take the closest k within the radius.

Quantization

By default (quant = 'none') the index stores full-precision vectors. Setting quant compresses the stored codes to shrink the index, trading some recall for size — recoverable with sdb_rerank_factor, which re-scores a candidate pool with exact distances before picking the final k:

quantCompressionNotes
none (default)noneFull-precision vectors; never reranks regardless of sdb_rerank_factor
sq88-bit scalar quantization per dimensionGood recall/size trade-off; a reasonable default when shrinking a large index
sq44-bit scalar quantization per dimensionSmaller than sq8, lower recall before reranking
pqProduct quantization — the vector is split into pq_m subvectors, each quantized against its own small codebookHighest compression; recall is sensitive to pq_m (must divide N)
rabitqRaBitQ binary quantization, 1 bit per dimension plus rabitq_bits − 1 extra magnitude bitsVery compact; rabitq_bits (1–9) trades size for recall

quant only applies to metric = 'l2' or 'ip' indexes — cosine and l1 indexes are always unquantized.

Quantization also speeds up the scan itself, not just the index size: quantized codes are stored inline in each cluster's postings, laid out contiguously per cluster, so a probe reads them sequentially instead of chasing full vectors elsewhere; and comparing quantized codes (a table lookup for pq, a popcount for rabitq, integer arithmetic for sq8/sq4) is cheaper than a full-precision FLOAT[N] distance. So quant is a query-latency optimization as much as a storage one — the smaller, posting-aware layout is what lets nprobe scan more clusters for the same latency budget.

Column types

A vector column must be a fixed-size FLOAT[N] array — all rows share dimension N (an unsized FLOAT[] is rejected). Unlike text and INCLUDEd columns, a vector column does not take a storage compression codec — use quant instead to control its on-disk size.

See also