Vector Databases Compared: pgvector, Qdrant, Weaviate, Pinecone

Index types, filtered search, scale limits, operational burden and real cost. Including the case nobody selling a vector database will make for you: when the Postgres you already run is the right answer.

k-nearest neighbours HNSW graph
4
Systems compared
6KB
One 1,536-dim float32 vector
2,000
pgvector vector() index dim ceiling
32x
Max binary quantization shrink

Pick pgvector if you already run Postgres and expect fewer than about ten million vectors. Pick Qdrant if filtered search at high query rates is your bottleneck and you have someone to operate it. Pick Weaviate if you want keyword and vector search fused in one query, with hard tenant isolation built in. Pick Pinecone if you want to never see a node again and the bill is smaller than the engineer-hours it replaces.

0.8.2
Current pgvector release, February 2026
1.18
Qdrant release that added TurboQuant compression
$0.33
Pinecone serverless list price per GB-month, mid-2026

Key Takeaways

Every retrieval project reaches the same fork: the prototype ran fine on a list in memory, and now there are five million chunks and a latency budget. This comparison is written from that point, not from a feature matrix, and the code runs as written against the versions named.

01

The Short Answer: Which One to Pick

Under roughly 10 million vectors with Postgres already in production, use pgvector. Above that, or when selective metadata filters dominate your queries, use Qdrant. Use Weaviate when hybrid keyword-plus-vector relevance and per-tenant isolation are product requirements. Use Pinecone when you have no platform engineer and want the storage layer to be someone else's problem.

Dimension pgvector Qdrant Weaviate Pinecone
Index types HNSW, IVFFlat HNSW + exact fallback HNSW, flat, dynamic Managed, not exposed
Filtered search Iterative scan (0.8+) Filterable HNSW links ACORN traversal Built in, not tunable
Compression halfvec, binary, sparse Scalar, binary, TurboQuant PQ, SQ, BQ, RQ Handled internally
Keyword + vector hybrid Write it yourself with tsvector Sparse vectors + fusion BM25 hybrid, one call Sparse-dense supported
Joins to relational data Native SQL Application-side Application-side Application-side
Multi-tenancy Row filters or partitions Payload filter, shard keys Per-tenant index, offloadable Namespaces
Operational burden Whatever Postgres already costs You own nodes and upgrades You own nodes and upgrades None
Where it gets uncomfortable ~10M vectors per node Cluster rebalancing Memory per tenant at scale The invoice, and lock-in
02

What Actually Differs Is the Index, Not the Database

All four answer the same question: given a query vector, return the k closest stored vectors under cosine, dot product or L2 distance. All four do it approximately, with a navigable small-world graph. What differs is how the graph handles filters, how vectors are compressed, and who runs the process.

Three index families cover almost everything in production:

Quantization is the second lever and the one people underuse. Scalar quantization stores each dimension as int8 instead of float32, roughly 4x smaller with small recall loss. Binary keeps one bit per dimension for up to 32x, and only works if you rescore top candidates. Qdrant 1.18 added TurboQuant, a rotation-based method from Google Research reaching scalar-level recall at half the memory. Our embeddings explainer and vector databases guide cover the math assumed here.

03

Filtering Is Where These Systems Separate

A pure nearest-neighbour benchmark tells you almost nothing, because production queries are never pure. They carry a tenant id, a date range, a document type or a permission scope. Filtering breaks the assumption HNSW rests on, and each system breaks it differently.

Graph search reaches the query neighbourhood by hopping through neighbours. If a filter excludes 99% of rows, the survivors may not be connected to each other, so a greedy walk gets stranded and returns three results when you asked for ten. That is overfiltering, and its signature is distinctive: results look fine in testing and go strangely empty for your smallest customer.

Post-filter

Search first, discard after

Fetch the top 100 by distance, drop rows failing the predicate. Fast, simple, silently short whenever the filter is selective. Source of most "why is my RAG missing documents" tickets.

Filter-aware

Filter inside the traversal

Evaluate the predicate during traversal, walk through non-matching nodes, fall back to an exact scan once the candidate set is small enough. Correct results, more engineering inside the database.

How each system handles it:

Test This Before You Commit

