The most common question we get on RAG engagements is which vector database should we use? The honest answer has changed since the first wave of RAG projects. Postgres and the search engines teams already run now do vector search well, object storage has become a credible place to keep vectors, and every hyperscaler sells a managed retrieval layer. The database matters less than it did. Retrieval quality — hybrid search, reranking, filtering, evaluation — matters more.
We have shipped production retrieval on pgvector, OpenSearch, Pinecone, Qdrant and Weaviate, and evaluated most of the newer options. This is the guide we walk clients through: first whether you need a dedicated store at all, then the trade-offs that actually decide it, the cost arithmetic at 1M, 10M and 100M vectors, and the operational work nobody puts in the benchmark.
The short version
- Already on Postgres, under a few tens of millions of vectors, filters that join application data? Use pgvector. Revisit when a measured limit forces you to.
- Already running OpenSearch or Elasticsearch for search? Add vectors there. You get BM25 and hybrid search in the same engine.
- Large corpora queried rarely, or thousands of small tenants? Look at object-storage-first options (S3 Vectors, turbopuffer, LanceDB). Their cost curves are different.
- High query rates at large scale, heavy continuous writes, or complex filtering, with no appetite for database work? A dedicated engine — Pinecone, Qdrant, Weaviate, Milvus/Zilliz.
- Whatever you pick: build a golden evaluation set before you choose, and plan for re-embedding from day one.
First question: do you need a dedicated vector database?
Usually not at the start. The reasons to default to what you already run are not glamorous, which is why vendor comparisons skip them: one system to back up, monitor and secure; retrieval next to the data it filters on; transactions, foreign keys and row-level access control you already trust.
pgvector and pgvectorscale
pgvector (0.8.7 at the time of writing) supports HNSW and IVFFlat indexes, half-precision halfvec, binary and sparse vectors.1 Two details matter in practice. First, the indexable dimension limits: 2,000 for vector and 4,000 for halfvec — so a 3,072-dimension embedding has to be stored as halfvec or shortened before you can index it.1 Second, filtering. With approximate indexes, pgvector applies the WHERE clause after the index scan, so a selective filter can return fewer rows than you asked for. Since 0.8.0, iterative index scans keep scanning until enough rows pass the filter.1
-- Postgres with pgvector 0.8.x
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE chunks (
id bigserial PRIMARY KEY,
tenant_id uuid NOT NULL,
doc_id uuid NOT NULL,
body text NOT NULL,
tsv tsvector GENERATED ALWAYS AS (to_tsvector('english', body)) STORED,
embedding halfvec(1024) NOT NULL -- half the memory of vector(1024)
);
CREATE INDEX ON chunks USING hnsw (embedding halfvec_cosine_ops)
WITH (m = 16, ef_construction = 64); -- pgvector defaults
CREATE INDEX ON chunks USING gin (tsv);
CREATE INDEX ON chunks (tenant_id);
-- Keep scanning when the tenant filter discards most candidates
SET hnsw.iterative_scan = 'relaxed_order';
SET hnsw.ef_search = 100; -- default is 40; tune against recall
SELECT id, body
FROM chunks
WHERE tenant_id = $1
ORDER BY embedding <=> $2::halfvec
LIMIT 20;pgvectorscale, from Timescale, adds a StreamingDiskANN index, Statistical Binary Quantization and label-based filtered search on top of pgvector, under the PostgreSQL licence.2 It is worth testing when your index no longer fits comfortably in memory. Its README publishes a benchmark against Pinecone; as with every vendor benchmark, run your own on your own data and filters.
The search engine you already run
OpenSearch and Elasticsearch both store dense vectors alongside their inverted indexes. Elasticsearch 9.x defaults new float vector fields to a quantized HNSW index (int8 or BBQ, depending on version and dimensions) and, from 9.4, to a disk-based BBQ index where the licence allows; it caps dimensions at 4,096.3 Both engines fuse BM25 and vector results with reciprocal rank fusion: Elasticsearch through an rrf retriever, OpenSearch through a score ranker processor added in 2.19.45 If your team already operates one of these, hybrid search is a configuration change, not a new system.
The landscape in late 2026
For the cases where an extension isn’t enough, the credible options fall into four groups. None are wrong choices; they optimise for different things.
| Group | Examples | Best for | Watch out for |
|---|---|---|---|
| Extend what you run | pgvector, pgvectorscale, OpenSearch, Elasticsearch | Teams with an existing database or search cluster; filters that join app data | Memory sizing, index build time, post-filtering behaviour |
| Dedicated engines | Pinecone, Qdrant, Weaviate, Milvus / Zilliz | High query rates at scale, continuous writes, rich filtering, built-in hybrid search | Another system to secure and sync; pricing models that punish one large namespace |
| Object-storage-first | S3 Vectors, turbopuffer, LanceDB | Large or many-tenant corpora with modest or bursty query rates | Cold-query latency; fewer tuning knobs |
| Cloud-managed retrieval | Bedrock Knowledge Bases, Azure AI Search, Google Vector Search | Teams standardised on one cloud who want ingestion, chunking and retrieval managed | Less control over chunking and ranking; harder to move later |
Dedicated engines
Pinecone is fully managed and bills on storage, read units and write units, with a $50 monthly minimum on the Standard plan; it also sells hosted embedding and reranking models.6 Qdrant is open source with a managed cloud. Its quantization options are the most extensive we use, and its multitenancy guidance — one collection, a tenant payload index, large tenants promoted to dedicated shards — is sound.78 Weaviate ships hybrid search with a tunable vector/keyword balance and stores each tenant on its own shard, with inactive and offloaded states for tenants nobody is querying.910 Milvus (and Zilliz, its managed service) has the widest menu of index types, including DiskANN and GPU indexes.11
Object-storage-first
turbopuffer keeps data in object storage and caches hot namespaces on NVMe and in memory. Its own documentation reports a p50 of 874 ms for a cold query and 14 ms once cached, on 1M documents, and it supports BM25 alongside vectors.12 Pricing is based on storage, bytes written and data queried.13 LanceDB runs as an embedded open-source library over the Lance format, with an Enterprise tier for large deployments, and supports vector, full-text and hybrid search.14 Amazon S3 Vectors became generally available on 2 December 2025, with up to two billion vectors per index and around 100 ms latency for frequent queries (sub-second for infrequent ones).15 AWS describes it as best suited to infrequent-query workloads.16
Cloud-managed retrieval
Bedrock Knowledge Bases manages ingestion and retrieval over a vector store you choose: OpenSearch Serverless or managed clusters, S3 Vectors, Aurora PostgreSQL, Neptune Analytics, Pinecone, Redis Enterprise Cloud or MongoDB Atlas.16 Azure AI Search runs BM25 and vector queries in one request, merges them with RRF, and can apply a semantic reranker on top; it also underpins Microsoft’s Foundry IQ knowledge layer.17 Google’s Vector Search, now documented under the Gemini Enterprise Agent Platform (formerly Vertex AI), is built on ScaNN and supports dense, sparse and hybrid search with filtering.18
Decision flow
How we pick a vector store
Extend what you already run. Add a system only when a measured constraint forces it — and let the query pattern pick which kind.
Hybrid search and reranking
If you take one thing from this article: do not ship dense-only retrieval without testing hybrid. Embeddings are good at meaning and bad at exact tokens — product codes, error numbers, names, clause references. BM25 is the reverse. The BEIR benchmark found BM25 a robust baseline that dense retrievers often failed to beat out of domain.19 Running both and fusing the results is cheap insurance.
Reciprocal rank fusion is the default way to combine them: each document scores the sum of 1 / (k + rank) across the result lists, so you never have to normalise BM25 scores against cosine similarities.20 Elasticsearch and OpenSearch both default k to 60.45 In Postgres you can write it yourself:
WITH dense AS (
SELECT id, row_number() OVER (ORDER BY embedding <=> $2::halfvec) AS r
FROM chunks
WHERE tenant_id = $1
ORDER BY embedding <=> $2::halfvec
LIMIT 50
),
keyword AS (
SELECT id, row_number() OVER (ORDER BY ts_rank_cd(tsv, q) DESC) AS r
FROM chunks, websearch_to_tsquery('english', $3) AS q
WHERE tenant_id = $1 AND tsv @@ q
ORDER BY ts_rank_cd(tsv, q) DESC
LIMIT 50
)
SELECT id, SUM(1.0 / (60 + r)) AS rrf_score
FROM (SELECT * FROM dense UNION ALL SELECT * FROM keyword) AS fused
GROUP BY id
ORDER BY rrf_score DESC
LIMIT 20;Note that Postgres’ built-in ts_rank_cd is not BM25. It is good enough to catch exact-token matches, which is most of what hybrid search buys you; if keyword relevance itself matters, use a search engine or a BM25 extension.
Reranking is the second stage: retrieve 50–100 candidates cheaply, then score each query–passage pair with a cross-encoder and keep the top handful. In our experience it is the single biggest quality lever after hybrid search, and it is independent of the database. Hosted rerankers are priced per request — Pinecone lists $2 per 1,000 rerank requests for several models — so budget it like an LLM call.6
Filtering and multi-tenancy
Most production retrieval is filtered: by tenant, by permission, by document type, by date. Filtering is where approximate indexes behave least intuitively, because a graph index that finds the 40 nearest neighbours and then discards 39 of them on a filter returns one result. Every engine has an answer — iterative scans in pgvector, filter-aware graph traversal in the dedicated engines — but you have to test it with your real filter selectivity.
Milvus’ guidance is a useful rule of thumb: graph indexes win when filters remove less than about 85% of rows, and brute-force search is the accurate choice when they remove more than 98%.11 In Postgres the equivalent is to let a B-tree index on the filter column drive an exact search when the filter is very selective, and to use partial indexes or partitioning when there are few distinct filter values.1
| Tenancy pattern | How it works | Use when |
|---|---|---|
| Shared index + tenant filter | One index, tenant ID as a filtered field (Qdrant: is_tenant payload index) | Many small tenants; the default in most engines8 |
| Partition / shard per tenant | Each tenant physically separate (Weaviate shards, Pinecone namespaces, Postgres partitions) | Strong isolation, per-tenant deletion, idle tenants offloaded10 |
| Tiered | Small tenants share; large tenants promoted to dedicated shards | Skewed tenant sizes (Qdrant 1.16+)8 |
| Index per tenant | Separate collection or database per tenant | A handful of tenants with contractual isolation |
Tenancy also drives cost on usage-priced services. Pinecone charges a query one read unit per GB of the namespace it searches, with a minimum of 0.25.21 Splitting one large namespace into per-tenant namespaces can cut read costs by orders of magnitude — we work through the numbers below.
Index types and quantization
Three index families cover almost everything you will meet.
| Index | How it works | Strengths | Costs |
|---|---|---|---|
| HNSW22 | Multi-layer proximity graph held in memory | High recall at low latency; the default nearly everywhere | Memory-hungry; slow builds; deletes leave work for maintenance |
| IVF | Vectors clustered around centroids; search probes the nearest clusters | Fast builds, lower memory; suits large top-k11 | Recall depends on probes; centroids drift as data changes |
| DiskANN | Graph stored on SSD with compressed vectors in memory | Large indexes on modest RAM; stable latency112 | Needs fast disks; IOPS can become the bottleneck |
Quantization is how you make any of them affordable at scale. Qdrant’s documentation gives typical ratios: scalar (float32 to int8) is about 4× smaller with usually under 1% error; binary is up to 32× smaller; product quantization reaches up to 64× at a larger accuracy cost.7 The trick that makes aggressive compression usable is oversampling and rescoring: fetch extra candidates with the compressed vectors, then re-rank them with the originals. Qdrant has this built in; pgvector documents the same pattern with a binary-quantized expression index.71 Our default: start with float16 or int8, measure recall against your golden set, and only go binary if memory forces it and rescoring recovers the loss.
Embedding models, dimensions and cost
The embedding model decides retrieval quality more than the database does, and its dimension count drives your memory bill linearly. Choose it on your own evaluation set, not a leaderboard: domain vocabulary, languages and document length all move the rankings.
Matryoshka embeddings make dimension a dial rather than a fixed cost. Models trained this way pack the most important information into the leading dimensions, so a truncated vector is still useful.23 OpenAI’s text-embedding-3 models accept a dimensions parameter (defaults 1,536 and 3,072), and Amazon Titan Text Embeddings V2 offers 1,024, 512 or 256.2416 A pattern we like: store shortened vectors in the index for the first pass, and rescore the candidates with full-length vectors kept in cheaper storage.
Check dimension limits before you commit. pgvector indexes up to 2,000 dimensions for vector, Elasticsearch and S3 Vectors cap at 4,096.1316
What it costs at 1M, 10M and 100M vectors
Vendor calculators make cost look either trivial or terrifying. The arithmetic below uses published prices and states its assumptions; substitute your own numbers.
Assumptions: 1,024-dimension float32 embeddings (4,096 bytes) plus about 1,000 bytes of ID and metadata per record — roughly 5.1 KB, following Pinecone’s storage formula of ID + metadata + dimensions × 4 bytes.21 One million queries a month. Pinecone Standard prices at the low end of its range: $0.33/GB-month storage and $16 per million read units.6 S3 Vectors storage at $0.06/GB-month (US East).25
| 1M vectors | 10M vectors | 100M vectors | |
|---|---|---|---|
| Stored size (≈5.1 KB/record) | 5.1 GB | 51 GB | 510 GB |
| Raw vectors in RAM: float32 / float16 / binary | 4.1 / 2.0 / 0.13 GB | 41 / 20.5 / 1.3 GB | 410 / 205 / 13 GB |
| Pinecone storage / month | ≈ $1.70 | ≈ $17 | ≈ $168 |
| Pinecone reads, one namespace (RU/query = GB) | 5.1M RU ≈ $82 | 51M RU ≈ $816 | 510M RU ≈ $8,160 |
| Pinecone reads, 1,000 equal tenant namespaces (each query searches one) | 0.25M RU ≈ $4 | 0.25M RU ≈ $4 | 0.51M RU ≈ $8 |
| S3 Vectors storage / month | ≈ $0.31 | ≈ $3.06 | ≈ $30.60 |
| Re-embed corpus (500 tokens/chunk): 3-small / 3-large | $10 / $65 | $100 / $650 | $1,000 / $6,500 |
Re-embedding uses OpenAI’s list prices of $0.02 and $0.13 per million tokens for text-embedding-3-small and -large.26 For S3 Vectors queries, AWS’s own worked example — 10M vectors across 40 indexes, 1M queries a month — totals $11.38 a month including storage, uploads and queries.25 The RAM row is raw vectors only; HNSW graph links add more on top, so treat it as a floor when sizing a self-hosted node.
Three things fall out of the table. Storage is rarely the problem; query pricing and RAM are. Tenancy design can matter more than vendor choice on usage-priced services. And re-embedding is cheap in API fees but expensive in engineering time, which is the next section.
Evaluating retrieval before you choose
Every vendor benchmark is run on someone else’s data with someone else’s filters. The only benchmark that counts is a golden set: a few hundred real queries, each labelled with the passages that should come back, ideally graded for relevance. Pull queries from logs or support tickets, not from the team’s imagination, and include the awkward ones — exact codes, negations, multi-hop questions.
Then track a small number of metrics on every change:
- Recall@k — what share of the relevant passages appear in the top k. This is the metric for the retrieval stage, because the LLM cannot use what was never retrieved.
- nDCG@10 — rewards putting the most relevant passages first. This is the metric for the reranker.
- Latency at p95 under realistic filters, and recall of the approximate index against exact search on the same queries, so you know what the index itself is costing you.
import math
def recall_at_k(retrieved: list[str], relevant: set[str], k: int = 10) -> float:
return len(set(retrieved[:k]) & relevant) / len(relevant)
def ndcg_at_k(retrieved: list[str], grades: dict[str, int], k: int = 10) -> float:
# grades: passage_id -> relevance 0..3; unlisted passages count as 0
dcg = sum(grades.get(p, 0) / math.log2(i + 2) for i, p in enumerate(retrieved[:k]))
ideal = sorted(grades.values(), reverse=True)[:k]
idcg = sum(g / math.log2(i + 2) for i, g in enumerate(ideal))
return dcg / idcg if idcg else 0.0With that harness in place, the database decision becomes an experiment: load the same corpus into two candidates, run the same queries with the same filters, and compare recall, latency and projected cost. It usually takes days, not weeks.
Operations: the part nobody benchmarks
Re-embedding on model change
Vectors from different embedding models are not comparable. When you upgrade the model — and you will — every vector must be regenerated. Design for it from the start: keep the source text and chunking parameters, record the model name and version on every row, and build the new index alongside the old one. Switch reads with an alias or a feature flag once the golden set confirms the new index is better, then retire the old one.
Updates and deletes
Documents change, permissions change, and deletion requests have deadlines. Graph indexes handle deletes by marking and cleaning up later, so heavy churn degrades recall and bloats storage until maintenance runs — vacuum in Postgres, compaction or optimisation in the dedicated engines. Key every chunk by its source document so you can replace a document’s chunks atomically, and test that a deleted document actually stops appearing in results.
Backups and disaster recovery
If vectors live in Postgres, your existing backups cover them. On a managed vector service, check what backup means and what it costs: Pinecone, for example, prices backups at $0.10/GB-month and restores at $0.15/GB.6 Because vectors can be regenerated from source text, some teams treat the index as a cache and back up only the source and metadata. That works only if you have measured how long a full rebuild takes.
Failure modes we see repeatedly
- Dense-only retrieval that misses exact identifiers users type every day.
- Filtered queries returning too few results because the index post-filters and nobody tested with a selective tenant.
- Choosing on a vendor benchmark that used no filters and a different embedding model.
- One giant namespace on a service that bills queries by namespace size.
- No model version on vectors, so a partial re-embed silently mixes incompatible vectors.
- Index sized to today’s data, swapping to disk six months later as the corpus grows.
- Permissions checked after generation rather than enforced as a retrieval filter.
Checklist
- Estimate vectors per tenant in 18 months, and the query rate.
- Decide whether you can extend Postgres or your search engine before adding a system.
- Build a golden set of real, labelled queries before choosing anything.
- Test hybrid (BM25 + dense with RRF) and a reranker against dense-only.
- Test filtered queries at your most selective real filter.
- Pick the embedding model and dimension on your data; check index dimension limits.
- Start with float16 or int8; go further only with rescoring and measured recall.
- Design tenancy for isolation, deletion and query cost.
- Store model name and version on every vector; plan a side-by-side re-embed.
- Work through the cost arithmetic at 1×, 10× and 100× today’s volume.
The vector database matters less than the marketing suggests. Pick the simplest option that meets your measured constraints and spend the saved time on chunking, hybrid retrieval, reranking and evaluation. If you want a second pair of hands on that work, ForthClover’s agent engineering team does exactly this.
Sources
- pgvector README (v0.8.7): index types, dimension limits, iterative index scans, filtering, binary quantization — github.com/pgvector/pgvector
- Timescale, pgvectorscale README: StreamingDiskANN, Statistical Binary Quantization, label-based filtering — github.com/timescale/pgvectorscale
- Elastic, dense_vector field type: index_options and default quantized index types by version — www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector
- Elastic, Reciprocal rank fusion (rrf retriever) — www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion
- OpenSearch, Score ranker processor (RRF for hybrid queries, OpenSearch 2.19+) — docs.opensearch.org/latest/search-plugins/search-pipelines/score-ranker-processor/
- Pinecone pricing (Standard and Enterprise plans, storage, read/write units, rerank, backups) — www.pinecone.io/pricing/
- Qdrant documentation, Quantization (scalar, binary, product, TurboQuant; rescoring and oversampling) — qdrant.tech/documentation/guides/quantization/
- Qdrant documentation, Multitenancy (payload partitioning, is_tenant, tiered multitenancy) — qdrant.tech/documentation/guides/multiple-partitions/
- Weaviate documentation, Hybrid search (alpha, fusion methods) — docs.weaviate.io/weaviate/search/hybrid
- Weaviate documentation, Multi-tenancy (shard per tenant, tenant states) — docs.weaviate.io/weaviate/manage-collections/multi-tenancy
- Milvus documentation, Index explained (IVF, HNSW, DiskANN, GPU indexes; filter-ratio guidance) — milvus.io/docs/index-explained.md
- turbopuffer documentation, Architecture (object storage, NVMe cache, cold vs cached latency, BM25) — turbopuffer.com/docs/architecture
- turbopuffer pricing — turbopuffer.com/pricing
- LanceDB documentation (OSS embedded library, Enterprise, vector/full-text/hybrid search) — docs.lancedb.com/
- AWS, “Amazon S3 Vectors is now generally available with 40 times the scale of preview,” 2 Dec 2025 — aws.amazon.com/about-aws/whats-new/2025/12/amazon-s3-vectors-generally-available/
- AWS, Amazon Bedrock Knowledge Bases: prerequisites for your own vector store (supported stores, S3 Vectors notes, Aurora iterative scans) — docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base-setup.html
- Microsoft Learn, Hybrid search overview — Azure AI Search (RRF, semantic ranker, Foundry IQ) — learn.microsoft.com/en-us/azure/search/hybrid-search-overview
- Google Cloud, Vector Search overview (Gemini Enterprise Agent Platform, formerly Vertex AI) — docs.cloud.google.com/vertex-ai/docs/vector-search/overview
- Thakur et al., “BEIR: A Heterogeneous Benchmark for Zero-shot Evaluation of Information Retrieval Models,” NeurIPS 2021 — arxiv.org/abs/2104.08663
- Cormack, Clarke & Büttcher, “Reciprocal Rank Fusion outperforms Condorcet and individual Rank Learning Methods,” SIGIR 2009 — cormack.uwaterloo.ca/cormacksigir09-rrf.pdf
- Pinecone documentation, Understanding cost (read units per GB of namespace, write units, storage formula) — docs.pinecone.io/guides/manage-cost/understanding-cost
- Malkov & Yashunin, “Efficient and robust approximate nearest neighbor search using Hierarchical Navigable Small World graphs,” 2016 — arxiv.org/abs/1603.09320
- Kusupati et al., “Matryoshka Representation Learning,” arXiv:2205.13147 — arxiv.org/abs/2205.13147
- OpenAI API documentation, Embeddings (text-embedding-3 models and the dimensions parameter) — developers.openai.com/api/docs/guides/embeddings
- Amazon S3 pricing — S3 Vectors (storage, PUT, query charges, pricing example 1) — aws.amazon.com/s3/pricing/
- OpenAI API pricing (embedding models) — developers.openai.com/api/docs/pricing
Product details and prices were checked against each primary source in October 2026. Prices vary by region and plan and change often; confirm current figures before budgeting.
