Skip to main content
Data Engineering

Vector Database Comparison 2025: Pinecone vs Weaviate vs Milvus vs Qdrant vs pgvector

Choosing a vector database for enterprise RAG comes down to one thing most published benchmarks ignore: filtered search, where a query combines vector similarity with metadata constraints. This is a practical comparison of Pinecone, Weaviate, Milvus, Qdrant and pgvector on that basis — how each handles filtered workloads, a production Qdrant configuration, and a clear when-to-choose guide.

Inductivee Intelligence Team· AI EngineeringSeptember 15, 2025(updated October 24, 2026)8 min read
TL;DR

There is no single best vector database for enterprise RAG — the right choice depends more on your team's operational model than on any benchmark leaderboard. For self-hosted deployments, Qdrant is strong on filtered search and cost-efficiency; Pinecone serverless trades cost for operational simplicity and consistent latency; and pgvector with HNSW is a credible option for teams already on Postgres at smaller scales. The rest of this piece explains why filtered search — not raw ANN speed — is usually the deciding factor, and how to choose.

Why Vector DB Benchmarks Are Hard to Trust

Most published vector database benchmarks measure pure ANN (approximate nearest neighbour) search on clean, unfiltered datasets at uniform scale. This is not what enterprise RAG pipelines experience. Real workloads combine dense vector similarity with metadata filters — 'find the 10 most similar documents to this query, but only from documents belonging to tenant X, created after 2024-01-01, with status=published.' Filtered search performance varies dramatically across databases and is often the deciding factor.

That is exactly why the comparison here is deliberately qualitative. Rather than publish a single leaderboard number — which, for the reason above, rarely reflects filtered production workloads — it weighs how each database handles filtered search, shows a production-grade Qdrant configuration, and sets out when each option is the right call. Benchmark the shortlist on your own data and filter selectivity before committing.

Production Qdrant Setup with Filtered Search

python
from qdrant_client import QdrantClient
from qdrant_client.models import (
    Distance, VectorParams, PointStruct,
    Filter, FieldCondition, MatchValue, Range,
    HnswConfigDiff, OptimizersConfigDiff
)
from openai import OpenAI
import uuid
import time
from typing import Optional


class EnterpriseVectorStore:
    """
    Production-grade Qdrant wrapper for enterprise RAG workloads.
    Handles collection creation, upsert, and filtered search.
    """
    COLLECTION = "enterprise_docs"
    VECTOR_DIM = 768  # OpenAI ada-002 / text-embedding-3-small at 768

    def __init__(self, host: str = "localhost", port: int = 6333):
        self.client = QdrantClient(host=host, port=port, timeout=30)
        self.openai = OpenAI()
        self._ensure_collection()

    def _ensure_collection(self):
        existing = [c.name for c in self.client.get_collections().collections]
        if self.COLLECTION not in existing:
            self.client.create_collection(
                collection_name=self.COLLECTION,
                vectors_config=VectorParams(
                    size=self.VECTOR_DIM,
                    distance=Distance.COSINE,
                ),
                hnsw_config=HnswConfigDiff(
                    m=16,
                    ef_construct=128,
                    full_scan_threshold=10_000,  # HNSW below this, full scan above
                    on_disk=True,  # Critical at 100M+ vectors
                ),
                optimizers_config=OptimizersConfigDiff(
                    indexing_threshold=20_000,  # Batch index, not per-upsert
                    memmap_threshold=50_000,
                ),
            )
            # Create payload index for filtered search performance
            for field in ["tenant_id", "status", "doc_type"]:
                self.client.create_payload_index(
                    collection_name=self.COLLECTION,
                    field_name=field,
                    field_schema="keyword"
                )
            self.client.create_payload_index(
                collection_name=self.COLLECTION,
                field_name="created_at",
                field_schema="float"
            )
            print(f"Collection '{self.COLLECTION}' created with payload indexes.")

    def embed(self, text: str) -> list[float]:
        response = self.openai.embeddings.create(
            input=text,
            model="text-embedding-3-small",
            dimensions=self.VECTOR_DIM
        )
        return response.data[0].embedding

    def upsert_document(self, doc_id: str, text: str, metadata: dict) -> None:
        vector = self.embed(text)
        self.client.upsert(
            collection_name=self.COLLECTION,
            points=[PointStruct(
                id=str(uuid.uuid5(uuid.NAMESPACE_DNS, doc_id)),
                vector=vector,
                payload={**metadata, "doc_id": doc_id, "text": text[:500]}
            )]
        )

    def search(
        self,
        query: str,
        tenant_id: str,
        doc_type: Optional[str] = None,
        min_created_at: Optional[float] = None,
        top_k: int = 10,
    ) -> list[dict]:
        query_vector = self.embed(query)

        must_conditions = [
            FieldCondition(key="tenant_id", match=MatchValue(value=tenant_id)),
            FieldCondition(key="status", match=MatchValue(value="published")),
        ]
        if doc_type:
            must_conditions.append(
                FieldCondition(key="doc_type", match=MatchValue(value=doc_type))
            )
        if min_created_at:
            must_conditions.append(
                FieldCondition(key="created_at", range=Range(gte=min_created_at))
            )

        start = time.perf_counter()
        results = self.client.search(
            collection_name=self.COLLECTION,
            query_vector=query_vector,
            query_filter=Filter(must=must_conditions),
            limit=top_k,
            with_payload=True,
        )
        latency_ms = (time.perf_counter() - start) * 1000
        print(f"Search latency: {latency_ms:.1f}ms, results: {len(results)}")

        return [
            {"doc_id": r.payload["doc_id"], "score": r.score, "text": r.payload.get("text")}
            for r in results
        ]


