Milvus vs Elasticsearch and OpenSearch for vector search

Milvus vs Elasticsearch is a question of what each system was built for: Milvus is an open-source database built for vector search, while Elasticsearch and OpenSearch are Lucene search engines that added vector fields. All three handle BM25 and hybrid search, so the real differences show up at scale: throughput, latency, index choice and cost. Choose Milvus when vector search is the core workload (RAG, semantic search, agents); keep Elasticsearch or OpenSearch when logs and analytics come first.

  • Apache License 2.0
  • 40K+ GitHub stars
  • Built-in BM25 full-text search
  • HNSW, IVF, RaBitQ, DiskANN and GPU indexes
  • VTS migrates from Elasticsearch
Milvus runs its own vector indexes on separated storage and compute; Elasticsearch and OpenSearch build an HNSW graph inside every Lucene segment of every shard.
Milvus runs its own vector indexes on separated storage and compute; Elasticsearch and OpenSearch build an HNSW graph inside every Lucene segment of every shard.
Milvus and Zilliz Cloud adopters
  • Salesforce
  • Walmart
  • Reddit
  • NVIDIA
  • eBay
  • Shell
  • Roblox
  • IBM

Milvus vs Elasticsearch performance: which is faster for vector search?

Elasticsearch vs Milvus on the VectorDBBench leaderboard: Zilliz Cloud and self-hosted Milvus serve several times the QPS of OpenSearch and Elastic Cloud at a fraction of the P99 latency, for plain vector search and for filtered search. Elasticsearch vector search performance improves with force_merge, which is why the labels below carry it.

Vector search throughput (QPS, higher is better)Cohere 1M, 768 dimensions
  • Zilliz CloudZillizCloud-8cu-perf13,316.23 QPSP99 2 ms · recall 0.9383
  • MilvusMilvus-16c64g-sq4u-fp16-force_merge9,575.69 QPSP99 2.3 ms · recall 0.9189
  • OpenSearchOpenSearch-16c128g-force_merge3,055.01 QPSP99 7.2 ms · recall 0.9066
  • Elastic CloudElasticCloud-8c60g-force_merge2,353.89 QPSP99 17.1 ms · recall 0.9143

Source: VectorDBBench, configuration labels as published. Results depend on configuration; benchmark on your own data.

Filtered search throughput (QPS, filter keeps 1% of data)Cohere 1M, 768 dimensions, filter ratio 0.99
  • Zilliz CloudZillizCloud-8cu-perf-partition_key10,020.53 QPSP99 2.6 ms · recall 0.9788
  • MilvusMilvus-16c64g-sq8-partition_key11,280.08 QPSP99 1.6 ms · recall 0.9507
  • OpenSearchOpenSearch-16c128g-routing-64shard-force_merge3,064.63 QPSP99 6.5 ms · recall 0.9507
  • Elastic CloudElasticCloud-8c60g-routing-64shard2,789.72 QPSP99 8.2 ms · recall 0.9538

VectorDBBench leaderboard (zilliz.com/vdbbench-leaderboard), labels as published. Results depend on configuration; run VectorDBBench on your own data.

Zilliz Cloud is fully managed Milvus: the same engine and top-tier throughput, with no clusters to size, upgrade or scale.

Try Zilliz Cloud free

Milvus vs Elasticsearch vs OpenSearch at a glance: license, community, clients and limits

What is the main difference between Elasticsearch and Milvus for vector search? Elasticsearch and OpenSearch build an HNSW graph inside every Lucene segment on JVM data nodes, and Elastic's tuning guidance is to keep most vector data in memory. Milvus loads segments onto query nodes from object storage and evicts cold data with tiered storage, so memory is sized for the hot set rather than the whole corpus.

