The best open source vector database depends on where your application data already lives. Start with pgvector when PostgreSQL owns the records. Choose Qdrant for a dedicated vector search service with metadata filtering, hybrid retrieval, and self-hosting requirements. Consider Milvus for larger distributed workloads where search and application storage need to scale separately, and Weaviate when integrated vector and keyword search matter more than keeping the system minimal.

I also loaded the same 10,000 random vectors into three open source vector databases, Qdrant, pgvector, and Chroma, on an 8 GB M1 MacBook Air. The largest differences came from default settings, not from the engines: at defaults Qdrant never built an index on a collection that small and returned exact results with recall@10 of 1, while pgvector's default HNSW index returned 0.372. A quick benchmark that skips matching the settings measures configuration.

No single engine wins every RAG workload. Choose from the matrix using your data model, filters, growth, updates, latency target, recovery process, and tolerance for another production system, then run the comparison protocol below on your own data before moving anything important.

What "open source vector databases" means here

For this guide, "open source" means the core project is released under a named OSI-approved license and has a self-hosted distribution. A public repository alone is not enough. The license names in the table apply to the core projects; hosted services, plugins, and commercial add-ons may have separate terms. Running a self-hosted vector database still requires compute, storage, backups, monitoring, upgrades, and on-call time.

The word "free" needs its own check. A project can have no software fee while still creating infrastructure and operator costs. Treat license permissions, hosted-service terms, and total operating effort as separate questions.

DatabaseCore project licenseBest fitFiltering and hybrid searchDeployment modelPostgreSQL relationshipMain operational tradeoff
QdrantApache-2.0Filtered and hybrid retrievalPayload and boolean filters; dense and sparse Query API fusionDocker, Kubernetes, or managed CloudSeparate service; sync IDs and metadataSelf-hosting means owning storage, HA, backups, and monitoring.
MilvusApache-2.0Distributed vector workloadsScalar filters, hybrid retrieval, and multiple index familiesLite, Standalone, or Distributed KubernetesSeparate system; joins stay elsewhereMore components and deployment planning as it grows.
WeaviateBSD 3-ClauseIntegrated vector and keyword searchStructured filters plus vector and BM25 hybrid searchHosted or self-managedSeparate store from PostgreSQLMore configuration than a minimal service.
pgvectorPostgreSQL LicensePostgreSQL applications with vector searchSQL predicates, HNSW or IVFFlat, and full-text hybrid searchPostgreSQL extension, managed or self-hostedNative joins, transactions, and recoveryANN indexes share resources and need maintenance.

The license column comes from each project's repository: Qdrant, Milvus, Weaviate, and pgvector. The other columns come from each project's documentation.

Decision criteria and the practical decision tree

Choose by workload shape, and answer these questions before picking an engine:

  1. Start with PostgreSQL. Joins, authorization, transactions, and point-in-time recovery favor pgvector when PostgreSQL already owns the records.
  2. Describe filters before embeddings. Tenant, ACL, language, product, time, and document type affect query behavior.
  3. Decide whether hybrid retrieval is required. Exact names, error codes, and product terms need lexical matching; check score fusion.
  4. Estimate collection size and growth. Fast growth may justify separate storage and query workers, which Milvus documents as part of its architecture.
  5. Model updates and deletes. Measure freshness and background index work, especially for filtered approximate search.
  6. Set latency and recall targets together. Measure p50 and p95 against exact or judged relevance, not an index label.
  7. Include backup, restore, upgrades, and monitoring. Qdrant's self-managed path makes these responsibilities explicit.
  8. Estimate migration risk. Compare dimensions, distances, IDs, metadata, filters, hybrid scoring, export, and rollback steps.

Practical decision tree

  • Choose pgvector when PostgreSQL already owns the data and you want one SQL and recovery path.
  • Choose Qdrant when retrieval needs a focused self-managed service with payload filters and dense plus sparse queries.
  • Choose Milvus when growth justifies separated components, multiple indexes, and Kubernetes-based operation.
  • Choose Weaviate when an integrated vector and keyword platform is worth extra configuration.

Qdrant: dedicated filtered and hybrid retrieval

Qdrant is a good starting point when search should sit beside the application database. Its payload model supports field, boolean, and nested filters. The Query API can prefetch named vectors and fuse dense and sparse retrieval. That matches RAG systems with tenant filters and exact-term recovery.

The tradeoff is another stateful service. Qdrant's self-managed guidance calls out storage, security, high availability, backups, disaster recovery, monitoring, and logging. It is a poor fit when transactionally consistent joins matter or the team cannot operate a second datastore.

Indexing is not automatic for small collections. In my test, Qdrant 1.19.2 at its defaults split 10,000 vectors into 4 segments, each under the default indexing threshold of 10,000 KB, so it built no HNSW graph at all and answered every query with an exact scan.

Before adoption, check payload indexes, dense and sparse fusion, snapshot and restore, version upgrades, and which system owns changed metadata.

Milvus: distributed vector search at larger scale

