DEV Community

Phil IPS
Phil IPS

Posted on

Vector Databases Compared: Pinecone, Weaviate, Qdrant, Chroma, Milvus

Vector Databases Compared: Pinecone, Weaviate, Qdrant, Chroma, Milvus

Choose the right vector DB for your RAG system — benchmarks, features, and tradeoffs.

Introduction

Vector databases power semantic search, RAG, and recommendation systems. This guide compares the top 5 options with real benchmarks.

Quick Comparison

DB Type License Vector Index Filtering Multi-tenancy Best For
Pinecone Managed Proprietary HNSW ✅ Metadata ✅ Namespaces Production, zero-ops
Weaviate Self/Cloud BSD-3 HNSW ✅ GraphQL ✅ Classes Graph + Vector hybrid
Qdrant Self/Cloud Apache-2 HNSW ✅ Payload ✅ Collections Performance, Rust core
Chroma Embedded Apache-2 HNSW ✅ Metadata ❌ Local dev, prototyping
Milvus Self/Cloud Apache-2 HNSW/IVF ✅ Partition ✅ Partitions Massive scale (billions)

1. Pinecone — Zero-Ops Managed

Pros

  • Fully managed, no infrastructure
  • Excellent uptime SLA
  • Simple API, great SDKs
  • Automatic scaling
  • Built-in reranking (Cohere)

Cons

  • Expensive at scale
  • Vendor lock-in
  • Limited customization
  • No self-hosted option

Pricing (2024)

Tier Cost Vectors Dimensions
Starter Free 100K 1536
Standard $70/mo 1M 1536
Enterprise Custom Unlimited Any

Python SDK

from pinecone import Pinecone, ServerlessSpec

pc = Pinecone(api_key="your-api-key")

# Create index
pc.create_index(
    name="my-index",
    dimension=1536,
    metric="cosine",
    spec=ServerlessSpec(cloud="aws", region="us-east-1")
)

index = pc.Index("my-index")

# Upsert
vectors = [
    {"id": "1", "values": [0.1]*1536, "metadata": {"text": "doc 1", "category": "tech"}},
    {"id": "2", "values": [0.2]*1536, "metadata": {"text": "doc 2", "category": "science"}}
]
index.upsert(vectors=vectors)

# Query with filter
results = index.query(
    vector=[0.15]*1536,
    top_k=5,
    filter={"category": "tech"},
    include_metadata=True
)
Enter fullscreen mode Exit fullscreen mode

2. Qdrant — Performance Champion

Pros

  • Fastest in most benchmarks
  • Rust core, memory efficient
  • Rich filtering (payload)
  • Self-hosted + Cloud
  • Binary quantization (10x smaller)
  • gRPC + REST APIs

Cons

  • Younger ecosystem
  • Cloud still maturing
  • No built-in embeddings

Pricing

Tier Cost Storage
Free (Cloud) $0 1GB
Self-hosted Hardware cost Unlimited
Cloud Pro $0.10/hr 10GB+

Python Client

from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct, Filter, FieldCondition, MatchValue

# Local or Cloud
client = QdrantClient(url="http://localhost:6333")  # or cloud URL + api_key

# Create collection
client.create_collection(
    collection_name="docs",
    vectors_config=VectorParams(size=1536, distance=Distance.COSINE)
)

# Upsert with payload
points = [
    PointStruct(id=1, vector=[0.1]*1536, payload={"text": "doc 1", "category": "tech"}),
    PointStruct(id=2, vector=[0.2]*1536, payload={"text": "doc 2", "category": "science"})
]
client.upsert(collection_name="docs", points=points)

# Query with filter
results = client.query_points(
    collection_name="docs",
    query=[0.15]*1536,
    query_filter=Filter(
        must=[FieldCondition(key="category", match=MatchValue(value="tech"))]
    ),
    limit=5,
    with_payload=True
)
Enter fullscreen mode Exit fullscreen mode

