All tutorials

Tutorial

Choosing a Vector Database for RAG: pgvector vs. Pinecone vs. Qdrant

A Staff Engineer's decision guide to picking a vector db for RAG — pgvector, Pinecone, or Qdrant — based on scale, latency, and ops cost.

postgresaiperformance

Your RAG prototype works. Retrieval feels fast enough, answers are grounded, and the demo gods are smiling. Then someone asks how this scales past 50,000 documents, whether it’ll still be fast with per-tenant filtering, and what it costs at 10x the current volume — and you realize the vector database you picked almost by default is now a load-bearing infrastructure decision.

Here’s the direct answer: if you’re already running PostgreSQL and expect fewer than roughly 10 million vectors, use pgvector — it adds no new infrastructure and is good enough. If you need the lowest latency and most flexible filtering at real scale, and you’re comfortable self-hosting or using a managed Kubernetes-style service, use Qdrant. If you want zero operational overhead and are willing to pay for it and accept its constraints, use Pinecone. The rest of this article explains why, and how to map your own constraints onto that decision instead of trusting a bar chart.

What a Vector Database Actually Does in a RAG System

A vector database stores embeddings alongside the original text and metadata, and it supports similarity search — finding the top-K nearest vectors to a query — combined with metadata filtering. That combination is the entire retrieval mechanism behind RAG: you embed a user’s query, ask the database for the K most similar chunks, optionally constrain the search to documents matching some filter (tenant ID, date range, document type), and hand the results to your LLM as context.

The part that trips people up isn’t the similarity search — nearest-neighbor math is well understood. It’s the interaction between approximate nearest-neighbor search and filtering. Get that interaction wrong and you’ll ship a RAG system that returns confidently wrong answers because it silently dropped the one relevant chunk that happened to live behind a selective filter. More on that below — it’s the crux of this whole decision.

The Architecture Landscape

Three architectures dominate conversations about production RAG retrieval, and it’s worth understanding what kind of system each one is before comparing numbers.

pgvector is a PostgreSQL extension — you add a native vector column type and distance operators to an existing table, and you compare embeddings using cosine or Euclidean distance like any other SQL predicate. That means vectors live in the same database as your relational data: you can join a vector similarity query against your users or orders tables, wrap retrieval in the same transaction as a write, and skip running a second piece of infrastructure entirely.

Pinecone is a purpose-built managed vector database with no relational data model at all — it exists purely to store and query vectors and their metadata, offered as a hosted service rather than something you run yourself.

Qdrant is an open-source vector database written in Rust, designed from the ground up for high-throughput ANN search, with its own query language for payload (metadata) filtering, and available both self-hosted and as a managed cloud offering.

Weaviate and Milvus occupy similar territory to Qdrant — open-source, purpose-built, horizontally scalable — but bring different tradeoffs (Weaviate leans into multimodal search and a GraphQL-style API, Milvus targets very large, sharded deployments). They’re worth knowing exist, but this article focuses on the three options that map most directly onto the constraints a Postgres-native team is actually weighing.

Index Types: HNSW, IVFFlat, and Quantization

Every one of these databases ultimately relies on approximate nearest-neighbor (ANN) indexing, because exact nearest-neighbor search over millions of high-dimensional vectors doesn’t scale. The two index families you’ll encounter constantly are:

  • IVFFlat clusters the vector space into a fixed number of lists (think: coarse buckets), then searches only the most promising buckets at query time. It’s fast to build and memory-light compared with graph-based indexes.
  • HNSW (Hierarchical Navigable Small World) is a graph-based index that generally gives better recall than IVFFlat, at the cost of higher memory use and slower index builds.

Production comparisons of pgvector, Pinecone, and Qdrant describe pgvector as supporting both IVFFlat and HNSW as index types, and describe Qdrant’s indexing as HNSW-based with support for scalar, product, and binary quantization to shrink memory footprint. Pinecone’s index internals are proprietary — it’s described as an ANN implementation with HNSW-like characteristics, but the details aren’t publicly documented, and these same comparisons report that Pinecone exposes little or no index-parameter tuning (like ef_search or m) of the kind that lets you trade accuracy for speed the way you can with pgvector or Qdrant. Check current Pinecone docs to confirm what’s exposed at the time you’re evaluating it — that’s a meaningful constraint if your workload needs that dial.

