<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[Sachin Frayne - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Sachin Frayne - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/search-labs/author/sachin-frayne</link>
    </image>
    <link>https://www.elastic.co/search-labs/author/sachin-frayne</link>
    <atom:link href="https://www.elastic.co/search-labs/rss/author/sachin-frayne.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[en]]></language>
    <lastBuildDate>Sat, 12 Sep 2026 06:24:08 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Elasticsearch DiskBBQ delivers 7x faster vector search than Qdrant on network-attached storage]]></title>
    <description><![CDATA[Elasticsearch DiskBBQ achieves up to 7x higher vector search throughput than Qdrant at comparable recall on network-attached storage. Explore the benchmark methodology and full results.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch DiskBBQ delivers up to 7x higher throughput than Qdrant at comparable recall, tested on network-attached persistent storage, the topology most managed-cloud deployments actually use. The gap is consistent across recall levels from 0.93 to 0.97, and it widens as recall increases. DiskBBQ keeps latency nearly flat as search breadth grows; Qdrant's latency rises sharply as <code>hnsw_ef</code> increases, driven by random reads of original vectors from disk during rescoring. If you're running vector search in Kubernetes or a managed cloud environment, this is what the tradeoff looks like.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf417e30d37bbe73a/6a46976e151035764d202f02/057e4d34719f4ca87c86f1b2a36b06d0839ad275-800x500.png" alt="Bar chart comparing throughput in queries per second between Elasticsearch 9.4.1 and Qdrant 1.18.1 at recall levels 0.93, 0.95, 0.96 and 0.97, showing Elasticsearch delivering approximately 7x higher throughput across all recall levels." /><p>Vector search is a critical foundation for large language model (LLM) applications, retrieval augmented generation (RAG), and other AI workloads. In this benchmark, Elasticsearch achieved up to 7x higher throughput than Qdrant at comparable recall on the same storage topology. Elasticsearch as a vector database offers strong vector search performance even when network-attached persistent storage remains on the query path.</p><p>The difference reflects how the two systems interact with disk. Elasticsearch DiskBBQ is designed to keep vector search efficient when persistent storage remains on the query path, using a compact quantized representation and limiting costly access to full precision vectors during search. In this setup, Qdrant relies on a graph-based search path with rescoring against original vectors stored on disk. On network-attached persistent storage, that random access cost becomes much more significant, which is why the performance gap widens as recall increases. This benchmark therefore focuses specifically on network-attached persistent storage, a common deployment model in managed cloud and Kubernetes environments.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte01d96b1db40d0e9/6a46977131bdbb595d8b33e7/3a39012cb09a841468a5295e226955769c4d18f0-800x500.png" alt="Line chart showing recall versus average latency in milliseconds for Elasticsearch 9.4.1 and Qdrant 1.18.1. Elasticsearch maintains low latency between 120 and 150ms across all recall levels, while Qdrant latency rises steeply from 315ms to 900ms as recall increases." /><p>The key pattern in the latency curve is not only the size of the gap but also its shape. Elasticsearch latency remains comparatively flat as recall increases, suggesting that higher recall doesn’t require a dramatic increase in expensive storage activity. Qdrant’s latency rises sharply as <code>hnsw_ef</code> increases, which is consistent with broader candidate exploration leading to more rescoring work against original vectors on disk.</p><h2>Full results table</h2><p>The table below shows the full parameter sweep for both Elasticsearch and Qdrant. Because the two engines expose different tuning controls for vector search, the results are reported using each engine’s full parameter key rather than attempting a one-to-one mapping between settings.</p><p>A few notes on the metrics:</p><ul><li><p>ParamKey: The complete parameter setting used for a given run.</p></li><li><p>Recall: Recall@100 against a ground-truth top-100 result set for the benchmark queries. Values range from 0 to 1, and higher is better.</p></li><li><p>Latency_Avg: The average end-to-end latency per query measured from the benchmarking client across the full run, in milliseconds. Lower is better.</p></li><li><p>Latency_P95: The 95th percentile query latency, in milliseconds, showing the upper range of typical slow queries. Lower is better.</p></li><li><p>Throughput: The average number of queries processed per second across the full run. Higher is better.</p></li></ul><p>Engine</p><p>ParamKey</p><p>Recall</p><p>Latency_Avg</p><p>Latency_P95</p><p>Throughput</p><p>qdrant</p><p>hnsw_ef=50, oversampling=1, size=100</p><p>0.8694</p><p>315.7849</p><p>503.4754</p><p>12.629</p><p>elasticsearch</p><p>k=100, oversample=1, size=100, visit_percentage=1</p><p>0.8789</p><p>135.0802</p><p>218.494</p><p>29.343</p><p>elasticsearch</p><p>k=100, oversample=1, size=100, visit_percentage=1.5</p><p>0.9123</p><p>127.8286</p><p>195.2318</p><p>31.1107</p><p>qdrant</p><p>hnsw_ef=100, oversampling=1, size=100</p><p>0.9287</p><p>895.9933</p><p>1213.0448</p><p>4.4493</p><p>elasticsearch</p><p>k=100, oversample=1, size=100, visit_percentage=2</p><p>0.9317</p><p>124.846</p><p>183.6314</p><p>31.8225</p><p>elasticsearch</p><p>k=100, oversample=1, size=100, visit_percentage=2.5</p><p>0.9444</p><p>123.517</p><p>180.4831</p><p>32.1883</p><p>qdrant</p><p>hnsw_ef=150, oversampling=1, size=100</p><p>0.9518</p><p>884.7236</p><p>1195.2603</p><p>4.5066</p><p>elasticsearch</p><p>k=100, oversample=1, size=100, visit_percentage=3</p><p>0.9532</p><p>123.276</p><p>183.8379</p><p>32.2364</p><p>elasticsearch</p><p>k=100, oversample=1, size=100, visit_percentage=3.5</p><p>0.9599</p><p>122.5559</p><p>184.2858</p><p>32.4469</p><p>qdrant</p><p>hnsw_ef=200, oversampling=1, size=100</p><p>0.964</p><p>883.2114</p><p>1188.6597</p><p>4.5143</p><p>elasticsearch</p><p>k=100, oversample=1, size=100, visit_percentage=4</p><p>0.965</p><p>122.7946</p><p>184.9058</p><p>32.3635</p><p>elasticsearch</p><p>k=100, oversample=1, size=100, visit_percentage=4.5</p><p>0.9689</p><p>122.7062</p><p>182.9559</p><p>32.3976</p><p>elasticsearch</p><p>k=100, oversample=1, size=100, visit_percentage=5</p><p>0.9722</p><p>122.5761</p><p>187.3536</p><p>32.4221</p><p>qdrant</p><p>hnsw_ef=256, oversampling=1, size=100</p><p>0.9722</p><p>881.9643</p><p>1185.4948</p><p>4.5192</p><p>elasticsearch</p><p>k=100, oversample=1, size=100, visit_percentage=5.5</p><p>0.9747</p><p>122.5609</p><p>184.5128</p><p>32.4176</p><p>Each row pairs the closest measured Elasticsearch and Qdrant configurations in the sweep by achieved recall.</p><h3>Matched comparisons at similar recall</h3><p>To make the comparison fair, speedup is calculated only between configurations that achieve similar recall. This avoids comparing settings that trade off accuracy very differently.</p><p>Recall band</p><p>Elasticsearch recall</p><p>Elasticsearch Latency_Avg</p><p>Elasticsearch throughput</p><p>Qdrant recall</p><p>Qdrant Latency_Avg</p><p>Qdrant throughput</p><p>Throughput speedup</p><p>~0.87</p><p>0.8789</p><p>135.0802</p><p>29.343</p><p>0.8694</p><p>315.7849</p><p>12.629</p><p>2.32x</p><p>~0.93</p><p>0.9317</p><p>124.846</p><p>31.8225</p><p>0.9287</p><p>895.9933</p><p>4.4493</p><p>7.15x</p><p>~0.95</p><p>0.9532</p><p>123.276</p><p>32.2364</p><p>0.9518</p><p>884.7236</p><p>4.5066</p><p>7.15x</p><p>~0.96</p><p>0.9599</p><p>122.5559</p><p>32.4469</p><p>0.964</p><p>883.2114</p><p>4.5143</p><p>7.19x</p><p>~0.97</p><p>0.9722</p><p>122.5761</p><p>32.4221</p><p>0.9722</p><p>881.9643</p><p>4.5192</p><p>7.17x</p><p>This matched-recall view is the clearest expression of the underlying systems difference. At similar recall levels, Elasticsearch delivers both lower latency and much higher throughput, and the gap widens as recall rises. The recall-throughput pattern matters because higher recall in this benchmark requires broader search. DiskBBQ absorbs that increase with relatively little additional cost, while Qdrant’s graph plus rescoring path becomes much more constrained by random access to original vectors on persistent storage.</p><h2>Benchmark methodology</h2><p><a href="https://github.com/elastic/jingra">Jingra</a>, the benchmarking tool used for these tests, was originally written in Python and has since been rebuilt as a Java project. For these tests, Jingra runs in a Kubernetes pod within the same cluster as the engine being measured. This helps reduce external network variability and keeps the test environment consistent across runs. For each run, Jingra executed the query set at a fixed client concurrency, recorded end-to-end client-side latency and throughput, and computed recall against a precomputed ground-truth top-100 set.</p><p>This benchmark was intentionally run on network-attached persistent storage rather than local NVMe. For the published results, the storage used the baseline performance allocation for a 200 GiB GCP Hyperdisk Balanced volume, with no explicit IOPS or throughput provisioning. We chose this topology on purpose because it’s a relevant cloud deployment model and because it keeps storage efficiency materially on the query path.</p><p>Qdrant often performs better on local NVMe, so deployments using local NVMe should expect different results than the ones shown here. This benchmark specifically tests network-attached persistent storage because that’s a common managed-cloud deployment model and because it makes storage-path efficiency visible in end-to-end query performance.</p><p>Because Elasticsearch and Qdrant expose different query parameters for controlling vector search behavior, there’s no clean one-to-one mapping between their tuning settings. Instead of comparing equivalent parameter values directly, we use recall as the primary point of comparison. The matched comparisons below therefore pair configurations that achieve similar recall, rather than configurations with superficially similar parameter values.</p><p>Recall cannot be known in advance for a given parameter setting, so we sweep across a range of search configurations for each engine and then compare results at similar recall levels. In the published results, oversampling was fixed at 1 for both engines so that recall was primarily tuned via search breadth rather than rescoring expansion.</p><h3>How does Elasticsearch configure vector search?</h3>{
  "query": {
    "knn": {
      "field": "embedding",
      "query_vector": "{{query_vector}}",
      "k": "{{k}}",
      "visit_percentage": "{{visit_percentage}}",
      "rescore_vector": {
        "oversample": "{{oversample}}"
      }
    }
  },
  "size": "{{size}}",
  "_source": false
}<ul><li><p><code>query_vector</code>: The input vector used for similarity search. Elasticsearch compares this vector against the stored vectors in the field.</p></li><li><p><code>k</code>: The number of nearest neighbors to retrieve.</p></li><li><p><code>visit_percentage</code>: Controls how much of the DiskBBQ, Elasticsearch’s disk optimized vector index, is explored during the approximate search phase. Higher values usually improve recall but increase latency.</p></li><li><p><code>oversample</code>: Controls how many extra candidate vectors are passed into rescoring relative to k. Higher values can improve recall, but usually at additional cost.</p></li><li><p><code>size</code>: The number of hits returned in the final response.</p></li><li><p><code>_source: false</code>: Disables returning the document _source field, reducing response size and avoiding extra retrieval overhead during benchmarking.</p></li></ul><p>Example</p>{
  "query": {
    "knn": {
      "field": "embedding",
      "query_vector": [ -0.0095683, 0.0072035934, ... ],
      "k": "100",
      "visit_percentage": "3",
      "rescore_vector": {
        "oversample": "1"
      }
    }
  },
  "size": "100",
  "_source": false
}<p>Params</p>  recall@100:
    - { size: 100, k: 100, visit_percentage: 1, oversample: 1 }
    - { size: 100, k: 100, visit_percentage: 1.5, oversample: 1 }
    - { size: 100, k: 100, visit_percentage: 2, oversample: 1 }
    - { size: 100, k: 100, visit_percentage: 2.5, oversample: 1 }
    - { size: 100, k: 100, visit_percentage: 3, oversample: 1 }
    - { size: 100, k: 100, visit_percentage: 3.5, oversample: 1 }
    - { size: 100, k: 100, visit_percentage: 4, oversample: 1 }
    - { size: 100, k: 100, visit_percentage: 4.5, oversample: 1 }
    - { size: 100, k: 100, visit_percentage: 5, oversample: 1 }
    - { size: 100, k: 100, visit_percentage: 5.5, oversample: 1 }<p>We keep <code>k = size = 100</code> so the search request is aligned with the benchmark target: returning the top 100 results. To improve recall, we tune <code>visit_percentage</code> rather than inflating the final result count, while keeping <code>oversample = 1</code> fixed across runs.</p><h3>How does Qdrant configure vector search?</h3>{
  "vector": "{{query_vector}}",
  "limit": "{{size}}",
  "with_payload": false,
  "with_vector": false,
  "params": {
    "hnsw_ef": "{{hnsw_ef}}",
    "quantization": {
      "rescore": true,
      "oversampling": "{{oversampling}}"
    }
  }
}<ul><li><p><code>query_vector / vector</code>: The input vector used for similarity search. Qdrant compares this vector against the stored vectors in the collection.</p></li><li><p><code>size / limit</code>: The number of nearest neighbor results returned in the response.</p></li><li><p><code>with_payload: false</code>: Disables returning payload fields, reducing response size and avoiding additional retrieval overhead during benchmarking.</p></li><li><p><code>with_vector: false</code>: Disables returning stored vectors in the response, again reducing response size and keeping the benchmark focused on search performance.</p></li><li><p>hnsw_ef: Controls the number of candidates explored during HNSW search. Higher values usually improve recall but increase latency. Like visit_percentage in Elasticsearch, it affects search breadth, but the two controls are engine-specific and not directly equivalent.</p></li><li><p><code>quantization.rescore: true</code>: Enables rescoring of the candidate set using the original vectors after quantized search.</p></li><li><p><code>oversampling</code>: Controls how many extra candidates are considered during rescoring relative to the final result count. Higher values can improve recall, but usually at additional cost.</p></li></ul><p>Example</p>{
  "vector":  [ -0.0095683, 0.0072035934, ... ],
  "limit": "100",
  "with_payload": false,
  "with_vector": false,
  "params": {
    "hnsw_ef": "150",
    "quantization": {
      "rescore": true,
      "oversampling": "1"
    }
  }
}<p>Params</p>  recall@100:
    - { size: 100, hnsw_ef: 50, oversampling: 1 }
    - { size: 100, hnsw_ef: 100, oversampling: 1 }
    - { size: 100, hnsw_ef: 150, oversampling: 1 }
    - { size: 100, hnsw_ef: 200, oversampling: 1 }
    - { size: 100, hnsw_ef: 256, oversampling: 1 }<p>We keep <code>size = 100</code> so that each request is aligned with the evaluation target, in this case top 100 retrieval. Recall is then tuned by sweeping <code>hnsw_ef</code>, which controls how many candidates are explored during search. Higher <code>hnsw_ef</code> values generally improve recall but also increase latency and reduce throughput. We keep <code>oversampling = 1</code> fixed across runs so that the main tuning variable is the search breadth rather than the rescoring expansion.</p><h2>Cluster setup and DiskBBQ configuration</h2><p>We ran the benchmark on GCP using three n4-standard-8 nodes, with each pod allocated 7 vCPUs and 26 GB of RAM, and using 200 GiB GCP Hyperdisk Balanced volumes at baseline performance allocation. The corpus contains 21 million vectors, (see dataset section below for more details and download links), which account for about 60.1 GiB of raw float vector data. With 2-bit quantization, the vector payload drops to roughly 3.8 to 4.0 GB. However, the full index footprint is much larger once graph and other index structures are included. That means the workload remains meaningfully sensitive to network-attached storage performance, especially because exact vector values still need to be read from disk during rescoring.</p><p>We chose this node size intentionally to keep the benchmark in a regime where network-attached persistent storage remains on the query path rather than allowing the full working set to remain comfortably memory-resident. Each system was therefore configured using the best-performing setup we identified for this workload within the tuning scope described in this post. In Elasticsearch, this meant <code>bbq_disk</code>. In Qdrant, the original vectors were stored on disk, while the 2-bit quantized representation used for approximate search was kept in RAM with <code>always_ram: true</code>. Because the two systems expose different search strategies and tuning controls, we compare them at matched recall rather than trying to map parameters one to one.</p><p>Elasticsearch was configured to use DiskBBQ, its disk-optimized approach for approximate nearest neighbor vector search, with 2-bit quantization. DiskBBQ uses aggressive quantization to keep the searchable index compact and then rescores with the original vectors to preserve accuracy. This helps maintain strong recall while keeping disk-based search efficient.</p><p><code>bbq_disk</code> is an Elasticsearch Enterprise feature. We used it here because the goal of this benchmark was to compare the strongest disk-oriented vector search configuration available in each engine for this workload, rather than licensing tiers or default features.</p><p>We didn’t include <code>bbq_hnsw</code> in this comparison because the benchmark was specifically designed to evaluate disk-oriented vector search under a disk-sensitive workload.</p><p>This storage topology matters because Qdrant’s rescore step reads the original <code>float32</code> vectors from disk with random access on each query. On local NVMe, those reads are much faster, and Qdrant correspondingly performs better. On network-attached persistent storage, the results are consistent with that random-read rescore path becoming a more important bottleneck. Qdrant latency rises sharply as <code>hnsw_ef</code> increases, while Elasticsearch remains comparatively flat across the same recall progression.</p><p>We chose 2-bit quantization because Qdrant couldn’t reach the target recall range with 1-bit binary quantization. Since the two systems expose different disk-oriented vector search strategies, we tuned each one to the strongest configuration available within its current feature set.</p><p>Both systems were configured with three shards distributed across the three nodes and with two total copies of each shard in the cluster. In Elasticsearch, <code>number_of_shards: 3</code> and <code>number_of_replicas: 1</code> means one primary plus one replica, for two total copies. In Qdrant, <code>shard_number: 3</code> and <code>replication_factor: 2</code> also means two total copies, since Qdrant’s replication factor refers to the total number of copies rather than the number of additional replicas. So although the field names differ, the effective replication level was the same in both systems.</p><p>Setting</p><p>Elasticsearch</p><p>Qdrant</p><p>Shards</p><p>number_of_shards: 3</p><p>shard_number: 3</p><p>Copies</p><p>number_of_replicas: 1 (1 primary + 1 replica = 2 total)</p><p>replication_factor: 2 (2 total)</p><p>Elasticsearch mapping</p>{
  "mappings": {
    "properties": {
      "embedding": {
        "type": "dense_vector",
        "element_type": "float",
        "dims": 768,
        "index": true,
        "similarity": "cosine",
        "index_options": {
          "type": "bbq_disk",
          "bits": 2
        }
      }
    }
  },
  "settings": {
    "number_of_shards": "3",
    "number_of_replicas": "1"
  }
}<p>Qdrant mapping</p>{
  "vectors": {
    "size": 768,
    "distance": "Cosine",
    "on_disk": true
  },
  "shard_number": 3,
  "replication_factor": 2,
  "hnsw_config": {
    "m": 16,
    "ef_construct": 256
  },
  "quantization_config": {
    "turbo": {
      "bits": "bits2",
      "always_ram": true
    }
  }
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6ff77646baa598e4/6a4697742d406b1032ba2bd9/8d6f3e8d3e2c620187d8d2841cc09b813cd77e57-881x401.png" alt="Architecture diagram showing the benchmark cluster setup on GCP. Two Kubernetes clusters side by side: the left contains three Elasticsearch nodes behind an ES Service, with Jingra as the benchmarking client. The right mirrors this with three Qdrant nodes behind a QD Service, also driven by Jingra." /><h2>Dataset</h2><p>For this benchmark, we used the <a href="https://huggingface.co/datasets/kenhktsui/wiki_dpr_e5"><code>kenhktsui/wiki_dpr_e5</code></a> dataset from Hugging Face, a large-scale Wikipedia passage retrieval dataset designed for dense vector search. The corpus contains 21 million embedded passages, each represented as a 768-dimensional float32 vector, or 3,072 bytes per vector. That corresponds to about 60.1 GiB of raw vector data, before accounting for additional fields and file format overhead in the source dataset. The downloadable <code>data.parquet</code> file is larger at 85.2 GB for that reason.</p><p>We chose this dataset because it reflects a common production pattern in LLM, RAG, and retrieval systems: searching a large corpus of semantically embedded text while balancing recall, latency, and throughput. At 21 million vectors and roughly 60 GiB of raw vector data, it’s large enough to make disk-based vector search a relevant operating mode to evaluate.</p><p>Both engines used 2-bit quantization, reducing each vector from 3,072 bytes to 192 bytes, a 16x reduction that brings the quantized vector corpus to around 4 GB. In Qdrant, that quantized representation was kept in RAM for search, while the original vectors remained on disk. Even so, the workload remained meaningfully sensitive to network-attached storage performance because rescoring still required access to the original vectors on disk.</p><p>You can download the dataset and query files from the links below:</p><ul><li><p><a href="https://storage.googleapis.com/elastic-benchmark-datasets/wiki-dpr-e5-768/data.parquet">data.parquet</a></p></li><li><p><a href="https://storage.googleapis.com/elastic-benchmark-datasets/wiki-dpr-e5-768/queries.parquet">queries.parquet</a></p></li></ul><h2>Jingra and recreating the benchmark</h2><p>For this benchmark, we used <a href="https://github.com/elastic/jingra/releases/tag/v0.2.3">Jingra v0.2.3</a> with the configurations described <a href="https://github.com/elastic/competitive-benchmarking-studies/tree/main/es-9.4-vs-qd-1.18-vector-search">es-9.4-vs-qd-1.18-vector-search</a>. Jingra handled data loading, query execution, parameter sweeps, and metric collection for both Elasticsearch and Qdrant, making the benchmark repeatable and easier to compare.</p><p>To reproduce the experiment, you need the published dataset, query set, engine configurations, and comparable cluster hardware. With those in place, Jingra can rerun the benchmark and generate similar recall, latency, and throughput measurements shown in this post.</p><h2>Conclusion</h2><p>At comparable recall levels, Elasticsearch DiskBBQ consistently delivered faster vector search than Qdrant in this benchmark, with higher throughput and lower latency across the recall range we tested. These results are especially notable because the comparison was made on network-attached persistent storage, where efficient storage-aware vector search becomes critical. Elasticsearch as a vector database allows organizations to achieve high recall with lower latency and higher throughput on slower persistent storage.</p><p>Just as importantly, this benchmark highlights the value of comparing engines at matched recall rather than by nominal parameter settings. Elasticsearch and Qdrant expose different controls, so the fairest comparison isn’t parameter to parameter but outcome to outcome. Across the recall range tested here, Elasticsearch maintained a clear advantage in both latency and throughput.</p><p>If you want to reproduce the experiment yourself, we’re publishing the dataset and query set used in this benchmark so others can validate the results and build on them.</p><p>Further reading:</p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction">Introducing a new vector storage format: DiskBBQ</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-bbq-osq-vs-turbo">Elasticsearch BBQ vs TurboQuant</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/vector-search-benchmark-elasticsearch-vs-qdrant</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/vector-search-benchmark-elasticsearch-vs-qdrant</guid>
    <category><![CDATA[Vector Database]]></category>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[ML Research]]></category>
    <dc:creator><![CDATA[Sachin Frayne]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf58ffc7bd7f3c826/6a469777945073eeed30d267/0fa30e54796aeb49baaa760590fa6dd3ee863c2d-1280x720.png" length="0" type="image/png"/>
    <pubDate>Wed, 24 Jun 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch vector search is up to 8x faster than OpenSearch]]></title>
    <description><![CDATA[Exploring filtered vector search benchmarks of OpenSearch vs. Elasticsearch and why vector search performance is critical for context-engineered systems.]]></description>
    <content:encoded><![CDATA[<h2>Why search speed matters for AI agents and context engineering</h2><p>Our benchmarks on a 20M document corpus show that Elasticsearch delivers up to 8x higher throughput than OpenSearch for filtered vector search, while also achieving higher Recall@100 across the configurations we tested. Context engineering depends on more than fast vector retrieval. Teams also need strong relevance controls, like hybrid search and filtering, operational simplicity, and predictable performance, as workflows iterate. But because agents often run retrieve, reason, retrieve loops many times per request, retrieval latency becomes a multiplier, so improvements here translate directly into better end-to-end responsiveness and lower cost.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9daec868eed84658/6a170bef6234e0c322db1a19/d5a52a07773f0942c2baa732dacfe782aac0f415-1600x683.png" alt="OpenSearch vs. Elasticsearch​: Throughput for filtered vector search benchmark" /><p>For context engineering, retrieval isn’t a one-time step. Agents and applications repeatedly run loops, such as retrieve → reason → retrieve, to refine queries, verify facts, assemble grounded context, and complete tasks. This pattern is common in agentic workflows and iterative retrieval augmented generation (RAG). Because retrieval may be invoked many times per user request, it adds delay to the response and/or increases infrastructure costs.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt718c72fb858e4c98/6a170bf00e2e496e7241a132/54ac476ff20a3cf93484298c9ae47612c12fc110-800x417.png" alt="Context engineering turns a large context pool into a limited LLM context window." /><h2>Why is vector search performance critical?</h2><p></p><p>Imagine a shopping assistant answering the question, “I need a carry-on backpack under $60 that fits a 15-inch laptop, is water resistant, and can arrive by Friday.”</p><p>In production, the assistant rarely issues one vector query and stops. It runs a retrieval loop to build the right context, and each step is typically constrained by filters, like availability, region, shipping promise, brand rules, and policy eligibility.</p><p><strong>Step 1: Interpret intent and translate to constraints.</strong></p><p>The agent turns the request into structured filters and a semantic query, such as:</p><ul><li><p>Filters: In stock, deliverable to the user’s postcode, delivery by Friday, price under $60, valid listing</p></li><li><p>Vector query: “Carry-on backpack 15-inch laptop water resistant”</p></li></ul><p><strong>Step 2: Retrieve candidates, and then refine.</strong></p><p>It often repeats retrieval with variations to avoid missing good matches:</p><ul><li><p>“travel backpack carry on laptop sleeve”</p></li><li><p>“water resistant commuter backpack 15 inch”</p></li><li><p>“lightweight cabin backpack”</p></li></ul><p>Each query uses the same eligibility filters, because retrieving irrelevant or unavailable items is wasted context.</p><p><strong>Step 3: Expand to confirm details and reduce risk.</strong></p><p>The agent then retrieves again to verify key attributes that affect the final answer:</p><ul><li><p>Material and water resistance wording</p></li><li><p>Dimensions and laptop compartment fit</p></li><li><p>Return policy or warranty constraints</p></li><li><p>Alternate options if inventory is low</p></li></ul><p>This is multistep context engineering: Retrieve, reason, retrieve, assemble.</p><h2>Why latency and recall matter for context engineering</h2><p>These interactions can involve dozens of filtered retrieval calls per user session. That makes per-call latency a direct multiplier on end-to-end response time, and low recall forces extra retries or causes the agent to miss eligible items, degrading answer quality.</p><p>Takeaway: In context-engineered systems, filtered approximate nearest neighbors (ANN) isn’t a single lookup. It’s a repeated operation under constraints, so vector search performance shows up immediately in latency, throughput, and cost, even when the large language model (LLM) is the most visible component.</p><h2>Benchmarking</h2><h3>Results</h3><p>In Graph 2, each dot represents one test configuration. The best results appear toward the top left, meaning higher recall with lower latency. Elasticsearch’s results are consistently closer to the top left than OpenSearch’s, indicating better speed and accuracy under the same workload settings.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb562b30a600cc8e1/6a170bf2cf4f253582b2d1b2/c50d1df00968cac18149a2799e6242fbe49b66a0-1600x990.png" alt=" Graph 2: Recall versus average latency (rescore 1)." /><h4>Some key insights</h4><ul><li><p><code>s_n_r_value</code>: Shorthand for <code>size_numCandidates_rescoreOversample</code> (k and numCandidates set equal to numCandidates in these tests), for example, <code>100_500_1</code> means size=100, numCandidates=500 and k=500, rescore oversample=1</p></li><li><p>Recall: Measured Recall@100 for that configuration</p></li><li><p>Avg latency (ms): Average end-to-end latency per query</p></li><li><p>Throughput: Queries per second</p></li><li><p>Recall %: Relative recall lift of Elasticsearch versus OpenSearch (Elasticsearch minus OpenSearch) / OpenSearch</p></li><li><p>Latency Xs: OpenSearch average latency divided by Elasticsearch average latency</p></li><li><p>Throughput Xs: Elasticsearch throughput divided by OpenSearch throughput</p></li></ul><p>Engine</p><p>`s_n_r_value`</p><p>Recall</p><p>Avg Latency (ms)</p><p>Throughput</p><p>Recall %</p><p>Latency Xs</p><p>Throughput Xs</p><p>Elasticsearch</p><p>100_250_1</p><p>0.7704</p><p>25</p><p>534.75</p><p>9.70%</p><p>2.28</p><p>1.91</p><p>OpenSearch</p><p>100_250_1</p><p>0.7023</p><p>57.08</p><p>279.58</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_500_1</p><p>0.8577</p><p>25.42</p><p>524.14</p><p>7.20%</p><p>2.4</p><p>2</p><p>OpenSearch</p><p>100_500_1</p><p>0.8001</p><p>60.9</p><p>262.12</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_750_1</p><p>0.8947</p><p>29.67</p><p>528.09</p><p>5.72%</p><p>2.25</p><p>2.21</p><p>OpenSearch</p><p>100_750_1</p><p>0.8463</p><p>66.76</p><p>239.11</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_1000_1</p><p>0.9156</p><p>29.65</p><p>534.5</p><p>4.66%</p><p>2.46</p><p>2.44</p><p>OpenSearch</p><p>100_1000_1</p><p>0.8748</p><p>72.88</p><p>219.01</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_1500_1</p><p>0.9386</p><p>31.84</p><p>497.3</p><p>3.38%</p><p>2.71</p><p>2.68</p><p>OpenSearch</p><p>100_1500_1</p><p>0.9079</p><p>86.16</p><p>185.4</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_2000_1</p><p>0.9507</p><p>34.69</p><p>457.2</p><p>2.57%</p><p>2.98</p><p>2.96</p><p>OpenSearch</p><p>100_2000_1</p><p>0.9269</p><p>103.36</p><p>154.55</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_2500_1</p><p>0.9582</p><p>37.9</p><p>418.43</p><p>1.99%</p><p>3.28</p><p>3.26</p><p>OpenSearch</p><p>100_2500_1</p><p>0.9395</p><p>124.29</p><p>128.53</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_3000_1</p><p>0.9636</p><p>41.86</p><p>379.4</p><p>1.62%</p><p>3.46</p><p>3.44</p><p>OpenSearch</p><p>100_3000_1</p><p>0.9482</p><p>144.67</p><p>110.34</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_4000_1</p><p>0.9705</p><p>50.28</p><p>316.21</p><p>1.06%</p><p>3.87</p><p>3.85</p><p>OpenSearch</p><p>100_4000_1</p><p>0.9603</p><p>194.36</p><p>82.22</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_5000_1</p><p>0.9749</p><p>58.77</p><p>270.91</p><p>0.73%</p><p>4.43</p><p>4.41</p><p>OpenSearch</p><p>100_5000_1</p><p>0.9678</p><p>260.33</p><p>61.38</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_6000_1</p><p>0.9781</p><p>66.75</p><p>238.59</p><p>0.52%</p><p>4.91</p><p>4.89</p><p>OpenSearch</p><p>100_6000_1</p><p>0.973</p><p>327.44</p><p>48.81</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_7000_1</p><p>0.9804</p><p>74.64</p><p>213.49</p><p>0.38%</p><p>5.28</p><p>5.27</p><p>OpenSearch</p><p>100_7000_1</p><p>0.9767</p><p>394.24</p><p>40.53</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_8000_1</p><p>0.9823</p><p>82.28</p><p>193.59</p><p>0.27%</p><p>6.86</p><p>6.83</p><p>OpenSearch</p><p>100_8000_1</p><p>0.9797</p><p>564.14</p><p>28.33</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_9000_1</p><p>0.9837</p><p>90.08</p><p>176.96</p><p>0.16%</p><p>7.63</p><p>7.61</p><p>OpenSearch</p><p>100_9000_1</p><p>0.9821</p><p>687.25</p><p>23.25</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_10000_1</p><p>0.9848</p><p>97.64</p><p>163.31</p><p>0.08%</p><p>8.38</p><p>8.36</p><p>OpenSearch</p><p>100_10000_1</p><p>0.984</p><p>818.64</p><p>19.53</p><p></p><p></p><p></p><p>For example, at <code>100_9000_1</code>, OpenSearch averages 687 milliseconds per retrieval versus 90 milliseconds on Elasticsearch, and in a 10-step retrieval loop that’s about 10 x (687 - 90) = six seconds of additional waiting time. </p><p>See the <a href="https://github.com/elastic/competitive-benchmarking-studies/tree/main/es-9.3-vs-os-3.5-vector-search/jingra/results/20260220">full results</a>.</p><h3>Methodology</h3><p>Using Python to send the queries and track the response timing and other statistics, we sent the following queries to the engines. Bear in mind that the performance of any vector search engine depends on how you tune its core parameters: how many candidates to consider, how aggressively to rescore, and how much context to return. These settings directly affect both recall (the likelihood of finding the right answer) and latency (how fast you get results).</p><p>In our benchmarks, we used the same candidate, rescore, and result-size settings you’d typically tune in an agentic retrieval loop, and we measured how Elasticsearch performs under that workload. We then ran OpenSearch with the same settings as a reference.</p><p>OpenSearch</p>GET &lt;INDEX_NAME&gt;/_search
{
  "query": {
    "knn": {
      "&lt;DENSE_VECTOR_FIELD_NAME&gt;": {
        "vector": [...],
        "k": &lt;NUMBER_OF_CANDIDATES&gt;,
        "method_parameters": {
          "ef_search": &lt;NUMBER_OF_CANDIDATES&gt;
        },
        "rescore": {
          "oversample_factor": &lt;OVERSAMPLE&gt;
        },
        "filter": {
          &lt;SOME_FILTER&gt;
        }
      }
    }
  },
  "size": &lt;RESULT_SIZE&gt;,
  "_source": {
    "excludes": [
      "&lt;DENSE_VECTOR_FIELD_NAME&gt;"
    ]
  }
}<ul><li><p><code>"size": &lt;RESULT_SIZE&gt;</code>: Number of hits returned to the client. In this benchmark, result size is 100 to compute Recall@100.</p></li><li><p><code>"k": &lt;NUMBER_OF_CANDIDATES&gt;</code>: The number of nearest neighbor candidates.</p></li><li><p><code>"ef_search": &lt;NUMBER_OF_CANDIDATES&gt;</code>: The number of vectors to examine.</p></li><li><p><code>"oversample_factor": &lt;OVERSAMPLE&gt;</code>: How many candidate vectors are retrieved before rescoring.</p></li></ul><p>Elasticsearch</p>GET &lt;INDEX_NAME&gt;/_search
{
  "query": {
    "knn": {
      "field": "&lt;DENSE_VECTOR_FIELD_NAME&gt;",
      "query_vector": [...],
      "k": &lt;NUMBER_OF_CANDIDATES&gt;,
      "num_candidates": &lt;NUMBER_OF_CANDIDATES&gt;,
      "rescore_vector": {
        "oversample": &lt;OVERSAMPLE&gt;
      },
      "filter": {
        &lt;SOME_FILTER&gt;
      }
    }
  },
  "size": &lt;RESULT_SIZE&gt;,
  "_source": {
    "excludes": [
      "&lt;DENSE_VECTOR_FIELD_NAME&gt;"
    ]
  }
}<ul><li><p><code>"size": &lt;RESULT_SIZE&gt;</code>: Number of hits returned to the client. In this benchmark, result size is 100 to compute Recall@100.</p></li><li><p><code>"k": &lt;NUMBER_OF_CANDIDATES&gt;</code>: Number of nearest neighbors to return from each shard.</p></li><li><p><code>"num_candidates": &lt;NUMBER_OF_CANDIDATES&gt;</code>: Number of nearest neighbor candidates to consider per shard while doing <code>knn</code> search.</p></li><li><p><code>"oversample": &lt;OVERSAMPLE&gt;</code>: How many candidate vectors are retrieved before rescoring.</p></li></ul><p>Example</p><p><code>Knn</code> query, (<code>100_500_1</code>), would be as follows:</p><p>OpenSearch</p>GET search_catalog_128/_search
{
  "query": {
    "knn": {
      "search_catalog_embedding": {
        "vector": [...],
        "k": 500,
        "method_parameters": {
          "ef_search": 500
        },
        "rescore": {
          "oversample_factor": 1
        },
        "filter": {
          "term": {
            "valid": true
          }
        }
      }
    }
  },
  "size": 100,
  "_source": {
    "excludes": [
      "search_catalog_embedding"
    ]
  }
}<p>Elasticsearch</p>GET search_catalog_128/_search
{
  "query": {
    "knn": {
      "field": "search_catalog_embedding",
      "query_vector": [...],
      "k": 500,
      "num_candidates": 500,
      "rescore_vector": {
        "oversample": 1
      },
      "filter": {
        "term": {
          "valid": true
        }
      }
    }
  },
  "size": 100,
  "_source": {
    "excludes": [
      "search_catalog_embedding"
    ]
  }
}<p>The full configuration, alongside Terraform scripts, Kubernetes manifests and the benchmarking code is available in this <a href="https://github.com/elastic/competitive-benchmarking-studies">repository</a> in the folder <a href="https://github.com/elastic/competitive-benchmarking-studies/tree/main/es-9.3-vs-os-3.5-vector-search">es-9.3-vs-os-3.5-vector-search</a>.</p><h3>Cluster setup</h3><p>We ran our tests on six e2-standard-16 cloud servers, each with 16 vCPUs and 64 GB RAM. On each server, we allocated 15 vCPUs and 56 GB RAM to each Kubernetes pod running the search engine node, with 28 GB reserved for the JVM heap.</p><p>The clusters ran Elasticsearch 9.3.0 and OpenSearch 3.5.0 (Lucene 10.3.2). Because both systems use the same Lucene version in this benchmark, the throughput and latency differences we observe cannot be attributed to Lucene alone and instead reflect differences in how each engine integrates and executes filtered k-nearest neighbor (kNN) retrieval and rescoring. We used a single index with three primary shards and one replica (so 6 shards total, 1 per node).</p><p>We also used a separate server in the same region to run the benchmark client and collect timing statistics.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7c7bdd567395d5b7/6a170bf3c1e8a56ee2f882f6/f81002c9186e4c2d3e92f49d72418fee9860fc5e-761x401.png" alt="Cluster setup for Elasticsearch and for OpenSearch benchmarks" /><h3>The dataset</h3><p></p><p>For this benchmark, we used a large-scale ecommerce-style catalog embedding dataset with 20 million documents, designed to reflect real-world filtered vector retrieval at scale.</p><p></p><p>Each document represents a catalog item and includes:</p><p></p><ul><li><p>A 128-dimensional dense vector embedding used for approximate kNN retrieval.</p></li><li><p>Structured metadata fields used for filtering (for example, item validity and availability plus other catalog constraints) enabling the common production pattern of retrieving the nearest neighbors but only within an eligible subset.</p></li></ul><p></p><p>We chose this dataset because it captures the core performance challenge we see in agentic and RAG-style systems in production: Vector similarity alone is not enough, retrieval is frequently constrained by filters, and the system must maintain high recall while keeping latency low under those constraints. Compared to smaller QA-style datasets, a 20M document corpus also better reflects the scale and candidate pressure that filtered ANN systems face in practice.</p><h2>Conclusion</h2><p>In modern AI architectures, especially those built around context engineering, vector search speed isn’t a minor implementation detail. It’s a multiplier. When agents and workflows iterate through retrieve → reason → retrieve, retrieval performance directly shapes end-to-end latency, throughput, and the quality of the context fed into the model.</p><p>In our benchmarks, Elasticsearch consistently delivered higher recall at lower latency than OpenSearch in scenarios where correctness depends on retrieving the right document, not just a similar vector. On a controlled dataset, the difference is clear, and in production those gains accumulate across large volumes of retrieval calls, improving responsiveness, increasing capacity headroom, and reducing infrastructure costs.</p><h3>Further reading</h3><ol><li><p><a href="https://www.elastic.co/search-labs/blog/context-engineering-overview">What is context engineering?</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/series/context-engineering-hybrid-search-evolution">The evolution of hybrid search and context engineering</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/context-engineering-relevance-ai-agents-elasticsearch">The impact of relevance in context engineering for AI agents</a></p></li></ol>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/opensearch-vs-elasticsearch-filtered-vector-search</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/opensearch-vs-elasticsearch-filtered-vector-search</guid>
    <category><![CDATA[Vector Database]]></category>
    <dc:creator><![CDATA[Sachin Frayne]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4b38e5114bbf098c/6a170bf560084b3a6c3c459d/fb7ee623925ca6696d643e437ce8efe5fe749079-1280x720.png" length="0" type="image/png"/>
    <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>