# Usage
if __name__ == "__main__":
    store = EnterpriseVectorStore(host="localhost", port=6333)
    store.upsert_document(
        doc_id="doc-001",
        text="Q4 2025 revenue increased 18% YoY driven by enterprise segment growth.",
        metadata={"tenant_id": "acme-corp", "status": "published",
                  "doc_type": "financial", "created_at": 1735000000.0}
    )
    results = store.search(
        query="What was revenue growth in Q4?",
        tenant_id="acme-corp",
        doc_type="financial",
    )
    print(results)

Enterprise Qdrant setup with payload indexes for filtered search. The on_disk=True HNSW config is essential at 100M+ vector scales — without it, the entire index must fit in RAM. Payload indexes on tenant_id and status are required for low-latency filtered search; without them, Qdrant falls back to full payload scan.

Warning

pgvector 0.7.0's HNSW support is a genuine improvement, but the performance gap versus dedicated vector databases widens significantly once you add metadata filtering — and it widens further with scale. If your Postgres instance is also serving transactional workloads, ANN search at scale will compete for the shared buffer pool and degrade OLTP performance. Keep vector search on a dedicated read replica at minimum, or move to a dedicated vector database as your corpus grows past the low tens of millions of vectors.

When to Choose Each Database

Qdrant 1.10 — Best self-hosted choice for most teams

Best at: filtered search accuracy and throughput, cost-efficient self-hosting, Rust-native reliability. Choose Qdrant when you have DevOps capacity to manage infrastructure, need strong filtered search performance, and are working at 10M-500M vector scale. The 1.10 release's sparse vector support also makes it the best choice for hybrid dense/sparse search (BM25 + semantic).

Pinecone Serverless — Best managed option for ops-light teams

Best at: zero-ops management, consistent latency SLAs, integrated authentication. Choose Pinecone when your team lacks vector DB operational expertise, when consistent p99 latency matters more than raw throughput, or when you need a managed service with an enterprise SLA. The serverless tier's per-query pricing scales down well at uneven workloads.

Milvus 2.4 — Best for GPU-accelerated workloads

Best at: GPU-accelerated index building at very large scale (500M+), complex multi-vector queries. Choose Milvus when you need to rebuild indexes frequently over very large collections or when you already have GPU infrastructure. The Kubernetes deployment is more operationally complex than Qdrant but the Attu UI provides better visibility.

pgvector 0.7.0 — Best for smaller corpora on existing Postgres

