Choose IVFFlat for very large vector sets where memory is the constraint and a small recall loss is acceptable at build time.
A library with N subject rooms. Each incoming vector is assigned to the k nearest rooms. To find neighbours, only those rooms are searched.
corpus -> k-means clustering -> centroids (probes)
query -> find nearest nprobe centroids -> flat-scan those clusters
nprobe * cluster_size = effective scanned vectors
IVFFlat clusters the corpus into k buckets at build time. Each query is compared first against a small set of cluster centroids (called probes); only those probes are flat-scanned. Scaling: O(k + nprobe * cluster_size) instead of O(n). Use IVFFlat when memory is critical; use HNSW when recall is critical. IVFFlat recall depends on nprobe (higher = better recall, slower).
# Postgres
sql
CREATE INDEX idx_chunks_ivf ON chunks
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100); -- rule of thumb: lists ~ sqrt(rows) for up to 1M rows
lists=100 is a sensible default for 100K rows; for 1M+ rows use sqrt(rows).
SET ivfflat.probes = 4; -- start small; raise for recall
SELECT id FROM chunks
ORDER BY embedding <=> :query_vec LIMIT 5;
probes=4 balances latency vs recall; raise to 8-16 when benchmark shows it lifts Quality without SLA breaks.
import faiss, numpy as np
quantiser = faiss.IndexFlatL2(d)
index = faiss.IndexIVFFlat(quantiser, d, 100, faiss.METRIC_INNER_PRODUCT)
index.train(np.random.random((100000, d)).astype('float32'))
index.nprobe = 4
index.add(...)
faiss IndexIVFFlat requires training data first; pick a representative sample.
Using IVFFlat on small datasets (<= 5K rows). With so few rows the clustering overhead exceeds the brute-force scan.
IVFFlat is the memory-efficient ANN. Train with a sample; pick lists ~= sqrt(rows); probes is your recall/latency knob.