Every team building a retrieval feature hits the same fork in the road: do you stand up a dedicated vector database like Pinecone, Weaviate, or Qdrant — or do you just use the Postgres you already run? Thanks to the pgvector extension, using Postgres as a vector database is a genuinely serious option now, not a toy. The honest answer to “is it good enough?” is further than you’d expect, with a real and knowable ceiling — and this piece is about where that ceiling actually is.
We’ll go through how vector search in Postgres works, the handful of knobs that matter, the vendor benchmarks (read critically), why hybrid search beats vector-only, and the rule of thumb for when a dedicated engine finally earns its keep. No hype, and no pretending the trade-offs don’t exist.

🎯 Two Kinds of Search in One Database
Postgres has quietly done full-text keyword search since 2008 — tsvector, tsquery, and GIN indexes give you stemming, ranking, and fast term lookups without a separate search engine. What changed recently is that pgvector added the other kind of search: similarity search over embeddings, the vectors of numbers that represent meaning. Ask for the rows “closest” to a query embedding and you get semantic matches — results that are about the same thing even when they share no keywords.
Having both in one database is the whole point. It means the keyword index, the vector index, and your actual application data live together, under one backup and one set of transactions. That co-location turns out to be the strongest argument in Postgres’s favor — more on that at the end.
🧭 Exact vs. Approximate: The Trade Every Vector Search Makes
Start with the simplest version. Without any index, pgvector does exact nearest-neighbor search: ORDER BY embedding <-> query LIMIT 10 scans every row, measures the distance, and returns the true top 10. It’s correct and perfectly fine up to roughly a few hundred thousand rows, depending on hardware. Past that, scanning everything gets slow.
So you add an approximate nearest-neighbor (ANN) index. The word “approximate” is the key trade: you give up a small, tunable amount of recall (the fraction of the true nearest neighbors you actually get back) in exchange for a large speedup. Every vector search system on the market makes this exact trade — pgvector is not special here, and neither is Pinecone.
One detail that bites people: pgvector has several distance operators — <-> for Euclidean, <=> for cosine, <#> for inner product — and your index has to be built with the operator class that matches the operator your query uses. Cosine (vector_cosine_ops) is the right default for the normalized embeddings that OpenAI, Cohere, and Voyage models produce. Build the wrong one and Postgres silently falls back to scanning every row. Always confirm with EXPLAIN that your index is actually being used.

🔧 HNSW, IVFFlat, and the One Dial You Actually Turn
pgvector offers two ANN index types. IVFFlat groups vectors into clusters and only searches the nearest few — cheap to build, smaller, but it needs your data already loaded to cluster well, and it generally loses to HNSW on the speed-versus-recall curve. HNSW builds a layered “navigable graph” you hop through to reach a query’s neighbors — slower and more memory-hungry to build, better at query time. HNSW is the modern default; reach for IVFFlat mainly when build cost or index size is the binding constraint.
Here’s the part worth memorizing. HNSW has two build-time settings — m (connections per node, default 16) and ef_construction (default 64) — that are baked into the index and require a rebuild to change. But the setting you actually reach for is ef_search, the query-time dial. It’s a per-session SET, so you can turn recall up for a nightly batch job and down for a latency-sensitive request path, live, with no rebuild. Crucially, pgvector’s shipped defaults are deliberately conservative — tuned for a clean first build, not production recall — so most teams raise ef_search (and often m) before going live. If you take one thing from this section: the defaults are a starting point, not a recommendation.

