Module 5: Vector Database Landscape for AI Engineers

Capsule 04: Feature Comparison for RAG

🎯 Capsule objective

Compare the technical features that most impact a RAG system in production — metadata filtering, hybrid search, multi-tenancy, batch operations, SDKs, and scaling — and understand how support varies across providers.

In the previous capsules you mapped the provider landscape and understood the managed vs self-hosted trade-off. Now it's time to evaluate the features that directly affect the quality, security, and efficiency of your RAG pipeline. A vector database can be cheap and easy to operate, but if it doesn't support advanced metadata filtering or multi-tenancy, your RAG will have serious problems in production.

This capsule is deliberately table-heavy: comparison tables are the most useful tool for a quick evaluation. Each table comes with context on why that feature matters for RAG and code examples that show API differences between providers.

By the end of this capsule:

  • ✅ You'll evaluate 6 critical features for RAG across 5 providers
  • ✅ You'll distinguish between nominal support and real support of each feature
  • ✅ You'll identify which features are "non-negotiable" for your case
  • ✅ You'll have reference tables for your decision tree

Estimated time: 25-30 minutes


📋 The 6 Critical Features for RAG

What makes a feature "critical" for RAG?

It's critical if it affects at least one of these four goals of a RAG system in production:

GoalKey question
Retrieval precisionAre the retrieved documents relevant?
p95 latencyIs retrieval fast enough for the UX?
Security / isolationCan one tenant's data leak to another?
Operational costDoes operation scale without blowing the budget?

Feature map

Critical Features for RAG
│
├── 1. Metadata Filtering     → Precision
├── 2. Hybrid Search          → Precision
├── 3. Multi-tenancy          → Security
├── 4. Batch Operations       → Cost
├── 5. SDKs and DX            → Speed
└── 6. Scaling                → Cost + Latency

1️⃣ Metadata Filtering

Why does it matter for RAG?

Metadata filtering reduces the search space before computing similarity. Without filtering, your RAG searches across all vectors — with filtering, it searches only the relevant subset.

Real impact:

Without filtering:
  Query: "How do I configure authentication?"
  Searches in: 500K vectors (all docs)
  Result: Mixes docs from v1, v2, v3 of the product
  → LLM generates an answer with outdated info

With filtering (version="v3"):
  Query: "How do I configure authentication?"
  Searches in: 80K vectors (only v3)
  Result: Relevant and up-to-date docs
  → LLM generates a precise answer

Metadata filtering comparison

CapabilityChromaDBPineconeWeaviateQdrantMilvus
Equality filters (=)
Range filters (>, <)
IN filters (list)
Logical AND / OR
NOT / negation
Nested filters (objects)
Full-text search in metadata
Geo filtering
Dedicated payload indexN/AN/AAuto✅ Manual✅ Manual
Performance with filters⚠️ Degrades✅ Good✅ Good✅ Excellent✅ Good

Code: Filtering in each provider

# ChromaDB — where syntax
results = collection.query(
    query_texts=["authentication"],
    n_results=5,
    where={
        "$and": [
            {"version": {"$eq": "v3"}},
            {"language": {"$eq": "en"}}
        ]
    }
)

# Pinecone — MongoDB-like syntax
results = index.query(
    vector=embedding,
    top_k=5,
    filter={
        "$and": [
            {"version": {"$eq": "v3"}},
            {"language": {"$eq": "en"}}
        ]
    }
)

# Weaviate — typed filter API
from weaviate.classes.query import Filter
response = collection.query.near_text(
    query="authentication",
    limit=5,
    filters=(
        Filter.by_property("version").equal("v3") &
        Filter.by_property("language").equal("en")
    )
)

# Qdrant — model-based filters
from qdrant_client.models import Filter, FieldCondition, MatchValue
results = client.query_points(
    collection_name="docs",
    query=embedding,
    limit=5,
    query_filter=Filter(
        must=[
            FieldCondition(key="version", match=MatchValue(value="v3")),
            FieldCondition(key="language", match=MatchValue(value="en"))
        ]
    )
)

# Milvus — SQL-like string
results = client.search(
    collection_name="docs",
    data=[embedding],
    limit=5,
    filter='version == "v3" and language == "en"'
)

RAG insight: Qdrant stands out in filtering performance because it supports dedicated payload indexes that avoid a full-scan of the metadata. Weaviate has the most expressive filters (geo, nested objects). ChromaDB covers the basics but can degrade with complex filters over large collections.


2️⃣ Hybrid Search

