Back to Blog
Vector DatabasesAIPostgreSQLQdrantWeaviatePinecone

Vector Databases Compared — pgvector vs Qdrant vs Weaviate vs Pinecone

A practical comparison of the four main vector database options for production AI stacks: pgvector for PostgreSQL-native simplicity, Qdrant for high-throughput filtered search, Weaviate for hybrid BM25+vector search and multi-tenancy, and Pinecone for zero-ops managed deployments. Includes code examples, performance benchmarks, and a decision framework.

2026-04-25

Why Every AI Stack Now Has a Vector Layer

Two years ago, adding a vector database to a production system was an exotic engineering decision. Today it is a default assumption. The rapid adoption of LLM-powered features — semantic search, RAG pipelines, recommendation engines, duplicate detection — created a new class of storage problem: you need to store and retrieve high-dimensional floating-point embeddings at millisecond latency, at a scale where brute-force comparison is not viable.

The result is a crowded market. You can run vector search inside PostgreSQL via pgvector, deploy a purpose-built open-source engine like Qdrant or Weaviate, or hand the operational burden to a managed cloud service like Pinecone. Each choice involves real trade-offs across performance, operational complexity, filtering capability, and cost. This article gives you a concrete framework for deciding.

DatabaseModelAlgorithmBest for
pgvectorOSS PostgreSQL extensionHNSW, IVFFlatTeams already on PostgreSQL, lower scale
QdrantOSS / CloudHNSW + payload filteringHigh-throughput search with rich filtering
WeaviateOSS / CloudHNSW + BM25 hybridHybrid search, multi-tenancy, GraphQL API
PineconeManaged SaaSProprietary ANNTeams that want zero ops overhead

pgvector — Vector Search Inside PostgreSQL

pgvector adds a vector column type and approximate nearest neighbor (ANN) index types to standard PostgreSQL. If your application already runs on Postgres, pgvector eliminates an entire service from your architecture — embeddings live in the same database as the rest of your data, with the same transactions, backups, and access controls.

pgvector 0.7+ ships two index types: IVFFlat (inverted file with flat quantization — fast build, slightly lower recall) and HNSW (hierarchical navigable small world — slower build, better recall at low latency). HNSW is the default choice for production workloads. The pgvector HNSW documentation covers the m and ef_construction parameters in detail.

-- Enable the extension
CREATE EXTENSION IF NOT EXISTS vector;

-- Store embeddings alongside structured data
CREATE TABLE documents (
  id          BIGSERIAL PRIMARY KEY,
  content     TEXT          NOT NULL,
  metadata    JSONB         NOT NULL DEFAULT '{}',
  embedding   vector(1536)  NOT NULL,  -- OpenAI text-embedding-3-small dims
  created_at  TIMESTAMPTZ   NOT NULL DEFAULT NOW()
);

-- HNSW index — build once, serves queries fast
-- m: number of connections per layer (16 is a good default)
-- ef_construction: search width during index build (64–128 is typical)
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops)
  WITH (m = 16, ef_construction = 64);

-- Similarity search — returns top 10 by cosine distance
-- ${query_embedding} would be the embedding of the search query
SELECT
  id,
  content,
  metadata,
  1 - (embedding <=> ${query_embedding}::vector) AS similarity
FROM documents
WHERE metadata->>'tenant_id' = ${tenant_id}   -- predicate pushdown
ORDER BY embedding <=> ${query_embedding}::vector
LIMIT 10;

-- Tune ef_search at query time for recall/speed trade-off
SET hnsw.ef_search = 100;

Note

pgvector HNSW index builds are single-threaded by default. For large tables (1M+ rows), set max_parallel_maintenance_workers and maintenance_work_mem before building the index. Expect build times of 30–90 minutes for 5M vectors at 1536 dimensions on typical hardware.

pgvector Limitations

pgvector shines for moderate scale (up to a few million vectors) but has real ceilings. HNSW indexes live entirely in shared memory — a 5M × 1536-dim index at float32 occupies roughly 30GB of RAM. Beyond ~10M vectors, query latency climbs and index builds become disruptive. Payload filtering is implemented as a post-filter on ANN results rather than a true filtered ANN, which means high-selectivity filters (fetching 1% of the dataset) degrade recall significantly unless you tune hnsw.ef_search upward.

Qdrant — Purpose-Built for High-Throughput Filtered Search

Qdrant is an open-source vector database written in Rust, designed from the ground up for production vector workloads. Its standout feature is filtrable HNSW: payload conditions (metadata filters) are pushed inside the HNSW graph traversal rather than applied as a post-filter. This preserves recall even for highly selective queries — exactly the scenario where pgvector struggles.