Two tuning knobs matter in practice across HNSW implementations: ef_search (how many candidates to examine at query time — higher means better recall, slower queries) and m (how many graph connections each node keeps — higher means better recall, more memory). Quantization (scalar, product, or binary) compresses vector representations to cut memory and speed up search, usually at a small recall cost. Where you land on these dials is a direct tradeoff between recall and latency, and it’s one reason raw benchmark numbers from someone else’s config won’t transfer cleanly to your workload.

Filtering: The Decision That Benchmarks Don’t Show You

This is the part generic comparisons gloss over, and it’s often the actual production failure mode. There are three architectural approaches to combining ANN search with metadata filters:

  1. Pre-filtering — filter the candidate set first (e.g., “only documents where tenant_id = X”), then run nearest-neighbor search over that subset. Accurate, but slow when the filter is highly selective, because you may be forced into something close to a brute-force scan of a small subset.
  2. Post-filtering — run the ANN search first across the whole index, then discard results that don’t match the filter. Fast, but you can lose recall badly: if your top-K candidates from the unfiltered search don’t happen to include enough matches for a selective filter, you return too few — or wrong — results.
  3. In-filtering — the filter is evaluated during graph traversal, so the ANN search itself is filter-aware. This is the approach Qdrant uses: payload filtering happens as part of HNSW traversal rather than before or after it, which is particularly relevant for high-cardinality filters like per-user or per-tenant IDs and date ranges, where pre- or post-filtering tend to break down.

If your RAG system is multi-tenant — and most production systems are — this is not a minor implementation detail. A system that quietly degrades to post-filtering behavior under a selective tenant filter will look fine in testing (small, uniform dataset) and then serve degraded or empty results in production once tenants have uneven data volumes. When you evaluate any vector database for a multi-tenant RAG use case, explicitly ask which of these three strategies it uses, and test with filters as selective as your worst-case real tenant, not your average one.

Latency, Recall, and Cost: What the Numbers Can (and Can’t) Tell You

Here’s an honest caveat before any numbers: benchmark results for vector databases vary significantly by configuration — embedding dimensionality, index parameters, hardware, and dataset — and figures reported in one blog’s test harness won’t necessarily reproduce in yours. Treat anything below as directionally useful, not a guarantee.

That said, the pattern that shows up repeatedly across comparisons of pgvector, Pinecone, and Qdrant is consistent in shape even when the exact numbers vary by source: self-hosted, tuned HNSW implementations (pgvector, Qdrant) tend to post lower query latency than fully managed serverless offerings (Pinecone), particularly once you account for Pinecone’s serverless cold-start behavior on infrequently queried indexes — the first query after idle time incurs extra latency because compute has to spin back up. Recall tells a similar story: these comparisons describe pgvector and Qdrant as letting you tune the latency/recall tradeoff directly via index parameters, while Pinecone’s managed nature means you’re largely accepting whatever tradeoff the service has made for you — worth confirming against current Pinecone documentation for your situation.

Because exact published benchmark figures differ by source, dataset, and date — and no single authoritative, reproducible benchmark was available to verify specific millisecond or percentage figures for this piece — the responsible move is to benchmark your own embedding dimensionality and query pattern before committing, rather than trusting any one vendor or blog’s numbers as universal. If you want a credible comparison, run the same corpus and query mix against at least two candidates in a staging environment before you build irreversible assumptions into your architecture.

Cost follows a similar shape: at moderate scale, the operational cost of these three options tends to converge, because at low vector counts you’re mostly paying for compute you’d need anyway (a Postgres instance you already run, versus a small managed vector service). The divergence shows up at scale, where a fully managed, pay-per-query service’s costs compound with query volume in a way that self-hosted or reserved-capacity infrastructure doesn’t — which is exactly the kind of cost curve that burns budget quietly until a monthly bill forces the conversation. Rather than asserting a specific multiplier here without a verified source, the practical move is to model your own projected query volume and storage against each pricing structure (pay-per-operation vs. reserved compute) before scaling past a prototype.