Vector database vs Elasticsearch and OpenSearch: project facts per official docs and repositories
MilvusElasticsearchOpenSearch
LicenseApache-2.0; LF AI & Data graduate projectELv2, SSPL or AGPLv3Apache-2.0; OpenSearch Software Foundation (Linux Foundation)
GitHub stars40K+70K+10K+
Written inGo control plane, C++ query engineJava (Lucene)Java (Lucene)
Max vector dimensions32,7684,096 (dense_vector)16,000 (knn_vector)
Hybrid search in the free buildhybrid_search with RRFRanker or WeightedRankerRRF retriever listed under the Enterprise subscriptionSearch pipeline with normalization or RRF processors
Clients and frameworksPython, Java, Go, Node.js, C#, C++, Rust, REST; langchain-milvus, LlamaIndex, Haystack, DifyREST and official language clients; langchain-elasticsearchREST and official language clients
DeploymentLite (pip), Standalone (one container), Distributed (Kubernetes), Zilliz CloudSelf-managed or Elastic CloudSelf-managed or Amazon OpenSearch Service

Which vector index types do Milvus, Elasticsearch and OpenSearch support?

Elasticsearch builds HNSW or flat indexes per Lucene segment, with int8, int4 and BBQ quantization. OpenSearch adds Faiss IVF and an on-disk mode. Milvus ships a wider menu, including GPU_CAGRA, GPU_IVF_FLAT, GPU_IVF_PQ and GPU_BRUTE_FORCE, plus scalar indexes for filters, so you can trade memory, recall and latency per field.

Vector index types per official docs (Elasticsearch dense_vector, OpenSearch knn_vector, Milvus index-explained)
Index familyMilvusElasticsearchOpenSearch
Graph (HNSW)HNSW, HNSW_SQ, HNSW_PQ, HNSW_PRQhnsw, int8_hnsw, int4_hnsw, bbq_hnswhnsw on Faiss or Lucene engine
Cluster-based / quantizedIVF_FLAT, IVF_SQ8, IVF_PQ, IVF_RABITQ, SCANNflat, int8_flat, int4_flat, bbq_flatFaiss ivf; sq, pq and binary quantization
Disk-basedDISKANN; tiered storage from object storagebbq_disk (Enterprise subscription)on_disk mode with compression levels and rescoring
Sparse and BM25SPARSE_INVERTED_INDEX (BM25 or IP metric)sparse_vector (ELSER), Lucene BM25Neural sparse search, Lucene BM25
Binary vectorsBIN_FLAT, BIN_IVF_FLAT, MINHASH_LSHbit element type with hnsw or flatBinary quantization
Scalar and JSON filtersINVERTED, BITMAP, STL_SORT, Trie, NGRAM, RTREE, JSON path indexLucene field indexes set by mappingLucene field indexes set by mapping

What does a purpose-built vector database add beyond vector search?

Elasticsearch vector search vs Milvus is a question of what the system is built around. Elasticsearch is strong when documents already live in an index. Milvus treats the collection schema as the product: typed vectors, JSON and arrays, built-in BM25 and hybrid search in the open-source build, and an architecture that scales each node role on its own.

Milvus capabilities for vector and hybrid search workloads (milvus.io docs)
CapabilityIn Milvus
Multi-tenancyDatabase (64 by default), collection (65,536 by default), partition (1,024 per collection), or partition key for millions of tenants
JSON and dynamic fieldsJSON fields with JSON path indexes; a dynamic field stores undeclared keys
ARRAY fieldsTyped arrays, ARRAY_CONTAINS filters, BITMAP or INVERTED indexes
Multi-vector hybrid searchUp to 10 vector fields; hybrid_search fuses dense, sparse and BM25 with RRF or weighted rerankers
BM25 full-text searchA BM25 function with standard, english, chinese, ICU or custom analyzers; query with raw text
Keyword and phrase matchTEXT_MATCH and PHRASE_MATCH (with slop) on analyzed VARCHAR fields
Pattern filtersLIKE and RE2 regex (=~, !~), accelerated by an NGRAM index
Partial update and TTLupsert with partial_update=True; collection.ttl.seconds expires entities
Storage-compute separationQuery, streaming and data nodes over shared object storage, scaled independently
Tiered storageHot data loads on demand and cold data is evicted, so query nodes hold less in memory