Qdrant supports multiple named vectors per point (sparse and dense), scalar and product quantization for memory reduction, on-disk indexing for datasets larger than RAM, and a gRPC API alongside REST. It runs as a single binary, a Docker container, or a Kubernetes StatefulSet, and offers a managed cloud tier at cloud.qdrant.io.

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

client = QdrantClient(url="http://localhost:6333")

# Create a collection with HNSW config
client.recreate_collection(
    collection_name="documents",
    vectors_config=VectorParams(size=1536, distance=Distance.COSINE),
    hnsw_config={
        "m": 16,
        "ef_construct": 100,
        "full_scan_threshold": 10_000,  # below this, skip ANN entirely
        "on_disk": False,               # set True for datasets > RAM
    },
    quantization_config={
        "scalar": {
            "type": "int8",
            "quantile": 0.99,
            "always_ram": True,         # keep quantized vectors in RAM
        }
    },
)

# Upsert points with payload (metadata)
client.upsert(
    collection_name="documents",
    points=[
        PointStruct(
            id=1,
            vector=[0.1, 0.2, ...],    # 1536-dim embedding
            payload={
                "tenant_id": "acme",
                "category": "technical",
                "published_year": 2025,
                "score": 0.92,
            },
        ),
    ],
)

# Filtered search — filter is evaluated INSIDE HNSW graph traversal
results = client.search(
    collection_name="documents",
    query_vector=query_embedding,
    query_filter=Filter(
        must=[
            FieldCondition(key="tenant_id", match=MatchValue(value="acme")),
            FieldCondition(key="published_year", range=Range(gte=2024)),
        ]
    ),
    limit=10,
    with_payload=True,
)

for hit in results:
    print(f"id={hit.id}  score={hit.score:.4f}  payload={hit.payload}")
FeatureQdrant behaviour
Filtered ANNFilter inside graph traversal — recall maintained at high selectivity
QuantizationScalar int8 and product quantization — 4–8× memory reduction
On-disk indexMemmap segments — query datasets larger than available RAM
Multi-vectorMultiple named vectors per point — dense + sparse (SPLADE) combos
DistributedSharded cluster mode with replication — horizontal scaling

Weaviate — Hybrid Search, GraphQL, and Built-in Vectorizers

Weaviate differentiates itself with two capabilities that neither pgvector nor Qdrant offer out of the box: built-in vectorizers (Weaviate can call OpenAI, Cohere, or a local model to embed data at write time) and hybrid search combining BM25 full-text and dense vector search via a tunable fusion algorithm. If your retrieval pipeline needs keyword precision alongside semantic recall — a common requirement for enterprise document search — Weaviate handles this natively.

Weaviate also has first-class multi-tenancy with per-tenant data isolation, which makes it a strong fit for SaaS products that need to serve multiple customers from a single cluster without cross-tenant data leakage. See the Weaviate multi-tenancy documentation for the implementation details.

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

client = weaviate.connect_to_local()

# Define schema with vectorizer — Weaviate calls OpenAI at write time
client.collections.create(
    name="Document",
    vectorizer_config=Configure.Vectorizer.text2vec_openai(
        model="text-embedding-3-small",
    ),
    generative_config=Configure.Generative.openai(model="gpt-4o-mini"),
    properties=[
        Property(name="content", data_type=DataType.TEXT),
        Property(name="category", data_type=DataType.TEXT),
        Property(name="tenant_id", data_type=DataType.TEXT),
        Property(name="published_year", data_type=DataType.INT),
    ],
)

collection = client.collections.get("Document")

# Hybrid search — alpha=0 is pure BM25, alpha=1 is pure vector
# Relative score fusion balances keyword and semantic results
results = collection.query.hybrid(
    query="distributed tracing OpenTelemetry",
    alpha=0.6,                             # lean towards semantic
    fusion_type=HybridFusion.RELATIVE_SCORE,
    filters=weaviate.classes.query.Filter.by_property("tenant_id").equal("acme"),
    limit=10,
    return_metadata=MetadataQuery(score=True, explain_score=True),
)

for obj in results.objects:
    print(f"score={obj.metadata.score:.4f}  content={obj.properties['content'][:80]}")

# Generative search — retrieve then generate in one API call
response = collection.generate.hybrid(
    query="how does Kafka handle exactly-once semantics",
    alpha=0.5,
    limit=5,
    grouped_task="Summarize the key points from these documents in 3 bullet points.",
)
print(response.generated)

client.close()