Deep Dive: pgvector

pgvector is a PostgreSQL extension — CREATE EXTENSION vector — that gives you a native vector column type usable in ordinary SQL. Its biggest strength isn’t raw ANN performance; it’s that your vectors live in the same ACID-compliant database as everything else, so you get real SQL joins between vector search results and relational data, transactional consistency between writes, and one fewer service to operate, monitor, and pay for.

The tradeoffs: it’s bounded by Postgres’s general-purpose architecture rather than being purpose-built for vector workloads, so at high scale and high query concurrency it can become a bottleneck compared to databases designed around ANN search from day one. It’s also only a sensible choice if you’re already running Postgres — standing up a dedicated Postgres instance just to use pgvector, when you have no other Postgres workload, gives up most of the “no new infrastructure” advantage.

Deep Dive: Pinecone

Pinecone offers two deployment modes: serverless, where you pay per query and storage with no infrastructure to manage, and pod-based, where you reserve dedicated compute for predictable latency. Serverless is attractive for variable or spiky workloads and genuinely zero-ops, but carries cold-start latency on the first query after a period of idleness — a real consideration if your RAG traffic is bursty rather than steady. Pod-based avoids that cold start and gives more predictable latency, at the cost of paying for reserved capacity whether or not you’re using it.

Pinecone uses namespaces to logically partition a single index, which is a clean mechanism for multi-tenant isolation without standing up separate indexes per tenant. The constraint worth internalizing up front: Pinecone doesn’t expose index-parameter tuning to trade accuracy for performance the way pgvector and Qdrant do, and its underlying index implementation isn’t publicly documented. That’s the tradeoff of a fully managed product — you give up low-level control in exchange for not operating anything. For teams who want to ship fast and whose priority is not re-litigating infrastructure decisions, that’s often a fair trade. For teams with specific latency SLAs or unusual filtering patterns, it’s a real limitation worth testing against before committing.

Deep Dive: Qdrant

Qdrant is written in Rust and built specifically for high-throughput vector search, with HNSW indexing, scalar/product/binary quantization to control memory footprint, sparse vector support, and native hybrid search combining dense and sparse vectors via rank fusion techniques like RRF. It supports named vectors (multiple vector representations per record — useful if you’re combining different embedding models) and payload filtering on indexed fields, with the in-filtering architecture discussed above. Its on-disk HNSW support lets you trade some latency for a much smaller memory footprint when your dataset doesn’t comfortably fit in RAM — worth confirming against current Qdrant docs before you commit to an architecture.

The tradeoff is operational: self-hosting means you own uptime, backups, and scaling decisions, even though the managed cloud option narrows that gap considerably. Qdrant is also a newer, narrower project than Postgres — you’re trusting a purpose-built but younger ecosystem rather than decades of relational-database operational maturity.

Hybrid Search: Dense + Sparse Isn’t Optional Forever

Hybrid search combines dense embeddings with sparse (BM25-style) term weighting and merges the result sets, typically via RRF or alpha-weighting. Published comparisons describe Qdrant as supporting this natively through its sparse vector and fusion capabilities — confirm the specifics against current Qdrant docs. If your corpus has exact-match-sensitive content such as codes, SKUs or citation numbers, test hybrid support explicitly rather than assuming dense vectors alone will cover it — this is a gap that can be discovered late, after a prototype quietly fails on exactly the queries that matter most to users.

Scaling Behavior and the ~10M-Vector Threshold

This is the threshold question that matters most for teams starting on Postgres: when does pgvector stop being the right default?

A commonly cited rule of thumb is to use pgvector up to roughly 10 million vectors, since it runs alongside your existing application data with no new infrastructure to stand up, and to move to a dedicated database like Qdrant, Weaviate, or Milvus beyond that point. A related framing puts pgvector’s comfortable range at under roughly 10–20 million vectors, noting that beyond roughly 50–100 million vectors, Postgres HNSW indexes can start hitting memory and throughput limits that purpose-built vector databases are specifically designed to avoid. Treat these as rule-of-thumb ranges rather than hard cutoffs — your actual ceiling depends on vector dimensionality, available RAM, query concurrency, and how aggressively you tune the index — but they’re a reasonable planning heuristic when you’re deciding whether to architect for migration now or later.