Why does it matter for RAG?

Hybrid search combines keyword match (BM25) with semantic search (vector) to capture both exact matches ("GPT-4", "INV-2024-001") and conceptual ones ("how to improve performance"). In RAG for technical documentation, hybrid search improves accuracy by 15-25%.

Hybrid search comparison

CapabilityChromaDBPineconeWeaviateQdrantMilvus
Native hybrid search⚠️ Sparse✅ Built-in✅ Sparse
Keyword index (BM25)✅ Inverted
Sparse vectors
Fusion algorithmN/AManualRRF (default)ManualRRF/Weighted
Alpha tuningN/AN/A✅ (0-1)Manual
Custom rankingN/A

Support levels:

  • Native (Weaviate): A single endpoint, automatic fusion, adjustable alpha.
  • Sparse vectors (Pinecone, Qdrant): You send dense + sparse vectors, manual or semi-auto fusion.
  • Not supported (ChromaDB): You need to implement BM25 externally (rank_bm25 library).

Code: Hybrid search per provider

# Weaviate — native hybrid with alpha
response = collection.query.hybrid(
    query="GPT-4 API rate limits",
    alpha=0.5,  # 0 = pure keyword, 1 = pure vector
    limit=10
)

# Pinecone — sparse + dense vectors
results = index.query(
    vector=dense_embedding,        # semantic
    sparse_vector={                 # keyword
        "indices": [102, 5483, 9201],
        "values": [0.8, 0.5, 0.3]
    },
    top_k=10
)

# Qdrant — sparse vectors with named vectors
results = client.query_points(
    collection_name="docs",
    query=embedding,
    using="dense",
    limit=10
)
# + separate query with a sparse vector, manual fusion

# ChromaDB — does NOT support hybrid, workaround with external BM25
from rank_bm25 import BM25Okapi

tokenized = [doc.split() for doc in all_documents]
bm25 = BM25Okapi(tokenized)
keyword_scores = bm25.get_scores(query.split())

semantic_results = collection.query(
    query_texts=[query], n_results=20
)
# Manual fusion (RRF or weighted)

RAG insight: If your RAG handles technical documentation with IDs, versions, or product names, hybrid search is not optional — it's essential. Weaviate is the most mature option for hybrid. Pinecone and Qdrant support it via sparse vectors but require more work.


3️⃣ Multi-tenancy

Why does it matter for RAG?

Multi-tenancy determines how you isolate data between users, organizations, or customers. In a SaaS RAG, if Tenant A can see Tenant B's documents, you have a data leakage — a serious security problem.

Multi-tenancy strategies

Strategy 1: Collection per tenant
┌──────────┐ ┌──────────┐ ┌──────────┐
│Tenant A  │ │Tenant B  │ │Tenant C  │
│Collection│ │Collection│ │Collection│
└──────────┘ └──────────┘ └──────────┘
Pro: Total isolation
Con: Management overhead, doesn't scale to 1000+ tenants

Strategy 2: Metadata filtering
┌─────────────────────────────────┐
│       Shared Collection          │
│  [tenant_id=A] [tenant_id=B]   │
│  [tenant_id=A] [tenant_id=C]   │
└─────────────────────────────────┘
Pro: Simple, scales to many tenants
Con: Filter overhead, risk of forgetting the filter (leakage)

Strategy 3: Namespace / Partition
┌─────────────────────────────────┐
│           Index                  │
│  ┌─────────┐  ┌─────────┐      │
│  │Namespace│  │Namespace│      │
│  │   "A"   │  │   "B"   │      │
│  └─────────┘  └─────────┘      │
└─────────────────────────────────┘
Pro: Native logical isolation
Con: Per-provider limits

Multi-tenancy comparison

CapabilityChromaDBPineconeWeaviateQdrantMilvus
Main strategyCollectionsNamespacesNative tenantsPayload filterPartitions
Data isolationMediumHighHighMedium-HighHigh
Tenant limit~100 collections10K namespaces100K+ tenantsNo soft limit4096 partitions
Inactive tenant offload✅ (activity-based)
Tenant-level metrics
Data leakage riskMediumLowLowMedium*Low

*Qdrant with a payload filter requires the developer to always include the tenant filter — if they forget, there's leakage.

Code: Multi-tenancy per provider

# Pinecone — native namespaces
index.upsert(
    vectors=[{"id": "doc_1", "values": emb, "metadata": {"content": "..."}}],
    namespace="tenant_acme"  # isolation by namespace
)
results = index.query(
    vector=query_emb, top_k=5,
    namespace="tenant_acme"  # searches only in this tenant
)