How do Elasticsearch and OpenSearch fields map to a Milvus schema?

Most mappings translate one to one. Text fields become analyzed VARCHAR with a BM25 function, so keyword relevance survives the move. Milvus docs also map the query DSL: match to full-text search, term to == or TEXT_MATCH, prefix and wildcard to LIKE, knn to search, and RRF to hybrid_search.

Elasticsearch to Milvus field mapping for migration
Elasticsearch / OpenSearchMilvus
IndexCollection (inside a database)
_idVARCHAR or INT64 primary key; keep the same ID for dual-write
dense_vector / knn_vectorFLOAT_VECTOR (or FLOAT16, BFLOAT16, INT8) with the same dimension and metric
text with analyzerVARCHAR with enable_analyzer + BM25 function to SPARSE_FLOAT_VECTOR
keywordVARCHAR with INVERTED or BITMAP index; NGRAM for wildcard
Numeric and dateINT64 / DOUBLE with STL_SORT or INVERTED index
keyword arraysARRAY of VARCHAR
object, nested, flattenedJSON field with JSON path indexes, or the dynamic field
sparse_vectorSPARSE_FLOAT_VECTOR with IP metric
Custom routing per tenantPartition key

How do you migrate from Elasticsearch or OpenSearch to Milvus?

Move the retrieval path first and keep the old cluster serving until results match. VTS (Vector Transport Service) lists Elasticsearch as a source connector; for OpenSearch, export with the scroll API and load files with BulkWriter.

  • Export: VTS es_to_milvus, or scroll / point-in-time reads of _source.
  • Create the schema: map fields, add a BM25 function, index filter fields.
  • Backfill with BulkWriter and bulk_import; use insert for small sets.
  • Dual-write new documents to both systems from the same source.
  • Validate recall and relevance on a query set, then shift reads gradually.

Milvus or Elasticsearch: how to choose, and when Elasticsearch fits

A fair call: if search is a side feature of an existing Elastic stack, adding vectors there can be enough. Operations differ too. Elasticsearch means JVM heap and shard sizing; Milvus Standalone is one container with embedded etcd, and Distributed runs on Kubernetes with the Milvus Operator. Cost follows memory: Elasticsearch holds HNSW graphs on data nodes, Milvus can leave cold segments in object storage.

Decision checklist: elasticsearch and milvus by situation
Your situationPick
Vector search is the product: RAG, semantic search, agentsMilvus
Logs, observability or security analytics on Kibana or OpenSearch DashboardsElasticsearch or OpenSearch
Aggregation-heavy text analytics with vectors as a small add-onElasticsearch or OpenSearch
An existing ELK pipeline and a modest vector corpusElasticsearch or OpenSearch
Corpus growing past what fits in data-node memoryMilvus with tiered storage or DiskANN
Millions of tenants in one collectionMilvus partition key
BM25 plus vector fusion in an Apache-2.0 buildMilvus or OpenSearch
Both needs at onceElasticsearch for analytics, Milvus for retrieval, joined by document ID

How do you map an Elasticsearch index to a Milvus collection in Python?

This script reads every document from an Elasticsearch index and writes it into a Milvus collection with dense, BM25, keyword, array and JSON fields.

python
# pip install -U pymilvus elasticsearch
from elasticsearch import Elasticsearch, helpers
from pymilvus import MilvusClient, DataType, Function, FunctionType

es = Elasticsearch("http://localhost:9200")
client = MilvusClient(uri="http://localhost:19530")