Benchmark your real filter selectivity, not a uniform random one. Run the same query with the filter matching 50%, 5% and 0.5% of rows, and measure both latency and the count of results returned. A system that returns 4 rows when you asked for 10 has failed, however fast it did so.

04

pgvector: When the Postgres You Already Run Wins

pgvector is an extension that adds vector types and index access methods to Postgres. Release 0.8.2 shipped in February 2026. If your application data already lives in Postgres, pgvector removes an entire distributed-systems problem: no second store to keep in sync, no dual write, no reconciliation job, no separate backup schedule.

That last point deserves weight. Once embeddings live outside your primary database, every insert has to succeed in two places, and the resulting drift stays invisible until a user reports that a document visible in the app is unfindable in search. Teams end up writing a nightly reconciler, a permanent tax no benchmark chart shows.

schema and HNSW index โ€” Postgres 17 + pgvector 0.8.2
-- one time, per database CREATE EXTENSION IF NOT EXISTS vector; CREATE TABLE chunk ( id bigserial PRIMARY KEY, doc_id bigint NOT NULL REFERENCES doc(id), tenant_id uuid NOT NULL, published date NOT NULL, body text NOT NULL, embedding vector(1536) NOT NULL ); -- build is CPU and memory hungry; give it room or it spills to disk SET maintenance_work_mem = '8GB'; SET max_parallel_maintenance_workers = 7; CREATE INDEX chunk_embedding_hnsw ON chunk USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64); -- index the columns you filter on, separately CREATE INDEX chunk_tenant_published ON chunk (tenant_id, published);

The query side is ordinary SQL, which is the whole point: join the parent document, apply row-level security, return everything in one round trip.

filtered vector query with iterative scan
SET hnsw.ef_search = 100; -- recall knob, higher is slower SET hnsw.iterative_scan = strict_order; -- 0.8+, avoids overfiltering SET hnsw.max_scan_tuples = 40000; -- ceiling on the extra work SELECT c.id, d.title, c.body, c.embedding <=> $1 AS distance FROM chunk c JOIN doc d ON d.id = c.doc_id WHERE c.tenant_id = $2 AND c.published >= now() - interval '2 years' ORDER BY c.embedding <=> $1 LIMIT 10;

Two limits catch people. The vector type indexes up to 2,000 dimensions, so a 3,072-dimension embedding stores fine and then refuses to index; the fix is halfvec, float16 up to 4,000 dimensions, half the storage, very little recall cost. And HNSW builds are slow on large tables, so create the index after bulk loading.

large embeddings and binary quantization
-- 3072-dim model: index the half-precision cast CREATE INDEX ON chunk USING hnsw ((embedding::halfvec(3072)) halfvec_cosine_ops); -- binary quantization: 32x smaller index, rescore for accuracy CREATE INDEX ON chunk USING hnsw ((binary_quantize(embedding)::bit(1536)) bit_hamming_ops); SELECT id, body, embedding <=> $1 AS distance FROM ( SELECT id, body, embedding FROM chunk ORDER BY binary_quantize(embedding)::bit(1536) <~> binary_quantize($1) LIMIT 200 ) candidates ORDER BY distance LIMIT 10;

pgvector stops being comfortable around ten million vectors on a single node, because Postgres does not shard for you. Before migrating, try pgvectorscale, which adds a StreamingDiskANN index built to keep search fast when vectors exceed available memory. Our PostgreSQL guide and database indexing guide cover the tuning that applies here too.

05

Qdrant: Filtered Search and Aggressive Compression

Qdrant is a Rust vector database with the most direct answer to filtered search: filter-aware links built into the HNSW graph itself, plus more compression options than anything else here. Version 1.18, May 2026, added TurboQuant. It self-hosts as a single container and offers a managed cloud with a free 1 GB tier.