# Weaviate — native multi-tenancy (v1.20+)
import weaviate.classes as wvc

collection = client.collections.create(
    name="Documents",
    multi_tenancy_config=wvc.config.Configure.multi_tenancy(enabled=True),
    vectorizer_config=wvc.config.Configure.Vectorizer.text2vec_openai()
)
collection.tenants.create([
    wvc.tenants.Tenant(name="tenant_acme"),
    wvc.tenants.Tenant(name="tenant_globex"),
])
tenant_collection = collection.with_tenant("tenant_acme")
tenant_collection.data.insert(properties={"content": "..."})

# Qdrant — payload filter (manual, no native namespace)
client.upsert(
    collection_name="docs",
    points=[PointStruct(
        id=1, vector=emb,
        payload={"tenant_id": "acme", "content": "..."}
    )]
)
results = client.query_points(
    collection_name="docs",
    query=query_emb, limit=5,
    query_filter=Filter(must=[
        FieldCondition(key="tenant_id", match=MatchValue(value="acme"))
    ])
)

# ChromaDB — collection per tenant
tenant_collection = client.get_or_create_collection(name="tenant_acme")
tenant_collection.add(documents=["..."], ids=["doc_1"])

RAG insight: If you build RAG for SaaS (multiple customers), multi-tenancy is a security requirement, not a feature. Weaviate has the most mature implementation with offload of inactive tenants. Pinecone with namespaces is simple and secure. Qdrant requires developer discipline to never forget the filter.


4️⃣ Batch Operations

Why does it matter for RAG?

Ingesting documents in RAG is inherently a batch problem: you chunked 10,000 documents into 50,000 chunks → you need to insert 50,000 vectors. The speed and efficiency of batch operations directly affects the setup time and the cost of re-indexing.

Batch operations comparison

CapabilityChromaDBPineconeWeaviateQdrantMilvus
Batch upsert
Max batch size~41K*100 vectorsNo limit**No limit**No limit**
Parallel upsertManual✅ Async✅ Batch API✅ Async
Streaming ingestion
Progress tracking
Ingestion rate (1536-dim)~5K/s local~1K/s API~10K/s local~15K/s local~20K/s local

*ChromaDB has a per-request size limit from SQLite. **Limited by server memory.

Code: Efficient batch ingestion

# ChromaDB — batch with chunks
def batch_insert_chromadb(collection, documents, batch_size=1000):
    for i in range(0, len(documents), batch_size):
        batch = documents[i:i + batch_size]
        collection.add(
            documents=[d["text"] for d in batch],
            metadatas=[d["metadata"] for d in batch],
            ids=[d["id"] for d in batch]
        )
        print(f"Inserted {min(i + batch_size, len(documents))}/{len(documents)}")


# Pinecone — batch with a limit of 100
def batch_insert_pinecone(index, vectors, batch_size=100):
    for i in range(0, len(vectors), batch_size):
        batch = vectors[i:i + batch_size]
        index.upsert(vectors=batch)


# Qdrant — batch with upload_points
def batch_insert_qdrant(client, collection_name, points, batch_size=1000):
    client.upload_points(
        collection_name=collection_name,
        points=points,
        batch_size=batch_size,
        parallel=4  # concurrent threads
    )


# Milvus — bulk insert
def batch_insert_milvus(client, collection_name, data, batch_size=5000):
    for i in range(0, len(data), batch_size):
        batch = data[i:i + batch_size]
        client.insert(collection_name=collection_name, data=batch)

RAG insight: Pinecone has the most restrictive batch size (100 vectors per request), which makes the initial ingestion slower via API. For massive loads (> 100K docs), Qdrant and Milvus self-hosted are significantly faster. ChromaDB is efficient for moderate sizes.


5️⃣ SDKs and Developer Experience

Why does it matter for RAG?

The quality of the SDK determines how much time your team spends integrating the vector DB with your RAG pipeline. A poorly designed SDK generates bugs, frustration, and time lost debugging.

SDK comparison

CapabilityChromaDBPineconeWeaviateQdrantMilvus
Python SDK✅ Excellent✅ Excellent✅ Good (v4)✅ Excellent✅ Good
TypeScript SDK
Go SDK
Rust SDK✅ (official)
Java SDK
Async support
Type hints (Python)Partial✅ CompletePartial
Auto-embedding✅ (modules)
LangChain integration
LlamaIndex integration
SDK documentation⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