# ES mapping: title(text) category(keyword) tags(keyword[]) attrs(object) embedding(dense_vector, 768)
schema = client.create_schema(enable_dynamic_field=True)
schema.add_field("id", DataType.VARCHAR, is_primary=True, max_length=64)  # ES _id
schema.add_field("title", DataType.VARCHAR, max_length=8192, enable_analyzer=True, enable_match=True)
schema.add_field("title_bm25", DataType.SPARSE_FLOAT_VECTOR)  # filled by the BM25 function
schema.add_field("category", DataType.VARCHAR, max_length=128)
schema.add_field("tags", DataType.ARRAY, element_type=DataType.VARCHAR, max_capacity=64, max_length=128)
schema.add_field("attrs", DataType.JSON, nullable=True)
schema.add_field("embedding", DataType.FLOAT_VECTOR, dim=768)
schema.add_function(Function(name="title_bm25_fn", function_type=FunctionType.BM25,
                             input_field_names=["title"], output_field_names=["title_bm25"]))

idx = client.prepare_index_params()
idx.add_index(field_name="embedding", index_type="HNSW", metric_type="COSINE",
              params={"M": 16, "efConstruction": 200})
idx.add_index(field_name="title_bm25", index_type="SPARSE_INVERTED_INDEX", metric_type="BM25")
idx.add_index(field_name="category", index_type="INVERTED")
idx.add_index(field_name="attrs", index_type="INVERTED", index_name="attrs_brand",
              params={"json_path": 'attrs["brand"]', "json_cast_type": "varchar"})
client.create_collection("docs", schema=schema, index_params=idx)

batch = []
for hit in helpers.scan(es, index="docs", query={"query": {"match_all": {}}}):
    src = hit["_source"]
    batch.append({"id": hit["_id"], "title": src["title"], "category": src["category"],
                  "tags": src.get("tags", []), "attrs": src.get("attrs"),
                  "embedding": src["embedding"]})
    if len(batch) == 1000:
        client.insert("docs", batch)
        batch = []
if batch:
    client.insert("docs", batch)

Match dim and metric_type to your dense_vector mapping. For large indexes, write files with BulkWriter and load them with bulk_import instead of insert. Code requires a running server and your own embedding model; it is an example, not a benchmark.

Frequently asked questions

Is Elasticsearch a vector database?

You can use Elasticsearch as a vector database, but it is a search engine with vector fields: dense_vector stores embeddings and the knn query searches HNSW or flat indexes per Lucene segment. Milvus is a database built around vector indexes, with BM25 and filtering added on the same collection.

How much faster is Milvus than Elasticsearch for vector search?

On the VectorDBBench Cohere 1M test, Milvus-16c64g-sq4u-fp16-force_merge lists 9,575.69 QPS at 0.9189 recall and ElasticCloud-8c60g-force_merge 2,353.89 QPS at 0.9143. Hardware differs by row; benchmark your own data.

Can Milvus replace Elasticsearch for BM25 full-text search?

For retrieval, yes: a BM25 function turns analyzed text into a sparse vector at insert time, and you query with raw text, alone or in hybrid search. Kibana dashboards and aggregation-heavy analytics stay with Elasticsearch.

How does hybrid search work in Elasticsearch vs Milvus?

Elasticsearch fuses BM25 and kNN with the reciprocal rank fusion (RRF) retriever, which Elastic lists under the Enterprise subscription. OpenSearch uses search-pipeline processors. Milvus runs hybrid_search with RRFRanker or WeightedRanker in the open-source build.

How do I migrate vector data from Elasticsearch to Milvus?

Map dense_vector to FLOAT_VECTOR with the same dimension and metric, then copy data with VTS or a scroll export plus BulkWriter. Dual-write during cutover and compare recall before moving reads.

Can I use Elasticsearch and a vector database together?

Yes, and it is common: keep Elasticsearch for logs and analytics, move vector and hybrid retrieval to Milvus, feed both from the same source, and join results by document ID.

Want the benchmarked results without running a cluster?

Zilliz Cloud is fully managed Milvus. On the VectorDBBench Cohere 1M test, ZillizCloud-8cu-perf lists 13,316.23 QPS at 0.9383 recall, with the same MilvusClient API as open-source Milvus.

Sources and technical documentation (51)