:::tip 🎮 Interactive Playground Visualize this concept: Try the FAISS Index Types demo on the EngineersOfAI Playground - no code required. :::
Vector Databases Compared
The Startup's Decision
The founding engineer at a Series A AI startup needs to choose a vector database for their core product - a knowledge management tool with per-customer data isolation, semantic search over documents, and hybrid keyword/semantic ranking. They have 6 months of runway to build and launch. They have heard of Pinecone, Weaviate, Qdrant, and Chroma. They are already running PostgreSQL.
After a week of evaluation, they notice something: every blog post they find either (a) promotes one specific database's advantages while ignoring its drawbacks, or (b) compares toy examples with 10,000 vectors that do not surface real operational differences. Neither helps them make a principled decision for a production system.
The right framework for vector database selection is not "which database has the best marketing" but rather: which properties does my application actually require, and which databases deliver those properties at my scale, budget, and operational complexity tolerance?
This lesson gives you that framework. You will understand the architectural differences between the major options, what each one actually optimizes for, and how to apply a systematic evaluation process to reach a defensible decision.
Why This Exists - The Specialization Problem
For the first decade of neural embeddings, engineers stored vectors in general-purpose databases: PostgreSQL with a vector column, Elasticsearch with dense vector fields, or custom solutions with FAISS wrapped in an API. These worked for small datasets but failed predictably:
- PostgreSQL sequential scans over vectors were unusably slow above 100K rows
- Elasticsearch's dense vector field was memory-hungry and had limited ANN algorithm support
- FAISS required writing your own API, replication, persistence, filtering, and metadata management
The vector database category emerged to solve the full problem: not just ANN indexing, but the complete infrastructure package - persistence, replication, filtering with metadata, consistent reads after writes, scale-out, and API-first developer experience.
Each database chose different tradeoffs, and understanding those tradeoffs is the entire point of this lesson.
The Five Databases
Pinecone - Fully Managed, Developer-First
Pinecone is a pure-cloud vector database - there is no self-hosted option. You send vectors to their API, and they handle everything else. Their architecture uses a proprietary index (not HNSW, not FAISS) optimized for high-QPS serving and fast cold start.
Architecture: Pinecone separates storage from compute. Vectors are stored in a distributed object store; the serving layer loads them on demand. Their "serverless" tier (launched 2024) bills per query rather than per hour, which makes it extremely cost-effective for sporadic workloads. The "pod-based" tier provides reserved compute for consistent latency SLAs.
Namespaces: Pinecone's mechanism for data isolation within an index. Each namespace is a separate partition. You can upsert, query, and delete within a namespace without affecting others. This is a coarse-grained multi-tenancy mechanism - 100 customers could each get a namespace, but at 1000+ customers with different data sizes, per-customer indices become expensive.
Metadata filtering: Pinecone supports filtering on metadata fields at query time. The filter is applied post-retrieval: HNSW retrieves candidates, then Pinecone applies the metadata filter. This can significantly degrade recall when filters are highly selective - a filter that keeps 1% of vectors means most HNSW candidates get filtered out.
Cost structure: Serverless charges per read unit and write unit. Pod-based charges per hour by pod type (s1.x1 to p2.x8). For high-throughput applications, pod-based can be more predictable. For low-traffic applications with occasional bursts, serverless is compelling.
from pinecone import Pinecone, ServerlessSpec
import numpy as np
# Initialize Pinecone client
pc = Pinecone(api_key="your-api-key")
# Create a serverless index
pc.create_index(
name="documents",
dimension=1536,
metric="cosine",
spec=ServerlessSpec(cloud="aws", region="us-east-1")
)
index = pc.Index("documents")
# Upsert vectors with metadata
vectors_to_upsert = []
for i in range(100):
vector = np.random.randn(1536).tolist()
vectors_to_upsert.append({
"id": f"doc_{i}",
"values": vector,
"metadata": {
"customer_id": f"customer_{i % 10}",
"doc_type": "report" if i % 2 == 0 else "email",
"created_at": "2024-01-15",
}
})
index.upsert(vectors=vectors_to_upsert, namespace="customer_42")
# Query with metadata filter
query_vector = np.random.randn(1536).tolist()
results = index.query(
vector=query_vector,
top_k=10,
namespace="customer_42",
filter={"doc_type": {"$eq": "report"}}, # Pinecone filter syntax
include_metadata=True,
)
for match in results["matches"]:
print(f"id={match['id']}, score={match['score']:.4f}")
Weaviate - Hybrid Search and GraphQL Native
Weaviate is open-source and available as managed cloud or self-hosted. Its defining characteristic is first-class hybrid search: combining dense vector search (HNSW) with BM25 keyword search in a single query. Weaviate also has a module ecosystem for embedding generation at ingestion time.
Architecture: Weaviate stores data in collections (formerly "classes"). Each collection has a HNSW index and optional inverted BM25 index for text properties. The schema defines what properties are indexed as vectors vs keyword vs other types.
Hybrid search: Weaviate's hybrid query runs both a vector search and a BM25 search in parallel, then fuses the results using Reciprocal Rank Fusion (RRF) or a weighted sum. The alpha parameter controls the balance: alpha=0 is pure BM25, alpha=1 is pure vector, alpha=0.7 (default) is hybrid-weighted-toward-vector.
Multi-tenancy: Weaviate supports multi-tenancy at the collection level, where each tenant gets logically isolated data. Unlike Pinecone namespaces, Weaviate tenants can have their own HNSW index parameters and can be activated/deactivated (cold storage) to manage costs.
import weaviate
from weaviate.classes.config import Configure, Property, DataType
client = weaviate.connect_to_weaviate_cloud(
cluster_url="your-cluster.weaviate.network",
auth_credentials=weaviate.auth.AuthApiKey("your-api-key"),
)
# Create a collection with hybrid search enabled
client.collections.create(
name="Documents",
properties=[
Property(name="content", data_type=DataType.TEXT),
Property(name="customer_id", data_type=DataType.TEXT),
Property(name="doc_type", data_type=DataType.TEXT),
],
vectorizer_config=Configure.Vectorizer.none(), # we provide our own vectors
)
documents = client.collections.get("Documents")
# Insert document with custom vector
import numpy as np
vector = np.random.randn(768).tolist()
documents.data.insert(
properties={
"content": "Machine learning model training techniques",
"customer_id": "customer_42",
"doc_type": "article",
},
vector=vector,
)
# Hybrid search query: blend keyword and semantic
from weaviate.classes.query import HybridFusion
results = documents.query.hybrid(
query="training neural networks", # keyword search
vector=np.random.randn(768).tolist(), # semantic search
alpha=0.7, # weight toward semantic
fusion_type=HybridFusion.RELATIVE_SCORE,
filters=weaviate.classes.query.Filter.by_property("customer_id").equal("customer_42"),
limit=10,
)
for obj in results.objects:
print(f"score={obj.metadata.score:.4f}, content={obj.properties['content'][:50]}")
client.close()
Qdrant - Performance, Filtering, and Rust-Native
Qdrant is open-source (Rust), available as managed cloud or self-hosted Docker. It is consistently the top performer on ANN-Benchmarks for filtered search - a scenario where other databases degrade significantly.
Architecture: Qdrant stores collections of "points" (vector + payload + ID). The HNSW index is built per collection. Unlike FAISS which requires in-memory storage, Qdrant supports on-disk HNSW with SSD-backed storage, enabling datasets that exceed RAM.
Payload filtering: Qdrant's standout feature. It supports pre-filtered HNSW using the ACORN algorithm (covered in the Filtering and Metadata lesson) - maintaining high recall even when filters eliminate 99% of vectors. Competitors typically degrade to sequential scan for highly selective filters.
Quantization: Qdrant natively supports scalar quantization (8-bit), product quantization, and binary quantization. Binary quantization reduces memory 32× with a recall loss of ~3–5% - useful for large-scale deployments.
Sparse vectors: Qdrant supports sparse vectors (for BM25/SPLADE hybrid search) alongside dense vectors, making it a natural fit for hybrid search without external BM25 infrastructure.
from qdrant_client import QdrantClient
from qdrant_client.models import (
Distance, VectorParams, PointStruct,
Filter, FieldCondition, MatchValue,
SearchRequest, SparseVector,
SparseVectorParams, NamedSparseVector,
)
import numpy as np
client = QdrantClient(url="http://localhost:6333")
# Create collection with both dense and sparse vectors (hybrid search)
client.create_collection(
collection_name="documents",
vectors_config=VectorParams(size=768, distance=Distance.COSINE),
sparse_vectors_config={
"sparse": SparseVectorParams() # for BM25/SPLADE sparse vectors
}
)
# Insert point with dense + sparse vector + payload
dense_vector = np.random.randn(768).tolist()
# Sparse vector: indices and values (like BM25 term weights)
sparse_indices = [42, 156, 789, 1024]
sparse_values = [0.8, 0.6, 0.4, 0.3]
client.upsert(
collection_name="documents",
points=[
PointStruct(
id=1,
vector={
"": dense_vector, # default dense vector
"sparse": SparseVector(indices=sparse_indices, values=sparse_values),
},
payload={
"customer_id": "customer_42",
"doc_type": "report",
"content": "Quarterly financial analysis",
}
)
]
)
# Filtered vector search with high recall even on selective filters
search_results = client.search(
collection_name="documents",
query_vector=np.random.randn(768).tolist(),
query_filter=Filter(
must=[
FieldCondition(
key="customer_id",
match=MatchValue(value="customer_42")
),
FieldCondition(
key="doc_type",
match=MatchValue(value="report")
),
]
),
limit=10,
with_payload=True,
)
for result in search_results:
print(f"id={result.id}, score={result.score:.4f}")
Chroma - Developer Simplicity and Local-First
Chroma is optimized for developer experience and local development. It runs in-process with Python, requires zero configuration, and stores everything to disk. This makes it excellent for prototyping, offline processing, and applications that do not need distributed serving.
Architecture: Chroma is backed by DuckDB (for metadata) and a custom HNSW implementation (Hnswlib). It runs entirely in Python with no separate server process required, though a server mode is available. As of 2024, Chroma also offers a managed cloud offering.
Positioning: Chroma is the right choice when you are: (a) prototyping a RAG system and want to start in 5 minutes, (b) building an offline ETL pipeline that processes documents, (c) teaching or learning. It is the wrong choice when you need production reliability, distributed serving, or strong consistency guarantees.
import chromadb
import numpy as np
# In-process mode - no server, stores to disk
chroma_client = chromadb.PersistentClient(path="./chroma_data")
# Get or create collection
collection = chroma_client.get_or_create_collection(
name="documents",
metadata={"hnsw:space": "cosine"} # cosine distance
)
# Add documents
documents = [
"Machine learning model training",
"Natural language processing with transformers",
"Vector database architecture",
]
embeddings = [np.random.randn(768).tolist() for _ in documents]
collection.add(
documents=documents,
embeddings=embeddings,
metadatas=[{"doc_type": "article"} for _ in documents],
ids=[f"doc_{i}" for i in range(len(documents))],
)
# Query
results = collection.query(
query_embeddings=[np.random.randn(768).tolist()],
n_results=5,
where={"doc_type": {"$eq": "article"}},
)
print(results)
pgvector - SQL-Native Vector Search
pgvector is a PostgreSQL extension that adds vector types and ANN index support to PostgreSQL. For applications already running PostgreSQL, it enables vector search without adding a new infrastructure component.
Architecture: pgvector adds:
vectortype for storing dense vectors<=>operator for cosine distance<->operator for L2 distance<#>operator for negative inner productivfflatindex: IVF-based ANNhnswindex: HNSW-based ANN (added in pgvector 0.5.0)
When pgvector wins: You already have PostgreSQL, your dataset is under 5M vectors, you want ACID transactions that span vector data and relational data, and you do not want to run a separate vector database process. A single SQL query can join vector search results with user tables, permission tables, and metadata - without an additional round trip to a separate service.
When pgvector loses: Above 5–10M vectors, PostgreSQL query planning overhead and single-node scale limits make dedicated vector databases significantly faster. pgvector's HNSW implementation is also slower to build than FAISS or Qdrant's native implementations.
-- Enable pgvector extension
CREATE EXTENSION IF NOT EXISTS vector;
-- Create table with vector column
CREATE TABLE documents (
id BIGSERIAL PRIMARY KEY,
content TEXT,
customer_id TEXT,
doc_type TEXT,
embedding vector(768),
created_at TIMESTAMP DEFAULT NOW()
);
-- Create HNSW index for fast ANN search
CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 200);
-- Or IVFFlat (faster to build, requires vacuum after inserts)
CREATE INDEX ON documents
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
import asyncpg
import numpy as np
async def vector_search_with_filter(
pool: asyncpg.Pool,
query_embedding: np.ndarray,
customer_id: str,
doc_type: str,
k: int = 10
):
"""
pgvector search with relational filtering.
Can join against any PostgreSQL table - huge advantage.
"""
# Set search parameter (like nprobe for IVF, efSearch for HNSW)
await pool.execute("SET ivfflat.probes = 10")
# For HNSW: SET hnsw.ef_search = 100
rows = await pool.fetch(
"""
SELECT
d.id,
d.content,
1 - (d.embedding <=> $1::vector) AS cosine_similarity,
u.name AS customer_name -- can JOIN relational data!
FROM documents d
JOIN customers u ON d.customer_id = u.id
WHERE d.customer_id = $2
AND d.doc_type = $3
ORDER BY d.embedding <=> $1::vector
LIMIT $4
""",
query_embedding.tolist(),
customer_id,
doc_type,
k,
)
return rows
Head-to-Head Comparison
| Feature | Pinecone | Weaviate | Qdrant | pgvector | Chroma |
|---|---|---|---|---|---|
| Self-hosted | No | Yes | Yes | Yes | Yes |
| Managed cloud | Yes | Yes | Yes | (via supabase) | Yes (beta) |
| Hybrid search | No (dense only) | Yes (native) | Yes (sparse+dense) | Partial | No |
| Filtered ANN | Post-filter | Pre+post | ACORN (best) | Index + filter | Post-filter |
| Multi-tenancy | Namespaces | Tenant API | Collections | Schemas/tables | Collections |
| Delete performance | Good | Good | Good | Excellent (MVCC) | Good |
| Consistency model | Eventual | Eventual | Eventual | Strong (ACID) | Eventual |
| SQL joins | No | No | No | Yes | No |
| Max scale (single node) | Cloud scales | ~100M | ~100M+ (on-disk) | ~5–10M | ~1M |
| Update frequency | Good | Moderate | Excellent | Excellent | Good |
The Selection Framework
Step 1: Are you running PostgreSQL already?
If yes, seriously evaluate pgvector first. Adding an extension is much cheaper operationally than running a separate database service. pgvector is appropriate up to ~5M vectors. Above that, consider pgvector for metadata/relational joins with a dedicated vector DB for ANN.
Step 2: Do you need hybrid search?
If keyword search matters (it does for most enterprise knowledge management applications), choose Weaviate or Qdrant (with sparse vector support). Pinecone is pure dense vector search - adding BM25 requires you to maintain a separate Elasticsearch/OpenSearch instance.
Step 3: Do you have complex metadata filters?
If filters eliminate more than 50% of your collection per query, you need efficient filtered ANN. Qdrant's ACORN algorithm maintains recall under heavy filtering. Other databases degrade to sequential scan when filters are highly selective.
Step 4: What is your operational complexity budget?
For a 3-person team that does not want to manage infrastructure: Pinecone serverless or Weaviate Cloud. For a team with DevOps capacity that wants cost control and full data ownership: Qdrant self-hosted on Kubernetes. For a solo developer building a RAG prototype: Chroma.
Step 5: What is your cost model?
Pinecone serverless: very cheap for low-traffic (under $10/month for small workloads); gets expensive at high QPS (thousands of dollars per month). Qdrant self-hosted: pay only for the machines, which can be 5–10× cheaper at scale than managed services. pgvector: zero additional cost if you are already paying for PostgreSQL.
Production Engineering Notes
Benchmark on your actual data distribution. Published benchmarks (ANN-Benchmarks) use synthetic or academic datasets (SIFT, GIST, DEEP1B). Your production embeddings from a specific model on your domain data will have different clustering properties, leading to different recall-latency curves. Always run recall evaluation on a representative sample of your production queries.
Start simple, migrate when you have evidence. The startup that opens this lesson should probably start with pgvector if they are already on PostgreSQL. They can migrate to Qdrant or Weaviate if they outgrow pgvector - migration is a few hours of work, not a months-long project, since you are just re-ingesting embeddings.
Consistency model matters for write-heavy applications. All vector databases (except pgvector) use eventual consistency. If you insert a vector and immediately query for it, you may not find it. For RAG applications where users upload documents and immediately search them, either use a strong-consistency path (pgvector, or Qdrant with wait: true on upserts) or design for eventual consistency with a "document processing" UX that indicates when a document is searchable.
Common Mistakes
:::danger Choosing a database based on marketing benchmarks Every vector database publishes benchmarks where it looks best. Pinecone benchmarks against cold-start scenarios where their managed infra shines. Qdrant benchmarks filtered search. Weaviate benchmarks hybrid search fusion. Run your own benchmark on: your embedding model's output, your actual query distribution, your filter patterns, and your scale. :::
:::warning Assuming Chroma is production-ready at scale Chroma is excellent for development and offline processing. Its in-process mode does not support concurrent writes from multiple processes. Its managed cloud offering (as of 2024) is newer and less battle-tested than Pinecone or Qdrant Cloud. Prototype with Chroma, validate with Qdrant or Weaviate in staging before launch. :::
:::tip Use pgvector for prototyping even if you plan to migrate Even if you plan to run Qdrant or Weaviate in production, building your application first with pgvector means one less new technology to learn during initial development. The pgvector API is just SQL. Migrating to a dedicated vector DB later requires adding an API client and changing a few dozen lines - the embedding pipeline and query logic are identical. :::
Interview Questions
Q1: Your startup is building a RAG-based customer support tool. You have 2M documents, PostgreSQL already running, and a 2-person team. Which vector database do you choose?
pgvector. You already have the PostgreSQL infrastructure, adding an extension requires no new service, and 2M vectors is well within pgvector's performance range. This avoids operational complexity (no new database to monitor, back up, scale, and secure) and enables SQL joins between document metadata and your user/ticket tables. Revisit at 10M+ documents or if you need hybrid BM25+dense search that pgvector cannot provide efficiently.
Q2: Pinecone's post-filtering approach vs Qdrant's ACORN - what is the difference and when does it matter?
Post-filtering: run ANN search to retrieve top-K candidates, then apply metadata filter, then return the filtered results. If the filter eliminates 95% of candidates, you often end up with fewer than K results, or you must retrieve K/(1-filter_rate) candidates (20K for a 95% filter), dramatically increasing compute. ACORN (Qdrant's approach): integrates the filter directly into the HNSW traversal, only visiting graph nodes that pass the filter. This maintains recall even for highly selective filters (99% elimination) with minimal latency increase. ACORN matters when your queries have filters that eliminate more than 50% of the collection.
Q3: You are migrating from Chroma to Qdrant. What breaks and what stays the same?
Stays the same: your embedding pipeline (embedding model, normalization, chunking logic), your application's conceptual search flow (embed query, find nearest neighbors, return results). What changes: client library (from chromadb to qdrant_client), collection configuration syntax, filter syntax (Chroma uses {"field": {"$eq": value}}, Qdrant uses FieldCondition(key=..., match=MatchValue(value=...))), and the upsert API shape. The biggest practical challenge is re-ingesting all documents into Qdrant - typically a batch job that takes a few hours.
Q4: A financial services company needs strong consistency guarantees for their vector search. Which solution fits and why?
pgvector (with PostgreSQL) is the only option with ACID transactions. If you insert a vector and commit the transaction, a subsequent read will always see it - no eventual consistency. For financial applications that need to atomically update a document record and its vector embedding while maintaining audit trails and rollback capability, only a SQL-backed solution provides these guarantees. Dedicated vector databases use eventual consistency models that do not satisfy financial-grade data integrity requirements.
Q5: How do you estimate the monthly cost of running Pinecone serverless vs Qdrant self-hosted for 5M vectors at 100 QPS?
Pinecone serverless (2024 pricing): 100 read units per query × 100 QPS × 86400 seconds × 30 days ≈ 26 billion read units/month. At 1,040/month plus storage costs (0.20/hour = $145/month. At 100 QPS, one instance handles this easily. Self-hosted is 7× cheaper at this scale, but requires DevOps time to operate, monitor, and maintain. The crossover where managed is worth the premium is typically teams of 1–3 engineers or very early stage where engineering time is the scarce resource.
