← Back to DevBytes

Pinecone vs Weaviate vs Milvus: Vector Database Comparison

Introduction to Vector Databases

Vector databases have become the backbone of modern AI applications, powering semantic search, recommendation systems, retrieval-augmented generation (RAG), and similarity matching at scale. Unlike traditional databases that store rows and columns, vector databases store high-dimensional embeddings — numerical representations of text, images, audio, or any data that captures semantic meaning.

When you query a vector database, you provide a query vector and the database returns the most similar vectors based on distance metrics like cosine similarity, Euclidean distance, or dot product. Under the hood, these databases use specialized indexing algorithms such as HNSW (Hierarchical Navigable Small World), IVF (Inverted File Index), or PQ (Product Quantization) to perform approximate nearest neighbor (ANN) search efficiently across millions or billions of vectors.

In this tutorial, we will compare three of the most popular vector databases — Pinecone, Weaviate, and Milvus — exploring their architecture, strengths, trade-offs, and practical usage with hands-on code examples.

Why Vector Database Choice Matters

Selecting the right vector database impacts several critical aspects of your application:

Each of the three databases we cover makes different trade-offs along these dimensions. Understanding these trade-offs will help you make an informed decision for your specific use case.

Pinecone: Managed Simplicity at Scale

What It Is

Pinecone is a fully managed, cloud-native vector database designed for high-performance similarity search. It abstracts away infrastructure management entirely — you create an index, insert vectors, and query. Pinecone handles scaling, replication, sharding, and optimization automatically. It is built for production workloads where reliability and ease of operations are paramount.

Why It Matters

Pinecone shines when you want to move fast without managing infrastructure. It offers enterprise-grade SLAs, automatic scaling, and a simple API. It supports metadata filtering, namespaces for multi-tenancy, and sparse-dense vector combinations for hybrid search. The trade-off is that it is a closed-source, proprietary service, meaning you are locked into their cloud and pricing model.

How to Use It

First, install the Pinecone client and set up your API key:

pip install pinecone-client

Here is a complete example of creating an index, inserting vectors with metadata, and performing a similarity search:

from pinecone import Pinecone, ServerlessSpec

# Initialize the client
pc = Pinecone(api_key="YOUR_API_KEY")

# Create an index if it does not exist
index_name = "documents-index"
existing_indexes = [i.name for i in pc.list_indexes()]

if index_name not in existing_indexes:
    pc.create_index(
        name=index_name,
        dimension=1536,
        metric="cosine",
        spec=ServerlessSpec(
            cloud="aws",
            region="us-east-1"
        )
    )

# Connect to the index
index = pc.Index(index_name)

# Insert vectors with metadata
vectors = [
    {
        "id": "doc-1",
        "values": [0.1, 0.2, 0.3, ...],  # 1536-dimensional embedding
        "metadata": {"category": "technology", "source": "blog"}
    },
    {
        "id": "doc-2",
        "values": [0.4, 0.5, 0.6, ...],
        "metadata": {"category": "science", "source": "paper"}
    }
]

index.upsert(vectors=vectors)

# Query with metadata filtering
query_vector = [0.15, 0.25, 0.35, ...]

results = index.query(
    vector=query_vector,
    top_k=5,
    include_metadata=True,
    filter={"category": {"$eq": "technology"}}
)

for match in results["matches"]:
    print(f"ID: {match['id']}, Score: {match['score']:.4f}")
    print(f"Metadata: {match['metadata']}")

Pinecone Strengths and Trade-offs

Weaviate: Flexible Open-Source with Built-in Modules

What It Is

Weaviate is an open-source vector database that can be self-hosted or used as a managed cloud service. What sets Weaviate apart is its modular architecture — it includes built-in integrations with popular embedding models (OpenAI, Cohere, Hugging Face, etc.) and vectorizer modules that can automatically vectorize your data on insertion. You can store objects and their vectors together, and Weaviate handles the embedding generation if you configure a vectorizer module.

Why It Matters

Weaviate is ideal for teams that want flexibility. You can run it locally for development, deploy it on Kubernetes for production, or use Weaviate Cloud for a managed experience. Its GraphQL API provides rich querying capabilities, and it supports hybrid search (combining BM25 keyword search with vector search) out of the box. The automatic vectorization feature significantly reduces boilerplate code in your application layer.

How to Use It

You can run Weaviate locally using Docker:

docker run -d \
  --name weaviate \
  -p 8080:8080 \
  -e AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED=true \
  -e PERSISTENCE_DATA_PATH=/var/lib/weaviate \
  -e DEFAULT_VECTORIZER_MODULE=none \
  semitechnologies/weaviate:latest

