Skip to content
Codeloom
RAG

Vector Databases: Pinecone vs Weaviate vs Chroma

Compare Pinecone, Weaviate, and Chroma vector databases for RAG applications. Covers setup, performance, pricing, and when to use each one.

·9 min read · By Codeloom
Intermediate 14 min read

What you'll learn

  • How vector databases work and why RAG needs them
  • Setting up Pinecone, Weaviate, and Chroma with Python
  • Performance and scaling characteristics of each database
  • How to choose the right vector database for your use case

Prerequisites

  • Understanding of embeddings and similarity search
  • Python fundamentals
  • Basic familiarity with RAG concepts

Vector databases are the backbone of every RAG system. They store embeddings, perform similarity search at scale, and return the documents your LLM needs for grounded responses. But choosing between them can be confusing. Pinecone is fully managed, Weaviate offers hybrid search, and Chroma is lightweight and local. This guide gives you hands-on experience with all three so you can make an informed decision.

How Vector Databases Work

A vector database stores high-dimensional vectors (embeddings) and supports fast approximate nearest neighbor (ANN) search. When you query with a vector, it finds the most similar vectors in the collection without comparing against every single entry.

The key algorithms behind this speed are:

  • HNSW (Hierarchical Navigable Small World): Builds a multi-layer graph for fast traversal. Used by Weaviate, Chroma, and pgvector.
  • IVF (Inverted File Index): Partitions vectors into clusters and searches only the nearest clusters. Used by FAISS.
  • Tree-based methods: Used by Pinecone’s proprietary engine for their managed service.

All three databases we compare support metadata filtering, batch operations, and Python SDKs.

Chroma: The Local-First Option

Chroma is designed for developers who want to get started quickly. It runs in-process with no server needed, stores data to disk, and requires zero configuration.

import chromadb
from chromadb.utils import embedding_functions

# Create a persistent client (data saved to disk)
client = chromadb.PersistentClient(path="./chroma_data")

# Use OpenAI embeddings (or Chroma's default all-MiniLM-L6-v2)
openai_ef = embedding_functions.OpenAIEmbeddingFunction(
    model_name="text-embedding-3-small"
)

# Create a collection
collection = client.get_or_create_collection(
    name="documents",
    embedding_function=openai_ef,
    metadata={"hnsw:space": "cosine"}
)

# Add documents (embeddings generated automatically)
documents = [
    "Python is a versatile programming language used in web development and data science.",
    "JavaScript powers interactive web pages and runs in every modern browser.",
    "Rust provides memory safety without garbage collection through its ownership system.",
    "Go was designed at Google for building scalable network services.",
    "TypeScript adds static typing to JavaScript for better developer experience.",
]

collection.add(
    documents=documents,
    ids=[f"doc_{i}" for i in range(len(documents))],
    metadatas=[
        {"language": "python", "category": "general-purpose"},
        {"language": "javascript", "category": "web"},
        {"language": "rust", "category": "systems"},
        {"language": "go", "category": "systems"},
        {"language": "typescript", "category": "web"},
    ]
)

# Query with automatic embedding
results = collection.query(
    query_texts=["systems programming with memory safety"],
    n_results=3
)

for doc, score, metadata in zip(
    results["documents"][0],
    results["distances"][0],
    results["metadatas"][0]
):
    print(f"  [{score:.4f}] {doc}")
    print(f"           Metadata: {metadata}")

# Query with metadata filtering
web_results = collection.query(
    query_texts=["best language for web development"],
    n_results=3,
    where={"category": "web"}
)
print(f"\nWeb-only results: {len(web_results['documents'][0])}")

Chroma strengths: Zero setup, runs in-process, great for prototyping and small datasets, automatic embedding generation, good Python API.

Chroma limitations: Single-node only, no built-in replication, performance degrades past a few million vectors, limited production tooling.

Pinecone: The Fully Managed Option

Pinecone is a cloud-native vector database. You do not run any infrastructure; everything is managed via their API. This makes it the easiest path to production for teams that do not want to manage database infrastructure.

from pinecone import Pinecone, ServerlessSpec
from openai import OpenAI
import time

# Initialize Pinecone
pc = Pinecone(api_key="your-pinecone-api-key")
openai_client = OpenAI()

# Create an index
index_name = "documents"

if index_name not in pc.list_indexes().names():
    pc.create_index(
        name=index_name,
        dimension=1536,  # Match your embedding model
        metric="cosine",
        spec=ServerlessSpec(
            cloud="aws",
            region="us-east-1"
        )
    )
    # Wait for index to be ready
    while not pc.describe_index(index_name).status["ready"]:
        time.sleep(1)

index = pc.Index(index_name)