RAG insight: ChromaDB wins on absolute simplicity (auto-embedding, 5 lines to get working). Qdrant has the best documentation and type hints. Weaviate requires more boilerplate but offers more power (schema, modules). For teams that use LangChain/LlamaIndex, the SDK difference matters less because the framework abstracts the API. The detailed comparison of code patterns per provider is in capsule 02.


6️⃣ Scaling

Why does it matter for RAG?

Your RAG starts with 10K documents in the MVP. In 6 months it has 500K. In a year, 5M. The ability to scale the vector DB without re-architecting the whole system is critical.

Scaling comparison

CapabilityChromaDBPineconeWeaviateQdrantMilvus
Max recommended scale~1M100M+100M+100M+Billions
Automatic shardingManual
Replication✅ (multi-AZ)✅ (Raft)
Horizontal scaling✅ (auto)
QuantizationPQScalar/PQ/BinarySQ8/PQ
On-disk indexN/A (cloud)✅ (mmap)✅ (DiskANN)
GPU acceleration
Segment managementAutoAutoManual/AutoAuto

Impact of quantization on costs

TypeExample (10M × 1536-dim)MemorySavingsAccuracy loss
No quantization (float32)57.3 GB
Scalar (Qdrant)14.3 GB75%< 1%Recommended
Product (Weaviate/Milvus)~1.8 GB96%2-5%Acceptable
Binary (Qdrant)~1.8 GB96%3-7%Requires rescoring

RAG insight: If your RAG grows to > 5M vectors, quantization becomes the most important cost factor. Qdrant leads with 3 types of quantization. Milvus scales further with GPU and DiskANN. ChromaDB is not an option at this scale.


📊 Master Table: Feature Comparison for RAG

FeatureChromaDBPineconeWeaviateQdrantMilvus
Metadata filtering✅ Basic✅ Good✅ Advanced✅ Excellent✅ Good
Hybrid search⚠️ Sparse✅ Native✅ Sparse
Multi-tenancy⚠️ Collections✅ Namespaces✅ Native⚠️ Payload✅ Partitions
Batch operations⚠️ Slow✅ Fast✅ Fast
SDK quality✅ Simple✅ Good✅ Good✅ Excellent✅ Good
Scaling❌ Limited✅ Auto✅ Enterprise
Quantization✅ PQ✅ 3 types✅ SQ8/PQ
Observability✅ Console✅ Metrics✅ Dashboard✅ Attu

Quick reading of the table

  • ChromaDB: Excellent to start, limited for production.
  • Pinecone: Solid across the board except hybrid search and quantization.
  • Weaviate: The most complete in features (native hybrid, multi-tenancy, PQ).
  • Qdrant: Best in filtering and quantization, hybrid search improving.
  • Milvus: The most scalable, but higher operational complexity.

🔍 How to Use This Comparison Without Bias

3-step process

Step 1: Mark 2-3 "non-negotiable" features for your RAG.

non_negotiables = {
    "saas_multitenant": ["multi_tenancy"],
    "documentation_rag": ["hybrid_search", "metadata_filtering"],
    "internal_search": ["scaling", "batch_operations"],
    "prototype": []  # for prototypes, no feature is a blocker
}

my_case = "documentation_rag"
print(f"Non-negotiable features: {non_negotiables[my_case]}")
# → Non-negotiable features: ['hybrid_search', 'metadata_filtering']

Step 2: Eliminate options that don't cover the minimums.

Case: documentation_rag (hybrid search + metadata filtering)

ChromaDB: ❌ No hybrid search → ELIMINATED
Pinecone: ⚠️ Hybrid via sparse vectors (requires extra work)
Weaviate: ✅ Native hybrid + advanced filtering
Qdrant:   ✅ Sparse vectors + excellent filtering
Milvus:   ✅ Hybrid + good filtering

Candidates: Weaviate, Qdrant, Milvus (Pinecone with reservations)

Step 3: Only then compare cost and operations among the remaining candidates.


🔧 Evaluation troubleshooting

1. "Everyone says they support everything"

Cause: Marketing vs reality. "Supports metadata filtering" can mean basic equality filters or a complete system with geo-filtering and nested objects.

Solution: For each "non-negotiable" feature, look for:

  • Official documentation with code examples
  • Documented limits (max filters per query, max tenants, etc.)
  • Open GitHub issues related to that feature
  • Independent benchmarks (not from the provider itself)

2. "I don't know how much hybrid search weighs in my case"

