Vector databases store and search numerical vectors (embeddings) efficiently. They're the backbone of RAG systems.
This guide covers embedding fundamentals, similarity metrics, database selection, indexing strategies, and production deployment patterns.
What is an Embedding?
Text: "The dog jumps quickly"
β Embedding Model
Vector: [0.234, -0.512, 0.891, ..., 0.123] (384-1536 dimensional)
Each dimension represents semantic properties (animality, motion, speed, etc.).
Similarity Metrics
Cosine Similarity (Standard)
cos(A, B) = (A Β· B) / (||A|| Γ ||B||)
Range: [-1, 1]
-1 = opposite direction
0 = orthogonal
1 = identical
Most common for semantic search.
Dot Product (Faster)
A Β· B
Faster if vectors pre-normalized. Only use if you know what you're doing.
Euclidean Distance
d = β(Ξ£(A_i - B_i)Β²)
Less common for semantic search.
Database Comparison (2026)
| Feature | ChromaDB | Pinecone | Weaviate | Qdrant | Milvus | pgvector |
|---|---|---|---|---|---|---|
| Type | In-Memory + Persist | Managed Cloud | Graph-Vector | Cloud/Self | Distributed | PostgreSQL Plugin |
| Setup | pip install | API | Container | Container | Kubernetes | SQL Extension |
| Local | β | β | β | β | β | β |
| Scaling | Medium | Unlimited | Good | Good | Excellent | DB-Limited |
| Cost (Self) | Free | - | Free | Free | Free | ~β¬5-20/Mo |
| Cost (Cloud) | $0.03/M vectors | Pay-per-API | β¬500+/Mo | Starter Free | Enterprise | Varies |
Detailed Options
ChromaDB (Beginner-Friendly)
from chromadb import Client
client = Client()
collection = client.create_collection(name="documents")
collection.add(
ids=["1", "2", "3"],
documents=["The dog jumps", "A cat rests", "The bird flies"],
)
results = collection.query(
query_texts=["dog jumps"],
n_results=2
)
Best for: Prototyping, small systems (<10M vectors)
Pinecone (Managed, Production)
- Fully managed, 99.99% SLA
- Unlimited scaling
- Pay-per-vector model
- Best filtering
Best for: Production, high availability, budgets exist
Weaviate (Knowledge Graphs)
- Multi-modal (text, images, audio)
- Graph traversal
- Complex relations
- Open-source
Best for: Complex relations, knowledge graphs
Qdrant (Rust-optimized, Fast)
- Fastest inference
- Hybrid search (vector + BM25 text)
- Open-source
- Cost-effective
Best for: Speed-critical, self-hosted
Milvus (Distributed, Kubernetes)
- Massive scale (millions of vectors)
- Enterprise features
- Overkill for small systems
Best for: Enterprise, huge scale, managed infrastructure
pgvector (PostgreSQL)
- SQL integration
- ACID guarantees
- Familiar PostgreSQL ecosystem
- HNSW indexing
Best for: Small-medium systems, existing PostgreSQL users
Indexing Strategies
HNSW (Hierarchical Navigable Small World)
- Fast O(log N) search
- Memory-efficient
- Probabilistic (best approximation, not guarantee)
- Best for: β€100M vectors
IVF (Inverted File)
- Cluster-based
- Good memory/speed tradeoff
- Risk of poor clusterings
- Best for: >100M vectors, memory-constrained
Self-Hosted vs Cloud
Self-Hosted (ChromaDB, Qdrant, Milvus)
- Cost: β¬150-300/month infrastructure
- Control: Full
- Scaling: Need engineering
Cloud (Pinecone, Weaviate Cloud)
- Cost: β¬50/month + query costs
- Control: Limited
- Scaling: Automatic
Breakeven: 10M vectors β same cost. Larger β self-hosted better.
Practical RAG Integration
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
vector_store = Chroma.from_documents(
documents=docs,
embedding=embeddings,
)
retriever = vector_store.as_retriever(
search_type="similarity",
search_kwargs={"k": 3}
)
from langchain.chains import RetrievalQA
qa = RetrievalQA.from_chain_type(
llm=llm,
retriever=retriever,
)
Advanced: Embedding Dimensions & Performance
Different embedding models produce different vector dimensions. This affects memory, compute, and quality.
Model | Dimensions | Speed | Quality | Cost | Size
---------------------------|-----------|---------|---------|-----------|----------
all-MiniLM-L6-v2 | 384 | Fastest | Medium | Free | 22MB
UAE-Large-V1 | 1024 | Fast | Best | Free | 134MB
OpenAI text-embedding-3 | 1536 | Medium | Excellent | β¬0.02/M | N/A
Cohere Embed-3 | 1024 | Medium | Excellent | β¬0.10/M | N/A
Gemini Embedding | 768 | Fast | Very Good | β¬0.0001/M | N/A
Rule of thumb: 384-768 dimensions work for most use-cases. Higher dimensions β better quality but slower search + more memory.
Metadata & Filtering
Vector search alone isn't enough. Add metadata (tags, timestamps, sources) for filtering before vector similarity.
# ChromaDB with metadata
collection.add(
ids=["doc1", "doc2", "doc3"],
embeddings=[vec1, vec2, vec3],
documents=["text1", "text2", "text3"],
metadatas=[
{"source": "wiki", "date": "2026-03-21", "language": "en"},
{"source": "blog", "date": "2026-03-20", "language": "en"},
{"source": "pdf", "date": "2025-12-01", "language": "de"},
]
)
# Hybrid search: Filter by metadata, then vector search
results = collection.query(
query_texts=["dog"],
n_results=3,
where={"source": "wiki"} # Only wiki results
)
Supported filtering:
- Exact match:
where={"language": "en"} - Range:
where={"date": {"$gte": "2026-01-01"}} - Complex:
where={"$and": [{"source": "wiki"}, {"date": {"$gte": "2026-01-01"}}]}
Scaling Patterns: From Prototype to Production
Pattern 1: Local Development (< 1M vectors)
pip install chromadb
# Stores in .chroma/ directory locally
# Restart reloads from disk
Best for: Learning, prototyping, small teams
Pattern 2: Shared PostgreSQL + pgvector (1-50M vectors)
from pgvector.django import VectorField
from django.db import models
class Document(models.Model):
content = models.TextField()
embedding = VectorField(dimensions=1536)
class Meta:
indexes = [
models.Index(fields=['embedding']),
]
Advantages:
- ACID transactions
- Row-level security
- Native SQL filtering
- Familiar PostgreSQL tooling
Disadvantages:
- Slower vector search than specialized DBs
- Scaling beyond 50M requires read replicas
Pattern 3: Self-Hosted Qdrant / Milvus (50M-1B vectors)
# Docker
docker run -p 6333:6333 qdrant/qdrant
# Python client
from qdrant_client import QdrantClient
client = QdrantClient("localhost", port=6333)
client.create_collection(
collection_name="documents",
vectors_config=VectorParams(size=1536, distance=Distance.COSINE),
)
Best for: Companies with DevOps resources, wanting full control
Pattern 4: Managed Pinecone / Weaviate (1B+ vectors)
- Automatic scaling
- Built-in redundancy
- Monitoring & alerts
- Multi-region support
Trade-off: Cost increases, but operational burden decreases
Common Pitfalls & Solutions
Pitfall 1: Dimension Mismatch
# WRONG: Using two different embedding models
embeddings_1 = OpenAIEmbeddings(model="text-embedding-3-small") # 1536-dim
embeddings_2 = sentence_transformers.SentenceTransformer(...) # 384-dim
# Result: Vectors incompatible, similarity metrics fail
Solution: Standardize on ONE embedding model across entire pipeline.
Pitfall 2: Stale Vectors After Updates
# Document changes, but vector in DB unchanged
doc.content = "Updated content"
doc.save()
# BUT: embedding vector still old!
# Solution: Re-embed after every update
new_embedding = embeddings.embed_query(doc.content)
collection.update(ids=[doc.id], embeddings=[new_embedding])
Pitfall 3: Cold Startup Problem
New deployment β empty vector DB β no retrieval β poor answers
Solution: Pre-populate with seed documents on startup.
def initialize_db(vector_db, seed_docs):
if vector_db.count() == 0:
logger.info("Initializing seed documents")
for doc in seed_docs:
vector_db.add(doc)
Pitfall 4: Similarity Threshold Too High
# Retrieve only if similarity > 0.95
results = collection.query(
query_texts=["dog"],
n_results=10,
where={"similarity": {"$gte": 0.95}} # Too strict!
)
# Result: Often 0 results returned, no fallback
Solution: Use k-retrieval first, then filter by threshold as a secondary step.
Benchmarking Vector Databases
Quick evaluation framework:
import time
import numpy as np
def benchmark_db(db_client, n_vectors=10000, dimension=1536):
vectors = np.random.randn(n_vectors, dimension).astype(np.float32)
# 1. Insert speed
start = time.time()
for i, vec in enumerate(vectors):
db_client.add(id=i, vector=vec)
insert_time = time.time() - start
print(f"Insert {n_vectors} vectors: {insert_time:.2f}s")
# 2. Query speed (1000 queries)
query_vecs = np.random.randn(1000, dimension).astype(np.float32)
start = time.time()
for q in query_vecs:
results = db_client.search(q, k=10)
query_time = time.time() - start
print(f"1000 queries: {query_time:.2f}s avg {query_time/1000*1000:.1f}ms per query")
# 3. Memory usage
memory_mb = db_client.memory_usage() / 1024 / 1024
print(f"Memory: {memory_mb:.0f}MB for {n_vectors} vectors")
# 4. Recall accuracy (comparison to brute-force)
brute_results = brute_force_search(vectors[0], vectors, k=10)
db_results = db_client.search(vectors[0], k=10)
recall = len(set(brute_results) & set(db_results)) / 10
print(f"Recall: {recall:.2%}")
Embedding Model Comparison Table (2026)
| Model | Provider | Dims | Speed (tok/s) | Quality | Cost/M tokens | License |
|---|---|---|---|---|---|---|
| text-embedding-3-small | OpenAI | 1536 | 100K | 9.2/10 | β¬0.02 | Proprietary |
| text-embedding-3-large | OpenAI | 3072 | 50K | 9.8/10 | β¬0.13 | Proprietary |
| Cohere Embed-3 | Cohere | 1024 | 150K | 9.5/10 | β¬0.10 | Proprietary |
| UAE-Large-V1 | Alibaba (Open) | 1024 | 300K | 9.1/10 | Free | Apache 2.0 |
| all-MiniLM-L6-v2 | Sentence-T. | 384 | 1000K | 7.8/10 | Free | Apache 2.0 |
| bge-large-en-v1.5 | BAAI (Open) | 1024 | 400K | 8.9/10 | Free | MIT |
| Gemini Embedding | 768 | 200K | 9.0/10 | β¬0.00013 | Proprietary | |
| Llama Embedding | Meta | 384 | 500K | 8.2/10 | Free | Llama License |
When to Use Vector Databases
β Excellent for:
- Semantic search (find similar documents)
- Recommendation systems
- Anomaly detection (outliers in vector space)
- Image retrieval (convert images β embeddings)
- FAQ matching
β Poor fit for:
- Exact keyword search (use traditional DBs)
- Structured data queries (use SQL)
- Real-time updates (latency issues)
- Guaranteed consistency (eventually consistent)
Troubleshooting Guide
Issue: "Retrieval returns irrelevant documents"
- Check embedding model is consistent
- Lower similarity threshold
- Increase k (retrieve more, rerank)
- Add metadata filtering
- Switch embedding model to higher quality
Issue: "Vector DB is very slow"
- Missing index (create HNSW or IVF)
- Wrong metric (use cosine, not euclidean)
- Too many dimensions (compress with PCA)
- Overloaded server (add replicas)
Issue: "Memory usage exploding"
- Switch from ChromaDB to Qdrant (more efficient)
- Reduce embedding dimensions
- Use int8 quantization instead of float32
- Implement pagination (don't load all)
Issue: "Cost is too high with Pinecone"
- Move to self-hosted (Qdrant/Milvus)
- Reduce update frequency (batch updates)
- Use cheaper embedding model
- Implement caching layer
Vector databases are critical for RAG. Choose based on scale, budget, and complexity needs.