# Generate embeddings
def embed_texts(texts: list[str]) -> list[list[float]]:
    response = openai_client.embeddings.create(
        model="text-embedding-3-small",
        input=texts
    )
    return [item.embedding for item in response.data]

# Upsert documents with metadata
documents = [
    {"id": "doc_0", "text": "Python is a versatile programming language.", "language": "python"},
    {"id": "doc_1", "text": "JavaScript powers interactive web pages.", "language": "javascript"},
    {"id": "doc_2", "text": "Rust provides memory safety without GC.", "language": "rust"},
    {"id": "doc_3", "text": "Go was designed for scalable services.", "language": "go"},
    {"id": "doc_4", "text": "TypeScript adds static typing to JS.", "language": "typescript"},
]

texts = [d["text"] for d in documents]
embeddings = embed_texts(texts)

vectors = [
    {
        "id": doc["id"],
        "values": emb,
        "metadata": {"text": doc["text"], "language": doc["language"]}
    }
    for doc, emb in zip(documents, embeddings)
]

index.upsert(vectors=vectors)
print(f"Upserted {len(vectors)} vectors")

# Query
query_embedding = embed_texts(["systems programming with memory safety"])[0]
results = index.query(
    vector=query_embedding,
    top_k=3,
    include_metadata=True
)

for match in results.matches:
    print(f"  [{match.score:.4f}] {match.metadata['text']}")

# Query with metadata filter
filtered_results = index.query(
    vector=query_embedding,
    top_k=3,
    include_metadata=True,
    filter={"language": {"$in": ["rust", "go"]}}
)

Pinecone strengths: Zero infrastructure management, scales to billions of vectors, built-in replication and high availability, serverless pricing option, fast query latency.

Pinecone limitations: Cloud-only (no self-hosting), vendor lock-in, cost grows with scale, limited query capabilities beyond vector search.

Weaviate: The Hybrid Search Option

Weaviate combines vector search with traditional keyword (BM25) search and offers a GraphQL API. It can run locally via Docker or as a managed cloud service.

import weaviate
from weaviate.classes.init import Auth
from weaviate.classes.config import Configure, Property, DataType
from weaviate.classes.query import MetadataQuery

# Connect to local Weaviate (run: docker run -p 8080:8080 semitechnologies/weaviate)
client = weaviate.connect_to_local()

# Or connect to Weaviate Cloud
# client = weaviate.connect_to_weaviate_cloud(
#     cluster_url="https://your-cluster.weaviate.network",
#     auth_credentials=Auth.api_key("your-api-key"),
#     headers={"X-OpenAI-Api-Key": "your-openai-key"}
# )

# Create a collection with vectorizer
collection = client.collections.create(
    name="Document",
    vectorizer_config=Configure.Vectorizer.text2vec_openai(
        model="text-embedding-3-small"
    ),
    properties=[
        Property(name="text", data_type=DataType.TEXT),
        Property(name="language", data_type=DataType.TEXT),
        Property(name="category", data_type=DataType.TEXT),
    ]
)

# Add documents (Weaviate generates embeddings automatically)
documents = [
    {"text": "Python is a versatile programming language.", "language": "python", "category": "general-purpose"},
    {"text": "JavaScript powers interactive web pages.", "language": "javascript", "category": "web"},
    {"text": "Rust provides memory safety without GC.", "language": "rust", "category": "systems"},
    {"text": "Go was designed for scalable services.", "language": "go", "category": "systems"},
    {"text": "TypeScript adds static typing to JS.", "language": "typescript", "category": "web"},
]

with collection.batch.dynamic() as batch:
    for doc in documents:
        batch.add_object(properties=doc)

# Vector search (semantic)
results = collection.query.near_text(
    query="systems programming with memory safety",
    limit=3,
    return_metadata=MetadataQuery(distance=True)
)

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

# Keyword search (BM25)
bm25_results = collection.query.bm25(
    query="memory safety",
    limit=3
)

# Hybrid search (combines vector + keyword)
hybrid_results = collection.query.hybrid(
    query="systems programming memory safety",
    limit=3,
    alpha=0.5  # 0 = pure keyword, 1 = pure vector
)

for obj in hybrid_results.objects:
    print(f"  {obj.properties['text']}")

client.close()

Weaviate strengths: Hybrid search (vector + BM25), built-in vectorization modules, GraphQL API, self-hostable with Docker, multi-tenancy support, active open-source community.

Weaviate limitations: More complex setup than Chroma, resource-heavy for local development, learning curve for GraphQL queries, some features require specific modules.

Head-to-Head Comparison