Cause: You haven't measured the distribution of query types.

Solution: Analyze 100 real queries from your system (or projected):

  • Count how many contain proper names, IDs, or exact terms
  • If > 20% are "specific", hybrid search will improve your accuracy significantly
  • If < 5% are specific, pure semantic is enough

3. "Multi-tenancy seems like overkill"

Cause: You only have one tenant today — but what about in 6 months?

Solution: If your product is B2B or SaaS, multi-tenancy is not optional. Implementing it later is a costly migration. If your product is B2C (a single tenant), metadata filtering with user_id is enough and formal multi-tenancy is unnecessary.

4. "How much accuracy do I lose with quantization?"

Cause: Fear of degrading the RAG's quality by compressing vectors.

Solution: The typical loss with Scalar Quantization is < 1% in recall@10. With Product Quantization it's 2-5%. For RAG, this loss is generally acceptable. Run an A/B test: compare recall of 100 queries with and without quantization before deciding.


✏️ Exercises

Exercise 1: Non-negotiable features

For your RAG project (real or hypothetical), define:

  1. The 3 most important features from the list of 6.
  2. The minimum acceptable level for each one.
  3. Which providers survive the filter.
Reference solution (case: RAG for SaaS technical support)
  1. Non-negotiable features:

    • Multi-tenancy (data isolation per customer)
    • Hybrid search (queries with ticket IDs + conceptual)
    • Metadata filtering (filter by product, version, language)
  2. Minimum level:

    • Multi-tenancy: native isolation (not just a metadata filter)
    • Hybrid search: built-in or sparse vectors
    • Metadata filtering: AND/OR + ranges
  3. Providers that survive:

    • ✅ Weaviate (native multi-tenancy, native hybrid, advanced filtering)
    • ⚠️ Pinecone (good namespaces, hybrid via sparse, good filtering)
    • ⚠️ Milvus (partitions, hybrid, good filtering)
    • ❌ ChromaDB (limited multi-tenancy, no hybrid)
    • ⚠️ Qdrant (multi-tenancy via payload filter, sparse vectors)

Exercise 2: Custom scoring table

Assign a score (1-5) to each provider for the 6 features, weighting by importance for your case:

FeatureWeightChromaDBPineconeWeaviateQdrantMilvus
Metadata filtering
Hybrid search
Multi-tenancy
Batch operations
SDK quality
Scaling
Weighted total
Reference solution (case: internal documentation RAG, small team)
FeatureWeightChromaDBPineconeWeaviateQdrantMilvus
Metadata filtering43 (12)4 (16)5 (20)5 (20)4 (16)
Hybrid search51 (5)3 (15)5 (25)4 (20)4 (20)
Multi-tenancy12 (2)4 (4)5 (5)3 (3)4 (4)
Batch operations33 (9)2 (6)4 (12)5 (15)5 (15)
SDK quality35 (15)4 (12)3 (9)5 (15)3 (9)
Scaling21 (2)5 (10)4 (8)4 (8)5 (10)
Weighted total4563798174

Result: Qdrant and Weaviate lead. For a small team without a need for multi-tenancy, Qdrant wins on filtering + SDK + batch.

Exercise 3: Implement advanced filtering

Using ChromaDB, implement a query that:

  1. Searches for documents about "performance optimization"
  2. Filters by version >= 3 AND language = "es"
  3. Returns the 3 most relevant results with scores
Solution
import chromadb

client = chromadb.PersistentClient(path="./exercise_filter")
collection = client.get_or_create_collection(
    name="docs",
    metadata={"hnsw:space": "cosine"}
)

collection.add(
    documents=[
        "Query optimization in databases",
        "Performance improvement in REST APIs",
        "Performance tuning for distributed systems",
        "CSS guide for designers",
        "How to optimize performance in Python v2",
    ],
    metadatas=[
        {"version": 3, "language": "es"},
        {"version": 4, "language": "es"},
        {"version": 3, "language": "en"},
        {"version": 2, "language": "es"},
        {"version": 2, "language": "es"},
    ],
    ids=["d1", "d2", "d3", "d4", "d5"]
)

results = collection.query(
    query_texts=["performance optimization"],
    n_results=3,
    where={
        "$and": [
            {"version": {"$gte": 3}},
            {"language": {"$eq": "es"}}
        ]
    }
)

for doc, dist, meta in zip(
    results["documents"][0],
    results["distances"][0],
    results["metadatas"][0]
):
    score = 1 - dist
    print(f"Score: {score:.4f} | v{meta['version']} | {doc}")

