1. Home
  2. IVFFlat
Guide · indexing

IVFFlat indexes in pgvector

IVFFlat splits your vectors into clusters and searches only the closest few. It builds fast and needs little memory to build — but with default settings it misses many true neighbours, and for 1,536-dim embeddings its index is no smaller than HNSW. Here's how to set it up properly, with measurements.

Tested on pgvector 0.8.7 · PostgreSQL 18.6Updated 2026-10-276 min read

Short answer: build IVFFlat after loading data, with lists ≈ rows / 1000, and raise ivfflat.probes well above its default of 1. On 100,000 OpenAI embeddings with 316 lists, probes = 1 found only 55.0% of the true top-10; probes = 16 found 91.2%. Unless build time is your constraint, HNSW is the better default.

How IVFFlat works#

At build time, k-means picks lists centre points from the data and assigns every vector to its nearest centre. A query compares itself with all centres, then scans every vector in the probes nearest lists. Vectors near a cell boundary are the problem: their true neighbours may sit in the next cell over.

probed centresfoundmissed

Real k-means and IVF search on 500 random 2-D points, in your browser. Drag the query to a cell boundary with probes = 1 and watch true neighbours go missing.

Creating the index#

psqlpsql · PostgreSQL 18.6 · pgvector 0.8.7
demo=# CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
CREATE INDEX

demo=# SET ivfflat.probes = 10;
SET

Unlike HNSW, IVFFlat learns from the rows present at build time. Built on an empty table, it warns — and recall collapses as rows arrive:

psqlpsql · PostgreSQL 18.6 · pgvector 0.8.7
demo=# CREATE TABLE empty_items (id bigserial PRIMARY KEY, embedding vector(3));
CREATE TABLE

demo=# CREATE INDEX ON empty_items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);
NOTICE:  ivfflat index created with little data
DETAIL:  This will cause low recall.
HINT:  Drop the index until the table has more data.
CREATE INDEX

probes: the recall knob#

IVFFlat recall@10 vs probes (100K rows)

pgvector's default is probes = 1. More lists means smaller lists, so each probe covers less of the table.

  • 100 lists
  • 316 lists
  • 1000 lists
listsprobesRecall@10p50p95
10010.6192.7 ms7.1 ms
10020.7634.3 ms9.1 ms
10040.8507.9 ms14 ms
10080.92116 ms27 ms
100160.96528 ms41 ms
100320.98554 ms75 ms
100640.998104 ms131 ms
31610.5501.4 ms2.4 ms
31620.6741.9 ms3.2 ms
31640.7783.1 ms4.9 ms
31680.8575.5 ms8.8 ms
316160.9129.9 ms15 ms
316320.95319 ms26 ms
316640.97835 ms49 ms
100010.4851.4 ms2.0 ms
100020.6131.5 ms2.0 ms
100040.7162.0 ms3.2 ms
100080.7972.7 ms4.1 ms
1000160.8604.4 ms6.9 ms
1000320.9107.4 ms12 ms
1000640.94713 ms18 ms
Measured on PostgreSQL 18.6 + pgvector 0.8.7, 4 vCPU / 15 GB, DBpedia × OpenAI text-embedding-3-large.Method & raw data

Each line is a different lists value on the same 100,000 rows. At probes = 1, more lists means fewer vectors per list and worse recall. As probes rises, every configuration converges; what changes is how much work it takes. Setting it is the same as for HNSW:

sql
SET ivfflat.probes = 16;          -- session
SET LOCAL ivfflat.probes = 32;    -- one transaction

Choosing lists#

We built three indexes at each size — rows / 1000, √rows, and one deliberately large — and swept probes:

RowslistsBuildSizeRecall, probes 1Recall, probes 16p50, probes 16
100,0001009.8 s782 MB0.6190.96528 ms
100,00031623 s784 MB0.5500.9129.9 ms
100,0001,0002.1 min789 MB0.4850.8604.4 ms
500,0005001.7 min3.82 GB0.5530.91330 ms
500,0007072.3 min3.82 GB0.5610.90223 ms
500,0002,0008.5 min3.83 GB0.4990.85613 ms

IVFFlat recall vs latency at 500K rows

Points are probes 1 → 64. lists = rows/1000 (500) and √rows (707) are the usual rules of thumb.

  • 500 lists
  • 707 lists
  • 2000 lists
