<?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[Benjamin Trent - 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[Benjamin Trent - 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/benjamin-trent</link>
    </image>
    <link>https://www.elastic.co/search-labs/author/benjamin-trent</link>
    <atom:link href="https://www.elastic.co/search-labs/rss/author/benjamin-trent.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[en]]></language>
    <lastBuildDate>Fri, 25 Sep 2026 13:09:22 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Cutting Elasticsearch DiskBBQ query quantization time by 5x]]></title>
    <description><![CDATA[See how asymmetric quantization cuts DiskBBQ query quantization overhead from about 20% to 4% with little recall impact.]]></description>
    <content:encoded><![CDATA[<p>Asymmetric quantization cuts the time Elasticsearch DiskBBQ spends quantizing queries by 5x. We discovered that too much time was spent quantizing queries. DiskBBQ started off quantizing queries with the same centroids as the indexed documents. However, we can make this cheaper by quantizing the queries with coarser-grained centroids. This improves query latency with very little observed recall impact in our tests.</p><h2>How DiskBBQ uses two centroid tiers for asymmetric quantization</h2><p>DiskBBQ now uses two centroid tiers (fine-grained document centroids and coarser query centroids) so queries are quantized once per parent centroid instead of once per document centroid.</p><p>The old mental model is "one centroid does everything for a posting list." The new model splits responsibilities:</p><ul><li><p>Document centroids (fine-grained): Still used for posting-list structure and document centering.</p></li><li><p>Query centroids (coarser): A parent centroid reused across multiple document centroids.</p></li></ul><p>So instead of quantizing the query independently for every document centroid we visit, we quantize per parent centroid and reuse that work across all of its children. Since we were already using two-tier clustering logic as the index size grew, it was a natural fit. We can reuse the work we already do during querying.</p><p>These images are a simple representation of our goal: Quantizing per centroid gives us overhead per centroid. Let’s get rid of it!</p><p>The goal is to significantly reduce the number of times we actually need to quantize a given query.</p><h2>The math behind asymmetric BBQ in Elasticsearch</h2><p>To center the data prior to computing quantized query and document vectors,  and , we rewrite the dot product  as  and expand. We can perform exactly the same operation but using different centroids for the query vector  and document vector . Specifically,</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd01f666c5dfa4f24/6a4695b015103586c4202edb/f395654cf3cc965254dfc8d7e66b57743d29cf41-1566x206.png" alt="" /><p>As for standard Better Binary Quantization (BBQ), we quantize  and  in order to estimate the per (document, query) pair component of the dot product. The quantities   and  are scalars so just two extra additions per dot product we compute. For , we compute naturally when finding the nearest centroid. For , this can be stored with the quantized document vectors, which are just 4 bytes overhead. Below, we’ll discuss how to manage the other term on the fly.</p><h3>Asymmetric BBQ in DiskBBQ</h3><p>We cluster the document centroids (using k-means, for example) into  clusters, for  and  the query and document centroid count, respectively. This means there’s a many-to-one mapping from document centroids to query centroids. We’ll denote the document centroids by their index  and define this mapping to the query centroids as →.</p><p>Since there’s a unique query centroid for each document centroid, we only need to cache one value for  per quantized document vector, that is, for each document vector  in posting list , we need to cache  with the quantized document vector.</p><p>When we come to compute the dot products between a query and the document vectors in a cluster, we look up the quantized query vector corresponding to  and we compute  once and use it to process the whole posting list. The quantization process is significantly more expensive than computing the dot product, so this is a big net win.</p><p>The  term is estimated using the usual <a href="https://www.elastic.co/search-labs/blog/scalar-quantization-optimization">BBQ machinery</a>, that is, these vectors will be quantized and the dot product value estimated from the quantized vectors. Then we can use (1) to compute the final dot product estimate. Notice that this means we only need to quantize the query at most  times. Furthermore, we typically visit many centroids from the same parent centroid in a search because they’re close to one another.</p><h3>Euclidean distance corrections for asymmetric quantization</h3><p>For Euclidean, we can write  and treat the  term exactly as above. In fact, there’s a slightly nicer form. Substituting, we have that:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf7cc1ab13a3dc512/6a4695b274bff735d8a05b44/8faf7cd146c3221f3a3929e07286ceb82ac95a04-1598x122.png" alt="" /><p>We can rewrite this as follows:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltce5ca0055196ceff/6a4695b62d406b3c77ba2bac/91d700d38d0e7842f2efef5d7778a6a34142c384-1172x362.png" alt="" /><p>The corrective terms are the norm of query vector  minus the document centroid , the norm of the document vector  minus the query centroid , and the norm of the difference of query and document centroids. As before,  can be stored as a single float with each document.</p><h2>What changed in DiskBBQ indexing and scoring</h2><p>At indexing/merge time, centroids can be clustered into parent groups when centroid count is large enough. Posting metadata moved from "centroid ordinal + centroid score" to a shape that explicitly carries query-centroid ordinal and document-centroid score. That decoupling is what lets scoring read documents and query centering from different places. For Euclidean, let’s break it down further by our mathematics above:</p><p> &lt;- This is the distance from a “query vector ” to “document centroid ”. We already gather this when we find the nearest centroids during querying. No new work.</p><p> &lt;- This is the distance from “document vector ” to “query centroid ”. However, recalling our <a href="https://www.elastic.co/search-labs/blog/scalar-quantization-optimization">original quantization work</a>, this can simply replace a previously stored float value. No new storage is required.</p><p> &lt;- This is just the distance between query centroid  and document centroid . This is just a single extra floating point value per postings list.</p><p>The practical change for dot product spaces is even simpler; the only correction value change is  being stored instead of .</p><p>These changes don’t introduce new computation costs and marginally reduce storage costs because we no longer quantize queries with document centroids. Those raw centroids don’t need to be present with the posting lists.</p><p>One cost we did add is a small cache of quantized query values. This is to account for clustering edge cases. For example, it's possible that query  is very close to query centroid  but not quite as close as . That said, the actual nearest three document centroids could have a relative order: . So, to prevent the query from being quantized twice, we keep a limited cache of the most recent quantized values for a given query.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1aeb75c07434e77f/6a4695b9c71ec47ccbb9846c/d0df2be8b601fb46005667bfa81fc89b2fdaee48-1538x1092.png" alt="Diagram showing a blue circle labeled “q” connected by colored arrows to two dashed oval regions. The green oval contains orange circles labeled dc_0–dc_2 and a green diamond labeled qc_0; and the purple oval contains pink circles labeled dc_3–dc_5 and a purple diamond labeled qc_1. Arrows illustrate relationships between q and the cluster components." /><p>Here’s a visualization of the situation described above. In the typical iteration scenario, we don’t want to risk unnecessarily quantizing the query against the same query centroid multiple times.</p><h2>DiskBBQ asymmetric quantization: performance results</h2><p>The flame graphs below show a before and after comparison. Before, about 20% of the time was spent quantizing queries when we visited each cluster. After our adjustment, it dropped to about 4%.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8e2b9d73c7ab998a/6a4695bc91c425d3b5732cb1/a17c3618a29b83d4196088d8422a7ede6eba5c3e-1999x655.png" alt="Flame graph showing computational costs using symmetric quantization, with stacked colored blocks labeled for Elasticsearch and JDK vectorization functions. Each block’s width represents relative processing time, and the tooltip highlights quantization activity within Elasticsearch query code." /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7bfd2b99e2fec276/6a4695bf5f1d903f44e3ba97/bb47abf8a7c7da5f5e8990beaaf3319741abfc52-1999x661.png" alt="Flame graph showing reduced computational time spent on quantization after introducing asymmetric quantization, with stacked colored blocks labeled for Elasticsearch and JDK vectorization functions. Each block’s width represents relative processing time, and a tooltip highlights quantization activity within Elasticsearch query code." /><p>Of course, the bulk of the cost is still just scoring the vectors in each cluster. But every little bit helps.</p><p>Here’s a better view of the full end-to-end performance and recall. The data set was 1 million <a href="https://github.com/iai-group/DBpedia-Entity/">DBpedia</a> docs encoded with the <a href="https://huggingface.co/thenlper/gte-base">GTE-Base</a> model. Here, “sec” indicates the number of clusters per secondary (parent) cluster. Note that symmetric quantization is still impacted by the secondary cluster size as it also impacts the two-tier clustering indexing we do already.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltefa647d89e1eb323/6a4695c1a3096d631b9ce7bf/92ec17f1d4ca18a98d3c20b430af72cdf8d8be8a-1260x900.png" alt="Line chart titled “Latency vs Recall Pareto (sec = 16),” comparing asymmetric and symmetric quantization. The blue asymmetric line shows higher recall at each latency value than the red symmetric line, indicating improved latency with minimal recall impact. Axes are labeled “Latency (ms)” and “Recall.”" /><p>However, the impact on our current index structure is still dominated by centroid scoring and scoring vectors in the cluster. Asymmetric quantization removes a frustratingly expensive part of our scoring overhead, but the impact isn’t dramatic given our current structure.</p><h2>What's next for DiskBBQ quantization</h2><p>This simple piece of mathematics decouples our query quantization from our document quantization, giving us better storage efficiency and faster queries. This is in Elasticsearch Serverless now and will be in Elastic Stack version 9.4.0.</p><p>This now means that query quantization time isn’t a direct concern for future decisions. We can make larger index changes without worrying about the consistent overhead of quantization directly with document centroids.</p><p>This was a nerdy one. I hope you survived all the math (and that I copied it all down correctly). It’s always fun to be able to tackle complex problems with simple mathematics, and the results are actually positive in real use cases and data.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/diskbbq-asymmetric-query-quantization</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/diskbbq-asymmetric-query-quantization</guid>
    <category><![CDATA[Vector Database]]></category>
    <dc:creator><![CDATA[Benjamin Trent,Thomas Veasey]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9fe4355576b40f9a/6a4695a774bff7d11ba05b40/265ce999fd38f21943d91e29c0bc49ab01f0196d-1999x1546.png" length="0" type="image/png"/>
    <pubDate>Wed, 27 May 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Speed up vector ingestion using Base64-encoded strings]]></title>
    <description><![CDATA[Introducing Base64-encoded strings to speed up vector ingestion in Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>We’re improving the ingestion speed of vectors in Elasticsearch. Now, in <a href="https://www.elastic.co/cloud/serverless">Elastic Cloud Serverless</a> and in v9.3, you can send your vectors to Elasticsearch encoded as Base64 strings, which will provide immediate benefits to your ingestion pipeline.</p><p>This change reduces the overhead of parsing vectors in JSON by an order of magnitude, which translates to almost a 100% improvement on indexing throughput for DiskBBQ and around 20% improvement for hierarchical navigable small world (HNSW) workloads. In this blog, we’ll take a closer look at Base64-encoded strings and the improvements it brings to vector ingestion.</p><h2>What’s the problem?</h2><p>At Elastic, we’re always looking for ways to improve our vector search capabilities, whether that’s enhancing existing storage formats or introducing new ones. Recently, for example, we added a new disk-friendly storage format called <a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction">DiskBBQ</a> and enabled vector indexing with <a href="https://www.elastic.co/search-labs/blog/elasticsearch-gpu-accelerated-vector-indexing-nvidia">NVIDIA cuVS</a>.</p><p>In both cases, we expected to see major gains in ingestion speed. However, once these changes were fully integrated into Elasticsearch, the improvements weren’t as large as we had hoped. A flamegraph of the ingestion process made the issue clear: JSON parsing had become one of the main bottlenecks.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7d4cd9d8628b64b3/6a170c5b2867148d5e93e353/a286408afc85ff1cd3dd448b8fdf59dd3e11d599-1600x675.png" alt="Vector ingestion before using Base64-encoded strings  " /><p>Parsing JSON requires walking through every element in the arrays and converting numbers from text format into 32-bit floating-point values, which is very expensive.</p><h3>Why Base64-encoded strings?</h3><p>The most efficient way to parse vectors is directly from their binary representation, where each element uses a 32-bit floating-point value. However, JSON is a text-based format, and the way to include binary data in it is by using <a href="https://en.wikipedia.org/wiki/Base64">Base64</a>-encoded strings. Base64 is just a binary-to-text encoding schema.</p>{
  “emb” : [1.2345678, 2.3456789, 3.4567891]
}<p>We can now send vectors encoded as Base64 strings:</p>{
  “emb” : ”P54GUUAWH5pAXTwI”
}<p>Is it worth it? Our benchmarks suggest yes. When parsing 1,000 JSON documents, using Base64 encoded strings instead of float arrays resulted in performance improvements of more than an order of magnitude, at the cost of a small encode/decode trade-off (client-side Base64 encoding and a temporary byte array on the server for decoding) in exchange for eliminating expensive per-element numeric parsing.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta9a1662fdf7d5849/6a170c5d839dfaf624dcff29/86e5a926e13b07bb3b0abe80bd4930464e8f6f9b-1200x742.png" alt="Base64 vs. Float32 parsing time" /><h3>Give me some ingestion numbers</h3><p>We can see these improvements in practice when running the <a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/README.md"><code>so_vector</code></a> rally track with the different approaches. The actual gains depend on how fast indexing is for each storage format. For <code>bbq_disk</code>, indexing throughput increases by about 100%, while for <code>bbq_hnsw</code>, the improvement is closer to 20%, since indexing is inherently slower there.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte35ffc920ae863f5/6a170c5e509168f193e1bb1c/4277057ee59cb84d068176b56bb7fa00b66e1cb3-1200x742.png" alt="Base64 vs Float32 indexing throughput" /><p>Starting with Elasticsearch v9.2, <a href="https://www.elastic.co/search-labs/blog/elasticsearch-exclude-vectors-from-source">vectors are excluded from </a><a href="https://www.elastic.co/search-labs/blog/elasticsearch-exclude-vectors-from-source"><code>_source</code></a> by default and are stored internally as 32-bit floating-point values. This behavior also applies to Base64-encoded vectors, making the choice of indexing format completely transparent at search time.</p><h2>Client support</h2><p>Adding a new format for indexing vectors might require changes on ingestion pipelines. To help this effort, in v9.3, Elasticsearch official clients can transform vectors with 32-bit floating-point values into Base64-encoded strings and the other way around. You might need to check the client documentation for the specific implementation.</p><p>For example, here’s a snippet for implementing bulk loading using the Python client:</p>from elasticsearch.helpers import bulk, pack_dense_vector

def get_next_document():
    for doc in dataset:
        yield {
            "_index": "my-index",
            "_source": {
                "title": doc["title"],
                "text": doc["text"],
                "emb": pack_dense_vector(doc["emb"]),
            },
        }

result = bulk(
    client=client,
    chunk_size=chunk_size,
    actions=get_next_document,
    stats_only=True,
)<p>The only difference from a bulk ingest using floats is that the embedding is wrapped with the <code>pack_dense_vector()</code> auxiliary function.</p><h2>Conclusion</h2><p>By switching from JSON float arrays to Base64-encoded vectors, we remove one of the largest remaining bottlenecks in Elasticsearch’s vector ingestion pipeline: numeric parsing. The result is a simple change with outsized impact: up to 2× higher throughput for DiskBBQ workloads and meaningful gains even for slower indexing strategies, like HNSW.</p><p>Because vectors are already stored internally in a binary format and excluded from <code>_source</code> by default, this improvement is completely transparent at search time. With official client support landing in v9.3, adopting Base64 encoding requires only minimal changes to existing ingestion code, while delivering immediate performance benefits.</p><p>If you’re indexing large volumes of embeddings, especially in high-throughput or serverless environments, Base64-encoded vectors are now the fastest and most efficient way to get your data into Elasticsearch.Those interested in the implementation details can follow the related Elasticsearch issues and pull requests: #<a href="https://github.com/elastic/elasticsearch/issues/111281">111281</a> and #<a href="https://github.com/elastic/elasticsearch/issues/135943">135943</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/base64-encoded-strings-vector-ingestion</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/base64-encoded-strings-vector-ingestion</guid>
    <category><![CDATA[Vector Database]]></category>
    <category><![CDATA[Inside Elastic]]></category>
    <dc:creator><![CDATA[Jim Ferenczi,Benjamin Trent,Ignacio Vera Sequeiros]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc5ffc7ac4c2b9d93/6a170c5f839dfa007ddcff2d/4c1ebbd7a1071e8e1721a9871cba87f6aed140e9-1280x720.png" length="0" type="image/png"/>
    <pubDate>Wed, 04 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Making Elasticsearch and Lucene the best vector database: up to 8x faster and 32x efficient]]></title>
    <description><![CDATA[Discover the recent enhancements and optimizations that notably improve vector search performance in Elasticsearch &amp; Lucene vector database.]]></description>
    <content:encoded><![CDATA[<h2>Elasticsearch and Lucene report card: noteworthy speed and efficiency investments</h2><p>Our mission at Elastic is to make Apache Lucene the best vector database out there, and to continue to make Elasticsearch the best retrieval platform out there for search and RAG. Our investments into Lucene are key to ensure that every release of Elasticsearch brings increasing faster performance and scale.</p><p>Customers are already building the next generation of AI enabled search applications with Elastic’s vector database and vector search technology. <a href="https://roboflow.com/">Roboflow</a> is used by over 500,000 engineers to create datasets, train models, and deploy computer vision models to production. Roboflow uses Elastic vector database to store and search billions of vector embeddings.</p><p>In this blog we summarize recent enhancements and optimisations that significantly improve vector search performance in Elasticsearch and Apache Lucene, over and above performance gains <a href="https://www.elastic.co/search-labs/blog/apache-lucene-9.9-search-speedups">delivered with Lucene 9.9</a> and Elasticsearch 8.12.x.</p><p>The integration of vector search into Elasticsearch relies on Apache Lucene, the layer that orchestrates data storage and retrieval. <a href="https://www.elastic.co/search-labs/blog/vector-search-elasticsearch-rationale">Lucene's architecture</a> organizes data into segments, immutable units that undergo periodic merging. This structure allows for efficient management of inverted indices, essential for text search. With vector search, Lucene extends its capabilities to handle multi-dimensional points, employing the hierarchical navigable small world (HNSW) algorithm to index vectors.</p><p>This approach facilitates scalability, enabling data sets to exceed available RAM size while maintaining performance. Additionally, Lucene's segment-based approach offers lock-free search operations, supporting incremental changes and ensuring visibility consistency across various data structures. The integration however comes with its own engineering challenges. Merging segments requires recomputing HNSW graphs, incurring index-time overhead. Searches must cover multiple segments, leading to possible latency overhead. Moreover, optimal performance requires scaling RAM as data grows, which may raise resource management concerns.</p><p>Lucene's integration into Elasticsearch comes with the benefit of robust vector search capabilities. This includes aggregations, document level security, geo-spatial queries, pre-filtering, to full compatibility with various Elasticsearch features. Imagine running vector searches using a geo bounding box, this is an example usecase enabled by Elasticsearch and Lucene.</p><p>Lucene's architecture lays a solid foundation for efficient and versatile vector search within Elasticsearch. Let’s explore optimization strategies and enhancements we have implemented to integrate vector search into Lucene, which delivers a high performance and comprehensive feature-set for developers.</p><h2>Harnessing Lucene's architecture for multi-threaded search</h2><p>Lucene's segmented architecture enables the implementation of multi-threaded search capabilities. Elasticsearch’s performance gains come from efficiently searching multiple segments simultaneously. Latency of individual searches is significantly reduced by using the processing power of all available CPU cores. While it may not directly improve overall throughput, this enhancement prioritizes minimizing response times, ensuring that users receive their search results as swiftly as possible.</p><p>Furthermore, this optimization is particularly beneficial for Hierarchical Navigable Small World (HNSW) searches, as each graph is independent of the others and can be searched in parallel, maximizing efficiency and speeding up retrieval times even further.</p><p>The advantage of having multiple independent segments extends to the architectural level, especially in serverless environments. In this <a href="https://www.elastic.co/blog/elastic-serverless-architecture">new architecture,</a> the indexing tier is responsible for creating new segments, each containing its own HSNW graph. The search tier can simply replicate these segments without incurring the CPU cost of indexation. This separation allows a significant portion of compute resources to be dedicated to searches, optimizing overall system performance and responsiveness.</p><h2>Accelerating multi-graph vector search</h2><p>In spite of gains achieved with parallelization, each segment's searches would remain independent, unaware of progress made by other segment searches. So our focus shifted towards optimizing the efficiency of concurrent searches across multiple segments.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt35f29a49abcd2e22/6a17d78ae31791cd572d5682/103e9a7a97e9c219edb028e0fc675346920002cc-974x407.png" alt="" /><p>The graph shows that the number queries per second increased from 104 queries/sec to 219 queries/sec.</p><p>Recognizing the potential for further speedups, we leveraged our insights from optimizing lexical search, to enable information exchange among segment searches allowing for better coordination and efficiency in vector search.</p><p><a href="https://www.elastic.co/search-labs/blog/multi-graph-vector-search">Our strategy for accelerating multi-graph vector search</a> revolves around balancing exploration and exploitation within the proximity graph. By adjusting the size of the expanded match set, we control the trade-off between runtime and recall, crucial for achieving optimal search performance across multiple graphs.</p><p>In multi-graph search scenarios, the challenge lies in efficiently navigating individual graphs, while ensuring comprehensive exploration to avoid local minima. While searching multiple graphs independently yields higher recall, it incurs increased runtime due to redundant exploration efforts. To mitigate this, we devised a strategy to intelligently share state between searches, enabling informed traversal decisions based on global and local competitive thresholds.</p><p>This approach involves maintaining shared global and local queues of distances to closest vectors, dynamically adapting search parameters based on the competitiveness of each graph's local search. By synchronizing information exchange and adjusting search strategies accordingly, we achieve significant improvements in search latency while preserving recall rates comparable to single-graph searches.</p><p>The impact of these optimizations is evident in our benchmark results. In concurrent search and indexing scenarios, we notice up to 60% reduction in query latencies! Even for queries conducted outside of indexing operations, we observed notable speedups and a dramatic decrease in the number of vector operations required. These enhancements, integrated into Lucene 9.10 and subsequently Elasticsearch 8.13, mark significant strides towards enhancing vector database performance for search while maintaining excellent recall rates.</p><h2>Harnessing Java's latest advancements for ludicrous speed</h2><p>In the area of Java development, automatic vectorization has been a boon, optimizing scalar operations into SIMD (Single Instruction Multiple Data) instructions through the HotSpot C2 compiler. While this automatic optimization has been beneficial, it has its limitations, particularly in scenarios where explicit control over code shape yields superior performance. Enter Project Panama Vector API, a recent addition to the JDK offering an API for expressing computations reliably compiled to SIMD instructions at runtime.</p><p>Lucene's vector search implementation relies on fundamental operations like dot product, square, and cosine distance, both in floating point and binary variants. Traditionally, these operations were backed by scalar implementations, leaving performance enhancements to the JIT compiler. However, recent advancements introduce a paradigm shift, enabling developers to express these operations explicitly for optimal performance.</p><p>Consider the dot product operation, a fundamental vector computation. Traditionally implemented in Java with scalar arithmetic, recent innovations leverage the Panama Vector API to express dot product computations in a manner conducive to SIMD instructions. This revised implementation iterates over input arrays, multiplying and accumulating elements in batches, aligning with the underlying hardware capabilities.</p><p><a href="https://www.elastic.co/blog/accelerating-vector-search-simd-instructions">By harnessing Panama Vector API,</a> Java code now interfaces seamlessly with SIMD instructions, unlocking the potential for significant performance gains. The compiled code, when executed on compatible CPUs, leverages advanced vector instructions like AVX2 or AVX 512, resulting in accelerated computations. Disassembling the compiled code reveals optimized instructions tailored to the underlying hardware architecture.</p><p>Microbenchmarks comparing traditional Java implementations to those leveraging Panama Vector API illustrate dramatic performance improvements. Across various vector operations and dimension sizes, the optimized implementations outperform their predecessors by significant margins, offering a glimpse into the transformative power of SIMD instructions.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8efb30efc7e6c157/6a17d78bfbc5f8285d49190c/d2a5f15bb0d16608b67753a82312d2f254370622-1204x120.png" alt="" /><p>Micro-benchmark comparing dot product with the new Panama API (dotProductNew) and the scalar implementation (dotProductOld).</p><p>Beyond microbenchmarks, the real-world impact of these optimizations is quite exciting to think about. Vector search benchmarks, such as <a href="https://elasticsearch-benchmarks.elastic.co/#tracks/so_vector/nightly/default/90d">SO Vector,</a> demonstrate notable enhancements in indexing throughput, merge times, and query latencies. Elasticsearch, embracing these advancements, incorporates the faster implementations by default, ensuring users reap the performance benefits seamlessly.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdd3a97bf5f0cd24a/6a17d78d3e9e452c84ba12e6/e82a7ee152fdebfc856106bdc1f68c6eab9b5798-1349x882.png" alt="" /><p>The graph shows indexing throughput increased from about 900 documents/sec to about 1300 documents/sec.</p><p>Despite the incubating status of Panama Vector API, its quality and potential benefits are undeniable. Lucene's pragmatic approach allows for selective adoption of non-final JDK APIs, balancing the promise of performance improvements with maintenance considerations. With Lucene and Elasticsearch, users can leverage these advancements effortlessly, with performance gains translating directly to real-world workloads.</p><p>The integration of Panama Vector API into Java development yields a new era of performance optimization, particularly in vector search scenarios. By embracing hardware-accelerated SIMD instructions, developers can unlock efficiency gains, visible both in microbenchmarks and macro-level benchmarks. As Java continues to evolve, leveraging its latest features promises to propel performance to new heights, enriching user experiences across diverse applications.</p><h2>Maximizing memory efficiency with scalar quantization</h2><p>Memory consumption has long been a concern for efficient vector database operations, particularly for searching large datasets. Lucene introduces a breakthrough optimization technique - scalar quantization - aimed at significantly reducing memory requirements without sacrificing search performance.</p><p>Consider a scenario where querying millions of float32 vectors of high dimensions demands substantial memory, leading to significant costs. By embracing byte quantization, Lucene slashes memory usage by approximately 75%, offering a viable solution to the memory-intensive nature of vector search operations.</p><p>For quantizing floats to bytes, Lucene implements <a href="https://www.elastic.co/search-labs/blog/scalar-quantization-in-lucene">Scalar quantization</a> a lossy compression technique that transforms raw data into a compressed form, sacrificing some information for space efficiency. Lucene's implementation of scalar quantization achieves remarkable space savings with minimal impact on recall, making it an ideal solution for memory-constrained environments.</p><p>Lucene's architecture, consisting of nodes, shards, and segments, which facilitates efficient distribution and management of documents for search. Each segment stores raw vectors, quantized vectors, and metadata, ensuring optimized storage and retrieval mechanisms.</p><p>Lucene's vector quantization adapts dynamically over time, adjusting quantiles during segment merge operations to maintain optimal recall. By intelligently handling quantization updates and re-quantization when necessary, Lucene ensures consistent performance while accommodating changes in data distribution.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4680278043366ea4/6a17d78e1d1b83ebd893e2d5/73fb017cce8096a108a7a7297c86cfb26866922c-1440x447.png" alt="" /><p>Example of merged quantiles where segments A and B have 1000 documents and C only has 100.</p><p>Experimental results demonstrate the efficacy of scalar quantization in reducing memory footprint while maintaining search performance. Despite minor differences in recall compared to raw vectors, Lucene's quantized vectors offer significant speed improvements and recall recovery with minimal additional vectors.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt62d403efee1b4ebb/6a17d790e317918e322d5686/aacf329d8eb54a9b73a1e4722e14f27379dd80d7-576x455.png" alt="" /><p>Recall@10 for quantized vectors vs raw vectors. The search performance of quantized vectors is significantly faster than raw, and recall is quickly recoverable by gathering just 5 more vectors; visible by quantized@15.</p><p>Lucene's scalar quantization presents a revolutionary approach to memory optimization in vector search operations. With no need for training or optimization steps, Lucene seamlessly integrates quantization into its indexing process, automatically adapting to changes in data distribution over time. As Lucene and Elasticsearch continue to evolve, widespread adoption of scalar quantization will revolutionize memory efficiency for vector database applications, paving the way for enhanced search performance at scale.</p><h2>Achieving seamless compression with minimal impact on recall</h2><p>To make compression even better, we aimed to reduce each dimension from 7 bits to just 4 bits. Our main goal was to compress data further while still keeping search results accurate. By making some improvements, we managed to compress data by a factor of 8 without making search results worse. Here's how we did it.</p><p>We focused on keeping search results accurate while making data smaller. By making sure we didn't lose important information during compression, we could still find things well even with less detailed data. To make sure we didn't lose any important information, we added a smart error correction system.</p><p>We checked our compression improvements by testing them with different types of data and real search situations. This helped us see how well our searches worked with different compression levels and what we might lose in accuracy by compressing more.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta71070ad46fd4573/6a17d791be608665ca00459d/7c32834cfb733a6ad3deb64eb9813d4855539823-972x602.png" alt="" /><p>Comparison of int4 dot product values to the corresponding float values for a random sample of 100 documents and their 10 nearest neighbors.</p><p>These compression features were created to easily work with existing vector search systems. They help organizations and users save space without needing to change much in their setup. With this simple compression, organizations can expand their search systems without wasting resources.</p><p>In short, moving to 4 bits per dimension for scalar quantization was a big step in making compression more efficient. It lets users compress their original vectors by 8 times. By optimizing carefully, adding error correction, testing with real data, and offering scalable deployment, organizations could save a lot of storage space without making search results worse. This opens up new chances for efficient and scalable search applications.</p><h2>Paving the way for binary quantization</h2><p>The optimization to reduce each dimension to 4 bits not only delivers significant compression gains but also lays the groundwork for further advancements in compression efficiency. Specifically, future advancements like binary quantization into Lucene, a development that has the potential to revolutionize vector storage and retrieval.</p><p>In an ongoing effort to push the boundaries of compression in vector search, we are actively working on integrating binary quantization into Lucene using the same techniques and principles that underpin our existing optimization strategies. The goal is to achieve binary quantization of vector dimensions, thereby reducing the size of the vector representation by a factor of 32 compared to the original floating-point format.</p><p>Through our iterations and experiments, we want to deliver the full potential of vector search while maximizing resource utilization and scalability. Stay tuned for further updates on our progress towards integrating binary quantization into Lucene and Elasticsearch, and the transformative impact it will have on vector database storage and retrieval.</p><h2>Multi-vector integration in Lucene and Elasticsearch</h2><p>Several real world applications rely on text embedding models and large text inputs. Most embedding models have token limits, which necessitate chunking of longer text into passages. Therefore, instead of a single document, multiple passages and embeddings must be managed, potentially complicating metadata preservation.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte7cf9100604bef40/6a17d793033c8d5c696baff7/b8a6073b44c078ef8ee5294e559cf8092bf40e38-1440x903.png" alt="" /><p>Now instead of having a single piece of metadata indicating, for example the first chapter of the book “Little Women”, you have to index that information data for every sentence.</p><p>Lucene's "join" functionality, integral to Elasticsearch's nested field type, offers a solution. This feature enables multiple nested documents within a top-level document, allowing searches across nested documents and subsequent joins with their parent documents. So, how do we deliver support for vectors in nested fields with Elasticsearch?</p><p>The key lies in how Lucene joins back to parent documents when searching child vector passages. The parallel concept here is the debate around pre-filtering versus post-filtering in kNN methods, as the timing of joining significantly impacts result quality and quantity. To address this, <a href="https://www.elastic.co/search-labs/blog/adding-passage-vector-search-to-lucene">recent enhancements to Lucene</a> enable pre-joining against parent documents while searching the HNSW graph.</p><p>Practically, pre-joining ensures that when retrieving the k nearest neighbors of a query vector, the algorithm returns the k nearest documents instead of passages. This approach diversifies results without complicating the HNSW algorithm, requiring only a minimal additional memory overhead per stored vector.</p><p>Efficiency is improved by leveraging certain restrictions, such as disjoint sets of parent and child documents and the monotonicity of document IDs. These restrictions allow for optimizations using bit sets, providing rapid identification of parent document IDs.</p><p>Searching through a vast number of documents efficiently required investing in nested fields and joins in Lucene. This work helps storage and search for dense vectors that represent passages within long texts, making document searches in Lucene more effective. Overall, these advancements represent an exciting step forward in the area of vector database retrieval within Lucene.</p><h2>Wrapping up (for now)</h2><p>We're dedicated to making Elasticsearch and Lucene the best vector database with every release. Our goal is to make it easier for people to search for things. With some of the investments we discuss in this blog, there is significant progress, but we're not done!</p><p>To say that the gen AI ecosystem is rapidly evolving is an understatement. At Elastic, we want to give developers the most flexible and open tools to keep up with all the innovation—with features available across recent releases until 8.13 and <a href="https://www.elastic.co/blog/elastic-serverless-architecture">serverless</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-lucene-vector-database-gains</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-lucene-vector-database-gains</guid>
    <category><![CDATA[Vector Database]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Mayya Sharipova,Benjamin Trent,Jim Ferenczi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt35f29a49abcd2e22/6a17d78ae31791cd572d5682/103e9a7a97e9c219edb028e0fc675346920002cc-974x407.png" length="0" type="image/png"/>
    <pubDate>Fri, 26 Apr 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Introducing kNN Query: An expert way to do kNN search]]></title>
    <description><![CDATA[Explore how the kNN query in Elasticsearch can be used and how it differs from top-level kNN search, including examples.]]></description>
    <content:encoded><![CDATA[<h3>kNN search as a top-level section</h3><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">kNN search</a> in Elasticsearch is organized as a top level section of a search request. We have designed it this way so that:</p><ul><li><p>It can always return global k nearest neighbors regardless of a number of shards</p></li><li><p>These global k results are combined with a results from other queries to form a hybrid search</p></li><li><p>The global k results are passed to aggregations to form facets.</p></li></ul><p>Here is a simplified diagram how kNN search is executed internally (some phases are omitted) :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf9df3a17c59cedde/6a170b2aab7f08ec09db9e96/24e227a8482aead389c0ad6298779fd8610006f7-2849x1020.gif" alt="Execution for top level kNN search" /><p>Figure 1: The steps for the top level kNN search are:</p><ol><li><p>A user submits a search request</p></li><li><p>The coordinator node sends a kNN search part of the request to data nodes in the DFS phase</p></li><li><p>Each data node runs kNN search and sends back the local top-k results to the coordinator</p></li><li><p>The coordinator merges all local results to form the global top k nearest neighbors.</p></li><li><p>The coordinator sends back the global k nearest neighbors to the data nodes with any additional queries provided</p></li><li><p>Each data node runs additional queries and sends back the local <code>size</code> results to the coordinator</p></li><li><p>The coordinator merges all local results and sends a response to the user</p></li></ol><p>We first run kNN search in the DFS phase to obtain the global top k results. These global k results are then passed to other parts of the search request, such as other queries or aggregations. Even the execution looks complex, from a user’s perspective this model of running kNN search is simple, as the user can always be sure that kNN search returns the global k results.</p><h3>Introducing kNN query in Elasticsearch</h3><p>With time we realized there is also a need to represent kNN search as a query. Query is a core component of a search request in Elasticsearch, and representing kNN search as a query allows for flexibility to combine it with other queries to address more complex requests.</p><p>kNN query, unlike the top level kNN search, doesn’t have a <code>k</code> parameter. The number of results (nearest neighbors) returned is defined by the <code>size</code> parameter, as in other queries. Similar to kNN search, the <code>num_candidates</code> parameter defines how many candidates to consider on each shard while executing a kNN search.</p>GET products/_search
{
 "size" : 3,
 "query": {
   "knn": {
     "field": "embedding",
     "query_vector": [2,2,2,0],
     "num_candidates": 10
   }
 }
}

<p>kNN query is executed differently from the top level kNN search. Here is a simplified diagram that describes how a kNN query is executed internally (some phases are omitted):</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf2768a24d52acbbd/6a170b2c7d8d6762fd70e71a/fe505bf7e95ae94c0f7605df1348ef15536134b9-2849x1020.gif" alt="Execution for kNN query" /><p>Figure 2: The steps for query based kNN search are:</p><ol><li><p>A user submits a search request</p></li><li><p>The coordinator sends to the data nodes a kNN search query with additional queries provided</p></li><li><p>Each data node runs the query and sends back the local size results to the coordinator node</p></li><li><p>The coordinator node merges all local results and sends a response to the user</p></li></ol><p>We run kNN search on a shard to get <code>num_candidates</code> results; these results are passed to other queries and aggregations on a shard to get size results from the shard. As we don’t collect the global k nearest neighbors first, in this model the number of nearest neighbors collected and visible for other queries and aggregations depend on the number of shards.</p><h3>kNN query API examples</h3><p>Let’s look at API examples that demonstrate differences between the top level kNN search and kNN query.</p><p>We create an index of products and index some documents:</p>PUT products
{
 "mappings": {
   "dynamic": "strict",
   "properties": {
     "department": {
       "type": "keyword"
     },
     "brand": {
       "type": "keyword"
     },
     "description": {
       "type": "text"
     },
     "embedding": {
       "type": "dense_vector",
       "index": true,
       "similarity": "l2_norm"
     },
     "price": {
       "type": "float"
     }
   }
 }
}
POST products/_bulk?refresh=true
{"index":{"_id":1}}
{"department":"women","brand": "Levi's", "description":"high-rise red jeans","embedding":[1,1,1,1],"price":100}
{"index":{"_id":2}}
{"department":"women","brand": "Calvin Klein","description":"high-rise beautiful jeans","embedding":[1,1,1,1],"price":250}
{"index":{"_id":3}}
{"department":"women","brand": "Gap","description":"every day jeans","embedding":[1,1,1,1],"price":50}
{"index":{"_id":4}}
{"department":"women","brand": "Levi's","description":"jeans","embedding":[2,2,2,0],"price":75}
{"index":{"_id":5}}
{"department":"women","brand": "Levi's","description":"luxury jeans","embedding":[2,2,2,0],"price":150}
{"index":{"_id":6}}
{"department":"men","brand": "Levi's", "description":"jeans","embedding":[2,2,2,0],"price":50}
{"index":{"_id":7}}
{"department":"women","brand": "Levi's", "description":"jeans 2023","embedding":[2,2,2,0],"price":150}
<p>kNN query similar to the top level kNN search, has <code>num_candidates</code> and an internal <code>filter</code> parameter that acts as a pre-filter.</p>GET products/_search
{
 "size" : 3,
 "query": {
   "knn": {
     "field": "embedding",
     "query_vector": [2,2,2,0],
     "num_candidates": 10,
     "filter" : {
       "term" : {
         "department" : "women"
       }
     }
   }
 }
} 
<p>kNN query can get more diverse results than kNN search for collapsing and aggregations. For the kNN query below, on each shard we execute kNN search to obtain 10 nearest neighbors which are then passed to collapse to get 3 top results. Thus, we will get 3 diverse hits in a response.</p>GET products/_search
{
 "size" : 3,
 "query": {
   "knn": {
     "field": "embedding",
     "query_vector": [2,2,2,0],
     "num_candidates": 10,
     "filter" : {
       "term" : {
         "department" : "women"
       }
     }
   }
 },
 "collapse": {
   "field": "brand"        
 }
}
<p>The top level kNN search first gets the global top 3 results in the DFS phase, and then passes them to collapse in the query phase. We will get only 1 hit in a response, as all the global 3 nearest neighbors happened to be from the same brand.</p>GET products/_search?size=3
{
 "knn" : {
   "field": "embedding",
     "query_vector": [2,2,2,0],
     "k" : 3,
     "num_candidates": 10,
     "filter" : {
       "term" : {
         "department" : "women"
       }
     }
 },
 "collapse": {
   "field": "brand"        
 }
}
<p>Similarly for aggregations, a kNN query allows us to get 3 distinct buckets, while kNN search only allows 1.</p>GET products/_search
{
"size": 0,
"query": {
   "knn": {
     "field": "embedding",
     "query_vector": [2,2,2,0],
     "num_candidates": 10,
     "filter" : {
       "term" : {
         "department" : "women"
       }
     }
   }
 },
 "aggs": {
   "brands": {
     "terms": {
       "field": "brand"
     }
   }
 }
}
​
GET products/_search
{
"size": 0,
"knn" : {
 "field": "embedding",
   "query_vector": [2,2,2,0],
   "k" : 3,
   "num_candidates": 10,
   "filter" : {
     "term" : {
       "department" : "women"
     }
   }
 },
 "aggs": {
   "brands": {
     "terms": {
       "field": "brand"
     }
   }
 }
}
<p>Now, let’s look at other examples that show the flexibility of the kNN query. Specifically, how it can be flexibly combined with other queries.</p><p>kNN can be a part of a <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html">boolean</a> query (with a caveat that all external query filters are applied as post-filters for kNN search). We can use a <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html#named-queries">_name</a> parameter for kNN query to enhance results with extra information that tells if the kNN query was a match and its score contribution.</p>GET products/_search?include_named_queries_score
{
 "size": 3,
 "query": {
   "bool": {
     "should": [
       {
         "knn": {
           "field": "embedding",
           "query_vector": [2,2,2,0],
           "num_candidates": 10,
           "_name": "knn_query"
         }
       },
       {
         "match": {
           "description": {
             "query": "luxury",
             "_name": "bm25query"
           }
         }
       }
     ]
   }
 }
}
<p>kNN can also be a part of complex queries, such as a <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-pinned-query.html">pinned</a> query. This is useful when we want to display the top nearest results, but also want to promote a selected number of other results.</p>GET products/_search
{
 "size": 3,
 "query": {
   "pinned": {
     "ids": [ "1", "2" ],
     "organic": {
       "knn": {
           "field": "embedding",
           "query_vector": [2,2,2,0],
           "num_candidates": 10,
           "_name": "knn_query"
         }
     }
   }
 }
}
<p>We can even make the kNN query a part of our <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html">function_score</a> query. This is useful when we need to define custom scores for results returned by kNN query: ​</p>GET products/_search
{
 "size": 3,
 "query": {
   "function_score": {
     "query": {
       "knn": {
           "field": "embedding",
           "query_vector": [2,2,2,0],
           "num_candidates": 10,
           "_name": "knn_query"
         }
     },
     "functions": [
       {
         "filter": { "match": { "department": "men" } },
         "weight": 100
       },
       {
         "filter": { "match": { "department": "women" } },
         "weight": 50
       }
     ]
   }
 }
}
<p>kNN query being a part of <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-dis-max-query.html">dis_max</a> query is useful when we want to combine results from kNN search and other queries, so that a document’s score comes from the highest ranked clause with a tie breaking increment for any additional clause. ​</p>GET products/_search
{
 "size": 5,
 "query": {
   "dis_max": {
     "queries": [
       {
         "knn": {
           "field": "embedding",
           "query_vector": [2,2, 2,0],
           "num_candidates": 3,
           "_name": "knn_query"
         }
       },
       {
         "match": {
           "description": "high-rise jeans"
         }
       }
     ],
     "tie_breaker": 0.8
   }
 }
}
<p>kNN search as a query has been introduced with the 8.12 release. Please try it out, and we would appreciate any feedback.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/knn-query-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/knn-query-elasticsearch</guid>
    <category><![CDATA[Vector Database]]></category>
    <dc:creator><![CDATA[Mayya Sharipova,Benjamin Trent]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd528ca7843f1946f/6a170b2e2b835ff0b7f4b21b/d2c2a3cddc393d80b11e4ed93672e345d0addd7d-1024x1024.png" length="0" type="image/png"/>
    <pubDate>Thu, 07 Dec 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Bringing maximum-inner-product into Lucene]]></title>
    <description><![CDATA[Explore how we brought maximum-inner-product into Lucene and the investigations undertaken to ensure its support.]]></description>
    <content:encoded><![CDATA[<p>Currently Lucene restricts <code>dot_product</code> to be only used over normalized vectors. Normalization forces all <a href="https://en.wikipedia.org/wiki/Magnitude_(mathematics)#Euclidean_vector_space">vector magnitudes</a> to equal one. While for many cases this is acceptable, it can cause relevancy issues for certain data sets. A prime example are embeddings built by <a href="https://cohere.com/">Cohere</a>. Their vectors use magnitudes to provide more relevant information.</p><p>So, why not allow non-normalized vectors in dot-product and thus enable maximum-inner-product? What's the big deal?</p><h2>Negative values and Lucene optimizations</h2><p>Lucene requires non-negative scores, so that matching one more clause in a disjunctive query can only make the score greater, not lower. This is actually important for dynamic pruning optimizations such as <a href="https://www.elastic.co/blog/faster-retrieval-of-top-hits-in-elasticsearch-with-block-max-wand">block-max WAND</a>, whose efficiency is largely defeated if some clauses may produce negative scores. How does this requirement affect non-normalized vectors?</p><p>In the normalized case, all vectors are on a unit sphere. This allows handling negative scores to be simple scaling.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta9a1c9f6f1c8eebd/6a170da3cdacbf64197d2a61/b6ddddc9103479474c3bdb5f3b5d0ef0491fee7f-1179x1209.png" alt="Normalized Vectors" /><p>Figure 1: Two opposite, two dimensional vectors in a 2d unit sphere (e.g. a unit circle). When calculating the dot-product here, the worst it can be is -1 = [1, 0] * [-1, 0]. Lucene accounts for this by adding 1 to the result.</p><p>With vectors retaining their magnitude, the range of possible values is unknown.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7fd8462a258df0ec/6a170da41949f744c8e7aaac/0a8549e941e3b4dec79905e00e22e04b38b469c7-1181x1209.png" alt="Normalized Vectors" /><p>Figure 2: When calculating the dot-product for these vectors <code>[2, 2] \* [-5, -5] = -20</code></p><p>To allow Lucene to utilize blockMax WAND with non-normalized vectors, we must scale the scores. This is a fairly simple solution. Lucene will scale non-normalize vectors with a simple piecewise function:</p>if (dotProduct &lt; 0) {
  return 1 / (1 + -1 * dotProduct);
}
return dotProduct + 1;
<p>Now all negative scores are between 0-1, and all positives are scaled above 1. This still ensures that higher values mean better matches and removes negative scores. Simple enough, but this is not the final hurdle.</p><h2>The triangle problem</h2><p>Maximum-inner-product doesn't follow the same rules as of <a href="https://en.wikipedia.org/wiki/Euclidean_space">simple euclidean spaces</a>. The simple assumed knowledge of the <a href="https://en.wikipedia.org/wiki/Triangle_inequality">triangle inequality</a> is abandoned. Unintuitively, a vector is no longer nearest to itself. This can be troubling. Lucene’s underlying index structure for vectors is Hierarchical Navigable Small World (HNSW). This being a graph based algorithm, it might rely on euclidean space assumptions. Or would exploring the graph be too slow in non-euclidean space?</p><p>Some research has indicated that a transformation into <a href="https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/XboxInnerProduct.pdf">euclidean space is required for fast search</a>. Others have gone through the trouble of <a href="https://blog.vespa.ai/announcing-maximum-inner-product-search/">updating their vector storage</a> enforcing transformations into euclidean space.</p><p>This caused us to pause and dig deep into some data. The key question is this: does HNSW provide good recall and latency with maximum-inner-product search? While the original <a href="https://arxiv.org/pdf/1603.09320.pdf">HNSW paper</a> and <a href="http://boytsov.info/pubs/thesis_boytsov.pdf">other published research</a> indicate that it does, we needed to do our due diligence.</p><h2>Experiments and results: Maximum-inner-product in Lucene</h2><p>The experiments we ran were simple. All of the experiments are over real data sets or slightly modified real data sets. This is vital for benchmarking as modern neural networks create vectors that adhere to specific characteristics (<a href="https://arxiv.org/pdf/1908.10396.pdf">see discussion in section 7.8 of this paper</a>). We measured latency (in milliseconds) vs. recall over non-normalized vectors. Comparing the numbers with the same measurements but with a euclidean space transformation. In each case, the vectors were indexed into Lucene’s HNSW implementation and we measured for 1000 iterations of queries. Three individual cases were considered for each dataset: data inserted ordered by magnitude (lesser to greater), data inserted in a random order, and data inserted in reverse order (greater to lesser).</p><p>Here are some results from real datasets from Cohere:</p><p>Figure 3: Here are results for the Cohere’s Multilingual model embedding wikipedia articles. <a href="https://huggingface.co/datasets/Cohere/wikipedia-22-12-en-embeddings">Available on HuggingFace</a>. The first 100k documents were indexed and tested.</p><p>Figure 4: This is a mixture of Cohere’s English and Japanese embeddings over wikipedia. <a href="https://huggingface.co/datasets/Cohere/wikipedia-22-12-en-embeddings">Both</a> <a href="https://huggingface.co/datasets/Cohere/wikipedia-22-12-ja-embeddings">datasets</a> are available on HuggingFace.</p><p>We also tested against some synthetic datasets to ensure our rigor. We created a data set with <a href="https://huggingface.co/intfloat/e5-small-v2">e5-small-v2</a> and scaled the vector's magnitudes by different statistical distributions. For brevity, I will only show two distributions.</p><p>Figure 5: <a href="https://en.wikipedia.org/wiki/Pareto_distribution">Pareto distribution</a> of magnitudes. A pareto distribution has a “fat tail” meaning there is a portion of the distribution with a much larger magnitude than others.</p><p>Figure 6: <a href="https://en.wikipedia.org/wiki/Gamma_distribution">Gamma distribution</a> of magnitudes. This distribution can have high variance and makes it unique in our experiments.</p><p>In all our experiments, the only time where the transformation seemed warranted was the synthetic dataset created with the gamma distribution. Even then, the vectors must be inserted in reverse order, largest magnitudes first, to justify the transformation. These are exceptional cases.</p><p>If you want to read about all the experiments, and about all the mistakes and improvements along the way, here is the <a href="https://github.com/apache/lucene/issues/12342">Lucene Github issue</a> with all the details (and mistakes along the way). Here’s one for open research and development!</p><h2>Conclusion</h2><p>This has been quite a journey requiring many investigations to make sure maximum-inner-product can be supported in Lucene. We believe the data speaks for itself. No significant transformations required or significant changes to Lucene. All this work will soon unlock maximum-inner-product support with Elasticsearch and allow models like the ones provided by Cohere to be first class citizens in the Elastic Stack.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/lucene-bringing-maximum-inner-product-to-lucene</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/lucene-bringing-maximum-inner-product-to-lucene</guid>
    <category><![CDATA[Lucene]]></category>
    <dc:creator><![CDATA[Benjamin Trent]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt762b38f91bd71c8e/6a170db8b339d547bb76a048/368db71c500e72d20fe225fe44c2c40231e29765-721x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 01 Sep 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Aggregate data faster with new the random_sampler aggregation]]></title>
    <description><![CDATA[Aggregate billions of documents in milliseconds instead of minutes with Elastic. Learn more about how the new random_sampler aggregation gives you statistically robust results at a lower cost.]]></description>
    <content:encoded><![CDATA[<p>With 8.2, the Elastic Stack gives users the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/master/search-aggregations-random-sampler-aggregation.html"><code>random_sampler</code></a> aggregation. It adds the capability to randomly sample documents in a statistically robust manner. Randomly sampling documents in aggregations allows you to balance speed and accuracy at query time. You can aggregate billions of documents with high accuracy at a fraction of the latency. This allows you to achieve faster results with fewer resources and comparable accuracy — all with a simple aggregation.</p><p>Let's run through some basic details, best practices, and how it works, so you can try it out in the Elasticsearch Service today.</p><h2>Delivering speed and accuracy</h2><p>Random sampling in Elasticsearch has never been easier or faster. If your query has many aggregations, you can quickly obtain results by using the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/master/search-aggregations-random-sampler-aggregation.html"><code>random_sampler</code></a> aggregation.</p>POST _search?size=0&amp;track_total_hits=false
{
  "aggs": {
    "sampled": {
      "random_sampler": {
        "probability": 0.001,
        "seed": 42
      },
      "aggs": {
        ...
      }
    }
  }
}
<p>All the above aggregations nested under <code>random_sampler</code> will return sampled results. Each agg is roughly seeing only 0.1% of the documents (or 1 in every 1000th document). Where computational cost correlates with the number of documents, the aggregation speed increases. You may have also noticed the “<code>seed</code>” parameter. You can provide a <code>seed</code>to get consistent results on the same shards. Without a seed, a new random subset of documents is considered and you may get slightly different aggregated results.</p><p>How much faster is the <code>random_sampler</code>? The speed improves according to the provided probability as fewer documents are aggregated. The improvements relative to probability will eventually flatten out. Each aggregation has its own computational overhead regardless of the number of documents. An example of this overhead cost is comparing multi-bucket to single metric aggregations. Multi-bucket aggregations have a higher overhead due to their bucket handling logic. While speed is improved for multi-bucket aggregations, the rate of that speed increase will flatten out sooner than single metric.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1427bb522f9a2e33/6a17113be8fbce617c39fd55/e6a33afc9b30709dd5606bfb23726005f40bb803-800x600.png" alt="expected speedup" /><p>Figure 1. The speedup expected for aggregations of different constant overhead.</p><p>Here are some results on expected speed and error rate over an APM data set of 64 million documents.</p><p>The calculations are from: 300 query and aggregation combinations, 5 seeds, and 9 sampling probabilities. In total, 13,500 separate experiments generated the following graphs for median speedup and median relative error as a function of the downsample factor which is 1 / sample probability.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd264a08140ef2bed/6a17113dacf0883d26be9c4d/77697af30ebd43c64216df0a9e99951191dd612d-800x600.png" alt="median speedup" /><p>Figure 2. Median speedup as a function of the downsample factor (or 1 / probability provided for the sampler).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1f3876b646dd7b14/6a17113e1949f77a4ae7ab20/047bb5dd28bedaee14998168d71ac152e3ea4392-800x600.png" alt="median error" /><p>Figure 3. Median relative error as a function of the downsample factor (or 1 / probability provided for the sampler).</p><p>With a probability of 0.001, for half of the scenarios tested, there was an 80x speed improvement or better with a 4% relative error or less. These tests involved a little over 64 million documents but spread across many shards. More compact shards and larger data can expect better results.</p><p>But, you may ask, do the visualizations look the same?</p><p>Below are two visualizations showing document counts for every 5 minutes over 100+ million documents. The total set loads in seconds and is sampled in milliseconds. This is with almost no discernible visual difference.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0e84bd402ad8ec58/6a171140b339d56a6976a0c2/925ab6b529edb5065ec7d0f1886f7bc3eaa7da62-800x158.png" alt="sampled vs unsampled count" /><p>Figure 4. Sampled vs unsampled document count visualizations.</p><p>Here is another example. This time the average transaction by hour is calculated and visualized. While visually these are not exactly the same, the overall trends are still evident. For a quick overview of the data to catch trends, sampling works marvelously.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt583f18fbd2c02001/6a171141acf0885acabe9c51/1998189c574de515e1bff2dca886bd923a2af584-800x250.png" alt="sampled vs unsampled average" /><p>Figure 5. Sampled vs. unsampled average transaction time by hour visualization.</p><h2>Best practices for using sampling aggregation</h2><p>Sampling shines when you have a large data set. In these cases you might ask, should I sample before the data is indexed in Elasticsearch? Sampling at query time and before ingestion are complimentary. Each has its distinct advantages.</p><p>When sampling at ingest time, it can save disk and indexing costs. However, if your data has multiple facets, you have to stratify sampling over facets when sampling before ingestion, unless you know exactly how it will be queried. This suffers from the <a href="https://en.wikipedia.org/wiki/Curse_of_dimensionality">curse of dimensionality</a> and you could end up with underrepresented sets of facets. Furthermore, you have to cater for the worst case when sampling before ingestion. For example, if you want to compute percentiles for two queries, one which matches 50% of the documents and one which matches 1% of documents in an index, you can get away with 7X more downsampling for the first query and achieve the same accuracy.</p><p>Here is a summary of what to expect from sampling with the <code>random_sampler</code> at query time.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt908ecf7a4a90ba82/6a1711436234e00ddcdb1ada/3a1cc19429ee33b95e6a0ab6eed2413665a5e0bd-640x480.png" alt="relative error" /><p>Figure 6. Relative error for different aggregations.</p><p>Sampling accuracy varies across aggregations (see Figure 5 for some examples). Here is a list of some aggregations in order of descending accuracy: <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-percentile-aggregation.html">percentiles</a>, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-valuecount-aggregation.html">counts</a>, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-avg-aggregation.html">means</a>, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-sum-aggregation.html">sums</a>, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-extendedstats-aggregation.html">variance</a>, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-min-aggregation.html">minimum</a>, and <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-max-aggregation.html">maximum</a>. Metric aggregation accuracy will also be affected by the underlying data variation: the lower the variation in the values, the fewer samples you need to get accurate aggregate values. The minimum and maximum will not be reliable with outliers, since there is always a reasonable chance that the sampled set misses the one very large (or small) value in the data set. If you are using terms aggregations (or some partitioning such as date histogram), aggregate values for terms (or buckets) with few values will be less accurate or missed altogether.</p><p>Aggregations also have fixed overheads (see Figure 1 for an example). This means as the sample size decreases, the performance improvement will eventually level out. Aggregations which have many buckets have higher overheads and so the speedup you will gain from sampling is smaller. For example, a <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html">terms aggregation</a> for a high cardinality field will show less performance benefit.</p><p>If in doubt, some simple experiments will often suffice to determine good settings for your data set. For example, suppose you want to speed up a dashboard; try reducing the sample probability while the visualizations look similar enough. Chances are your data characteristics will be stable and so this setting will remain reliable.</p><h2>Uncovering how sampling works</h2><p>Sampling considers the entire document set within a shard. Once it creates the sampled document set, sampling applies any provided user filter. The documents that match the filter and are within the sampled set are then aggregated (see Figure 7).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfed879f2c11f87c2/6a171144ab7f08018ddb9f8f/9145ccea9747229b2d08a8badf6120c20bd0e271-800x227.png" alt="request data flow" /><p>Figure 7. Typical request and data flow for the random_sampler aggregation.</p><p>The key to the sampling is generating this random subset of the shard efficiently and without statistical biases. Taking <a href="https://en.wikipedia.org/wiki/Geometric_distribution">geometrically distributed random</a> steps through the document set is equivalent to uniform random sampling, meaning each document in the set has an equally likely chance of being selected into the sample set. The advantage of this approach is that the sampling cost scales with p (where p is the probability configured in the aggregation). This means no matter how small p is, the relative latency of performing the sampling adds will remain fixed.</p><h2>Ensuring performance reliability and accuracy</h2><p>To achieve the highest performance, accuracy, and robustness, we evaluated a range of realistic scenarios.</p><p>In the case of <code>random_sampler,</code> the evaluation process is complicated by two factors:</p><ol><li><p>It cuts right across the aggregation framework and so it needs to be evaluated with many different combinations of query and aggregation,</p></li><li><p>The results are random numbers, so rather than running just once, you need to run multiple times and test the statistical properties of the result set.</p></li></ol><p>We began with a proof of concept that showed that the overall strategy worked and the performance characteristics were remarkable. However, there are multiple factors which can affect implementation performance and accuracy. For example, we found the off-the-shelf sampling code for the geometric distribution was not fast enough. We decided to roll our own using some tricks to extract more random samples per random bit along with a very fast quantized version of the log function. You also need to be careful that you are generating statistically independent samples for different shards. In summary, as is often the case, the devil is in the details.</p><p>Undaunted, we wrote a test harness using the <a href="https://elasticsearch-py.readthedocs.io/en/stable/">Elastic Python client</a> to programmatically generate aggregations and queries, and perform statistical tests of quality.</p><p>We wanted the approximations we produce to be unbiased. This means if you run a sampled aggregation repeatedly and averaged the results it would converge towards the true value. Standard machinery allows you to test if there is statistically significant evidence of bias. We used a <a href="https://en.wikipedia.org/wiki/Student%27s_t-test">t-test</a> for the difference between the statistic and true value for each aggregation. In over 300 different experiments, the minimum p-value was around 0.0003 which — given we ran 300 experiments — has about a 9% odds of occurring by chance. This is a little low, but not enough to worry about; furthermore the median p-value was 0.38.</p><p>We also tested whether various index properties affect the statistical properties. For example, we wanted to see if we could measure a statistically significant difference between the distribution of results with and without index sorting. A <a href="https://en.wikipedia.org/wiki/Kolmogorov%E2%80%93Smirnov_test">K-S test</a> can be used to check if samples come from the same distribution. In our 300 experiments the smallest p-value was around 0.002 which occurs with odds of about 45% by chance.</p><h2>Get started today</h2><p>We're not done with this feature yet. Once you have the ability to generate fast approximate results, a key question is: how accurate are those results? We're planning to integrate a confidence interval calculation directly into the aggregation framework to answer this efficiently in a future release. Learn more about random_sampler_aggregation in this documentation. You can explore this feature and more with a <a href="https://cloud.elastic.co/registration?elektra=whats-new-elastic-8-1-0-blog">free 14-day trial of Elastic Cloud</a>.</p><p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/aggregate-data-faster-with-new-the-random-sampler-aggregation</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/aggregate-data-faster-with-new-the-random-sampler-aggregation</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Benjamin Trent,Thomas Veasey]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte47734cb679b1cc8/6a171146a929cf44d5ae0ac5/bc75e4b6f15f183c75c931db011791301523d0cb-1217x840.png" length="0" type="image/png"/>
    <pubDate>Wed, 20 Apr 2022 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>