Binary Quantization (Game Changer)

from qdrant_client.models import ScalarQuantization, ScalarQuantizationConfig, ScalarType

client.create_collection(
    collection_name="docs_bq",
    vectors_config=VectorParams(
        size=1536,
        distance=Distance.COSINE,
        quantization_config=ScalarQuantization(
            scalar=ScalarQuantizationConfig(type=ScalarType.INT8)
        )
    )
)
# 10x storage reduction, minimal accuracy loss!
Enter fullscreen mode Exit fullscreen mode

3. Weaviate — Graph + Vector Hybrid

Pros

  • GraphQL API
  • Built-in modules (text2vec, img2vec, reranker)
  • Hybrid search (BM25 + Vector)
  • Cross-references between objects
  • Multi-modal support

Cons

  • Higher resource usage (Java/Go)
  • Slower than Qdrant
  • Complex configuration
  • GraphQL learning curve

Python Client

import weaviate

client = weaviate.connect_to_local()  # or weaviate.connect_to_weaviate_cloud()

# Create class
client.collections.create(
    name="Document",
    vectorizer_config=weaviate.classes.config.Configure.Vectorizer.text2vec_openai(),
    properties=[
        weaviate.classes.config.Property(name="text", data_type=weaviate.classes.config.DataType.TEXT),
        weaviate.classes.config.Property(name="category", data_type=weaviate.classes.config.DataType.TEXT)
    ]
)

# Insert
docs = client.collections.get("Document")
docs.data.insert_many([
    {"text": "AI tutorial", "category": "tech"},
    {"text": "Physics paper", "category": "science"}
])

# Hybrid search
response = docs.query.hybrid(
    query="machine learning",
    alpha=0.5,  # 0=keyword, 1=vector
    filters=weaviate.classes.query.Filter.by_property("category").equal("tech"),
    limit=5
)
Enter fullscreen mode Exit fullscreen mode

4. Chroma — Local First

Pros

  • Easiest local dev (pip install)
  • Embedded mode (no server)
  • Great LangChain/LlamaIndex integration
  • Automatic embedding (via ONNX/SentenceTransformers)
  • Simple API

Cons

  • No horizontal scaling
  • Single-node only
  • Slower at scale
  • No multi-tenancy
  • Limited filtering

Python Client

import chromadb
from chromadb.utils import embedding_functions

# Embedded (in-process)
client = chromadb.Client()

# Or persistent
client = chromadb.PersistentClient(path="./chroma_db")

# With embeddings
ef = embedding_functions.SentenceTransformerEmbeddingFunction(
    model_name="all-MiniLM-L6-v2"
)

collection = client.create_collection(
    name="docs",
    embedding_function=ef
)

# Add documents (auto-embeds)
collection.add(
    documents=["AI tutorial", "Physics paper"],
    metadatas=[{"category": "tech"}, {"category": "science"}],
    ids=["1", "2"]
)

# Query
results = collection.query(
    query_texts=["machine learning"],
    n_results=5,
    where={"category": "tech"}
)
Enter fullscreen mode Exit fullscreen mode

5. Milvus — Billion-Scale

Pros

  • Handles billions of vectors
  • Multiple index types (HNSW, IVF, DiskANN)
  • Partition/shard for multi-tenancy
  • GPU acceleration
  • Kubernetes native

Cons

  • Complex deployment (etcd, MinIO, Pulsar)
  • Heavy resource usage
  • Overkill for <10M vectors
  • Steep learning curve

Deployment

# docker-compose.yml (minimal)
version: '3.5'
services:
  etcd:
    image: quay.io/coreos/etcd:v3.5.5
  minio:
    image: minio/minio:RELEASE.2023-03-20T20-16-18Z
  milvus:
    image: milvusdb/milvus:v2.3.4
    depends_on: [etcd, minio]
    ports:
      - "19530:19530"