Expected result: Only d1 and d2 pass the filter (version >= 3 AND language = "es"). d3 fails by language, d4 and d5 by version.

Exercise 4: Design a multi-tenant strategy

Your company has 3 customers (Acme, Globex, Initech) with separate documents. Design the multi-tenancy strategy for:

  • Scenario A: ChromaDB (development)
  • Scenario B: Pinecone (production)

For each one, write the insert and query code that guarantees isolation.

Solution
# Scenario A: ChromaDB — collection per tenant
import chromadb

client = chromadb.PersistentClient(path="./multi_tenant")

tenants = ["acme", "globex", "initech"]
for tenant in tenants:
    col = client.get_or_create_collection(name=f"docs_{tenant}")
    col.add(
        documents=[f"Internal document from {tenant}"],
        ids=[f"{tenant}_doc_1"]
    )

acme_results = client.get_collection("docs_acme").query(
    query_texts=["internal document"], n_results=5
)
# Returns only Acme's docs — isolation by collection


# Scenario B: Pinecone — namespace per tenant
from pinecone import Pinecone

pc = Pinecone(api_key="...")
index = pc.Index("production-rag")

for tenant in ["acme", "globex", "initech"]:
    index.upsert(
        vectors=[{"id": f"{tenant}_1", "values": embedding, "metadata": {"text": "..."}}],
        namespace=tenant
    )

results = index.query(
    vector=query_embedding,
    top_k=5,
    namespace="acme"  # isolation by namespace
)
# Returns only the "acme" namespace's docs

Key difference: ChromaDB creates separate collections (more management overhead). Pinecone uses namespaces within the same index (more efficient, with native isolation).

Exercise 5: Feature decision matrix

Given the following case, recommend a provider with justification:

Case: An e-commerce company. 5M products. Queries with an exact product name + conceptual ("comfortable shoes for running"). 200 brands (multi-tenant per brand). A team of 10 with 2 DevOps. Medium budget.

Solution

Analysis of non-negotiable features:

  1. Hybrid search — Mandatory (exact name + conceptual) → Eliminates ChromaDB
  2. Multi-tenancy — 200 brands → Needs robust support → Eliminates Qdrant (payload filter to 200 tenants is viable but not ideal)
  3. Scaling — 5M vectors → All except ChromaDB

Final candidates: Weaviate, Pinecone, Milvus

Scoring:

FeatureWeaviatePineconeMilvus
Hybrid search✅ Native (alpha)⚠️ Sparse vectors
Multi-tenancy 200 brands✅ Native (offload)✅ Namespaces✅ Partitions
Scaling 5M✅ (overkill)
DevOps team✅ Self-hosted viable✅ Zero ops⚠️ Complex

Recommendation: Weaviate — native hybrid search is the main differentiator for e-commerce. Multi-tenancy with offload of inactive brands reduces costs. The team has 2 DevOps who can operate self-hosted.

Alternative: Pinecone if the team prefers zero ops, accepting that hybrid search requires more work with sparse vectors.


🔗 Connection with the project (Decision Tree)

This capsule gives you the technical criteria for the decision nodes of your decision tree:

Do you need hybrid search?
├── YES → Weaviate (native) or Qdrant/Milvus (sparse)
└── NO → Anything works
         │
         Do you need native multi-tenancy?
         ├── YES → Weaviate or Pinecone
         └── NO → Qdrant or ChromaDB (if scale < 1M)

Combine these criteria with those of managed vs self-hosted (previous capsule) and costs (next capsule) to complete your decision tree.


📝 Summary

  • Metadata filtering is universal, but the depth varies: Qdrant and Weaviate lead with payload indexes and geo-filtering.
  • Hybrid search is essential for RAG with specific queries — Weaviate is the most mature option with native RRF fusion.
  • Multi-tenancy is a security requirement in SaaS — Weaviate has the most complete implementation with tenant offload.
  • Batch operations matter for massive ingestion — Pinecone is the slowest (100 vectors/request), Qdrant and Milvus the fastest.
  • SDK quality affects development speed — ChromaDB wins on simplicity, Qdrant on type safety and documentation.
  • Scaling and quantization determine long-term cost — Qdrant leads in quantization, Milvus in absolute scale.
  • No provider wins in all categories; define your "non-negotiables" first and compare afterward.

📚 Additional resources


Reading time: 25-30 minutes Next: 05-costs-and-tradeoffs.md