FeatureChromaPineconeWeaviate
HostingLocal / embeddedCloud onlySelf-hosted or cloud
Setup complexityMinimalAPI key onlyDocker or cloud
Hybrid searchNoNoYes (BM25 + vector)
Max scale~1M vectorsBillionsMillions+
PricingFree (open source)Free tier, then per-usageFree (open source) or cloud
ReplicationNoBuilt-inBuilt-in
Metadata filteringBasicAdvancedAdvanced
Auto-vectorizationYesNoYes (with modules)
Best forPrototyping, small appsProduction at scaleHybrid search, self-hosted production

Performance Benchmarks

Real-world performance depends on your data and queries. Here is a simple benchmarking framework to test with your own data:

import time
import numpy as np

def benchmark_database(db_client, documents: list[str], queries: list[str]):
    """Benchmark insert and query performance."""
    
    # Benchmark insertion
    start = time.perf_counter()
    # Insert documents (implementation depends on the database)
    insert_time = time.perf_counter() - start
    
    # Benchmark queries
    query_times = []
    for query in queries:
        start = time.perf_counter()
        # Run query (implementation depends on the database)
        elapsed = time.perf_counter() - start
        query_times.append(elapsed)
    
    return {
        "insert_time_total": insert_time,
        "insert_time_per_doc": insert_time / len(documents),
        "query_time_avg": np.mean(query_times),
        "query_time_p50": np.percentile(query_times, 50),
        "query_time_p95": np.percentile(query_times, 95),
        "query_time_p99": np.percentile(query_times, 99),
    }

# Generate test data
num_docs = 10000
test_documents = [f"This is test document number {i} about topic {i % 50}" for i in range(num_docs)]
test_queries = [f"topic {i}" for i in range(100)]

In typical benchmarks with 100K documents and 1536-dimensional vectors:

  • Chroma: ~5ms p50 query latency, simple to set up, memory usage grows linearly
  • Pinecone: ~10-20ms p50 query latency (includes network), consistent at any scale
  • Weaviate: ~5-10ms p50 query latency (local), hybrid search adds ~2-5ms overhead

Decision Framework

Choose Chroma when:

  • You are building a prototype or proof of concept
  • Your dataset is under 500K documents
  • You want the simplest possible setup
  • You are building a local application or CLI tool

Choose Pinecone when:

  • You need production reliability without managing infrastructure
  • Your dataset is large (millions to billions of vectors)
  • You want automatic scaling and high availability
  • Your team does not have database operations expertise

Choose Weaviate when:

  • You need hybrid search (combining keyword and semantic)
  • You want to self-host for data privacy or compliance
  • You need multi-tenancy for SaaS applications
  • You want an open-source solution with production capabilities

Migration Between Databases

If you start with Chroma and need to migrate to Pinecone or Weaviate later, abstract your vector database behind an interface:

from abc import ABC, abstractmethod

class VectorStore(ABC):
    """Abstract interface for vector database operations."""
    
    @abstractmethod
    def add_documents(self, documents: list[str], ids: list[str], metadatas: list[dict] = None):
        pass
    
    @abstractmethod
    def search(self, query: str, top_k: int = 5, filters: dict = None) -> list[dict]:
        pass
    
    @abstractmethod
    def delete(self, ids: list[str]):
        pass

class ChromaStore(VectorStore):
    def __init__(self, collection_name: str):
        self.client = chromadb.PersistentClient(path="./data")
        self.collection = self.client.get_or_create_collection(collection_name)
    
    def add_documents(self, documents, ids, metadatas=None):
        self.collection.add(documents=documents, ids=ids, metadatas=metadatas)
    
    def search(self, query, top_k=5, filters=None):
        kwargs = {"query_texts": [query], "n_results": top_k}
        if filters:
            kwargs["where"] = filters
        results = self.collection.query(**kwargs)
        return [
            {"id": id, "text": doc, "score": dist, "metadata": meta}
            for id, doc, dist, meta in zip(
                results["ids"][0], results["documents"][0],
                results["distances"][0], results["metadatas"][0]
            )
        ]
    
    def delete(self, ids):
        self.collection.delete(ids=ids)

# Swap implementations without changing application code
# store = ChromaStore("my_collection")
# store = PineconeStore("my_index")
# store = WeaviateStore("MyCollection")

Wrapping Up

The vector database landscape is maturing rapidly, but the decision does not need to be overwhelming. Start with Chroma for development and prototyping because it has zero friction. Move to Pinecone when you need managed production infrastructure. Choose Weaviate when hybrid search or self-hosting is a requirement. Whichever you choose, abstract the database behind an interface so migration remains straightforward. The most important factor is not which database you pick but how well your embeddings and chunking strategy work, because a great vector database cannot compensate for poor embeddings.