IVFFlat vs HNSW Indexes
pgvector offers IVFFlat (inverted file with coarse quantization) and HNSW (hierarchical navigable small world) for approximate nearest neighbor search. Choose based on recall targets, RAM budget, and build-time tolerance.
Recipe
-- IVFFlat: cluster lists, probe at query time
CREATE INDEX ON documents
USING ivfflat (embedding extensions.vector_cosine_ops)
WITH (lists = 100);
SET ivfflat.probes = 10;
-- HNSW: graph index (pgvector 0.5+, default choice for many workloads)
CREATE INDEX ON documents
USING hnsw (embedding extensions.vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
SET hnsw.ef_search = 40;When to reach for this: Table exceeds ~50k vectors and exact ORDER BY distance LIMIT k misses latency SLO.
Working Example
-- Benchmark recall vs latency (run in staging with labeled query set)
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, embedding <=> $1 AS dist
FROM documents
ORDER BY embedding <=> $1
LIMIT 10;
-- Compare index scans
DROP INDEX IF EXISTS documents_ivfflat_idx;
CREATE INDEX documents_ivfflat_idx ON documents
USING ivfflat (embedding extensions.vector_cosine_ops) WITH (lists = 200);
DROP INDEX IF EXISTS documents_hnsw_idx;
CREATE INDEX documents_hnsw_idx ON documents
USING hnsw (embedding extensions.vector_cosine_ops)
WITH (m = 16, ef_construction = 128);
-- Measure: p50/p95 latency, recall@10 vs brute force on sample queriesWhat this demonstrates:
- IVFFlat needs
liststuning andivfflat.probesat query time - HNSW exposes
m,ef_constructionat build andhnsw.ef_searchat query EXPLAIN ANALYZEconfirms index scan vs seq scan- Recall measurement requires brute-force gold standard on held-out queries
Deep Dive
IVFFlat
Build: k-means clusters (lists centroids). Each vector assigned to nearest list.
Query: Search probes nearest lists, then exact distance within those lists.
| Parameter | Effect |
|---|---|
lists | More lists = smaller cells, larger index, slower build |
ivfflat.probes | Higher = better recall, slower queries |
Pros: Lower memory than HNSW for some shapes; mature on older pgvector.
Cons: Recall sensitive to data distribution; needs REINDEX after large data shifts; lists should scale with sqrt(rows) rule of thumb.
-- lists heuristic for N rows: sqrt(N) to N/1000
-- 1M rows: lists between 1000 and 10000 depending on benchmarksHNSW
Build: Layered graph connecting neighbors.
| Parameter | Effect |
|---|---|
m | Max edges per node (16 common) |
ef_construction | Build-time candidate list (higher = better graph, slower build) |
hnsw.ef_search | Query-time candidate list (higher = better recall, slower) |
Pros: Strong recall/latency default for pgvector 0.8+; less sensitive to insert order than IVFFlat.
Cons: Higher RAM during build; index larger on disk; vacuum interactions need monitoring at scale.
Decision Matrix
| Factor | Prefer IVFFlat | Prefer HNSW |
|---|---|---|
| Recall priority | Moderate, tunable with probes | High out of box |
| Build RAM | Tighter budget | Can spare RAM |
| Steady inserts | Batch rebuild IVFFlat periodically | HNSW handles inserts better |
| pgvector version | Legacy constraints | 0.8+ production default |
| Query latency SLO | Flexible | Strict p95 |
Index Maintenance
-- After bulk embed import
REINDEX INDEX CONCURRENTLY documents_hnsw_idx;
ANALYZE documents;
-- Monitor invalid indexes
SELECT indexrelid::regclass FROM pg_index WHERE NOT indisvalid;Gotchas
- IVFFlat before data loaded - Empty table build yields useless centroids. Fix: Build index after bulk load or use
REINDEXafter import completes. - lists = 1 - Absurd recall. Fix: Start
sqrt(n)and sweep probes in benchmark script. - ef_search too low - HNSW misses neighbors. Fix: Raise
hnsw.ef_searchuntil recall@k plateaus on golden set. - Opclass mismatch - Still the top pgvector footgun. Fix:
vector_cosine_opswith<=>. - CONCURRENTLY build failure - Invalid HNSW left behind. Fix: Drop invalid index; reduce
maintenance_work_memspikes or build off-peak without CONCURRENTLY on empty clone. - Comparing apples to oranges - Different k, different metrics. Fix: Fixed k=10, same 100 queries, same distance operator for all runs.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Exact scan | < 10k rows | Million-row corpus |
| Partitioned HNSW per tenant | Massive multi-tenant | Tiny tenants, ops overhead |
| External ANN service | Proven PG miss at scale | Join-heavy retrieval pipelines |
| IVFFlat now, HNSW later | Memory emergency | You can afford one rebuild |
FAQs
Default recommendation PG 18?
HNSW with m=16, ef_construction=64-128; tune ef_search per recall target.Can I have both indexes?
Only one should be used by planner; drop unused to save space.Halfvec HNSW?
Supported in recent pgvector; halves storage when model allows.Parallel index build?
Use max_parallel_maintenance_workers; HNSW build is CPU and RAM heavy.Hot updates?
Frequent embedding updates fragment graph; schedule REINDEX if recall drifts.Filtered ANN?
Partial indexes per tenant or metadata predicate; pgvector lacks rich pre-filter in all versions.Cloud limits?
Some managed instances cap maintenance_work_mem; affects build success.IVFFlat probes per session?
SET ivfflat.probes in connection pool session or SET LOCAL in transaction.Benchmark script?
Export 100 query embeddings; compare top-10 ids to brute force.Upgrade 0.7 to 0.8?
Read release notes; plan REINDEX after ALTER EXTENSION UPDATE.Related
- pgvector Basics - operators and types
- pgvector at Scale - memory and vacuum
- RAG Storage Schema - schema layout
- REINDEX CONCURRENTLY - online rebuild
- pgvector Best Practices - do not trust defaults
Stack versions: This page was written for PostgreSQL 18.4 (stable 18, maintenance 17), pgvector 0.8+, PostGIS 3.5+, pgbouncer 1.x, and Patroni 3.x.