Every index has the same three knobs under different names. Which one you reach for depends entirely on which currency you are short of — and a default cannot know that.
Every index parameter spends one of three currencies to buy another. Which currency is scarce in your system decides every value — and it is the thing a default cannot know.
What fraction of the true answers you actually return.
How long a query takes.
What the structure costs, permanently and per replica.
How many neighbours each node keeps: the width of every step.
How large a candidate pool the build searches before choosing each node’s M neighbours.
How many candidates the bottom-layer walk keeps alive.
nlist · k-means iterations · nprobe
R · build L, alpha · search L, rescore depth
leaves, chunk size · training sample, anisotropic weight · leaves_to_search, reorder depth
—
M 16 · efConstruction 64
M 16 · efConstruction 100
M 16 recommended, range 5–48
M 16 · efConstruction 100
It was chosen deliberately, balancing recall against build time for general use. But at 1536 dimensions with a 95% recall target it will usually leave recall on the table — and no amount of efSearch gets it back.
A genuinely bad configuration at scale. One probe loses every boundary query.
pgvector’s vector type has a 2,000-dimension limit for HNSW indexes. A 3072-dimension embedding cannot be HNSW-indexed there without reducing dimension first.
efConstruction ≥ 4 × M
efSearch ≥ k, target ~5×k
The ratio is what matters
nlist ≈ √(nprobe × N) as a sanity check
L > R
A node should fill a block
Reorder repairs chunk loss
Higher dimension wants higher M
It improves — which feels like progress, because it is.
Latency doubles. Recall moves a little.
Latency is blown and recall is still short.
The graph was built at efConstruction = 64.
You have a search problem.
You have a structure problem.
Ninety-five per cent mean recall can mean every query is slightly imperfect, or it can mean five per cent of queries return near-garbage. Those are wildly different products and the mean cannot tell them apart. Users experience the tail.
Poor edges from a low efConstruction — most queries route fine and some land in a badly connected region and stall.
Queries landing near partition boundaries.
5M products, 768-d. p99 25 ms. Recall@10 target 0.90. 2,000 QPS.
200M chunks, 768-d. p95 200 ms. Recall 0.95. Rebuilt nightly. One machine.
efSearch, nprobe and rescore depth should be explicit values in a config file with a comment saying what recall they were chosen for.
A small gold set, a threshold, and a build that fails when retrieval quality drops.
What is a good value for M?
Recall is below target. What do you do?
Mean recall is 95% but users complain.
Recall dropped and nobody changed anything.
Memory is over budget but recall and latency are fine.
An engineer wants a week to tune the index.
QCan I copy a configuration from a benchmark or a blog post?
As a starting point, not as an answer. The numbers encode somebody else’s scarce currency, corpus size, dimension and recall target. nlist/nprobe is the clearest example: what matters is the ratio and the fraction of the corpus it scans.
QDoes raising efSearch cost memory?
No — and this is load-bearing in the runbook. Live dials cost latency and nothing else, in every index here. The consequence: if memory is your binding constraint, no amount of live-dial tuning will help — and none of it will hurt either. Memory problems are always structural.
QHow do I choose efConstruction if build time is not a problem?
Higher, within reason, because it costs nothing at query time. The returns diminish sharply, so build at two or three values on a sample and compare tail recall, not mean recall — efConstruction shows up in the tail, which is exactly where the mean will not show it.
QShould nprobe be the same for every query?
Not necessarily, and this is a genuinely useful production pattern. nprobe goes in the request, so an interactive path can run cheap while an analytical path runs thorough — against the same index, with no second copy. Very few teams exploit it.
QWhat if I cannot rebuild the index at all?
Then you are limited to the live dial and to what sits outside the index: a reranker over a deeper candidate list, hybrid retrieval, better chunking. And name why you cannot rebuild — usually a node sized so tightly that a rebuild does not fit, which is a capacity decision made months earlier.
QHow often should the sweep be repeated?
When the data or traffic changes materially, and quarterly otherwise. The reason is drift: the operating point that hit 95% on last year’s corpus is not guaranteed to hit it on this year’s, and nothing will tell you. That is what the gold set in CI is for.
QIs there a single number that summarises index quality?
No — be suspicious of anyone offering one. The honest summary is a pair: recall at a stated latency, on your corpus. Latency without recall is meaningless because anything can be fast if allowed to be wrong; recall without latency is meaningless because anything can be accurate if allowed to be slow.
QMy engine hides these parameters. Is that better?
Better until it is not. Auto-tuning handles the common case and removes a real source of misconfiguration. What you lose is the ability to make the tradeoff deliberately — to say “memory is what is scarce here, so buy recall with latency instead”. Ask what the service optimises for, and whether you can see the recall it is achieving.
Name the scarce currency first, because it decides every value — and no default can know it. Then check the two constraints before measuring anything: efSearch at least k (and k is a hundred, not ten, if a reranker follows) and efConstruction at least four times M. Then sweep the live dial and read the shape of the curve, because the plateau is the diagnosis: still climbing means keep turning the free knob; flat below target while latency rises means no amount of search effort repairs a badly built graph. And plot the distribution, not the mean — users experience the tail.
Which currency is scarce here — recall, latency or memory?
Is there a flat baseline, and a gold set?
Is the operating point pinned in config with a comment saying what recall it was chosen for?
Is efSearch sized against k — or against the reranker’s candidate pool?
Has anyone looked at the distribution of recall and latency, not the mean?
When did the sweep last run, and what changed since?