Milvus is aimed at teams that want vector search separate from the application database. Its architecture separates access, coordinator, worker, and storage responsibilities, with object storage and a write-ahead log. Deployments range from Lite and Standalone to Distributed Kubernetes. The docs cover HNSW, IVF, FLAT, SCANN, DiskANN, scalar filters, and sparse or full-text hybrid retrieval.

That flexibility has a cost. A small RAG application with existing PostgreSQL may gain little from the extra components. Milvus is a poor fit when the team cannot explain ownership of ingestion, object storage, message flow, and recovery.

Before adoption, pin the deployment shape, storage and message dependencies, index settings, compaction plan, failure behavior, and clean restore test.

Weaviate: an integrated search platform

Weaviate fits a team that wants more of search in one product. Its filters combine property conditions with boolean operators. Hybrid search runs vector and BM25 retrieval together, then fuses scores through relative-score or ranked fusion. That suits RAG applications needing semantic recall, exact terms, and structured filters in one search layer.

Weaviate can run hosted or self-managed. It is a poor fit when you only need a thin vector endpoint, when PostgreSQL must remain the relational source of truth, or when the team does not want to own schema, persistence, replica, and version decisions. The platform can save integration work, but it still needs operating discipline.

Before adoption, settle vectorizer ownership, filter schema, fusion setting, replica and persistence plan, and upgrades. Test empty filters, highly selective filters, lexical terms, and mixed-language records.

pgvector: vector search inside PostgreSQL

pgvector is the natural first test when searchable data already lives in PostgreSQL. It supports exact nearest-neighbor search plus HNSW and IVFFlat approximate indexes. SQL can apply predicates, joins, and authorization rules. The README also documents full-text hybrid patterns, replicas, point-in-time recovery, and PostgreSQL monitoring.

The cost is shared resource contention and maintenance. Approximate indexes can use more memory or take longer to build, and predicates may be applied after a scan. pgvector documents iterative scans, partial indexes, and partitioning for filtered workloads. It is a poor fit when vector-only traffic needs independent scaling or would compromise the application database.

The defaults favor speed over recall. pgvector 0.8.7 on PostgreSQL 17.11 reported a default ef_search of 40, and on my random-vector test that default HNSW index returned recall@10 of 0.372, against 1 for the same queries run as a sequential scan before the index existed.

Before adoption, choose exact, HNSW, or IVFFlat from measured recall and latency; test update churn, vacuum, reindex, and restore from PostgreSQL's normal backup path. The question is whether one database can meet both transaction and retrieval SLOs.

How to compare them fairly

Do not compare tuned Qdrant with default pgvector settings and call it a product verdict. Freeze retrieval variables, then report operational cost beside query numbers. The 2026 independent evaluation used multiple datasets and metrics for this reason. To see how much the settings matter on their own, I put three engines through a small version of that discipline on one laptop.

What a small same-data test showed

The dataset was 10,000 uniform random 384-dimension vectors from a fixed numpy seed (42), normalized, with cosine distance. Each engine answered the same 100 top-10 queries (seed 7) after 10 warm-up queries, and recall@10 is the share of the exact top 10, computed by brute force in numpy, that the engine returned. Each engine ran alone in Docker, once at its own defaults and once at matched HNSW settings: m 16, ef_construction 100, and ef_search 64. Chroma is not covered above; it is here as a third reference point. Latency is the Python client round trip through Docker Desktop's port forward, so it includes client and network overhead.

EngineSettingsInsertIndex buildp50p95Recall@10Peak RAM
Qdrant 1.19.2Defaults1.58 sNone built, exact scan4.52 ms5.52 ms1520.4 MiB
Qdrant 1.19.2Matched1.73 s4.15 s4.24 ms4.76 ms0.754520.4 MiB
pgvector 0.8.7No index, sequential scan0.16 sNone4.04 ms5.12 ms1158.4 MiB
pgvector 0.8.7Defaults0.16 s2.89 s1.07 ms1.51 ms0.372158.4 MiB
pgvector 0.8.7Matched0.27 s3.48 s1.34 ms1.79 ms0.479158.4 MiB
Chroma 1.0.0Defaults6.86 sBuilt during insert4.56 ms10.45 ms0.53385 MiB
Chroma 1.0.0Matched6.27 sBuilt during insert2.95 ms4.82 ms0.42185 MiB

Peak RAM is the highest docker stats reading across both configurations of an engine. With both collections still loaded after the runs, the containers sat at 329.4 MiB for Qdrant, 100.5 MiB for pgvector, and 84.8 MiB for Chroma. Empty, before any data, they used 231.9 MiB, 22.8 MiB, and 11 MiB.