Best at: zero additional infrastructure, SQL joins with relational data, familiar ops model. Use pgvector when your team is Postgres-native and you value the ability to JOIN vector results directly with relational data. As a rough rule of thumb — not a measured boundary — it stays comfortable into the low tens of millions of vectors, and is worth moving to a dedicated vector DB somewhere past that as filtered-search latency and resource contention grow.

Inductivee Intelligence's Recommended Stack for Enterprise RAG

For RAG deployments built in 2025, the default recommendation is Qdrant self-hosted on a dedicated node for teams with any DevOps capacity, and Pinecone serverless for teams where 'managed, no ops' is a hard requirement. The cost difference between self-hosting and a managed service is meaningful but secondary to the operational overhead of running your own Qdrant cluster.

The decision that teams consistently underweight is payload index design. A Qdrant collection with well-designed payload indexes on tenant_id, document_type, and date will substantially outperform a poorly indexed collection on filtered search latency. Spend time on your metadata schema before loading data — retroactively adding payload indexes on a 100M vector collection requires a full scan and temporarily degrades query performance.

For teams doing hybrid search (keyword + semantic), Qdrant 1.10's sparse vector support is the cleanest implementation we have found. BM25 sparse vectors combined with dense semantic search using Qdrant's built-in reciprocal rank fusion consistently outperforms pure semantic search on factual enterprise queries.

Frequently Asked Questions

Which vector database is best for filtered search?

For filtered search, Qdrant tends to lead among self-hosted options, with Pinecone serverless close behind and offering a consistent managed SLA. But the single biggest factor is not the database — it is payload indexing: every database needs explicit payload indexes on its filter fields to keep filtered search fast at scale. Benchmark the shortlist on your own data rather than trusting a headline latency number.

Should I use pgvector or a dedicated vector database?

pgvector 0.7.0 with HNSW indexing is viable at smaller scales — roughly the low tens of millions of vectors — when you want to avoid additional infrastructure. As your corpus grows, the filtered-search latency gap versus dedicated vector databases, and the risk of ANN search competing with OLTP workloads for shared resources, make migration to a dedicated vector DB the right call.

How much does a vector database cost to run?

It depends heavily on scale, instance choice and query throughput, so price it against your own workload rather than a headline figure. The consistent shape: self-hosted Qdrant on a mid-size compute instance is cost-efficient at scale; pgvector on managed Postgres is cheaper still at small scale but needs a larger instance to hold latency as it grows; and Pinecone serverless costs more in exchange for zero operational overhead.

What embedding dimension should I use for enterprise RAG?

768 dimensions is the enterprise standard — it is the native output of OpenAI's text-embedding-3-small (configured at 768) and the original ada-002 output dimension. It balances retrieval quality and storage cost. Using 1536 dimensions doubles storage and memory requirements with marginal recall improvement on most enterprise document retrieval tasks.

How do I benchmark my own vector database workload?

Use the ANN-Benchmarks framework as a baseline but add a filtered search layer that matches your production filter selectivity (typically 10-30% of corpus). Measure recall@10 alongside p50/p99 latency — databases that win on latency by sacrificing recall are not genuinely faster. Run benchmarks on your actual embedding model output, not synthetic vectors, as real embeddings cluster differently and stress index structures differently.

Written By

Inductivee Intelligence Team — AI Engineering at Inductivee Intelligence

Inductivee Intelligence Team

Author

Agentic AI Engineering Team

The Inductivee Intelligence engineering team — multi-agent orchestration, RAG pipeline architecture, and data liquidity engineering. Our writing is grounded in what we actually build, break, and operate in production.

Agentic AI ArchitectureMulti-Agent OrchestrationLangChainLangGraphCrewAIMicrosoft Agent Framework
LinkedIn profile

Inductivee Intelligence is a remote-first agentic AI engineering studio. Our engineering content is written by active practitioners and technically reviewed before publication.

Engineer This With Inductivee Intelligence

The engineering patterns in this article are what our team builds into production every day. Explore the related service to see how we deliver this capability at enterprise scale.

Ready to Build This Into Your Enterprise?

Inductivee Intelligence engineers agentic systems, RAG pipelines, and enterprise data liquidity solutions. Let's scope your project.

Start a Project