collection with payload index and int8 quantization
from qdrant_client import QdrantClient, models client = QdrantClient(url="http://localhost:6333") client.create_collection( collection_name="chunks", vectors_config=models.VectorParams( size=1536, distance=models.Distance.COSINE, on_disk=True, # originals on SSD ), hnsw_config=models.HnswConfigDiff( m=16, ef_construct=100, payload_m=16, # extra links for filtered traversal ), quantization_config=models.ScalarQuantization( scalar=models.ScalarQuantizationConfig( type=models.ScalarType.INT8, quantile=0.99, always_ram=True, # quantized copy stays hot ) ), ) # a payload index is required for filter-aware search to help client.create_payload_index( collection_name="chunks", field_name="tenant_id", field_schema=models.PayloadSchemaType.KEYWORD, )
filtered query with rescoring
hits = client.query_points( collection_name="chunks", query=query_vector, query_filter=models.Filter( must=[ models.FieldCondition( key="tenant_id", match=models.MatchValue(value="acme"), ) ] ), limit=10, search_params=models.SearchParams( hnsw_ef=128, quantization=models.QuantizationSearchParams( rescore=True, # re-rank with full-precision vectors oversampling=2.0, # fetch 2x candidates first ), ), ).points

Beginners hit two failure modes. Forgetting the payload index means filters still apply but the graph gains no filter-aware structure, so selective queries fall back to slow paths. Enabling on_disk without quantization means every graph hop touches SSD. Quantized-in-RAM plus originals-on-disk is what works.

Operationally, one Qdrant node is easy. A cluster is not: sharding, replication factor and resharding during growth are real work, and the pager is yours.

06

Weaviate: Hybrid Search and Tenant Isolation

Weaviate's strengths are hybrid retrieval and multi-tenancy. BM25 and dense scoring fuse in a single call with a tunable alpha, and each tenant gets its own index that can be deactivated or offloaded to object storage when idle. Version 1.38 shipped in June 2026.

Hybrid is not a nice-to-have. Dense retrieval is weakest exactly where users are most precise: part numbers, statute citations, error codes, quoted names. A hybrid query recovers those cases without a second pipeline. Weaviate will also run a vectorizer inside the database, so text goes in and embedding happens server-side.

weaviate python client v4 โ€” self-provided vectors, ACORN, hybrid query
import weaviate from weaviate.classes.config import ( Configure, Property, DataType, VectorFilterStrategy ) from weaviate.classes.query import MetadataQuery client = weaviate.connect_to_local() client.collections.create( "Chunk", properties=[ Property(name="body", data_type=DataType.TEXT), Property(name="doc_type", data_type=DataType.TEXT), ], vector_config=[ Configure.Vectors.self_provided( name="content", vector_index_config=Configure.VectorIndex.hnsw( filter_strategy=VectorFilterStrategy.ACORN, quantizer=Configure.VectorIndex.Quantizer.sq(), ), ) ], multi_tenancy_config=Configure.multi_tenancy( enabled=True, auto_tenant_creation=True ), ) chunks = client.collections.get("Chunk").with_tenant("acme") res = chunks.query.hybrid( query="retention schedule for contract files", vector=query_vector, target_vector="content", alpha=0.6, # 1.0 = pure vector, 0.0 = pure BM25 limit=10, return_metadata=MetadataQuery(score=True), )

The cost is complexity. Collections, named vectors, tenants, modules and replication settings are more surface area than the alternatives, and if you only need "store vectors, search vectors" you pay for it in onboarding time. Multi-tenancy also multiplies memory: a thousand active tenants means a thousand HNSW graphs, and idle tenants must actually be deactivated for offloading to help.

07

Pinecone: Paying to Never Think About It

Pinecone is managed only; there is no self-hosted build. Create a serverless index, upsert into namespaces, query. Nothing about the index is exposed: no m, no ef, no quantization setting. In exchange you never size a node, never run an upgrade, and pay nothing for an idle index.

pinecone serverless โ€” create, upsert, filtered query
import os from pinecone import Pinecone, ServerlessSpec pc = Pinecone(api_key=os.environ["PINECONE_API_KEY"]) pc.create_index( name="chunks", dimension=1536, metric="cosine", spec=ServerlessSpec(cloud="aws", region="us-east-1"), ) index = pc.Index("chunks") # namespaces are the tenancy boundary; queries never cross them index.upsert( vectors=[ {"id": "c-1", "values": vec, "metadata": {"doc_type": "policy", "year": 2026}}, ], namespace="acme", ) res = index.query( vector=query_vector, top_k=10, namespace="acme", filter={"doc_type": {"$eq": "policy"}, "year": {"$gte": 2024}}, include_metadata=True, )

Two honest cautions. Read-unit billing scales with namespace size, so one large namespace makes every query more expensive than the same data split across many small ones, and reshaping the layout later means a full re-upsert. And because the index is opaque, fixing a recall problem means changing your embeddings, your chunking or your top_k, because the index is not yours to change.

08

Scale Limits: What Breaks and at What Number

The limit is not vector count, it is bytes resident in memory. A 1,536-dimension float32 vector is 6,144 bytes. One million is about 6 GB. Five million is about 30 GB, plus roughly 10 to 30 percent for HNSW graph links. Once that exceeds RAM plus page cache, every graph hop becomes a random disk read and p99 latency moves from single-digit milliseconds to hundreds.

Memory Budget for 5M Vectors at 1,536 Dimensions

Practical thresholds, assuming finite RAM. Below one million vectors everything works and the choice is about operations. From one to ten million, pgvector holds up on a properly sized instance, and most applications live here. From ten to fifty million, quantization is mandatory and dedicated systems earn their keep. Above fifty million you are running a distributed system whether you wanted one or not. One more number: rebuilding an HNSW index over tens of millions of vectors takes hours, so never assume a re-embed and reindex fits inside a deploy.

09

What It Actually Costs

Compare total cost of ownership, not per-vector list prices. Managed pricing is easy to forecast. Self-hosted pricing looks cheaper on the invoice and hides the engineer-hours, the on-call rotation and the upgrade weekends. The published models, as of mid-2026:

The honest framing: at small scale, managed services are cheap enough that self-hosting to save money is a bad trade. At large scale, self-hosted Qdrant or Weaviate is the lower bill and the gap widens with data. The crossover is not a vector count. It is the moment you have someone whose job includes running databases.

10

Operational Burden, Honestly

Ask five questions of any candidate: how do I back it up, how do I restore it, how do I upgrade without downtime, what happens when a node dies, and who gets paged. pgvector answers all five with "the same way you already do for Postgres," which is worth more than a benchmark chart.

01

Backup and restore

Snapshot APIs exist; scheduling, storage and restore testing are yours. A snapshot you have never restored is a hope, not a backup.

Test a restore before launch
02

Upgrades

Minor versions ship often. Weaviate supports the latest three, so falling behind forces a multi-hop upgrade.

Budget a monthly patch window
03

Reindexing

Changing embedding model, dimension or metric means a rebuild. Keep raw text and chunk boundaries durable so re-embedding needs no re-crawl.

Never treat vectors as the source of truth
04

Observability

Track recall, not just latency. A degrading index still answers fast. Keep fixed query-and-expected-document pairs and run them every deploy.

Alert on recall regressions
11

When Not to Use a Vector Database at All

Below roughly 50,000 vectors, a NumPy matrix multiply gives exact results in a few milliseconds with no server, no index and no tuning. Approximate search exists to trade accuracy for speed. If exact search is already fast enough, you are trading accuracy for nothing.

exact search that beats an index at small scale
import numpy as np # vectors: (n, d) float32, L2-normalised at write time def search(query, vectors, ids, k=10): q = query / np.linalg.norm(query) scores = vectors @ q # cosine, since rows are unit norm top = np.argpartition(-scores, k)[:k] top = top[np.argsort(-scores[top])] return [(ids[i], float(scores[i])) for i in top] # 50k x 1536 float32 = ~300 MB, ~5 ms per query on a laptop CPU

Three more cases where dense retrieval is the wrong tool. When users search with exact identifiers, quoted phrases or product codes, keyword search wins and Postgres full-text search is already installed. When the corpus fits in a model's context window, retrieval may be unnecessary. And when the real problem is chunking or ranking quality, swapping databases fixes nothing. Our RAG tutorial in Python walks that pipeline, and RAG explained covers where quality comes from.

12

Migration and Lock-In

Vectors are portable: an array of floats with an id and a metadata blob moves anywhere. Everything around them does not. Filter syntax, hybrid scoring, tenancy and consistency guarantees all differ, so a migration rewrites query code and re-tunes recall even when the data copies cleanly.

Two habits keep the door open. Keep source text, chunk boundaries and embedding model version in your own durable storage, so any store can be rebuilt. And put a thin interface in front of the store: upsert, delete, search, search_with_filter. LangChain and LlamaIndex provide that abstraction but expose only the intersection of features, so hand-writing it is often better once you need per-store tuning. A small FastAPI retrieval endpoint means swapping engines changes one deployment, not forty call sites.

The bottom line: start with pgvector on the Postgres you already run, and instrument recall and p99 latency from week one. Move to Qdrant when filtered throughput is the bottleneck and you have someone to operate it, to Weaviate when hybrid relevance and tenant isolation are product requirements, and to Pinecone when engineer-hours cost more than the invoice. Choosing a store before you have a measured bottleneck is how teams end up running two databases to serve one feature.

Frequently Asked Questions

Is pgvector good enough for production, or do I need a dedicated vector database?

Under roughly 5 to 10 million vectors, pgvector on a Postgres you already operate is good enough and often better: one backup story, one set of credentials, and vector results joined to relational rows in a single query. Move to a dedicated store when throughput under selective filters stalls, when the index outgrows the memory you will pay for, or when you need something Postgres lacks such as multi-tenant index isolation.

Which vector database handles metadata filtering best?

Qdrant and Weaviate, because both build filtering into graph traversal instead of applying it before or after the search. Qdrant adds HNSW links for indexed payload fields so the graph stays connected inside a filtered subset; Weaviate's ACORN strategy lets traversal step through non-matching nodes. pgvector 0.8 closed much of the gap with iterative index scans, and Pinecone filters inside its own search path with no tuning knobs exposed.

How much memory does an HNSW index need?

A 1,536-dimension float32 vector is 6,144 bytes, so one million is about 6 GB before any index exists, and HNSW links add roughly 10 to 30 percent depending on m. Five million land near 35 GB resident. Exceed RAM plus page cache and latency shifts from single-digit milliseconds to hundreds. Half-precision halves it, int8 cuts it fourfold, binary up to thirty-twofold with a rescoring pass.

Is Pinecone worth the cost compared to self-hosting Qdrant?

It depends what an engineer-hour costs you. Pinecone serverless list pricing in mid-2026 is $0.33 per GB-month plus per-million read and write units against a $50 monthly minimum on Standard, and you never touch a node. Self-hosted Qdrant is cheaper per stored vector at scale, but you own upgrades, backups, replica placement, disk growth and the pager.

Can I use more than one of these at the same time?

Yes. Keep authoritative chunks and embeddings in Postgres with pgvector, and mirror a hot subset into a dedicated store for high-throughput serving. Postgres stays the source of truth so rebuilds stay possible and the fast store stays disposable. The cost is the dual-write problem, so take it on only after measuring a real bottleneck.

Sources: pgvector on GitHub, Qdrant documentation, Weaviate release notes, Pinecone cost documentation

Explore More Guides

The Bottom Line
Start with the database you already run. Measure recall and p99 latency from week one. Move only when a number you can point to says you have to.
PA
Our Take

The vector database category is converging, and Postgres is quietly absorbing it.

In 2023 the four systems in this guide were genuinely different products. Three years later they have converged on the same core: an HNSW graph, quantization to fit memory, filter-aware traversal, hybrid keyword scoring and some tenancy story. The remaining differences are real but narrow, and they show up under load rather than in a getting-started tutorial. That convergence is why the choice increasingly comes down to operations rather than retrieval quality.

Meanwhile pgvector kept closing the gap on the two things that used to disqualify it. Iterative index scans in 0.8 fixed the overfiltering problem that made filtered queries unreliable, and half-precision plus binary quantization pushed the memory ceiling out far enough that a single well-sized Postgres instance covers the majority of real workloads. Postgres has absorbed JSON, full-text search and time-series workloads the same way. Vector search looks like the next one.

The teams we see struggling are rarely struggling with the database. They are struggling with chunk boundaries that split a table in half, an embedding model mismatched to their domain, and no evaluation set to tell them retrieval got worse. Those problems follow you across every migration. Fix retrieval quality first, then pick infrastructure for the load you have measured.

PA

About the Publisher

Precision AI Academy

Practitioner-focused AI education ยท tech news, guides, and 145 free courses

Precision AI Academy publishes in-depth guides on applied AI engineering for working professionals. Founded by Bo Peng, Kaggle Top 200 data scientist and former university instructor.

Kaggle Top 200 Federal AI Practitioner 5 U.S. Cities Thu–Fri Cohorts