Note

Weaviate's built-in vectorizer convenience comes with a dependency: write latency includes the embedding API call latency. For bulk imports, use batch mode with async vectorization enabled. For latency-sensitive workloads, pre-compute embeddings externally and import them directly — Weaviate supports both patterns.

Pinecone — Fully Managed, Zero Operational Overhead

Pinecone is the only fully managed option in this comparison. You provision an index via API or console, upsert vectors, query — and Pinecone handles scaling, replication, hardware provisioning, and index optimization internally. There is no Docker container to run, no Kubernetes StatefulSet to maintain, no index rebuild to schedule.

The trade-off is cost and control. Pinecone's pricing is per pod or per serverless write/read unit — at high scale, the operational savings must be weighed against bills that can exceed the cost of a self-hosted cluster. Pinecone's serverless indexes eliminate pod provisioning entirely, billing per query and storage — a better model for variable-traffic workloads.

from pinecone import Pinecone, ServerlessSpec

pc = Pinecone(api_key="${PINECONE_API_KEY}")

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

index = pc.Index("documents")

# Upsert with metadata — Pinecone stores vectors + sparse metadata dict
index.upsert(
    vectors=[
        {
            "id": "doc-001",
            "values": [0.1, 0.2, ...],   # 1536-dim embedding
            "metadata": {
                "tenant_id": "acme",
                "category": "technical",
                "published_year": 2025,
            },
        },
    ],
    namespace="production",      # namespace = logical partition
)

# Query with metadata filter
results = index.query(
    vector=query_embedding,
    top_k=10,
    namespace="production",
    filter={
        "tenant_id": {"\$eq": "acme"},
        "published_year": {"\$gte": 2024},
    },
    include_metadata=True,
)

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

Note

Pinecone metadata filtering is a post-filter in pod-based indexes (recall degrades for high-selectivity filters), but serverless indexes use a different internal architecture that handles filtered queries more efficiently. For applications with heavy metadata filtering, test serverless versus pod-based behavior on your actual query distribution before committing.

Performance: What the Benchmarks Actually Show

The ANN Benchmarks project provides standardized recall-vs-throughput curves for approximate nearest neighbor algorithms, but it tests algorithms without metadata filtering — a key gap for real-world workloads. The more useful comparison comes from Qdrant's own benchmark suite, which tests filtered search at various selectivity levels.

The pattern that emerges from production deployments at scale:

Unfiltered recall at 95%+

All four databases achieve this with tuned HNSW parameters. The difference is latency at p99: Qdrant and Weaviate (Rust and Go respectively) typically show 2–5ms p99 for 1M vectors; pgvector on PostgreSQL shows 5–20ms depending on shared memory pressure; Pinecone serverless shows 15–40ms due to network round-trips to the managed service.

Filtered search at 1% selectivity

This is where the databases diverge most sharply. Qdrant's filterable HNSW maintains ~95% recall even at 1% selectivity. pgvector with post-filtering can drop to 60–70% recall unless ef_search is increased significantly (at the cost of latency). Weaviate handles this via a separate filtered ANN implementation. Pinecone serverless shows improved behavior over pod-based for high-selectivity filters.

Memory per million 1536-dim vectors

Uncompressed float32: ~6GB. With Qdrant scalar int8 quantization: ~1.5GB (4× reduction, minor recall impact). With product quantization: ~0.5–1GB (6–12× reduction, moderate recall impact). pgvector does not support quantization; Weaviate supports product quantization via HNSW config. For large datasets, quantization is the single biggest lever for cost reduction.

Hybrid Search: Combining Dense and Sparse Retrieval

Pure vector search is semantically powerful but keyword-blind — it can miss exact phrase matches and proper nouns that do not appear in the embedding model's training data. Pure BM25 keyword search is precise for exact terms but has no semantic understanding. Hybrid search combines both, and for most production retrieval systems it outperforms either alone.

Weaviate implements hybrid search natively. Qdrant supports it via sparse vectors (you bring your own sparse encoder, such as SPLADE) stored as a separate named vector. pgvector has no native sparse vector support; you would combine it with PostgreSQL full-text search at the application layer. Pinecone supports sparse-dense hybrid natively in recent index types.

# Qdrant sparse + dense hybrid search with SPLADE
from qdrant_client.models import (
    SparseVector,
    NamedSparseVector,
    NamedVector,
    SearchRequest,
    FusionQuery,
    Prefetch,
)

