omiid
homenotebookai usage

PgVector indexing options for vector similarity search

July 31, 2024 · Updated on August 09, 2026

Adding pgvector to PostgreSQL does not make vector similarity search fast on its own. Without an index, every query scans the whole table and computes an exact distance for each row. The index types are what buy you speed, and pgvector ships two of them. If you're using Drizzle ORM, I also wrote about how to set up pgvector with Drizzle including schema definition and index creation.

The two pgvector index types are HNSW (Hierarchical Navigable Small World) and IVFFlat (Inverted File Flat). Both are approximate, so both trade some recall for speed. They differ in where that trade lands.

I write short, practical notes like this one. Get the next one by email:

Unsubscribe anytime.

HNSW wins on the speed-recall trade-off

HNSW arrived in pgvector 0.5.0. It usually returns better recall at a given query speed than IVFFlat, and it pays for that with a slower build. Three things to know:

  1. It creates a multilayer graph structure for efficient searching.
  2. You can create an HNSW index on an empty table, so it works before you load any rows.
  3. It supports vectors up to 2,000 dimensions, or 4,000 if you're using half-precision.

To create an HNSW index, you'd use something like this:

CREATE INDEX ON items USING hnsw (embedding vector_l2_ops);

You can tune HNSW performance with two main parameters:

  • m: The max number of connections per layer (default is 16)
  • ef_construction: The size of the dynamic candidate list for constructing the graph (default is 64)

For example:

CREATE INDEX ON items USING hnsw (embedding vector_l2_ops) WITH (m = 16, ef_construction = 64);

During queries, you can adjust the ef_search parameter to balance between speed and recall:

SET hnsw.ef_search = 100;

IVFFlat builds faster and uses less memory

IVFFlat is the older method and takes a different approach:

  1. It divides vectors into lists and searches a subset of those lists.
  2. It has faster build times and uses less memory than HNSW.
  3. It's better suited for scenarios where you need a large number of results (500+).

The shortest IVFFlat create index example takes the defaults and needs data in the table first:

CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops);

Set lists yourself to control how many partitions the index builds:

CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);

The key to good performance with IVFFlat is choosing the right number of lists. A good rule of thumb is:

  • For up to 1M rows: use rows / 1000
  • For over 1M rows: use sqrt(rows)

During queries, you can adjust the number of probes to balance between speed and recall:

SET ivfflat.probes = 10;

IVFFlat vs HNSW: pick by query speed or build time

Four guidelines cover most cases:

  1. If you need the fastest queries and can afford longer index build times, go with HNSW.
  2. If you're dealing with a large number of vectors and need faster index creation, IVFFlat might be a better choice.
  3. For queries that return a large number of results (500+), IVFFlat is generally more suitable.
  4. If you're working with an empty or small table that will grow over time, HNSW can be a good option since it doesn't require data to create the index.

You can also create both index types on the same column and compare their performance on your own data.

Index the halfvec type to halve index size

The pgvector halfvec type stores each dimension as a 16-bit float instead of a 32-bit one. Casting to it inside the index expression halves the index size and raises the dimension ceiling to 4,000:

CREATE INDEX ON items USING hnsw ((embedding::halfvec(3)) halfvec_l2_ops);

The column itself stays vector, so only the index pays the precision cost. That matters most on large datasets with high-dimensional embeddings.

tl;dr

pgvector offers two main indexing options: HNSW and IVFFlat. HNSW is faster for queries but slower to build, while IVFFlat is quicker to build and better for large result sets. Choose HNSW for speed and IVFFlat for flexibility with large datasets. Don't forget to experiment with parameters like ef_search for HNSW and probes for IVFFlat to fine-tune performance. Not sure which distance operator to pair with your index? Here's what <->, <=>, and <#> actually mean.

Join My Newsletter

Occasional notes on software, tools, and things I learn. No spam.

Unsubscribe anytime.

Continue Reading
  • Tuning Postgres and pgvector: the three knobs that matter08-18-2026 · Most pgvector performance problems come down to three settings. This post shows how to read an ANN query plan and tune ef_search, shared_buffers, and work_mem in the right order.
  • AI text watermarking: how it works and what it can't do08-16-2026 · Claude now watermarks its text. The watermark changes where the randomness in word choice comes from, not what the model can say. Here is the whole pipeline, with simulations you can poke at.
  • HNSW vs IVFFlat: choosing and building your pgvector index08-14-2026 · Past a few hundred thousand rows, an exact scan stops being fast enough. Here is how to pick between HNSW and IVFFlat and build the index without locking the table.
  • Vector search relevance: chunking, metadata, and the 0.81 problem08-11-2026 · Most bad vector search results come from one of three failure modes: chunking, modality mismatch, or a confused model. Each one has a specific diagnostic and a specific fix.
  • pgvector setup: your first multimodal query in TypeScript08-03-2026 · One Postgres table can hold text and screenshot embeddings in the same vector column. This post sets up the schema, the Voyage embedding call, and the first query that returns both.