Two more levers. halfvec stores your embeddings at 16-bit instead of 32-bit precision — roughly half the storage and memory for a recall hit small enough that several Postgres experts now recommend it as the default type, not an optimization you add later. And binary quantization shrinks vectors ~32x, but the recall cost is real; teams recover it by over-fetching candidates and re-ranking them against the full-precision vectors.
🕳️ The Sharpest Edge: Filtered Vector Search
The place pgvector historically drew blood was filtered search — “find the nearest neighbors where category = ‘invoices’.” The old behavior applied your WHERE filter after the ANN index returned its candidates, so a selective filter could silently hand you back three rows when you asked for ten. That’s the “overfiltering” trap.
pgvector 0.8.0 addressed it with iterative index scans, which automatically widen the search and keep filtering until they’ve collected enough rows (bounded by a cap like hnsw.max_scan_tuples). This is a real, documented improvement — but not a full solution. A sufficiently selective filter can still push the scan toward its cap, and it isn’t the same as the filter-aware graph traversal some dedicated engines implement natively. If you do filtered vector search, benchmark your actual filter distribution rather than trusting defaults, and consider a partial index or a restructured pre-filter subquery.
📊 The Benchmarks, Read Honestly
Now the question everyone actually asks: how does it compare? A companion extension, pgvectorscale (from TigerData, formerly Timescale), adds a disk-based index called StreamingDiskANN that scales past the memory ceiling limiting in-memory HNSW graphs, plus a binary-quantization scheme. Its published numbers are strong — but read them the way you’d read any benchmark a vendor runs on its own product.
Against Pinecone, TigerData’s benchmark (50 million Cohere embeddings, 768 dimensions, on an AWS r6id.4xlarge) reports pgvector + pgvectorscale beating Pinecone’s storage-optimized index by roughly 28× lower p95 latency, 16× higher throughput at 99% recall, at about 75% lower self-hosted cost. Impressive — and vendor-published, so treat the multipliers as a starting point for your own test, not gospel.
The Qdrant comparison is the honest one, and it’s more instructive precisely because TigerData didn’t sand off the parts where it loses. On the same 50M-vector dataset at 99% recall, Postgres delivered 11.4× higher throughput — but worse tail latency (p95 of 60ms vs. Qdrant’s 37ms; Qdrant roughly 39–48% faster at the high percentiles). At 90% recall, Qdrant was faster on every latency percentile while Postgres still won on throughput. Qdrant also built its index in about a third of the time. Which system “wins” depends entirely on whether your workload is throughput-bound (batch RAG ingestion, analytics) or tail-latency-bound (interactive user-facing search). Never quote “11.4× faster” without saying at what.
And Google’s AlloyDB publishes its own comparison of its proprietary ScaNN index beating standard pgvector HNSW by up to 10× on latency — which is real, and also Google marketing for a proprietary, AlloyDB-only index, aimed squarely at HNSW’s known weak spot (build and write cost on huge datasets). The reliable signal across all three: pgvector has closed most of the gap, while disk-based and proprietary indexes still win on tail latency and build time at large scale. Discount every vendor benchmark in proportion to how badly the vendor wants to sell the winner.
🔀 Hybrid Search: Why Vector-Only Leaves Recall on the Table
Because keyword and vector search live in the same database, you can combine them — and you usually should. Pure vector search quietly fails on exact terms: proper nouns, product codes, error IDs, precise terminology that embeddings blur together. Keyword search nails those and misses the semantic matches. Run both and fuse the results.
The standard fusion method is Reciprocal Rank Fusion (RRF): run a full-text query and a vector query, each producing a ranked list, then score every result by summing 1 / (k + rank) across the lists, with k = 60 as the near-universal default. In Postgres it’s two ranked CTEs and a FULL OUTER JOIN — cheap to implement. One cited case study reports retrieval precision improving from about 62% (vector-only) to 84% (hybrid + RRF); that’s a single vendor-published figure, not a universal constant, but it’s directionally why hybrid retrieval is now the default recommendation for production RAG. If your native tsvector ranking isn’t relevant enough, the pg_search extension brings true BM25 scoring (via a Rust/Lucene-like engine) into Postgres as a heavier but more capable alternative.

🧱 The RAG Data Layer — and Why Co-Location Wins
Put it together and you get the standard retrieval-augmented generation (RAG) pipeline: chunk your source documents, embed each chunk, store the chunk text plus its embedding plus metadata (with a foreign key back to the source document), retrieve by similarity (ideally hybrid + RRF), and optionally re-rank the top candidates with a cross-encoder before handing them to the model. As a baseline, chunking research converges on splitting at roughly 400–512 tokens with 10–20% overlap (around 85–90% recall in cited evaluations); semantic chunking pushes a bit higher at real ingestion cost. Treat those numbers as directional — they shift with your embedding model and corpus.
The architectural reason to keep all of this inside Postgres rather than syncing embeddings out to a separate vector store is transactional consistency. When a document changes, its embedding changes in the same transaction — they can never drift apart the way an external index can when a sync job fails. The price is that you couple your normal database load and your vector-search load onto the same resources. And one caveat no architecture removes: RAG reduces hallucination, it doesn’t eliminate it. Retrieve a stale or mis-chunked passage and the model will still answer confidently and wrongly.
⚖️ So: Postgres or a Dedicated Vector Database?
Here’s the rule of thumb the evidence supports. Postgres as a vector database is the right call when you’re in the range of up to roughly tens of millions of vectors, single-tenant, and either throughput-tolerant or not chasing the absolute lowest tail latency — and you value keeping embeddings consistent with your source data in one system you already operate. A dedicated engine (Pinecone, Weaviate, Qdrant, Milvus) earns its complexity at very large scale, under strict tail-latency SLAs, when you want GPU-accelerated ANN, or when you need to scale and isolate vector compute independently of everything else.
It’s a trade-off with a crossover point, not a verdict for one side. And the on-ramp is easy: pgvector ships on Supabase, Neon (every plan, including free), AWS RDS/Aurora, and Google Cloud SQL/AlloyDB, usually one CREATE EXTENSION away.