# Your collection must be created with both dense and sparse vector configs
# Collection setup (run once at init):
# client.recreate_collection(
#   collection_name="documents",
#   vectors_config={
#     "dense": VectorParams(size=1536, distance=Distance.COSINE),
#   },
#   sparse_vectors_config={
#     "sparse": SparseVectorParams(index=SparseIndexParams(on_disk=False)),
#   },
# )

# At query time — fuse results from both retrievers
response = client.query_points(
    collection_name="documents",
    prefetch=[
        Prefetch(
            query=NamedVector(name="dense", vector=dense_embedding),
            limit=20,
        ),
        Prefetch(
            query=NamedSparseVector(
                name="sparse",
                vector=SparseVector(indices=sparse_indices, values=sparse_values),
            ),
            limit=20,
        ),
    ],
    query=FusionQuery(fusion="rrf"),   # Reciprocal Rank Fusion
    limit=10,
    with_payload=True,
)

Decision Framework: Choosing the Right Database

The decision is rarely about raw performance — all four databases are fast enough for most workloads. The real factors are operational fit, scale requirements, and team capability.

Choose pgvector if:

  • You already run PostgreSQL and want to avoid a new service
  • Dataset is under 5M vectors and growing slowly
  • You need ACID transactions across vector and relational data in the same query
  • Filters have low to medium selectivity (returning 10%+ of the dataset)

Choose Qdrant if:

  • You need filtered search with high selectivity (returning 1–5% of the dataset)
  • Dataset exceeds 10M vectors and memory cost is a concern (use quantization)
  • You want the best unfiltered latency with a self-hosted open-source engine
  • You need sparse+dense hybrid search with full control over the sparse encoder

Choose Weaviate if:

  • You want hybrid BM25 + vector search without building the fusion layer yourself
  • You are building a multi-tenant SaaS and need per-tenant data isolation
  • Your team prefers a GraphQL-style query interface and schema-driven data modeling
  • You want built-in retrieval-augmented generation (generate queries) out of the box

Choose Pinecone if:

  • Your team cannot afford the operational overhead of a self-hosted stateful system
  • Traffic is bursty and you need automatic scale-to-zero without capacity planning
  • You are in an early-stage product where time to market beats infrastructure cost
  • You need enterprise compliance certifications (SOC 2, HIPAA) without implementing them yourself

Production Checklist

Benchmark on your actual query distribution

The published benchmarks use synthetic query distributions. Your real workload — specific filter selectivity ratios, dataset size, query concurrency — will produce different results. Before committing to a database, run at least a 48-hour load test with production-representative queries. Measure p50, p95, p99 latency and recall@10 at each percentile.

Plan your embedding dimensionality carefully

Higher dimensions give better semantic quality but cost more memory and increase query latency. OpenAI's text-embedding-3-small at 1536 dims is a strong default. For cost-sensitive deployments, consider 512-dim models (e.g., Cohere embed-v3) — the quality gap is smaller than the memory savings. Qdrant's Matryoshka embedding support lets you truncate at query time.

Design your metadata schema before you import

All four databases support metadata/payload filtering, but schema changes are painful at scale — re-indexing millions of vectors takes hours. Define your filter attributes upfront. For Qdrant, create payload indexes on the fields you filter by most; unindexed payload filters force a full scan. For Weaviate, define a typed schema to enable efficient filtering.

Monitor embedding drift

If you upgrade your embedding model (e.g., from text-embedding-ada-002 to text-embedding-3-small), all existing vectors become incompatible — cosine similarity between embeddings from different models is meaningless. Plan for a dual-index migration: write to both old and new collections during transition, backfill the new collection, cutover reads, then delete the old index. Budget 24–48 hours for large collections.

Use namespaces or tenants to partition multi-tenant data

All four databases offer some form of data partitioning: Pinecone namespaces, Weaviate multi-tenancy, Qdrant collections-per-tenant or payload filters. Using metadata filters alone for tenant isolation is a security anti-pattern — a bug in your filter logic exposes cross-tenant data. Use structural isolation (separate namespaces or collections) for any data that must not cross tenant boundaries.

Back up vector indexes independently

For self-hosted databases, vector indexes are stateful data — treat them like databases, not caches. Qdrant supports snapshot exports to S3; Weaviate has backup modules for S3/GCS/Azure. pgvector data is part of your PostgreSQL backup strategy. Schedule daily snapshots and test restore procedures. Losing a vector index means re-embedding your entire corpus, which may take days.

Building an AI-powered search or retrieval system and not sure which vector database fits your stack?

We design and implement vector search architectures — from embedding pipelines and index configuration to hybrid search, multi-tenancy, and production monitoring. Let’s talk.

Get in Touch

Related Articles