listsprobesRecall@10p50
50010.5535.0 ms
50020.6866.8 ms
50040.78010 ms
50080.85816 ms
500160.91330 ms
500320.95156 ms
500640.976111 ms
70710.5613.5 ms
70720.6764.6 ms
70740.7717.9 ms
70780.84314 ms
707160.90223 ms
707320.94143 ms
707640.97082 ms
200010.4993.8 ms
200020.6234.1 ms
200040.7225.9 ms
200080.7978.0 ms
2000160.85613 ms
2000320.90419 ms
2000640.94232 ms
Measured on PostgreSQL 18.6 + pgvector 0.8.7, 4 vCPU / 15 GB, DBpedia × OpenAI text-embedding-3-large.Method & raw data

Read the curves by latency, not by probe count. More lists need more probes for the same recall, but each probe scans a smaller list, so per millisecond the larger lists values came out ahead at both sizes: at 500K rows, 2,000 lists reached 0.942 in 32 ms while 500 lists took 56 ms for 0.951. What you pay is build time — k-means over more centres — 8.5 min versus 1.7 min. The rows / 1000 rule is a reasonable middle; go higher if query latency matters more than rebuild time.

IVFFlat vs HNSW#

Recall vs latency: HNSW and IVFFlat on 100K embeddings

Each point is one setting of ef_search (HNSW) or probes (IVFFlat). Up and to the left is better.

  • HNSW (m 16, ef_construction 64)
  • IVFFlat (316 lists)
IndexSettingRecall@10p50 latency
HNSWef_search 100.8151.4 ms
HNSWef_search 200.8971.9 ms
HNSWef_search 400.9472.9 ms
HNSWef_search 640.9703.9 ms
HNSWef_search 1000.9835.3 ms
HNSWef_search 2000.9909.1 ms
HNSWef_search 4000.99516 ms
IVFFlatprobes 10.5501.4 ms
IVFFlatprobes 20.6741.9 ms
IVFFlatprobes 40.7783.1 ms
IVFFlatprobes 80.8575.5 ms
IVFFlatprobes 160.9129.9 ms
IVFFlatprobes 320.95319 ms
IVFFlatprobes 640.97835 ms
Measured on PostgreSQL 18.6 + pgvector 0.8.7, 4 vCPU / 15 GB, DBpedia × OpenAI text-embedding-3-large.Method & raw data

Build time and index size: IVFFlat vs HNSW (100K rows)

Same table, maintenance_work_mem 4 GB, single-process builds.

IndexBuild timeSize
IVFFlat 100 lists9.8 s782 MB
IVFFlat 316 lists23 s784 MB
IVFFlat 1000 lists2.1 min789 MB
HNSW m16/efc643.4 min781 MB
Measured on PostgreSQL 18.6 + pgvector 0.8.7, 4 vCPU / 15 GB, DBpedia × OpenAI text-embedding-3-large.Method & raw data
IVFFlatHNSW
Recall vs latencyLower at every latency we measuredHigher
Build time (100K rows)23 s (316 lists)3.4 min
Build memorySmall: list centres onlyWhole graph
Index size (100K × 1,536)784 MB781 MB — both store one 6 KB vector per 8 KB page
Empty-table creationNo — needs data to trainYes
Inserts after buildFine, but centres go stale as data driftsFine
DistancesL2, inner product, cosine, HammingAll six

Choose IVFFlat for large, mostly static tables where build time or build memory is the binding constraint and you can rebuild periodically. Choose HNSW otherwise. Don't choose IVFFlat to save disk: for full-size embeddings it isn't smaller — use halfvec for that.

Iterative scans#

Since 0.8.0, IVFFlat can keep probing additional lists until a filtered query has enough rows: SET ivfflat.iterative_scan = relaxed_order; (capped by ivfflat.max_probes). Only relaxed_order is supported for IVFFlat. Details and measurements in the filtering guide.

When to rebuild#

List centres are fixed at build time. If your data distribution shifts — a new product category, a new language, a different embedding model — recall degrades even though nothing errors. Rebuild with REINDEX INDEX CONCURRENTLY after large changes, and re-derive lists from the new row count.

Frequently asked questions

How many lists should I use for IVFFlat?

The upstream rule of thumb is rows / 1000 for up to 1M rows and √rows beyond that. More lists means each probe scans fewer vectors, so you need more probes but each is cheaper: at 100K rows, 1,000 lists reached 0.860 recall in 4.4 ms (16 probes), while 100 lists needed 7.9 ms for 0.850 (4 probes). The price is build time: 2.1 min versus 9.8 s.

What should ivfflat.probes be set to?

Not 1, the default. With 316 lists on 100K rows, probes = 1 found 55.0% of the true top-10; probes = 16 found 91.2% at a median 9.9 ms. Start around √lists and measure.

Is IVFFlat or HNSW better in pgvector?

HNSW gave higher recall at lower latency in every comparison we ran, handles inserts without retraining and can be built on an empty table. IVFFlat's advantages are build time (23 s vs 3.4 min on our 100K table) and lower build memory.