Skip to content
Blog

Vector Search at Scale: pgvector vs. Qdrant vs. Pinecone

Three vector stores, one decision. Compare pgvector, Qdrant, and Pinecone on latency, filtering, scale ceiling, and cost to pick the right one for your RAG stack.

Published on August 6, 2026

AI Assistant

Embeddings are the backbone of RAG, and the vector store you choose decides your latency, your bill, and how much operational work you inherit. The 2026 market has largely consolidated around three options, and they are very different beasts:

  • pgvector — a Postgres extension. Search lives inside the database you already run.
  • Qdrant — a purpose-built, Rust-based vector engine you host (or buy as a cloud).
  • Pinecone — a fully managed, serverless vector database; you never touch the index.

There is no universal winner — the right choice is a function of your vector count, your filters, and whether you want to run a second system.

Prerequisites

  • PostgreSQL 13+ for the pgvector path, or Docker for Qdrant
  • Python 3.10+ and psycopg / qdrant-client
  • Embeddings from any model (the examples use a 1536-dim text embedder)

Option A: pgvector — search inside Postgres

If you already run Postgres, pgvector is the lowest-friction choice. Add the extension, create an HNSW index, and query with cosine distance — reusing your existing backups, connection pooling, and transactional guarantees.

CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE chunks (
    id    bigserial PRIMARY KEY,
    text  text,
    embedding vector(1536)
);

CREATE INDEX ON chunks USING hnsw (embedding vector_cosine_ops);
import psycopg

q = [0.1, 0.2, ...]  # 1536-dim query embedding
with psycopg.connect(DSN) as conn:
    rows = conn.execute("""
        SELECT text, 1 - (embedding <=> %s) AS score
        FROM chunks
        ORDER BY embedding <=> %s
        LIMIT 5
    """, (q, q)).fetchall()

Where it shines: <5–10M vectors, ACID transactions, and complex SQL WHERE filters (tenant + status + date) that Postgres plans well — often faster than a dedicated store’s metadata filter. Cost is effectively $0 if you already run Postgres.

Where it strains: beyond ~10M vectors, HNSW memory (ef_search tuning becomes real work), no native sharding, and you share CPU/memory with your transactional workload. Index builds on large tables need planning.

Option B: Qdrant — purpose-built performance

Qdrant is designed for the vector workload itself: best-in-class filtered search (it applies filters during the traversal, not after), native hybrid sparse+dense search, and quantization (int8 scalar cuts memory ~4x; binary ~32x) that lets one 16GB node hold ~10M vectors.

from qdrant_client import QdrantClient, models

client = QdrantClient(url="http://localhost:6333")
client.recreate_collection(
    collection_name="chunks",
    vectors_config=models.VectorParams(size=1536, distance=models.Distance.COSINE),
)

client.upsert(collection_name="chunks", points=[
    models.PointStruct(id=i, vector=vec, payload={"tenant": t, "status": s}) for i, (vec, t, s) in enumerate(rows)
])

hits = client.query_points(
    collection_name="chunks",
    query=q,
    query_filter=models.Filter(must=[models.FieldCondition(key="tenant", match=models.MatchValue(value="acme"))]),
    limit=5,
).points

Where it shines: sub-10ms p95, heavy and selective filtering that doesn’t degrade recall, horizontal scaling to 100M+ vectors, and cheap self-hosting ($20–50/mo on a modest VM). Quantization is a genuine superpower for scale.

Where it strains: it’s a stateful distributed system you run — sharding, replication, snapshots, upgrades are on you. It replaces Postgres for vectors but you’ll still keep Postgres for relational data.

Option C: Pinecone — zero ops, consumption priced

Pinecone abstracts away the index entirely: you upsert and query an API, it scales to billions of vectors. For a small team with no database specialist, that’s real value.

Where it costs you: you buy the managed convenience. Billing is consumption-based (storage, reads, writes, egress). The subtle trap is that read units scale with namespace size — one read unit per 1GB of namespace per query (0.25 floor). A query on a 100GB namespace costs 100x the same query on a 1GB one, even though the query didn’t get harder. Lock-in is also real: it’s closed-source and cloud-only, with no self-host escape hatch.

from pinecone import Pinecone

pc = Pinecone(api_key="...")
index = pc.Index("chunks")
results = index.query(vector=q, top_k=5,
                      filter={"tenant": {"$eq": "acme"}})

The comparison

Measured against a ~5M/1536-dim workload at 100 QPS (typical published numbers):

MetricpgvectorQdrantPinecone
p50 query latency~14–28ms~4–8ms~22–25ms
p95 latency~38–65ms~18–21ms~55ms+
Filtered query costFast (SQL planner)Fast (filter-in-search)Fast, but billed per-GB
Scale ceiling~5M (10M with care)100M+Billions (managed)
Ops burdenNone (reuses Postgres)Medium (you run it)None
Cost (10M vectors)~$30–80/mo~$120–350/mo~$300–900/mo
Lock-inNoneNoneHigh

Decision matrix

  • Already on Postgres + <5M vectors: start with pgvector. The complexity of a second system isn’t justified.
  • >10M vectors, heavy filtering, or sub-10ms latency → Qdrant. Open source, cheap to run, and it actually delivers on raw throughput.
  • No infra team, ship-in-a-week, budget available → Pinecone. Buy away operations; just model the read-unit bill and the lock-in.
  • Cross 100M+ vectors and Milvus (purpose-built for billion-scale) is worth evaluating — but that’s a different conversation.

Putting It All Together

Start on pgvector and benchmark with your real filters from day one — plain top-k recall is misleading because production queries have WHERE clauses. When latency or scale forces a move, your embedding schema and application layer shouldn’t care which store backs it, so keep retrieval behind a thin interface.

Conclusion & Next Steps

Pick pgvector when Postgres exists and you’re under ~10M vectors; pick Qdrant when vector search is your primary workload at scale; pick Pinecone when zero ops is a hard requirement. Whatever you choose, benchmark on your own corpus with realistic filters — recall is a function of your embedding model, index parameters, and filters, in that order — and pin versions of any dedicated store you operate.

References / Sources