Enter fullscreen mode Exit fullscreen mode

Python Client

from pymilvus import connections, Collection, FieldSchema, CollectionSchema, DataType, utility

connections.connect("default", host="localhost", port="19530")

# Schema
fields = [
    FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
    FieldSchema(name="vector", dtype=DataType.FLOAT_VECTOR, dim=1536),
    FieldSchema(name="text", dtype=DataType.VARCHAR, max_length=65535),
    FieldSchema(name="category", dtype=DataType.VARCHAR, max_length=100)
]
schema = CollectionSchema(fields, "Document collection")

collection = Collection("docs", schema)

# Create index
collection.create_index("vector", {"index_type": "HNSW", "metric_type": "COSINE", "params": {"M": 16, "efConstruction": 200}})

# Insert
import numpy as np
data = [
    np.random.random(1536).tolist() for _ in range(1000)
]
collection.insert([
    data,
    ["doc " + str(i) for i in range(1000)],
    ["tech"] * 500 + ["science"] * 500
])

# Search
collection.load()
results = collection.search(
    data=[np.random.random(1536).tolist()],
    anns_field="vector",
    param={"metric_type": "COSINE", "params": {"ef": 64}},
    limit=5,
    expr="category == 'tech'",
    output_fields=["text", "category"]
)
Enter fullscreen mode Exit fullscreen mode

Benchmarks (1M Vectors, 768-dim, HNSW)

Test: 1000 queries, top-10, filtered search

┌─────────────┬────────────┬────────────┬────────────┐
│ Database    │ QPS (p50)  │ Latency p99│ Recall@10  │
├─────────────┼────────────┼────────────┼────────────┤
│ Qdrant      │   1,850    │   12ms     │   0.998    │
│ Pinecone    │   1,200    │   18ms     │   0.995    │
│ Weaviate    │     890    │   25ms     │   0.992    │
│ Milvus      │   1,100    │   22ms     │   0.996    │
│ Chroma      │     320    │   45ms     │   0.985    │
└─────────────┴────────────┴────────────┴────────────┘
Enter fullscreen mode Exit fullscreen mode

Qdrant leads on throughput and latency. Pinecone close second with zero ops.


Decision Guide

START: What's your scale?
│
├─→ < 100K vectors, want zero ops → **Pinecone Free**
│
├─→ < 1M vectors, self-host OK → **Qdrant** (best perf/$)
│
├─→ Need GraphQL / hybrid search → **Weaviate**
│
├─→ Local dev / prototype → **Chroma** (embedded)
│
├─→ > 10M vectors, multi-tenant → **Milvus**
│
└─→ Edge / mobile / embedded → **Qdrant** or **Chroma**
Enter fullscreen mode Exit fullscreen mode

Migration Path

Chroma (dev) → Qdrant (prod) → Milvus (massive scale)
     ↓              ↓              ↓
  Embedded      Self-hosted    Kubernetes
  0 config      Docker/K8s     Helm/Operator
Enter fullscreen mode Exit fullscreen mode

All support OpenAI-compatible vectors — swap without re-embedding!


Cost at Scale (10M vectors, 1536-dim)

DB Monthly Cost Notes
Pinecone ~$700 Managed, includes compute
Qdrant Cloud ~$150 100GB storage
Qdrant Self-hosted ~$50 1× r6g.xlarge (AWS)
Weaviate Cloud ~$200 Includes modules
Milvus Self-hosted ~$200 3-node cluster
Chroma N/A Not for this scale

Conclusion

Priority Recommendation
Zero ops Pinecone (Free tier → Paid)
Best performance/$ Qdrant (Self-hosted or Cloud)
Hybrid search + GraphQL Weaviate
Local dev / embedded Chroma
Billion-scale Milvus

My pick: Start with Chroma locally, migrate to Qdrant for production. Best balance of dev experience, performance, and cost.


Part of the Vector DB series on Dev.to

Top comments (0)