Engineering Atlas › Building AI Systems

Embeddings and Vector Search in Production

11 min read · last reviewed 2026-10-17

Open in the interactive app: practice, quiz, mock interview →

In one minute

How it works

What an embedding is

An embedding model is a neural network trained so that texts with related meaning produce vectors pointing in similar directions. Similarity is measured with cosine similarity (the angle between vectors) or the dot product; when vectors are normalised to length 1, the two are the same. Dimensions range from a few hundred to a few thousand; pgvector, for instance, can index vector columns only up to 2,000 dimensions and halfvec up to 4,000 (pgvector README). Models differ in languages, domains and maximum input length; public benchmarks such as MTEB compare them, but your own retrieval eval decides.

Exact versus approximate search

Exact (brute-force) search is simple and perfectly accurate: compute the similarity of the query with every vector and keep the top k. At 100,000 vectors it takes milliseconds; at 100 million it is far too slow per query. Approximate nearest neighbour (ANN) indexes trade a little recall for orders of magnitude of speed. Faiss, the reference library, implements IVF, product quantisation and HNSW, and handles sets too big for RAM (Faiss):

Measuring recall

Recall at k for an ANN index means: of the true top k (from exact search), how many did the index return? Measure it on a sample of real queries by running both exact and approximate search. Aim for a target, often 0.95 or higher, and tune parameters to the cheapest setting that meets it. Remember that index recall is only part of the story: if the embedding model itself ranks the wrong passages highly, a perfect index returns the wrong passages perfectly.

Quantisation

Storing vectors as 32-bit floats costs 4 bytes per dimension. Scalar quantisation stores each dimension as an 8-bit integer (4 times smaller); product quantisation compresses further by encoding groups of dimensions as codebook entries; binary quantisation keeps one bit per dimension (32 times smaller). A common pattern is to search compressed vectors to get candidates, then rescore the top few hundred with full-precision vectors. In one MTEB retrieval test, int8 kept about 97 percent of full-precision NDCG@10 (about 99 percent with rescoring) and binary kept about 92.5 percent (96.45 percent with rescoring), with exact-search speedups of roughly 3.7 and 25 times (Hugging Face). Treat those as one model's results and measure your own.

Operating it

The same shapes as any system

Vector search is the finding-things-fast shape with a new distance function. Every indexing lesson applies: build cost versus query cost, memory versus speed, freshness versus rebuild effort.

Down the stack

Layer 1The contract

A vector search service promises top-k results for a query vector, with filters, at a latency target (for example p99 under 50 ms), with recall at 10 above a target measured against exact search, and with new or updated items searchable within a freshness window.

Layer 2The mechanism

HNSW stores, for each vector, a list of neighbour IDs per layer. Insertion picks a random top layer for the new node (with exponentially decreasing probability for higher layers), then connects it to its nearest neighbours found by searching from the entry point. Search keeps a priority queue of ef_search best candidates, expanding neighbours until no closer candidate is found. IVF trains k-means centroids on a sample, assigns each vector to its nearest centroid's list, and at query time scans the lists of the nprobe nearest centroids. Postgres's pgvector implements both as index types inside the database, so vector search can join with ordinary tables and use the same backups and transactions.

Layer 3The machine

Similarity is arithmetic: a dot product of two 1,024-dimension float vectors is 1,024 multiply-adds, which modern CPUs do 8 to 16 at a time with SIMD instructions (AVX2, AVX-512, NEON). The real bottleneck is memory: HNSW search jumps between nodes stored at random addresses, so each hop risks a cache miss costing around 100 ns from main memory, and a search touching a few thousand nodes spends most of its time waiting for memory. That is why keeping the index in RAM matters so much, and why quantisation, by shrinking vectors, speeds up search as well as saving space.

Why it's built this way

  1. Meaning-based similarity needs a vector representation and a distance function.
  2. Exact search scales linearly with the corpus, too slow at large sizes.
  3. Graph and cluster indexes cut the work per query at a small recall cost.
  4. Memory capacity and bandwidth dominate cost, so compression pays off twice.
  5. Vectors depend on the model that made them, so model changes are migrations.

Diagram

flowchart TD
  T["Text chunk"] --> E["Embedding model"]
  E --> V["Vector (1,024 floats)"]
  V --> Q8["Quantise to int8"]
  Q8 --> H["HNSW graph in RAM"]
  QY["Query text"] --> E2["Same embedding model"]
  E2 --> QV["Query vector"]
  QV --> H
  H --> C["Top 200 candidates"]
  C --> RS["Rescore with full vectors"]
  RS --> K["Top 10"]

Worked example

A marketplace wants semantic search over 30 million product listings with 768-dimension embeddings. Raw float32 vectors take 30 million times 768 times 4 bytes, about 92 GB, before HNSW links (with M = 16, roughly another 30 million times 32 neighbour IDs times 4 bytes, about 4 GB per layer-0 level). That needs large-memory machines and replicas.

They measure on 2,000 real queries with exact search as ground truth. HNSW with float32 and ef_search = 100 gives recall at 10 of 0.97 at p99 18 ms. Scalar int8 quantisation cuts vector memory to about 23 GB; recall drops to 0.94. Rescoring the top 100 candidates with float32 vectors stored on SSD restores recall to 0.97 with p99 26 ms. Filters on category and country are applied inside the index search. Total memory per replica falls from about 100 GB to about 30 GB, so they run three replicas on smaller machines instead of two large ones. When they later switch embedding models, they build the new index alongside the old, A/B test click-through for two weeks, then switch.

In the wild

Trade-offs

ChoiceStrengthsWeaknessesChoose when
Brute-force exact searchPerfect recall, no indexLinear costUnder about 100k vectors, or as ground truth
HNSWHigh recall, low latencyMemory-heavy, slow deletesMost interactive search
IVFLess memory, fast buildsRecall depends on probesVery large, batch-built corpora
Quantisation plus rescoringMuch less memory, near-full recallExtra step and tuningLarge corpora, memory-bound
pgvector in existing PostgresOne system, joins, transactionsScale limits per nodeUp to millions of vectors
Dedicated vector databaseScale, advanced filteringAnother system to runHundreds of millions, heavy filters

Pitfalls in production

Leadership lens

Check yourself

  1. Why can a perfectly accurate vector index still return irrelevant results?

    Show answer

    The index finds the nearest vectors exactly, but if the embedding model places irrelevant passages near the query, nearness does not mean relevance.

  2. What does ef_search control in HNSW?

    Show answer

    How many candidates the search explores; higher values improve recall at the cost of latency.

  3. Why does quantisation also make search faster, not just smaller?

    Show answer

    Search is memory-bound, so smaller vectors mean fewer bytes moved and more of the index in cache.

  4. Why can't you add vectors from a new embedding model to an existing index?

    Show answer

    Each model defines its own vector space; distances between vectors from different models are meaningless.

Sources and further study

Primary

Systems docs

Books

Other curricula

Free resources

Open in the interactive app: practice, quiz, mock interview →