Vector Database Comparison 2026: pgvector vs Qdrant vs Pinecone vs Weaviate vs Milvus
A practitioner's guide to picking a vector store for your RAG stack in 2026 โ with real tradeoffs, code, and the questions that actually decide the answer.
Every RAG tutorial picks a vector database in the first paragraph and never explains why. Then six months later a team is migrating off it because it can't do metadata filtering at scale, or it costs 8x what they budgeted, or it can't run in their VPC. This post is the "why" they skipped.
The problem
Vector search is not a monolith. Under the hood you're choosing across five independent axes โ index algorithm, storage model, filtering approach, deployment model, and consistency guarantees โ and every vendor makes different tradeoffs on each. Picking "the most popular one" or "the one with the best marketing" is how teams end up re-architecting mid-project.
The good news: for 90% of teams, the decision comes down to answering three questions honestly. This post gives you those questions, then a comparison table and code so you can validate the answer yourself instead of trusting a chart.
The three questions that actually decide it
1. Do you already have a Postgres database in production?
If yes, pgvector (or pgvectorscale) is very likely your answer, at least to start. Adding a new database to your stack has a real cost โ a new backup strategy, a new monitoring target, a new thing that pages someone at 2am. pgvector rides on infrastructure you already operate.
2. What's your scale, in vectors, not documents?
A "large" RAG corpus of 500K chunks is not large for a vector database โ it's large for you to manage. Below ~5M vectors, almost every option here performs adequately; the differences are about operations, not raw ANN speed. Above 50M vectors, index build time, memory footprint, and horizontal sharding start to matter and narrow the field to Qdrant, Milvus, and managed Pinecone.
3. Do you need hybrid search (vector + keyword + metadata filters) as a first-class citizen?
If your queries look like "find chunks similar to X, from documents owned by tenant Y, uploaded in the last 30 days, where category is 'invoice'" โ that's not a vector search problem, it's a filtered vector search problem, and not every engine handles pre-filtering efficiently. Some apply filters after the ANN search and silently return fewer results than top_k when filters are selective. This is the single most common production bug teams hit.
Comparison table
| pgvector | Qdrant | Weaviate | Pinecone | Milvus | |
|---|---|---|---|---|---|
| Deployment | Self-host (any Postgres) | Self-host or managed cloud | Self-host or managed cloud | Managed only | Self-host or managed (Zilliz) |
| Index types | HNSW, IVFFlat | HNSW | HNSW | Proprietary (undisclosed) | HNSW, IVF, DiskANN |
| Pre-filtering (filter before ANN) | Yes, via SQL WHERE |
Yes, native payload filtering | Yes | Yes (metadata filters) | Yes |
| Hybrid search (BM25 + vector) | Manual (join with tsvector) |
Native | Native | Native (sparse-dense) | Native |
| Multi-tenancy | Row-level security or schema-per-tenant | Native (via payload + collection sharding) | Native (multi-tenant collections) | Namespaces | Partitions |
| Ops burden | Low if you run Postgres already | Medium | Medium-high | Zero (managed) | High (self-host), low (Zilliz) |
| Cost at 10M vectors, 1536-dim | Postgres instance cost only | ~$150-400/mo self-host | ~$200-500/mo self-host | ~$300-800/mo | Similar to Qdrant self-host |
| Best for | Existing Postgres shops, <20M vectors | Best filtering/perf balance, self-host teams | Strong hybrid search + GraphQL-style queries | Zero-ops, fastest to production | Very large scale (100M+), enterprises with SRE capacity |
Costs are directional โ always benchmark with your own embedding dimension and query volume before committing budget.
Code: the pre-filtering bug, reproduced
This is the bug worth testing for before you pick anything. Post-filtering (filter after retrieving top_k) silently under-returns results. Here's how to check which behavior an engine has, using Qdrant as an example โ the same test applies conceptually to any engine you're evaluating:
from qdrant_client import QdrantClient
from qdrant_client.models import Filter, FieldCondition, MatchValue
client = QdrantClient(url="http://localhost:6333")
# Suppose 10,000 chunks total, only 50 belong to tenant "acme-corp"
results = client.query_points(
collection_name="docs",
query=query_vector,
query_filter=Filter(
must=[FieldCondition(key="tenant_id", match=MatchValue(value="acme-corp"))]
),
limit=20,
)
# Correct (pre-filter) behavior: you get up to 20 results, all tenant "acme-corp",
# ranked by similarity among ONLY that tenant's vectors.
# Broken (post-filter) behavior: engine finds top 20 globally, THEN filters โ
# if fewer than 20 of the global top-20 belong to acme-corp, you silently get fewer results.
assert all(r.payload["tenant_id"] == "acme-corp" for r in results.points)
print(f"Got {len(results.points)} results (requested 20)")
Run this against any candidate engine with a deliberately small, cardinality-heavy filter (a tenant with few documents in a large collection) and check the returned count. If it's consistently below limit when it shouldn't be, you've found a pre- vs post-filtering engine โ write that fact down, it decides real architecture questions later.
Minimal pgvector setup, for comparison
If your default is "just use Postgres," here's the actual footprint:
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE chunks (
id bigserial PRIMARY KEY,
tenant_id text NOT NULL,
content text NOT NULL,
embedding vector(1536),
created_at timestamptz DEFAULT now()
);
CREATE INDEX ON chunks USING hnsw (embedding vector_cosine_ops);
CREATE INDEX ON chunks (tenant_id);
SELECT id, content, 1 - (embedding <=> $1) AS similarity
FROM chunks
WHERE tenant_id = $2
ORDER BY embedding <=> $1
LIMIT 20;
That's a correct pre-filter (Postgres applies the WHERE before the ordering scan when the planner chooses to, though at very high selectivity you should verify with EXPLAIN ANALYZE that it's not falling back to a sequential scan of the whole HNSW index). For most teams under 10-20M vectors, this is genuinely enough โ the "you'll need a real vector DB eventually" advice is true later, not immediately.
Checklist before you commit
- Benchmarked recall@10 and p95 latency with your embedding model and dimension, not the vendor's demo dataset
- Tested filtered queries with a high-cardinality, high-selectivity filter (few results in a large collection)
- Confirmed the engine's pre-filter vs post-filter behavior explicitly, not assumed it
- Priced out cost at 3x current data volume, not just current volume
- Checked if you need cross-region replication or VPC peering (rules out some managed options)
- Verified index rebuild time at your scale โ some HNSW implementations rebuild in minutes, others in hours
- Decided your re-embedding strategy in advance (model upgrades happen; know how you'll backfill)
Common pitfalls
Choosing based on ANN benchmarks alone. ann-benchmarks.com numbers are useful for algorithm comparison, nearly useless for picking a production database โ they don't measure filtering, multi-tenancy, ingestion throughput, or operational cost, which is where teams actually get stuck.
Not testing recall degradation under filtering. HNSW recall can drop meaningfully when combined with a restrictive filter, because the graph traversal has fewer valid neighbors to walk. Test recall with your real filters applied, not just on the raw vector set.
Treating embedding dimension as free. 1536-dim vs 3072-dim doubles memory and roughly doubles query latency. If you're not measurably better off with the larger embedding model, the smaller one is often the right call for both cost and speed.
Ignoring re-indexing cost. Every time you change embedding models (and you will), you re-embed and re-index everything. Engines with slow bulk-insert or index-build times turn a routine model upgrade into a multi-day migration.
Picking managed-only options before checking data residency requirements. If you're building for enterprise customers, "can this run in our customer's VPC" kills Pinecone-only architectures fast. Confirm this constraint before the POC, not after the contract is signed.
Next steps
Start with pgvector if you already run Postgres and are under ~20M vectors โ it removes an entire category of infrastructure risk. Move to Qdrant or Milvus when you hit scale or filtering-performance walls pgvector can't clear, and reach for a managed option like Pinecone when you'd rather pay for zero ops than staff it.
Whatever you pick, the pre-filtering test above takes 20 minutes to run and will save you a production incident. Run it before you write a single line of retrieval code around the engine.
If you want to go deeper on RAG architecture โ chunking strategy, retrieval evaluation, and how vector store choice interacts with the rest of the pipeline โ that's core material in Joinloop's AI engineering cohort.
Want to build this yourself?
Join a live cohort and go from reading about AI to shipping it, with practitioners who do it daily.