What actually breaks at scale isn’t usually “pgvector stops working” — it’s a gradual degradation: index build times stretch, memory pressure on your primary Postgres instance competes with your OLTP workload, and query latency creeps upward under concurrent load. If you’re anywhere near that threshold, the pragmatic move is to design your embedding storage and retrieval layer behind an interface that doesn’t assume Postgres specifically, so a later migration to Qdrant (or similar) is a backend swap, not a rewrite.

A Decision Framework

Map your actual constraints to a choice rather than chasing benchmark bragging rights:

Your situationLikely best fitWhy
Already running Postgres, under ~10M vectors, want zero new infrastructurepgvectorSQL joins against relational data, ACID transactions, one less service to operate
Multi-tenant RAG with high-cardinality filters (per-user, per-tenant) at meaningful scaleQdrantFilter evaluated during HNSW traversal, which suits selective per-tenant filters
Want to ship fast, minimal ops appetite, willing to pay for fully managedPineconeFully managed hosted service (per published comparisons); no infrastructure to run
Approaching or past ~10M vectors and growingQdrant (or similar dedicated DB)Planning heuristic (~10M); Postgres HNSW memory/throughput limits are more commonly cited at 50-100M vectors, so treat ~10M as a conservative trigger to start planning migration
Need fine-grained control over the latency/recall tradeoffpgvector or QdrantBoth expose index-tuning parameters; comparisons report Pinecone exposes little or no equivalent tuning — check current Pinecone docs
Bursty, unpredictable query traffic and cold-start latency is acceptablePinecone serverlessPay-per-query model fits spiky load without reserved capacity
Need predictable p99 latency with no cold startPinecone pod-based or QdrantDedicated compute avoids the serverless cold-start penalty

One practitioner-level summary of these three options’ production fit frames it simply: pgvector suits teams already on Postgres who don’t want new infrastructure, Pinecone offers a zero-ops managed path for fast deployment, and Qdrant delivers the strongest latency and filtering characteristics for performance-critical applications at scale. That framing matches the reasoning above — let your constraints choose the database, not the other way around.

FAQ

Can I start with pgvector and migrate later without a full rewrite? Yes, if you design for it — keep your retrieval logic behind an interface that treats “fetch top-K similar chunks with filters” as an abstraction rather than hardcoding Postgres-specific SQL throughout your application. That makes a later swap to Qdrant or another dedicated database a backend change rather than an application rewrite.

Does hybrid search (BM25 + vectors) matter if my corpus is small? Less urgently — at small scale, over-fetching and re-ranking in application code can mask the gap. It matters more as your corpus grows and users start expecting exact-match recall on things like codes, SKUs, or names alongside semantic search.

Is Pinecone’s lack of index-parameter tuning actually a problem? It depends on whether your workload needs to trade recall for latency or vice versa. If a managed service’s default tradeoff is good enough for your use case, it’s a non-issue and you benefit from not thinking about it. If you have strict latency SLAs or unusual recall requirements, test this constraint explicitly before committing.

Should I benchmark myself instead of trusting published comparisons? Yes. Benchmark figures across pgvector, Pinecone, and Qdrant vary by source, configuration, and dataset, and no single authoritative number applies universally — running your own embedding dimensionality and query pattern against two candidates in staging is the only reliable way to know what you’ll actually get.

What about Weaviate or Milvus instead of these three? Both are legitimate purpose-built alternatives to Qdrant, with Weaviate leaning toward multimodal search and a GraphQL-style interface, and Milvus targeting very large, sharded deployments. They’re worth evaluating if Qdrant’s specific feature set doesn’t fit, but for a team deciding between “stay on Postgres,” “go fully managed,” or “go purpose-built and self-hosted,” pgvector, Pinecone, and Qdrant cover the three distinct tradeoff shapes most teams actually face.