Skip to content
AI.info

Technical Deep Dives

Vector Databases and Embedding Search

A deep dive into vector databases, embedding spaces, similarity search algorithms, and how they power modern AI applications from RAG to recommendation systems.

Vector Databases and Embedding Search

Gabriele Masetti ·

Why nearest-neighbor search needs its own database

An embedding is a vector that a model produces so that semantic similarity becomes geometric proximity. Two sentences that mean similar things land near each other in the vector space; two unrelated ones land far apart. Once you have millions of these vectors, the only useful operation is: given a query vector, find its nearest neighbors under some distance function. That sounds like a job for an index, and for small datasets a brute-force scan works fine — compare the query against every stored vector and sort.

The trouble is that exact nearest-neighbor search is linear in the number of vectors and linear in dimensionality, and modern embedding models produce vectors with hundreds to thousands of dimensions. OpenAI's text-embedding-3-small defaults to 1536 dimensions, and text-embedding-3-large defaults to 3072 (both models let you truncate via a dimensions parameter, trading a bit of retrieval quality for storage).

At tens of millions of vectors, brute-force comparison against every candidate stops being feasible for interactive latency budgets. This is the problem vector databases and approximate nearest neighbor (ANN) libraries exist to solve: return neighbors that are almost certainly correct, in milliseconds, by refusing to look at every vector.

The "approximate" in ANN is the central design tradeoff of this entire field. Every index structure below buys speed by accepting some probability of missing the true nearest neighbor. The metric for how often it succeeds is recall@k — the fraction of true top-k neighbors the index actually returns — and every practical system exposes a knob that trades recall against latency and memory. Understanding that knob, and where it lives in each algorithm, is most of what you need to operate these systems well.

Distance metrics: cosine, dot product, and Euclidean

Before indexing, you need a similarity function. Three dominate:

These aren't three arbitrary options — they collapse into one when your vectors are normalized to unit length. OpenAI's embeddings are normalized this way, which is why their documentation notes that cosine similarity, dot product, and Euclidean distance produce identical rankings on their outputs; dot product is just the fastest to compute, so it's the practical default. If you use a model that doesn't normalize its output, the three metrics can disagree, and picking the wrong one measurably hurts retrieval quality.

This is a detail worth checking for any embedding model before you pick an index metric, because getting it wrong is a silent failure — search still returns results, just worse ones.

HNSW: navigating a small-world graph

The dominant ANN algorithm in production vector databases today is HNSW — Hierarchical Navigable Small World graphs — introduced by Yury Malkov and Dmitry Yashunin, with the definitive paper published in IEEE Transactions on Pattern Analysis and Machine Intelligence (vol. 42, issue 4, 2018), following an earlier arXiv preprint in 2016. HNSW is the index behind FAISS's IndexHNSWFlat, pgvector's hnsw index type, and the default or primary index in Weaviate, Qdrant, and Milvus.

The core idea is a multi-layer proximity graph. Each vector is a node, and nodes are connected to nearby nodes by edges — but instead of one flat graph, HNSW builds a hierarchy of graphs stacked on top of each other. Layer 0 at the bottom contains every vector.

Each layer above it contains a randomly-selected, exponentially shrinking subset of the vectors below — a vector's maximum layer is chosen randomly with an exponentially decaying probability distribution, so most vectors only exist in layer 0, a few climb to layer 1, fewer still to layer 2, and so on. This mirrors a skip list: sparse long-range shortcuts on top, dense local connections at the bottom.

A search starts at an entry point in the topmost layer and greedily walks toward the query vector, hopping to whichever neighbor is closer, until it can't improve within that layer. It then drops down one layer and repeats the greedy walk, using the previous layer's local optimum as its new starting point, all the way down to layer 0, where it does a wider best-first search over a candidate set to assemble the final top-k.

Because the upper layers act as coarse routing tables that get you to roughly the right neighborhood in a handful of hops, and the lower layers refine locally, search complexity scales logarithmically with the number of stored vectors — a property that lets HNSW stay fast well past the dataset sizes where naive graph search degrades.

Two build-time parameters control the recall/resource tradeoff: M, the number of edges maintained per node (higher M means denser connectivity, better recall, and more memory), and efConstruction, which controls how thorough the search is while inserting new nodes and thus how good the resulting graph structure is. At query time, efSearch controls the width of the candidate list kept during the layer-0 search — raise it and recall goes up while latency goes up with it.

This is the single most important tuning lever across every HNSW-based system: ef/efSearch is the recall-vs-latency dial you turn per query, while M and efConstruction are the recall-vs-memory-and-build-time dial you set once.

HNSW's weaknesses are the mirror of its strengths: the graph structure with its per-node edge lists is memory-hungry (uncompressed, expect memory on the order of a few times the raw vector size), and because it's an incrementally-built graph, deletions are awkward — most implementations mark nodes as tombstoned rather than truly remove them, and the graph benefits from periodic rebuilds under high churn.

IVF and product quantization: the FAISS approach