🧭 What This Means for You
Start with pgvector. Use halfvec and an HNSW index, raise ef_search above the conservative default, and verify with EXPLAIN that the index is actually used. Add hybrid search with RRF before you assume vector-only is enough. Benchmark filtered queries against your real filter distribution, and validate retrieval quality against your own labeled eval set rather than someone’s blog number. Then — and only then — migrate to a dedicated engine if your measurements cross the line above. One last honest note: pgvector, pgvectorscale, and pg_search all ship fast, so re-check version numbers and feature availability against the current docs before you commit anything to production.
📚 Further Reading
- Why Postgres Became the Default Database for AI Apps — the bigger case this piece sits inside.
- Postgres as Your Entire AI Backend — where vector search fits alongside your queue, scheduler, and agent memory.
- What Is an Embedding? — the plain-English foundation for everything “vector” in this article.
- Primary sources: the pgvector project, pgvectorscale, and PostgreSQL’s full-text search docs.
❓ Frequently Asked Questions
Can Postgres really replace a dedicated vector database?
For many workloads, yes — up to roughly tens of millions of vectors, single-tenant, with pgvector (plus pgvectorscale at the larger end). You outgrow it at very large scale, under strict tail-latency SLAs, or when you need GPU-accelerated search or isolated vector compute. It’s a crossover point, not a permanent verdict.
Is pgvector actually fast enough?
Vendor benchmarks show pgvector + pgvectorscale competitive with Pinecone on throughput and cost, and roughly at parity with Qdrant depending on which latency percentile you optimize for. Those are vendor-published numbers — treat them as a starting point for your own benchmark, and pay attention to whether your workload cares more about throughput or tail latency.
HNSW or IVFFlat — which index should I use?
HNSW is the modern default: better speed-versus-recall, at the cost of slower builds and more memory. IVFFlat is worth it mainly when build time or index size is the binding constraint. Either way, the live dial you tune is ef_search (HNSW) or probes (IVFFlat).
Do I need hybrid search, or is vector search enough?
Add hybrid search if your content has exact terms embeddings blur past — names, codes, IDs, precise jargon. Combining keyword (tsvector) and vector results with Reciprocal Rank Fusion is cheap and, in one cited case, lifted precision from ~62% to ~84%. It’s the default recommendation for production RAG.
Why keep embeddings in Postgres instead of a separate vector store?
Consistency. When your source data and its embeddings live in the same transactional database, a document update can’t leave behind a stale, orphaned embedding the way a failed sync to an external store can. The trade-off is that your normal database load and vector-search load share the same resources.
📚 Recommended Resources
Disclosure: some links below are Amazon affiliate links. If you buy through them, I earn a small commission — at no extra cost to you — which helps keep this site running.
AI Engineering: Building Applications with Foundation Models
Chip Huyen · Book · ~$60 on Amazon
The most complete field guide to building on top of LLMs — retrieval, RAG, evaluation, and the data plumbing this article is really about. The natural next read once you’ve decided where your vectors live.
Build a Large Language Model (From Scratch)
Sebastian Raschka · Book · ~$50 on Amazon
Builds the intuition under the word “embedding” — how models turn text into the vectors you’re indexing in pgvector — by having you construct one step by step.
Relevant Search
Doug Turnbull & John Berryman · Book · ~$50 on Amazon
The classic on search relevance — ranking, scoring, and why combining signals beats any single one. Exactly the mindset behind the hybrid keyword-plus-vector approach in this piece.