Three things in that table are about configuration rather than engine quality:

  • Qdrant's perfect default recall is not a better index. At defaults, the optimizer went idle with 0 vectors indexed, because each of the 4 segments was below the 10,000 KB indexing threshold. Every query was an exact scan, which is why its latency sits next to pgvector's sequential scan (4.52 ms against 4.04 ms at p50). The matched run lowered the indexing threshold to 1000 KB and the full-scan threshold to 10 KB, the minimum Qdrant accepts, and only then were all 10,000 vectors indexed, 4.15 s after the last upsert.
  • pgvector's default search width is narrow. Its default ef_search is 40, which returned 0.372 recall@10 at 1.07 ms. Matching ef_search to 64 and ef_construction to 100 raised recall to 0.479 for a p50 of 1.34 ms. Neither number is wrong; they are two points on the same recall and latency curve.
  • The insert column compares different write paths. pgvector loaded with a single binary COPY and built the index afterwards, Qdrant took batched upserts and indexed in the background, and Chroma built its HNSW graph during insert. The 0.16 s against 6.86 s gap mostly reflects those paths.

Chroma's own defaults scored higher recall (0.533) than the matched settings (0.421) and were slower at p50, which suggests its default search width is wider than 64. The run did not record Chroma's default values, so treat that as my reading rather than a measured setting.

The recall figures themselves need a warning. Uniform random vectors have no cluster structure, which is close to the hardest case for HNSW, and real embeddings usually score much higher at the same settings. At the same ef_search of 64, Qdrant reached 0.754 while pgvector reached 0.479 and Chroma 0.421, but Qdrant searched 4 separate segment graphs and merged the results, and I think that explains much of the gap. This test does not rank the engines for production retrieval.

The lesson is about method. A naive run at defaults would have shown Qdrant with perfect recall, pgvector at 0.372, and Chroma as the slowest loader, and each of those results comes from a threshold, a search width, or a write path that a single setting changes. Before trusting any comparison, match the index parameters, confirm from the engine that the index exists (Qdrant's indexed_vectors_count, pgvector's EXPLAIN plan), and use your own embeddings and queries.

A protocol for your own comparison

Use this reproducible protocol:

  1. Use the same embedding model and version, vector dimension, distance function, and normalization rule.
  2. Use the same dataset, IDs, metadata, language mix, and chunking rules. Keep the chunk manifest.
  3. Use the same query set, top-k value, query order, and metadata filters, including broad and selective filters.
  4. Record index goals, engine parameters, build time, ingest throughput, and insert or delete visibility. Do not hide tuning behind "optimized."
  5. Run cold and warm query passes. Record p50 and p95 latency, timeouts, concurrency, and throughput.
  6. Measure recall against exact results where possible. Add judged relevance or NDCG when exact recall is insufficient.
  7. Record peak memory, steady-state memory, index size, raw storage, backup size, machine, versions, and replicas.

Finally, restore a backup on a clean target. Verify counts, filters, top-k results, restore time, and acceptable recovery point. If two options remain close after that, compare restore behavior and operator effort, not just query latency.

Is an open-source vector database always necessary?

No. If PostgreSQL owns the records, PostgreSQL full-text search plus pgvector may cover semantic retrieval, exact terms, relational filters, and transactions with one recovery path. That is often the lowest-risk design for a smaller application if the combined workload meets its latency and availability targets.

An existing search system may be enough if it supports vector fields, metadata filters, lexical search, replicas, and the team's backup process. MongoDB is one example: it is primarily a document database, and its vectorSearch operator searches embeddings and supports pre-filtering. That makes it a valid choice for document-centric applications, even though it is not a dedicated vector database.

Add a dedicated engine only for a measured scaling, filter, recall, or isolation problem: vector retrieval that needs independent scaling, specialized filtering or hybrid search, or an operating model separate from the transactional database. If you are building RAG, first ask whether the current database meets the retrieval contract, then compare a dedicated engine with the same dataset and queries. A tool with no software fee is not automatically cheaper than the database your team already monitors.

How I tested this

  • Machine: a MacBook Air with an Apple M1 and 8 GB of unified memory, with desktop apps open and more than 12 GB of swap already in use before the runs, so memory pressure was real. Docker Desktop ran a Linux VM with 8 vCPUs and just under 4 GiB of memory. One engine ran at a time, and its container, volume, and image were removed before the next.
  • Date: October 6, 2026.
  • Versions: Qdrant 1.19.2 with qdrant-client 1.19.1; pgvector 0.8.7 (on the PostgreSQL release named in the pgvector section) with psycopg 3.3.6 and the pgvector Python package 0.5.0; Chroma server 1.0.0 with chromadb-client 1.5.9; numpy 2.5.3. Each image was pulled from Docker Hub the same day.
  • What was run: the dataset, queries, and two configurations described in the comparison section, with exact numpy brute force as ground truth. pgvector's HNSW index was created after the load and timed separately; Qdrant's index time ran until indexed_vectors_count covered every vector; Chroma has no separate index step. Memory came from docker stats with the container empty, sampled during the run, and at rest afterwards.
  • Not tested: Milvus and Weaviate, though both are covered above, and LanceDB; filters, hybrid or sparse search, concurrency, larger collections, real embeddings, cold-cache latency, persistence, backup and restore, upgrades, and replication; and any tuning beyond the two configurations. The results describe one small synthetic collection on one 8 GB Mac.
  • Raw record: every metric, the capture scripts, and a README are in the public results file and the folder beside it.

References