Vector Databases / Indexing
Trading a little accuracy for enormous speed.
Reviewed by Yuvaraj
Approximate nearest neighbor (ANN) search is the engine behind fast vector retrieval. Exact nearest-neighbor search compares your query against every stored vector, perfectly accurate, but linear in the size of the collection, which collapses at millions or billions of vectors. ANN indexes trade a sliver of accuracy for orders-of-magnitude speedups by cleverly avoiding most of those comparisons. Two index families dominate production systems: HNSW, a proximity graph, and IVF, a clustering scheme often paired with compression. Tuning either one well means understanding exactly what you give up.
HNSW (Hierarchical Navigable Small World) builds a layered proximity graph. Every vector is a node linked to its nearest neighbors, and each node is assigned a random maximum layer from an exponentially decaying distribution, so upper layers are sparse while the bottom layer (layer 0) holds every node with dense connectivity. Search greedily walks the graph from the top down.
For a curious beginner
Finding a neighbor is like asking for directions in an unfamiliar city. You never visit every house, you hop from one person to the next, each pointing you a little closer to your destination, until nobody nearby knows anyone closer.
How it is actually used
It is greedy best-first search over a graph. Sparse long-range links in the upper layers cover large distances in a few hops; dense short-range links at layer 0 fine-tune the result. A candidate heap of size efSearch controls how much of the frontier stays alive.
The underlying mechanism
Navigable small-world graphs have expected path length on the order of . The hierarchy makes the number of distance evaluations scale roughly as , versus for a brute-force scan.
Ask about this lesson, or about anything in AI. Answers cite the lessons they draw on.
Finished this lesson?
Mark it complete to earn XP, keep your streak, and schedule a review.
Three parameters govern behavior. M is the maximum neighbors per node: higher M improves recall and connectivity but costs memory and slows build and search. efConstruction is the candidate-list size during insertion: larger values build a higher-quality graph (a higher recall ceiling) but take longer. efSearch is the candidate-list size at query time: larger values explore more of the graph, raising recall while adding latency. efSearch must be at least .
IVF (inverted file) takes the opposite approach: partition instead of connect. A k-means pass over a training sample produces nlist centroids that carve the space into Voronoi cells, and every vector is filed under its nearest centroid. At query time you measure the query against the centroids and scan only the nprobe closest cells. Small nprobe is fast but risks missing a true neighbor that fell just across a cell boundary; large nprobe approaches an exhaustive scan.
IVF is frequently paired with product quantization (PQ) to shrink memory. PQ splits each vector into subvectors and replaces each with the nearest entry from a small learned codebook (commonly 256 entries, one byte each). A vector that was thousands of bytes becomes bytes, and distances are approximated from precomputed lookup tables. IVF-PQ is the classic recipe for billion-scale search on limited RAM, at the cost of accuracy lost to compression.
Every ANN index balances three quantities that pull against one another: recall, latency, and memory. You can usually improve any two by sacrificing the third. Turning up efSearch or nprobe buys recall with latency. Quantization buys memory with recall. Raising M or efConstruction buys recall with memory and build time. No single setting maximizes all three, which is why tuning is workload-specific.
Crucially, recall is measured against exact k-NN. Recall@k is the fraction of the true top-k neighbors, as returned by a brute-force exact search, that your index actually retrieves. Without that exact baseline you cannot tell whether an index returns good results or quietly drops half of them.
The core loop at each layer is simple:
Picture a tiny bottom-layer graph where lower numbers mean closer to the query . You enter at node A (distance 0.9). A links to B (0.6) and C (0.7), so you hop to B. B links to D (0.3) and C (0.7), so you hop to D. D's neighbors are F (0.35) and B (0.6), neither beats 0.3, so greedy search stops and returns D. But the true nearest is G (0.2), reachable only through F, which looked slightly worse than D. D is a local optimum.
This is exactly what efSearch fixes. With efSearch = 1 the search keeps only its single best candidate and commits to D. Raising it keeps a priority queue of the best efSearch candidates, so F (0.35) stays on the frontier, its neighbor G (0.2) gets evaluated, and recall improves, paid for by the extra distance computations that widen latency.
efSearch | Candidate frontier | Recall@10 | Relative latency |
|---|---|---|---|
| 16 | narrow | ~0.82 | 1.0x |
| 64 | moderate | ~0.95 | 2.1x |
| 128 | wide | ~0.98 | 3.4x |
| 256 | very wide | ~0.99 | 5.6x |
The numbers are illustrative, not a benchmark, but the trend is universal: recall rises with diminishing returns while latency keeps climbing. You tune efSearch to hit a recall target, then stop.
Common mistakes
efSearch. Retrieval that misses obvious matches is often an index tuned too tight (efSearch or nprobe too low), not a bad model.M and efConstruction inflate index size and build time; an HNSW graph can dwarf the raw vectors in RAM.