Skip to content

HNSW & IVFFlat Indexing

Build, tune, and maintain HNSW and IVFFlat approximate nearest-neighbor indexes in pgvector, and compare with vchord.

By default, pgvector performs exact nearest-neighbor search (sequential scan), which delivers 100% recall. When your dataset grows beyond tens of thousands of rows, add an approximate nearest-neighbor (ANN) index to trade a small amount of recall for orders-of-magnitude higher query speed.

pgvector includes two built-in index access methods:

  • HNSW (Hierarchical Navigable Small World): Multi-layer proximity graph. Higher recall-speed trade-off and no training step required, at the cost of slower build times and higher memory usage.
  • IVFFlat (Inverted File with Flat Vectors): K-means clustering over vectors. Faster index builds and smaller index footprint, but requires data to exist before index creation and delivers lower recall per probe than HNSW.
Scaling Beyond HNSW & IVFFlat

When datasets exceed available RAM (e.g., 10M–1B+ vectors) or HNSW index build times become a bottleneck, install vchord alongside pgvector. vchord’s vchordrq and vchordg index access methods work directly on pgvector’s vector and halfvec columns, compressing vectors with RaBitQ to build up to 100x faster and run efficiently from disk/SSD.


HNSW Indexes

An HNSW index constructs a multilayer navigation graph. Unlike IVFFlat, it has no training phase, so you can create an HNSW index on an empty table before loading data.

Creating an HNSW Index

Create an index matching the distance operator used in your queries:

-- L2 distance (<->)
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops);

-- Inner product (<#>)
CREATE INDEX ON items USING hnsw (embedding vector_ip_ops);

-- Cosine distance (<=>)
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);

-- L1 distance (<+>)
CREATE INDEX ON items USING hnsw (embedding vector_l1_ops);

-- Hamming distance (<~>) on bit columns
CREATE INDEX ON items USING hnsw (embedding bit_hamming_ops);

-- Jaccard distance (<%>) on bit columns
CREATE INDEX ON items USING hnsw (embedding bit_jaccard_ops);

Supported dimensionality limits for HNSW:

  • vector: up to 2,000 dimensions
  • halfvec: up to 4,000 dimensions
  • bit: up to 64,000 dimensions
  • sparsevec: up to 1,000 non-zero elements

HNSW Build Parameters

  • m (default 16, range 2–100): Maximum number of bidirectional links per node per layer. Higher m improves recall on high-intrinsic-dimensionality datasets at the cost of larger index size and longer build times.
  • ef_construction (default 64, range 4–1000): Size of the dynamic candidate list during graph construction. Must be ≥2×m\ge 2 \times m.
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops)
WITH (m = 16, ef_construction = 64);

HNSW Query Options

Specify the size of the dynamic candidate list during search (hnsw.ef_search, default 40, range 1–1000). Higher values increase recall at the cost of latency:

SET hnsw.ef_search = 100;

-- Or scope to a single transaction:
BEGIN;
SET LOCAL hnsw.ef_search = 100;
SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 5;
COMMIT;

Accelerating HNSW Index Builds

HNSW construction speed drops sharply once the graph no longer fits into maintenance_work_mem. Before building an HNSW index on a large table, increase maintenance_work_mem and max_parallel_maintenance_workers:

SET maintenance_work_mem = '8GB';
SET max_parallel_maintenance_workers = 7; -- plus leader = 8 total workers
Note

Always create HNSW indexes after bulk-loading initial data with COPY for significantly faster ingestion. In production, use CREATE INDEX CONCURRENTLY to avoid blocking writes.


IVFFlat Indexes

An IVFFlat index partitions vectors into lists Voronoi cells using k-means clustering, and at query time inspects the closest ivfflat.probes lists.

Create IVFFlat After Loading Data

Because IVFFlat computes k-means centroids during CREATE INDEX, you must load representative data into the table before creating the index.

Creating an IVFFlat Index

Choose lists based on table size:

  • Up to 1 million rows: rows / 1000
  • Over 1 million rows: sqrt(rows)
-- L2 distance (<->)
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);

-- Inner product (<#>)
CREATE INDEX ON items USING ivfflat (embedding vector_ip_ops) WITH (lists = 100);

-- Cosine distance (<=>)
CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);

-- Hamming distance (<~>) on bit columns
CREATE INDEX ON items USING ivfflat (embedding bit_hamming_ops) WITH (lists = 100);

Supported dimensionality limits for IVFFlat:

  • vector: up to 2,000 dimensions
  • halfvec: up to 4,000 dimensions
  • bit: up to 64,000 dimensions

IVFFlat Query Options

Set the number of lists probed per query (ivfflat.probes, default 1). A good starting point is sqrt(lists):

BEGIN;
SET LOCAL ivfflat.probes = 10;
SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 5;
COMMIT;

Index Build Progress & Maintenance

Monitoring Index Build Progress

Query pg_stat_progress_create_index while CREATE INDEX is running:

SELECT
    phase,
    round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS "%"
FROM pg_stat_progress_create_index;
  • HNSW phases: initializing →\rightarrow loading tuples
  • IVFFlat phases: initializing →\rightarrow performing k-means →\rightarrow assigning tuples →\rightarrow loading tuples

Vacuuming HNSW Indexes

Vacuuming an HNSW index after heavy deletes can be slow because graph links must be repaired. To speed up DELETE + VACUUM cycles on HNSW tables, rebuild the index concurrently first:

REINDEX INDEX CONCURRENTLY items_embedding_idx;
VACUUM items;