Install the Python client:

pip install weaviate-client

Here is a complete example of creating a schema, inserting objects with custom vectors, and performing a hybrid search:

import weaviate
import weaviate.classes as wvc

# Connect to local Weaviate instance
client = weaviate.connect_to_local(
    host="localhost",
    port=8080
)

# Create a collection
if not client.collections.exists("Document"):
    documents = client.collections.create(
        name="Document",
        vectorizer_config=wvc.config.Configure.Vectorizer.none(),
        properties=[
            wvc.config.Property(
                name="title",
                data_type=wvc.config.DataType.TEXT
            ),
            wvc.config.Property(
                name="category",
                data_type=wvc.config.DataType.TEXT
            ),
        ]
    )
else:
    documents = client.collections.get("Document")

# Insert objects with custom vectors
documents.data.insert_many([
    wvc.data.DataObject(
        properties={"title": "Intro to Vector DBs", "category": "technology"},
        vector=[0.1, 0.2, 0.3, 0.4]
    ),
    wvc.data.DataObject(
        properties={"title": "Quantum Computing Basics", "category": "science"},
        vector=[0.5, 0.6, 0.7, 0.8]
    ),
])

# Perform a vector similarity search with filtering
results = documents.query.near_vector(
    near_vector=[0.15, 0.25, 0.35, 0.45],
    limit=5,
    return_metadata=wvc.query.MetadataQuery(distance=True),
    filters=wvc.query.Filter.by_property("category").equal("technology")
)

for obj in results.objects:
    print(f"Title: {obj.properties['title']}")
    print(f"Distance: {obj.metadata.distance:.4f}")

# Perform a hybrid search (keyword + vector)
hybrid_results = documents.query.hybrid(
    query="vector databases",
    vector=[0.15, 0.25, 0.35, 0.45],
    alpha=0.5,  # 0 = keyword only, 1 = vector only
    limit=5,
    return_metadata=wvc.query.MetadataQuery(score=True)
)

for obj in hybrid_results.objects:
    print(f"Title: {obj.properties['title']}, Score: {obj.metadata.score:.4f}")

client.close()

Weaviate Strengths and Trade-offs

Milvus: Open-Source Powerhouse for Massive Scale

What It Is

Milvus is an open-source vector database built specifically for billion-scale vector similarity search. Developed by Zilliz, it is distributed by design and supports multiple deployment modes: Milvus Lite for local development, Milvus Standalone for single-machine setups, and Milvus Distributed for cluster deployments on Kubernetes. Milvus supports a wide range of index types (IVF_FLAT, IVF_SQ8, HNSW, DiskANN, and more) and gives you fine-grained control over indexing parameters.

Why It Matters

If your use case involves massive datasets — hundreds of millions or billions of vectors — Milvus is purpose-built for that scale. Its separation of storage and compute architecture allows independent scaling of each component. DiskANN support enables efficient search on disk-based indexes, reducing memory requirements dramatically. Milvus is the go-to choice for organizations that need maximum control, scalability, and cost efficiency at extreme scale.

How to Use It

Install the Milvus Python SDK and start a Milvus Lite instance for development:

pip install pymilvus

Here is a complete example using Milvus Lite, including collection creation, data insertion, index building, and searching:

from pymilvus import MilvusClient

# Connect using Milvus Lite (local file-based storage)
client = MilvusClient("./milvus_demo.db")

collection_name = "documents"

# Drop collection if it exists
if client.has_collection(collection_name):
    client.drop_collection(collection_name)

# Create a collection with a schema
client.create_collection(
    collection_name=collection_name,
    dimension=4,
    metric_type="COSINE",
    # AutoID generates IDs automatically
    # The schema includes id (auto) and vector fields by default
)

# Insert data
data = [
    {"id": 1, "vector": [0.1, 0.2, 0.3, 0.4], "category": "technology"},
    {"id": 2, "vector": [0.5, 0.6, 0.7, 0.8], "category": "science"},
    {"id": 3, "vector": [0.15, 0.25, 0.35, 0.45], "category": "technology"},
    {"id": 4, "vector": [0.9, 0.1, 0.2, 0.3], "category": "health"},
]

client.insert(collection_name=collection_name, data=data)

# Vector similarity search
search_results = client.search(
    collection_name=collection_name,
    data=[[0.12, 0.22, 0.32, 0.42]],
    limit=3,
    output_fields=["category"]
)