FAISS (Facebook AI Similarity Search), the library out of Meta's AI research group, popularized a different family of index: inverted file indexes (IVF). IVF first clusters the vector space using k-means into a set of partitions — conceptually like Voronoi cells, each with a centroid. At index time, every vector is assigned to its nearest centroid's list ("inverted file" is a nod to the same structure used in text search, where a term maps to a list of documents; here a centroid maps to a list of vectors).

At query time, rather than scanning everything, IVF computes distance to each centroid, picks the nprobe closest cells, and only scans vectors inside those cells. nprobe is the recall/latency knob here: scanning one cell is fast but risks missing neighbors that fell into an adjacent cell near the boundary; scanning many cells raises recall back toward exhaustive search at the cost of speed.

IVF alone controls how much of the dataset you touch. It says nothing about how much space each vector takes or how fast an individual distance computation is — that's where product quantization (PQ) comes in. PQ splits each high-dimensional vector into sub-vectors, and separately quantizes each sub-vector to the nearest of a small set of learned centroids (typically 256 per sub-space, so each sub-vector is representable in a single byte).

A 1536-dimension float32 vector that costs 6 KB raw can compress to well under 100 bytes as a PQ code — a lossy compression that trades exact distance computation for compact codes and fast approximate distance lookup via precomputed tables. Combined, IVFPQ is FAISS's workhorse for billion-scale search: cluster to narrow the candidate set, then quantize to make scanning that set cheap.

On a single GPU, this pipeline can return top-k results in microseconds per query, which is why FAISS remains the reference implementation cited in nearly every paper on large-scale retrieval infrastructure, even when production systems wrap it in a different index or ship their own reimplementation.

The quantization idea has since been generalized well beyond FAISS's original PQ. Qdrant implements scalar quantization, which converts float32 components to int8, cutting memory by roughly 75% while keeping search error typically under 1% — and as a side benefit, it lets the search loop use SIMD instructions built for 8-bit integers, which is faster on top of being smaller. Qdrant also implements binary quantization, compressing each vector component to a single bit — a 32x memory reduction that can yield roughly 40x faster search in benchmarks, at the cost of accuracy.

Binary quantization only works well on higher-dimensional embeddings (roughly 1024+ dimensions); it degrades badly on smaller embeddings because there isn't enough redundant information in the vector to survive being flattened to bits. All quantization schemes share the same operational pattern: quantized vectors are used to cheaply narrow a candidate set, and the original full-precision vectors are used to rescore the survivors — you get most of the memory and speed win without eating the full accuracy cost.

Technique Compression Effect
Product quantization (PQ) 6 KB → well under 100 bytes (1536-dim vector) lossy, fast approximate distance
Scalar quantization (int8) ~75% memory reduction search error typically under 1%
Binary quantization 32x memory reduction ~40x faster search, accuracy cost; needs ~1024+ dims

Choosing an index and a store

The practical decision isn't "HNSW or IVF" in the abstract — it's a set of tradeoffs against your workload:

There's no universally correct choice. A team running retrieval-augmented generation over a few hundred thousand internal documents, with moderate write volume and a Postgres database already in production, gets more value from pgvector than from standing up a new managed service. A team building consumer search over hundreds of millions of product embeddings with unpredictable traffic spikes has a much stronger case for a managed, serverless system built for that scale. A team that needs hybrid keyword-plus-vector search as a core requirement, not an afterthought, should weight that capability as heavily as raw ANN benchmark numbers.

Scaling beyond a single node

At sufficient scale, the index itself stops being the only problem — distribution does. Sharding a vector index means partitioning vectors across nodes (commonly by hashing or by a coarse clustering step similar to IVF's centroids) and either querying all shards and merging results (scatter-gather) or routing queries to the shard likely to hold the answer.

Scatter-gather preserves recall better but multiplies query cost by the number of shards touched; routing is cheaper but reintroduces the risk of missing a true neighbor that landed on the "wrong" shard, which is the same recall/cost tradeoff that shows up inside a single IVF index, just moved up a layer of abstraction.

Replication for read throughput is comparatively simple — vector indexes are mostly read-heavy and embarrassingly parallel to serve once built — but keeping replicas consistent under continuous writes without stalling queries is where the real engineering effort in distributed vector databases like Milvus and Weaviate's cluster mode goes. And because rebuilding an HNSW graph from scratch is expensive at scale, production systems increasingly favor incremental index structures or a two-tier design: a fast, mutable buffer for recent writes searched exhaustively, periodically compacted into the main, immutable, quantized index — the same log-structured-merge pattern common in write-optimized databases, applied to vectors instead of rows.

The recurring tradeoff

Every layer of this stack — metric choice, HNSW's ef, IVF's nprobe, PQ's code size, binary quantization's bit-per-dimension — is the same knob wearing a different name: how much exhaustiveness are you willing to give up for speed and memory, and how much error can your downstream application tolerate. A recommendation system reranking a shortlist can tolerate more approximation than a legal-document retrieval system where a missed match is a real failure.

Getting vector search right is less about picking the "best" algorithm and more about identifying where your workload sits on that tradeoff, and choosing the index and store that let you tune it explicitly rather than discovering it by accident in production.

Explore

More articles