pgvector HNSW vs IVFFlat: A Reproducible Comparison

Ghazi · July 31, 2026

HNSW and IVFFlat are approximate-nearest-neighbor indexes with different build, memory, update, and recall behavior. A benchmark copied from another machine cannot tell you which one fits your vectors, filters, hardware, and latency target.

Measure exact search first, then build each approximate index separately on the same loaded dataset. Use identical query vectors and filters, record recall against exact top-k results, and compare build time, index size, latency percentiles, and write behavior.

Create the two candidate index definitions

-- Test one index at a time.
create index documents_embedding_hnsw_idx
  on documents using hnsw (embedding vector_cosine_ops)
  with (m = 16, ef_construction = 64);

create index documents_embedding_ivfflat_idx
  on documents using ivfflat (embedding vector_cosine_ops)
  with (lists = 100);

Do not leave both indexes present during the comparison. PostgreSQL may choose either, and both consume storage and write work.

Establish exact-search ground truth

Run the chosen query vectors without an approximate index and save the exact top-k document IDs. This is the reference used to calculate recall. If exact search already meets the latency target, an approximate index may add complexity without useful benefit.

Use real query vectors rather than randomly generated vectors alone. Production queries can cluster differently from stored documents and expose failures that a synthetic uniform sample misses.

Exact cosine-distance search

select id, embedding <=> $1::vector as distance
from documents
where tenant_id = $2
order by embedding <=> $1::vector
limit 20;

Run this before creating an HNSW or IVFFlat index, or in a controlled session where approximate index scans are disabled.

Build after loading representative data

HNSW can be created without a training step and handles ongoing inserts well, but construction can use substantial time and memory. IVFFlat should be built after the table contains representative data because it partitions the existing vectors into lists.

Record elapsed build time, peak memory pressure, and pg_relation_size for each index. A fast query index that makes deployment or recovery impractical is not automatically the better choice.

Tune the query-side controls

HNSW recall and latency are influenced by hnsw.ef_search. IVFFlat uses ivfflat.probes. Higher values inspect more candidates and usually improve recall at a latency cost. Set them locally inside a transaction so pooled connections do not inherit accidental session state.

Test search parameters locally

begin;
set local hnsw.ef_search = 100;
-- Or, for the IVFFlat run:
-- set local ivfflat.probes = 10;

select id
from documents
order by embedding <=> $1::vector
limit 20;
rollback;

Include filters and writes in the test

A global nearest-neighbor query can look excellent while a tenant or category filter returns too few relevant rows. Repeat the benchmark with the same filters used by the application and watch the plan with EXPLAIN ANALYZE.

Insert and update a realistic batch after index creation. HNSW generally tolerates changing data more naturally. IVFFlat centroids can become less representative as the dataset drifts, so recall should be monitored over time.

Choose from recorded tradeoffs

HNSW is often the practical default when query recall matters and the dataset changes. IVFFlat remains useful when build time or memory is constrained and the data is large and relatively stable. Small tables may need neither.

If the application also searches text, test the index inside the hybrid-search pipeline rather than optimizing vector latency in isolation.