Embedding and Vector Database Patterns
Intro
Semantic search lives or dies on three choices: the embedding model,
the vector store, and the index parameters. Default to cosine
similarity, hybrid retrieval (dense + BM25), and HNSW indexes — then
benchmark on your own eval set before tuning.
Overview
Embedding model selection
Pick on domain match, dimensionality, context window, latency, and
cost. MTEB scores are general — benchmark on YOUR data. Smaller
models (MiniLM) suit real-time; larger ones (e5-mistral) suit batch.
Open-source is free to run but needs GPU infrastructure.
Commercial:
| Model |
Dims |
Max tokens |
Strengths |
| OpenAI text-embedding-3-small |
1536 |
8191 |
Good default, low cost |
| OpenAI text-embedding-3-large |
3072 |
8191 |
Higher quality, Matryoshka |
| Cohere embed-v3 |
1024 |
512 |
Multilingual, search/classify |
| Voyage voyage-3 |
1024 |
32000 |
Long context, strong on code |
Open-source:
| Model |
Dims |
Max tokens |
Strengths |
| nomic-embed-text-v1.5 |
768 |
8192 |
Strong MTEB, Matryoshka |
| bge-large-en-v1.5 |
1024 |
512 |
English, well-tested |
| e5-mistral-7b-instruct |
4096 |
32768 |
Best open quality, high compute |
| all-MiniLM-L6-v2 |
384 |
256 |
Tiny, fast, prototyping |
Dimensionality and Matryoshka
Matryoshka embeddings (OpenAI text-embedding-3, nomic-embed) allow
truncating dimensions without retraining: 3072 → 1024 or 512 trades
modest quality loss for faster search. Test at each dimension on
your eval set before committing. Rule of thumb: 256–512 dims is
enough for most retrieval; 1024+ for fine-grained similarity.
Similarity metrics
| Metric |
Range |
Best for |
| Cosine similarity |
[-1, 1] |
Normalized embeddings (most common) |
| Dot product |
(-inf, inf) |
When magnitude matters |
| Euclidean (L2) |
[0, inf) |
Spatial clustering |
Default is cosine — most embedding models are trained with it. For
unit-length embeddings, cosine equals dot product.
Vector database selection
| Database |
Architecture |
Best for |
Scaling |
| FAISS |
In-memory library |
Prototyping, < 10M |
Single machine |
| pgvector |
Postgres extension |
Postgres shops, joins + filtering |
Vertical |
| Chroma |
Embedded DB |
Local dev, quick experiments |
Single, < 1M |
| Qdrant |
Rust client-server |
Production, advanced filtering |
Horizontal |
| Weaviate |
Go, built-in vectorizers |
Multimodal, auto-vectorization |
Horizontal |
| Pinecone |
Managed SaaS |
Zero-ops, serverless |
Fully managed |
| Milvus |
Distributed cloud-native |
Billions of vectors |
Horizontal |
Decision flow: prototyping → Chroma or FAISS. Already on Postgres →
pgvector. Production with advanced filtering → Qdrant or Weaviate.
Zero ops → Pinecone. Billions of vectors → Milvus.
Index types
HNSW is the default for most vector DBs. Key params: M
(connections per node, default 16), ef_construction (build quality,
default 200), ef_search (query quality, default 100). Higher M and
ef = better recall, more memory, slower build. Start with defaults;
tune ef_search up for recall, down for latency.
IVF clusters vectors and searches only relevant clusters. Params:
nlist ≈ sqrt(N), nprobe ≈ nlist/10 to nlist/5. Faster than HNSW
above ~100M vectors.
PQ (Product Quantization) compresses vectors with slight quality
loss. Use when the dataset will not fit in memory. Often combined as
IVF-PQ.
Hybrid search
Combine dense (semantic) and sparse (keyword/BM25) retrieval: dense
catches "automobile" matching "car," sparse catches exact matches
(product IDs, acronyms, proper nouns). Fuse with Reciprocal Rank
Fusion: RRF = sum(1 / (k + rank_i)) with k typically 60. Hybrid
almost always outperforms either method alone. Qdrant and Weaviate
have built-in hybrid; for others, run BM25
(Elasticsearch/OpenSearch) and vector search separately and fuse.
Gotchas
Agent-specific failure modes — provider-neutral pause-and-self-check items:
- Choosing an embedding model based solely on MTEB leaderboard scores. MTEB benchmarks general-purpose retrieval; your domain may have different vocabulary, query patterns, or document lengths. Always benchmark candidate models on a small eval set from your own data before committing to one.
- Using L2 (Euclidean) distance with embeddings trained for cosine similarity. Most embedding models are trained with cosine similarity as the loss. Using L2 distance with them produces incorrect similarity rankings. Check the model card for the intended distance metric before configuring the vector store index.
- Embedding raw documents and chunking after the fact. An embedding represents the full input as a single vector. Embedding a 10,000-word document produces one vector that represents everything and nothing specifically. Chunk first, then embed each chunk.
- Pure dense retrieval for catalogs with exact identifiers. Dense (semantic) retrieval fails on product SKUs, proper nouns, acronyms, and precise version numbers — it is too fuzzy. Combine with sparse (BM25 / keyword) retrieval using reciprocal rank fusion for these use cases.
- No eval set to detect retrieval regressions. Without a golden set of queries with expected results, there is no way to know whether a change to chunking, embedding model, or index parameters improved or degraded retrieval quality. Build an eval set before the first production deployment.
- Single embedding model for both query and document without asymmetric fine-tuning. Queries and documents have different linguistic patterns. Some models (e.g., Voyage,
e5-instruct) require instruction prefixes like "Represent this document:" for passages and "Represent this query:" for queries. Using the wrong prefix produces poorer retrieval.
- Sharing one DataLoader (or equivalent batch object) instance across search requests. Results from a cached retrieval object may belong to a different request's context. Always create per-request retrieval objects when batching or caching at the database layer.
Full reference
Metadata filtering and multi-tenancy
Store metadata alongside vectors: source, date, category, tenant_id.
Pre-filter (filter before vector search) is more efficient than
post-filter. For multi-tenancy, use a metadata filter on tenant_id
or separate collections per tenant. Index metadata fields used in
filters for performance.
Embedding pipeline best practices
- Batch processing — embed in batches of 100–500 for API
efficiency
- Caching — cache by content hash to skip re-embedding unchanged
content
- Normalization — normalize to unit length when using cosine
- Chunking alignment — chunk text before embedding, never embed
then chunk
- Monitoring — track embedding latency, cache hit rate, and
index size over time
Worked examples
- Semantic search for a docs site:
text-embedding-3-small for
cost-effective embedding; pgvector if already on Postgres,
otherwise Qdrant. Hybrid search (BM25 + dense). Chunk by section
headers at 512 tokens. Build a 50-query eval set with expected
results to tune retrieval.
- Slow vector search at 5M vectors (> 500ms): If flat index,
switch to HNSW. If HNSW, lower
ef_search (recall vs speed).
Consider Matryoshka 3072 → 1024. Enable metadata pre-filtering to
shrink the search space. Benchmark each change against the eval
set.
- Migrating Chroma → Qdrant: Export vectors and metadata.
Create Qdrant collection with matching distance metric and
dimensions. Batch-upload. Verify record count. Run the eval set
against both for recall and latency. Configure Qdrant replication
for HA.
Anti-patterns
- Picking a model from MTEB without benchmarking on your domain
- Using L2 distance with embeddings trained for cosine
- Embedding raw documents and chunking after the fact
- Pure dense retrieval for catalogs full of SKUs and proper nouns
- Production vector store with no eval set to detect regressions
1---2name: embedding-vectordb3description: Vector embeddings and vector DB patterns — model choice, similarity metrics, index tuning. Use when choosing an embedding model, picking or migrating between vector databases, optimizing semantic search quality or latency, or building a hybrid (dense + sparse) retrieval pipeline.4---56# Embedding and Vector Database Patterns78## Intro910Semantic search lives or dies on three choices: the embedding model,11the vector store, and the index parameters. Default to cosine12similarity, hybrid retrieval (dense + BM25), and HNSW indexes — then13benchmark on your own eval set before tuning.1415## Overview1617### Embedding model selection1819Pick on domain match, dimensionality, context window, latency, and20cost. MTEB scores are general — benchmark on YOUR data. Smaller21models (MiniLM) suit real-time; larger ones (e5-mistral) suit batch.22Open-source is free to run but needs GPU infrastructure.2324Commercial:2526| Model | Dims | Max tokens | Strengths |27|---|---|---|---|28| OpenAI text-embedding-3-small | 1536 | 8191 | Good default, low cost |29| OpenAI text-embedding-3-large | 3072 | 8191 | Higher quality, Matryoshka |30| Cohere embed-v3 | 1024 | 512 | Multilingual, search/classify |31| Voyage voyage-3 | 1024 | 32000 | Long context, strong on code |3233Open-source:3435| Model | Dims | Max tokens | Strengths |36|---|---|---|---|37| nomic-embed-text-v1.5 | 768 | 8192 | Strong MTEB, Matryoshka |38| bge-large-en-v1.5 | 1024 | 512 | English, well-tested |39| e5-mistral-7b-instruct | 4096 | 32768 | Best open quality, high compute |40| all-MiniLM-L6-v2 | 384 | 256 | Tiny, fast, prototyping |4142### Dimensionality and Matryoshka4344Matryoshka embeddings (OpenAI text-embedding-3, nomic-embed) allow45truncating dimensions without retraining: 3072 → 1024 or 512 trades46modest quality loss for faster search. Test at each dimension on47your eval set before committing. Rule of thumb: 256–512 dims is48enough for most retrieval; 1024+ for fine-grained similarity.4950### Similarity metrics5152| Metric | Range | Best for |53|---|---|---|54| Cosine similarity | [-1, 1] | Normalized embeddings (most common) |55| Dot product | (-inf, inf) | When magnitude matters |56| Euclidean (L2) | [0, inf) | Spatial clustering |5758Default is cosine — most embedding models are trained with it. For59unit-length embeddings, cosine equals dot product.6061### Vector database selection6263| Database | Architecture | Best for | Scaling |64|---|---|---|---|65| FAISS | In-memory library | Prototyping, < 10M | Single machine |66| pgvector | Postgres extension | Postgres shops, joins + filtering | Vertical |67| Chroma | Embedded DB | Local dev, quick experiments | Single, < 1M |68| Qdrant | Rust client-server | Production, advanced filtering | Horizontal |69| Weaviate | Go, built-in vectorizers | Multimodal, auto-vectorization | Horizontal |70| Pinecone | Managed SaaS | Zero-ops, serverless | Fully managed |71| Milvus | Distributed cloud-native | Billions of vectors | Horizontal |7273Decision flow: prototyping → Chroma or FAISS. Already on Postgres →74pgvector. Production with advanced filtering → Qdrant or Weaviate.75Zero ops → Pinecone. Billions of vectors → Milvus.7677### Index types7879**HNSW** is the default for most vector DBs. Key params: `M`80(connections per node, default 16), `ef_construction` (build quality,81default 200), `ef_search` (query quality, default 100). Higher M and82ef = better recall, more memory, slower build. Start with defaults;83tune `ef_search` up for recall, down for latency.8485**IVF** clusters vectors and searches only relevant clusters. Params:86`nlist` ≈ sqrt(N), `nprobe` ≈ nlist/10 to nlist/5. Faster than HNSW87above ~100M vectors.8889**PQ (Product Quantization)** compresses vectors with slight quality90loss. Use when the dataset will not fit in memory. Often combined as91IVF-PQ.9293### Hybrid search9495Combine dense (semantic) and sparse (keyword/BM25) retrieval: dense96catches "automobile" matching "car," sparse catches exact matches97(product IDs, acronyms, proper nouns). Fuse with Reciprocal Rank98Fusion: `RRF = sum(1 / (k + rank_i))` with k typically 60. Hybrid99almost always outperforms either method alone. Qdrant and Weaviate100have built-in hybrid; for others, run BM25101(Elasticsearch/OpenSearch) and vector search separately and fuse.102103## Gotchas104105Agent-specific failure modes — provider-neutral pause-and-self-check items:106107- **Choosing an embedding model based solely on MTEB leaderboard scores.** MTEB benchmarks general-purpose retrieval; your domain may have different vocabulary, query patterns, or document lengths. Always benchmark candidate models on a small eval set from your own data before committing to one.108- **Using L2 (Euclidean) distance with embeddings trained for cosine similarity.** Most embedding models are trained with cosine similarity as the loss. Using L2 distance with them produces incorrect similarity rankings. Check the model card for the intended distance metric before configuring the vector store index.109- **Embedding raw documents and chunking after the fact.** An embedding represents the full input as a single vector. Embedding a 10,000-word document produces one vector that represents everything and nothing specifically. Chunk first, then embed each chunk.110- **Pure dense retrieval for catalogs with exact identifiers.** Dense (semantic) retrieval fails on product SKUs, proper nouns, acronyms, and precise version numbers — it is too fuzzy. Combine with sparse (BM25 / keyword) retrieval using reciprocal rank fusion for these use cases.111- **No eval set to detect retrieval regressions.** Without a golden set of queries with expected results, there is no way to know whether a change to chunking, embedding model, or index parameters improved or degraded retrieval quality. Build an eval set before the first production deployment.112- **Single embedding model for both query and document without asymmetric fine-tuning.** Queries and documents have different linguistic patterns. Some models (e.g., Voyage, `e5-instruct`) require instruction prefixes like `"Represent this document:"` for passages and `"Represent this query:"` for queries. Using the wrong prefix produces poorer retrieval.113- **Sharing one DataLoader (or equivalent batch object) instance across search requests.** Results from a cached retrieval object may belong to a different request's context. Always create per-request retrieval objects when batching or caching at the database layer.114115## Full reference116117### Metadata filtering and multi-tenancy118119Store metadata alongside vectors: source, date, category, tenant_id.120Pre-filter (filter before vector search) is more efficient than121post-filter. For multi-tenancy, use a metadata filter on tenant_id122or separate collections per tenant. Index metadata fields used in123filters for performance.124125### Embedding pipeline best practices126127- **Batch processing** — embed in batches of 100–500 for API128 efficiency129- **Caching** — cache by content hash to skip re-embedding unchanged130 content131- **Normalization** — normalize to unit length when using cosine132- **Chunking alignment** — chunk text before embedding, never embed133 then chunk134- **Monitoring** — track embedding latency, cache hit rate, and135 index size over time136137### Worked examples138139- **Semantic search for a docs site:** `text-embedding-3-small` for140 cost-effective embedding; pgvector if already on Postgres,141 otherwise Qdrant. Hybrid search (BM25 + dense). Chunk by section142 headers at 512 tokens. Build a 50-query eval set with expected143 results to tune retrieval.144- **Slow vector search at 5M vectors (> 500ms):** If flat index,145 switch to HNSW. If HNSW, lower `ef_search` (recall vs speed).146 Consider Matryoshka 3072 → 1024. Enable metadata pre-filtering to147 shrink the search space. Benchmark each change against the eval148 set.149- **Migrating Chroma → Qdrant:** Export vectors and metadata.150 Create Qdrant collection with matching distance metric and151 dimensions. Batch-upload. Verify record count. Run the eval set152 against both for recall and latency. Configure Qdrant replication153 for HA.154155### Anti-patterns156157- Picking a model from MTEB without benchmarking on your domain158- Using L2 distance with embeddings trained for cosine159- Embedding raw documents and chunking after the fact160- Pure dense retrieval for catalogs full of SKUs and proper nouns161- Production vector store with no eval set to detect regressions