for hits in search_results:
    for hit in hits:
        print(f"ID: {hit['id']}, Distance: {hit['distance']:.4f}, Category: {hit['entity']['category']}")

# Query with metadata filtering
query_results = client.query(
    collection_name=collection_name,
    filter="category == 'technology'",
    output_fields=["id", "category"]
)

for result in query_results:
    print(f"Filtered result - ID: {result['id']}, Category: {result['category']}")

# Clean up
client.drop_collection(collection_name)
client.close()

For production deployments, you would use Milvus Standalone via Docker Compose or Milvus Distributed on Kubernetes. The client API remains the same — only the connection URI changes:

# Connect to Milvus Standalone
client = MilvusClient(uri="http://localhost:19530")

# Connect to Zilliz Cloud (managed Milvus)
client = MilvusClient(
    uri="https://your-cluster.zillizcloud.com",
    token="user:password"
)

Milvus Strengths and Trade-offs

Head-to-Head Comparison

Architecture and Deployment

Performance and Scale

Search Capabilities

Cost Considerations

Best Practices for Vector Database Development

Choose the Right Embedding Model

Your vector database is only as good as your embeddings. Invest time in selecting and evaluating embedding models that capture the semantics of your domain. OpenAI's text-embedding-3-small/large, Cohere's embed models, and open-source options like BGE or E5 all produce different quality embeddings. Benchmark retrieval quality, not just embedding speed.

Normalize Your Vectors

If you are using cosine similarity, normalize your vectors before insertion. This ensures consistent distance calculations and can improve search performance. Some databases handle this automatically, but it is good practice to verify:

import numpy as np

def normalize_vector(vec):
    norm = np.linalg.norm(vec)
    if norm == 0:
        return vec
    return vec / norm

# Apply before upserting
normalized_vector = normalize_vector(raw_embedding)

Use Metadata Filtering Strategically

Metadata filtering can dramatically improve result relevance by narrowing the search space. However, overly restrictive filters can eliminate good matches. Design your metadata schema to support the filtering patterns your application needs — such as tenant isolation, time-based filtering, or category restrictions.

Monitor and Tune Index Parameters

Index parameters like HNSW's M and ef_construction affect the trade-off between search speed and recall. For self-hosted Weaviate and Milvus, experiment with these parameters using a representative dataset. Measure recall@k against query latency to find the optimal configuration for your workload.

Implement Proper Error Handling

Vector database operations can fail due to network issues, rate limits, or capacity constraints. Always implement retry logic with exponential backoff and circuit breakers:

import time
from functools import wraps

def retry_with_backoff(max_retries=3, base_delay=1.0):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_retries - 1:
                        raise
                    delay = base_delay * (2 ** attempt)
                    print(f"Attempt {attempt + 1} failed: {e}. Retrying in {delay}s...")
                    time.sleep(delay)
        return wrapper
    return decorator

@retry_with_backoff(max_retries=3, base_delay=1.0)
def safe_upsert(index, vectors):
    return index.upsert(vectors=vectors)

Batch Your Operations

Whether inserting or querying, batch operations are significantly more efficient than single-item operations. Aim for batch sizes between 100 and 1000 vectors depending on vector dimensionality and network conditions. This reduces round-trip overhead and improves throughput.

Plan for Multi-Tenancy

If your application serves multiple customers or organizations, design for multi-tenancy from the start. Pinecone uses namespaces, Weaviate supports multi-tenancy at the collection level, and Milvus supports partition keys. Isolating tenant data improves security and query performance by reducing the search space.

Version Your Embeddings

When you change your embedding model, your existing vectors become incompatible with new queries. Plan a migration strategy — store the embedding model version as metadata, create a new index or collection for the new embeddings, and gradually migrate your data. This prevents subtle retrieval quality degradation.

Conclusion

Choosing between Pinecone, Weaviate, and Milvus ultimately depends on your specific requirements around scale, control, cost, and operational capacity. Pinecone is the best choice for teams that want a frictionless, fully managed experience and are willing to pay for convenience. Weaviate offers an excellent balance of flexibility, built-in vectorization, and hybrid search capabilities, making it ideal for teams that want open-source freedom with a managed option available. Milvus stands out for extreme scale scenarios where billion-vector datasets, fine-grained index control, and cost efficiency at massive volumes are the driving requirements. Regardless of your choice, following best practices around embedding model selection, vector normalization, metadata design, batch operations, and multi-tenancy planning will ensure your vector search infrastructure remains performant, maintainable, and ready to scale as your application grows.

— Ad —

Google AdSense will appear here after approval

← Back to all articles