<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.4">Jekyll</generator><link href="https://opensearch.org/feed.xml" rel="self" type="application/atom+xml" /><link href="https://opensearch.org/" rel="alternate" type="text/html" /><updated>2025-01-09T20:51:58+00:00</updated><id>https://opensearch.org/feed.xml</id><title type="html">OpenSearch</title><subtitle></subtitle><entry><title type="html">Announcing OpenSearchCon Europe 2025</title><link href="https://opensearch.org/blog/announcing-OpenSearchCon-europe-2025/" rel="alternate" type="text/html" title="Announcing OpenSearchCon Europe 2025" /><published>2025-01-09T00:00:00+00:00</published><updated>2025-01-09T20:49:28+00:00</updated><id>https://opensearch.org/blog/announcing-OpenSearchCon-europe-2025</id><content type="html" xml:base="https://opensearch.org/blog/announcing-OpenSearchCon-europe-2025/"><![CDATA[<p><img src="/assets/media/blog-images/2025-01-13-announcing-OpenSearchCon-europe-2025/OpenSearchConEU2025-banner.png" alt="OpenSearchCon Europe 2025" class="img-centered" /></p>

<p>We are delighted to announce <a href="https://events.linuxfoundation.org/opensearchcon-europe/">OpenSearchCon Europe 2025</a>, the premier event for the OpenSearch community in Europe! Mark your calendars for 30 April and 1 May as we bring together developers, IT professionals, and business leaders in Amsterdam for an immersive exploration of the world of open-source search and analytics.</p>

<h2 id="event-highlights">Event highlights</h2>

<p>This year’s OpenSearchCon Europe promises to be an exciting gathering of minds, featuring:</p>

<ul>
  <li>Presentations from industry leaders and OpenSearch core contributors.</li>
  <li>Technical deep dives and hands-on workshops.</li>
  <li>Case studies and success stories from organizations leveraging OpenSearch.</li>
  <li>Networking opportunities with peers and experts in the field.</li>
  <li>An exhibition area showcasing the latest innovations in the OpenSearch ecosystem.</li>
</ul>

<h2 id="why-attend">Why attend?</h2>

<p>Whether you’re a seasoned OpenSearch user or just beginning your journey, OpenSearchCon Europe 2025 offers something for everyone:</p>

<ul>
  <li>Learn about the latest features and roadmap for OpenSearch.</li>
  <li>Gain insights into best practices for deployment, scaling, and optimization.</li>
  <li>Explore real-world applications of OpenSearch across various industries.</li>
  <li>Contribute to the future direction of the project through community sessions.</li>
  <li>Connect and network with folks you might collaborate with in the future.</li>
</ul>

<h2 id="call-for-proposals">Call for proposals</h2>

<p>Our call for proposals is now open. We invite speakers to share their knowledge and experience with the community in the following areas:</p>

<ul>
  <li>Search: Connect with other search and relevance practitioners with demos, deep dives, or new and novel techniques for building rich search applications.</li>
  <li>Analytics, Security, and Observability: Deploying observability tools at scale? Using OpenSearch to turn log data into insights or to help protect your infrastructure? We want to hear from you!</li>
  <li>Community: Engage a community of builders on a topic that means something to you. Anything that might educate and inspire a community of open-source technologists is up for consideration here.</li>
  <li>Operating OpenSearch: Have you have built out an implementation of OpenSearch? Do you use OpenSearch in a solution that helps your own business or project? Share how you operate OpenSearch.</li>
</ul>

<p><a href="https://events.linuxfoundation.org/opensearchcon-europe/program/cfp/">Submit your proposal</a> by 19 January for a chance to present at OpenSearchCon Europe 2025.</p>

<h2 id="registration">Registration</h2>

<p>Early bird registration is now open! Secure your spot at OpenSearchCon Europe 2025 and take advantage of discounted rates available for a limited time. <a href="https://events.linuxfoundation.org/opensearchcon-europe/register/">Register now</a>!</p>

<h2 id="sponsorship-opportunities">Sponsorship opportunities</h2>

<p>Showcase your brand and connect with the OpenSearch community by becoming a sponsor. Various sponsorship packages are available to suit different goals and budgets. Learn more <a href="https://events.linuxfoundation.org/opensearchcon-europe/sponsor/">here</a>.</p>

<h2 id="stay-updated">Stay updated</h2>

<p>For the latest information on speakers, schedules, and special events, follow us on social media:
<a href="https://www.linkedin.com/company/opensearch-project/">LinkedIn</a> - <a href="https://fosstodon.org/@OpenSearchProject">Mastodon</a> - <a href="https://x.com/OpenSearchProj">X/Twitter</a></p>

<p>If you have questions, feel free to reach out on the <a href="https://opensearch.slack.com/archives/C05K9ER975Z">#opensearchcon</a> channel on <a href="https://opensearch.org/slack.html">Slack</a>.</p>

<p>We can’t wait to see you at #OpenSearchCon Europe 2025 as we shape the future of open-source search and analytics together!</p>]]></content><author><name>krisfreedain</name></author><category term="community" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Solving the cold start search problem in OpenSearch</title><link href="https://opensearch.org/blog/cold-start-search/" rel="alternate" type="text/html" title="Solving the cold start search problem in OpenSearch" /><published>2025-01-07T00:00:00+00:00</published><updated>2025-01-09T20:49:28+00:00</updated><id>https://opensearch.org/blog/cold-start-search</id><content type="html" xml:base="https://opensearch.org/blog/cold-start-search/"><![CDATA[<p>Upgrading to OpenSearch offers many advantages, but it can also introduce unexpected challenges. One such issue we’ve encountered while assisting with upgrades from older Elasticsearch versions is the “cold start search” problem. You might notice that the first search after a period of inactivity is unusually slow, even though subsequent searches perform as expected. This blog post will explore the root cause of this behavior and offer potential solutions tailored to your needs.</p>

<h2 id="understanding-the-cold-start-search-problem">Understanding the cold start search problem</h2>

<p>After upgrading from Elasticsearch 6.x to OpenSearch (or even to later Elasticsearch versions), you may see a pattern: the first search after some inactivity is slow, while subsequent searches run much faster. After another idle period, the slow search recurs. This issue is particularly noticeable in non-production environments, where search activity isn’t as constant as in live systems. The following image presents a typical search rate metric illustrating this behavior.</p>

<p><img src="/assets/media/blog-images/2024-12-23-cold-start-search/search-metric.png" alt="Search rate metric" /></p>

<p>At first glance, this might look like a cache-warming issue. However, the pattern persists even for queries that don’t use caching. Both simple and complex queries are affected equally, and slow logs don’t identify these as slow queries. This means that caching or query complexity isn’t the cause of the problem.</p>

<h2 id="uncovering-the-root-cause">Uncovering the root cause</h2>

<p>Through detailed investigation using <a href="https://opensearch.org/docs/latest/install-and-configure/configuring-opensearch/logs/#shard-slow-logs">search slow logs</a> and <a href="https://opensearch.org/docs/latest/api-reference/profile/">query profiling</a>, we traced the root cause to two key settings in OpenSearch:</p>

<ul>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">refresh_interval</code></strong>: OpenSearch buffers newly indexed documents in memory until a refresh operation transfers them to searchable segments. By default, <code class="language-plaintext highlighter-rouge">refresh_interval</code> is set to 1 second for near real-time (NRT) search. However, if a shard becomes idle (determined by the <code class="language-plaintext highlighter-rouge">index.search.idle.after</code> time period), it stops refreshing until a search request triggers a refresh.</p>
  </li>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">index.search.idle.after</code></strong>: This setting defines how long a shard can stay idle before it stops automatic refreshes. Its default value is 30 seconds. While this improves bulk indexing performance by reducing refresh frequency, it introduces a delay for the first search after a period of inactivity.</p>
  </li>
</ul>

<p>When upgrading from Elasticsearch 6.x to OpenSearch or Elasticsearch 7.x, this behavior can cause the first search after a long idle period to wait for the refresh to complete before executing. Older Elasticsearch versions didn’t exhibit this behavior because <code class="language-plaintext highlighter-rouge">index.search.idle.after</code> didn’t exist. The severity of the delay depends on how much data needs to be refreshed, which in turn depends on how much indexing occurred during the idle period.</p>

<h2 id="practical-solutions-for-cold-start-searches">Practical solutions for cold start searches</h2>

<p>The best way to address this issue depends on your workload. Below are some common scenarios and recommended solutions:</p>

<ul>
  <li>
    <p><strong>Predictable business hours with idle periods</strong><br />
If your search activity is heavy during specific times (for example, during typical 9–5 work hours) and indexing happens off-hours, you can leave the default settings in place. Perform a <a href="https://opensearch.org/docs/latest/api-reference/index-apis/refresh/">manual refresh</a> before the busy period begins or right after nightly indexing completes.</p>
  </li>
  <li>
    <p><strong>Write-heavy use cases (for example, observability or log analytics)</strong>: For workloads where search latency isn’t as critical, increasing <code class="language-plaintext highlighter-rouge">refresh_interval</code> to 30 or 60 seconds can improve indexing performance. Explicitly setting <code class="language-plaintext highlighter-rouge">refresh_interval</code> avoids interference from <code class="language-plaintext highlighter-rouge">index.search.idle.after</code>.</p>
  </li>
  <li>
    <p><strong>Read-heavy use cases with sporadic writes</strong>: Setting <code class="language-plaintext highlighter-rouge">refresh_interval</code> to 1 second ensures NRT search and eliminates delays caused by idle shards.</p>
  </li>
  <li>
    <p><strong>Balanced workloads (where search latency, indexing, and NRT results are equally important)</strong>: Retain the default settings. Don’t base your decision on behavior in non-production systems because live systems typically have more consistent search activity.</p>
  </li>
  <li>
    <p><strong>Predictable but infrequent searches</strong>: Consider increasing <code class="language-plaintext highlighter-rouge">index.search.idle.after</code> to 5 or 10 minutes if search patterns are predictable. This reduces refresh overhead without affecting responsiveness during active periods.</p>
  </li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>Addressing the cold start search problem requires understanding your specific workload and priorities. Explicitly setting <code class="language-plaintext highlighter-rouge">refresh_interval</code> or adjusting <code class="language-plaintext highlighter-rouge">index.search.idle.after</code> can help, but each solution comes with trade-offs. For most production systems, this issue is less likely to occur because of continuous search activity.</p>

<p>Always test these configurations in your environment to find the right balance for your needs. For more tips on optimizing refresh intervals, check out our <a href="https://opensearch.org/blog/optimize-refresh-interval/">blog post on optimizing OpenSearch refresh intervals</a>.</p>]]></content><author><name>aswath</name></author><category term="technical-posts" /><summary type="html"><![CDATA[Upgrading to OpenSearch offers many advantages, but it can also introduce unexpected challenges. One such issue we’ve encountered while assisting with upgrades from older Elasticsearch versions is the “cold start search” problem. You might notice that the first search after a period of inactivity is unusually slow, even though subsequent searches perform as expected. This blog post will explore the root cause of this behavior and offer potential solutions tailored to your needs.]]></summary></entry><entry><title type="html">Optimizing hybrid search in OpenSearch</title><link href="https://opensearch.org/blog/hybrid-search-optimization/" rel="alternate" type="text/html" title="Optimizing hybrid search in OpenSearch" /><published>2024-12-30T00:00:00+00:00</published><updated>2025-01-09T20:49:28+00:00</updated><id>https://opensearch.org/blog/hybrid-search-optimization</id><content type="html" xml:base="https://opensearch.org/blog/hybrid-search-optimization/"><![CDATA[<h1 id="introduction">Introduction</h1>

<p><a href="https://opensearch.org/docs/latest/search-plugins/hybrid-search">Hybrid search combines lexical and neural search to improve search relevance</a>; this combination shows promising results across industries and <a href="https://opensearch.org/blog/semantic-science-benchmarks/">in benchmarks</a>.</p>

<p>In OpenSearch 2.18, <a href="https://opensearch.org/docs/latest/search-plugins/hybrid-search/">hybrid search</a> is an arithmetic combination of the lexical (match query) and neural (k-NN) search scores. It first normalizes the scores and then combines them with one of three techniques (arithmetic, harmonic, or geometric mean), each of which includes weighting parameters.</p>

<p>The search pipeline configuration is how OpenSearch users define score normalization, combination, and weighting.</p>

<h1 id="finding-the-right-hybrid-search-configuration-can-be-difficult">Finding the right hybrid search configuration can be difficult</h1>

<p>The primary question for a user of hybrid search in OpenSearch is how to choose the normalization and combination techniques and the weighting parameters for their application.</p>

<p>What is best depends strongly on the corpus, on user behavior, and on the application domain—there is no one-size-fits-all solution.</p>

<p>However, there is a systematic way to arrive at this ideal set of parameters. We call identifying the best set of parameters <em>global hybrid search optimization</em>: we identify the best parameter set for all incoming queries; it is “global” because it doesn’t depend on per-query factors. We will cover this approach first before moving on to a dynamic approach that takes into account per-query signals.</p>

<h1 id="global-hybrid-search-optimizer">Global hybrid search optimizer</h1>

<p>We treat hybrid search configuration as a parameter optimization problem. The parameters and combinations are:</p>

<ul>
  <li>Two <a href="https://opensearch.org/blog/How-does-the-rank-normalization-work-in-hybrid-search/">normalization techniques: <code class="language-plaintext highlighter-rouge">l2</code> and <code class="language-plaintext highlighter-rouge">min_max</code></a>.</li>
  <li>Three combination techniques: arithmetic mean, harmonic mean, geometric mean.</li>
  <li>The lexical and neural search weights, which are values ranging from 0 to 1.</li>
</ul>

<p>With this knowledge we can define a collection of parameter combinations to try out and compare. To follow this path we need three things:</p>

<ol>
  <li>Query set: A collection of queries.</li>
  <li>Judgments: A collection of ratings that indicate the relevance of a result for a given query.</li>
  <li>Search quality metrics: A numeric expression indicating how well the search system performs in returning relevant documents for queries.</li>
</ol>

<h2 id="query-set">Query set</h2>

<p>A query set is a collection of queries. Ideally, query sets contain a representative set of queries. “Representative” means that different query classes are included in this query set:</p>

<ul>
  <li>Very frequent queries (head queries) but also queries that are rarely used (tail queries)</li>
  <li>Queries that are important to the business</li>
  <li>Queries that express different user intent classes (for example, searching for a product category, searching for product category + color, searching for a brand)</li>
  <li>Other classes, depending on the individual search application</li>
</ul>

<p>These different queries are best sourced from a query log that captures all queries your users send to your system. One way of sampling these efficiently is <a href="https://opensourceconnections.com/blog/2022/10/13/how-to-succeed-with-explicit-relevance-evaluation-using-probability-proportional-to-size-sampling/">Probability-Proportional-to-Size Sampling</a> (PPTSS). This method can generate a frequency-weighted sample.</p>

<p>We will first run each query in the query set against a baseline to determine our search result quality at the beginning of this experimentation phase.</p>

<h2 id="judgments">Judgments</h2>

<p>Once a query set is available, judgments come next. A judgment describes how relevant a particular document is for a given query. A judgment consists of three parts: the query, the document, and a (typically) numerical rating.</p>

<p>Ratings can be binary (0 or 1, that is, irrelevant or relevant) or graded (for example, 0 to 3, definitely irrelevant to definitely relevant). In the case of explicit judgments, human raters review query-document pairs and assign these ratings. Implicit judgments, on the other hand, are derived from user behavior: user queries and viewed and clicked documents. Implicit judgments can be modeled with <a href="https://clickmodels.weebly.com/">click models that emerged from web search</a> in the early 2010s and range from simple click-through rates to more <a href="https://www.youtube.com/watch?v=wa88XShl7hs">complex approaches</a>. All come with limitations and/or deal differently with biases like position bias.</p>

<p>Recently, a third category of judgment generation has emerged: LLM-as-a-judge. Here a large language model like GPT-4o judges query-doc pairs.</p>

<p>All three categories have different strengths and weaknesses. Whichever you choose, you need to have a decent amount of judgments. Twice the depth of your default search result page per query is usually a good starting point for explicit judgments. So if you show your users 24 results per result page, you should rate the first 48 results for each query.</p>

<p>Implicit judgments have the advantage of scale: when already collecting user events (like queries, viewed documents, and clicked documents), this is an enabling step for calculating thousands of judgments by modeling these events as judgments.</p>

<h2 id="search-metrics">Search metrics</h2>

<p>With a query set and the corresponding judgments, we can calculate search quality metrics. Widely used <a href="https://opensourceconnections.com/blog/2020/02/28/choosing-your-search-relevance-metric/">search metrics are Precision, DCG, or NDCG</a>.</p>

<p>Search metrics provide a way of measuring the search result quality of a search system numerically. We calculate search metrics for each configuration, and this enables us to compare them objectively against each other. As a result we know which configuration scored best.</p>

<p>If you’re looking for guidance and support in generating a query set, creating implicit judgments based on user behavior signals, or calculating metrics based on these signals, feel free to <a href="https://github.com/o19s/opensearch-search-quality-evaluation/">check out the search result quality evaluation framework</a>.</p>

<h2 id="create-a-baseline-with-the-esci-dataset">Create a baseline with the ESCI dataset</h2>

<p>Let’s put all the pieces together and calculate search metrics for one particular example: in the <a href="https://github.com/o19s/opensearch-hybrid-search-optimization/">hybrid search optimizer repository</a> we use the <a href="https://github.com/amazon-science/esci-data">ESCI dataset</a>, and in <a href="https://github.com/o19s/opensearch-hybrid-search-optimization/tree/main/notebooks">notebooks 1–3</a> we configure OpenSearch to run hybrid queries, index the products of the ESCI dataset, create a query set, and execute each of the queries in a lexical search setting that we assume to be our baseline. The search metrics can be calculated because the ESCI dataset comes not only with products and queries but also with judgments.</p>

<p>We chose a <code class="language-plaintext highlighter-rouge">multi_match</code> query of the type <code class="language-plaintext highlighter-rouge">best_fields</code> as our baseline. We search in the different dataset fields with “best guess” fields weights. In a real-world scenario we recommend techniques like learning to boost based on Bayesian optimization to figure out the best field and field weight combination.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{  
  "_source": {  
    "excludes": [  
      "title_embedding"  
    ]  
  },  
  "query": {  
    "multi_match" : {  
      "type": "best_fields",  
      "fields": [  
        "product_id^100",  
        "product_bullet_point^3",  
        "product_color^2",  
        "product_brand^5",  
        "product_description",  
        "product_title^10"  
      ],  
      "operator":   "and",  
      "query":      query[2]  
    }  
  }  
}
</code></pre></div></div>

<p>To arrive at a query set, we used two random samples: a small one containing 250 queries and a large one containing 5,000 queries. Unfortunately, the ESCI dataset does not contain any information about the frequency of queries, which excludes frequency-weighted approaches like the above-mentioned PPTSS.</p>

<p>The following are the results of running the test set of both query sets independently.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">Metric</th>
      <th style="text-align: center">Baseline BM25 – Small</th>
      <th style="text-align: center">Baseline BM25 – Large</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">DCG@10</td>
      <td style="text-align: center">9.65</td>
      <td style="text-align: center">8.82</td>
    </tr>
    <tr>
      <td style="text-align: center">NDCG@10</td>
      <td style="text-align: center">0.24</td>
      <td style="text-align: center">0.23</td>
    </tr>
    <tr>
      <td style="text-align: center">Precision@10</td>
      <td style="text-align: center">0.27</td>
      <td style="text-align: center">0.24</td>
    </tr>
  </tbody>
</table>

<p>We applied an 80/20 split on the query sets to arrange for a training and test dataset. Every optimization step uses the queries of the training set, whereas search metrics are calculated and compared for the test set. For the baseline, we calculated the metrics for only the test set because there is no actual training occurring.</p>

<p>These numbers are now the starting point for our optimization journey. We want to maximize these metrics and see how far we get when looking for the best global hybrid search configuration in the next step.</p>

<h2 id="identifying-the-best-hybrid-search-configuration">Identifying the best hybrid search configuration</h2>

<p>With this starting point, we can explore the parameter space that hybrid search offers. Our global hybrid search optimization notebook tries out 66 parameter combinations for hybrid search with the following set:</p>

<ul>
  <li>Normalization technique: [<code class="language-plaintext highlighter-rouge">l2</code>, <code class="language-plaintext highlighter-rouge">min_max</code>]</li>
  <li>Combination technique: [<code class="language-plaintext highlighter-rouge">arithmetic_mean</code>, <code class="language-plaintext highlighter-rouge">harmonic_mean</code>, <code class="language-plaintext highlighter-rouge">geometric_mean</code>]</li>
  <li>Lexical search weight: [<code class="language-plaintext highlighter-rouge">0.0</code>, <code class="language-plaintext highlighter-rouge">0.1</code>, <code class="language-plaintext highlighter-rouge">0.2</code>, <code class="language-plaintext highlighter-rouge">0.3</code>, <code class="language-plaintext highlighter-rouge">0.4</code>, <code class="language-plaintext highlighter-rouge">0.5</code>, <code class="language-plaintext highlighter-rouge">0.6</code>, <code class="language-plaintext highlighter-rouge">0.7</code>, <code class="language-plaintext highlighter-rouge">0.8</code>, <code class="language-plaintext highlighter-rouge">0.9</code>, <code class="language-plaintext highlighter-rouge">1.0</code>]</li>
  <li>Neural search weight: [<code class="language-plaintext highlighter-rouge">1.0</code>, <code class="language-plaintext highlighter-rouge">0.9</code>, <code class="language-plaintext highlighter-rouge">0.8</code>, <code class="language-plaintext highlighter-rouge">0.7</code>, <code class="language-plaintext highlighter-rouge">0.6</code>, <code class="language-plaintext highlighter-rouge">0.5</code>, <code class="language-plaintext highlighter-rouge">0.4</code>, <code class="language-plaintext highlighter-rouge">0.3</code>, <code class="language-plaintext highlighter-rouge">0.2</code>, <code class="language-plaintext highlighter-rouge">0.1</code>, <code class="language-plaintext highlighter-rouge">0.0</code>]</li>
</ul>

<p>Neural and lexical search weights always add up to 1.0, so we don’t need to choose them independently.</p>

<p>This leaves us with 66 combinations to test: 2 normalization techniques * 3 combination techniques * 11 lexical/neural search weight combinations.</p>

<p>For each of these combinations, we run the queries of the training set. To do so we use OpenSearch’s <a href="https://opensearch.org/docs/latest/search-plugins/search-pipelines/using-search-pipeline/#using-a-temporary-search-pipeline-for-a-request">temporary search pipeline capability</a>, making it unnecessary to pre-create all pipelines for the 66 parameter combinations.</p>

<p>Here is a template of the temporary search pipelines we use for our hybrid search queries:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"search_pipeline": {  
  "request_processors": [  
    {  
      "neural_query_enricher" : {  
        "description": "one of many search pipelines for experimentation",  
        "default_model_id": model_id,  
        "neural_field_default_id": {  
          "title_embeddings": model_id  
        }  
      }  
    }  
  ],  
  "phase_results_processors": [  
    {  
      "normalization-processor": {  
        "normalization": {  
          "technique": norm  
        },  
        "combination": {  
          "technique": combi,  
          "parameters": {  
            "weights": [  
              lexicalness,  
              neuralness  
            ]  
          }  
        }  
      }  
    }  
  ]  
}
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">norm</code> is the variable for the normalization technique, <code class="language-plaintext highlighter-rouge">combi</code> is the variable for the combination technique, <code class="language-plaintext highlighter-rouge">lexicalness</code> is the lexical search weight, and <code class="language-plaintext highlighter-rouge">neuralness</code> is the neural search weight.</p>

<p>The neural part of the hybrid query searches in a field with embeddings that were created based on the title of a product with the model <code class="language-plaintext highlighter-rouge">all-MiniLM-L6-v2</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{  
  "neural": {  
    "title_embedding": {  
      "query_text": query[2],  
      "k": 100  
    }  
  }  
}
</code></pre></div></div>

<p>Using the queries of the training dataset and retrieving the results, we calculate the three search metrics DCG@10, NDCG@10, and Precision@10. For the small dataset, there is one pipeline configuration that scores best for all three metrics. The pipeline uses the l2 norm, arithmetic mean, a lexical search weight of 0.4, and a neural search weight of 0.6.</p>

<p>The following metrics are calculated:</p>

<ul>
  <li>DCG: 9.99</li>
  <li>NDCG: 0.26</li>
  <li>Precision: 0.29</li>
</ul>

<p>Applying the potentially best hybrid search parameter combination to the test set and calculating the metrics for these queries results in the following numbers.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">Metric</th>
      <th style="text-align: center">Baseline BM25 – Small</th>
      <th style="text-align: center">Global Hybrid Search Optimizer – Small</th>
      <th style="text-align: center">Baseline BM25 – Large</th>
      <th style="text-align: center">Global Hybrid Search Optimizer – Large</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">DCG@10</td>
      <td style="text-align: center">9.65</td>
      <td style="text-align: center">9.99</td>
      <td style="text-align: center">8.82</td>
      <td style="text-align: center">9.30</td>
    </tr>
    <tr>
      <td style="text-align: center">NDCG@10</td>
      <td style="text-align: center">0.24</td>
      <td style="text-align: center">0.26</td>
      <td style="text-align: center">0.23</td>
      <td style="text-align: center">0.25</td>
    </tr>
    <tr>
      <td style="text-align: center">Precision@10</td>
      <td style="text-align: center">0.27</td>
      <td style="text-align: center">0.29</td>
      <td style="text-align: center">0.24</td>
      <td style="text-align: center">0.27</td>
    </tr>
  </tbody>
</table>

<p>Improvements are seen across all metrics for both datasets. To recap, up to this point, we performed the following steps:</p>

<ul>
  <li>Create a query set by randomly sampling.</li>
  <li>Generate judgments (to be precise, we only used the existing judgments of the ESCI dataset).</li>
  <li>Calculate search metrics for a baseline.</li>
  <li>Try out several hybrid search combinations.</li>
  <li>Compare search metrics.</li>
</ul>

<p>Two things are important to note:</p>

<ul>
  <li>While the systematic approach can be transferred to other applications, the experiment results cannot. It is necessary to always evaluate and experiment with your own data.</li>
  <li>The ESCI dataset does not provide 100% judgment coverage. On average we saw roughly 35% judgment coverage among the top 10 retrieved results per query. This leaves us with some uncertainty.</li>
</ul>

<p>The improvements tell us that we optimize our metrics on average when switching to hybrid search with the above parameter values. But of course there are queries that benefit (as in their search quality metrics improve) and queries that do not benefit (as in their search quality metrics decrease) when conducting this switch. This is something we can virtually always observe when comparing two search configurations with each other. While one configuration outperforms the other on average, not every query will profit from the configuration.</p>

<p>The following chart shows the DCG@10 values of the training queries of the small query set. The x-axis represents the search pipeline with l2 norm, arithmetic mean, 0.1 lexical search weight, and 0.9 neural search weight (configuration A). The y-axis represents the search pipeline with an identical normalization and combination technique but switched weights: 0.9 lexical search weight and 0.1 neural search weight (configuration B).</p>

<p><img src="/assets/media/blog-images/2024-12-xx-optimizing-hybrid-search/1_search_config_comparison.png" alt="Scatter Plot of DCG values for lexical-heavy search configuration and Neural-heavy search configuration" class="center" style="width: 100%; max-width: 800px; height: auto; text-align: center" /></p>

<p>The queries with the highest search quality metric improvements of configuration B are those that are located on the y-axis: they have a DCG score of 0 for this configuration. And for configuration A some even score above 15.</p>

<p>Striving to improve the search quality metrics for all queries raises the following question: improvements on average are fine, but how can we tackle this in a more targeted way to come up with an approach that provides the best configuration per query instead of one good configuration for all queries?</p>

<h1 id="dynamic-hybrid-search-optimizer">Dynamic hybrid search optimizer</h1>

<p>We call identifying a suitable configuration individually per hybrid search query <em>dynamic hybrid search optimization</em>. To move in that direction we treat hybrid search as a query understanding challenge: by understanding certain features of the query, we develop an approach to predict the “neuralness” of a query. “Neuralness” is used to describe the neural search weight for the hybrid search queries.</p>

<p>You may ask: Why predict only the “neuralness” and none of the other parameter values? The results of the global hybrid search optimizer (large query set) showed us that the majority of search configurations share two parameter values: the l2 normalization technique and the arithmetic mean as the combination technique.</p>

<p>Looking at the top 5 configurations per search metric (DCG@10, NDCG@10, and Precision@10), only 5 out of the 15 pipelines have <code class="language-plaintext highlighter-rouge">min_max</code> as an alternative normalization technique, and none of these configurations has another combination technique.</p>

<p>With this knowledge we assume the l2 normalization and the arithmetic mean combination technique to be best suited throughout the whole dataset.</p>

<p>That leaves us with the parameter values for the neural search weight and the lexical search weight. By predicting one we can calculate the other by subtracting the prediction from 1: by predicting the “neuralness” we can calculate the “lexicalness” by 1 - “neuralness”.</p>

<p>To validate our hypothesis, we created a couple of feature groups and features within these groups. Afterwards we trained machine learning models to predict an expected NDCG value for the given “neuralness” of a query.</p>

<h2 id="feature-groups-and-features">Feature groups and features</h2>

<p>We divide the features into three groups: query features, lexical search result features, and neural search result features:</p>

<ul>
  <li>Query features: These features describe the user query string.</li>
  <li>Lexical search result features: These features describe the results that the user query retrieves when executed as a lexical search.</li>
  <li>Neural search result features: These features describe the results that the user query retrieves when executed as a neural search.</li>
</ul>

<h3 id="query-features">Query features</h3>

<ul>
  <li>Number of terms: How many terms does the user query have?</li>
  <li>Query length: How long is the user query (measured in characters)?</li>
  <li>Contains number: Does the query contain one or more numbers?</li>
  <li>Contains special character: Does the query contain one or more special characters (non-alphanumeric characters)?</li>
</ul>

<h3 id="lexical-search-result-features">Lexical search result features</h3>

<ul>
  <li>Number of results: The number of results for the lexical query.</li>
  <li>Maximum title score: The maximum score of the titles of the retrieved top 10 documents. The scores are BM25 scores calculated individually per result set. That means that the BM25 score is not calculated on the whole index but only on the retrieved subset for the query, making the scores more comparable to each other and less prone to outliers that could result from high IDF values for very rare query terms.</li>
  <li>Sum of title scores: The sum of the title scores of the top 10 documents, again calculated per result set. We use the sum of the scores (and no average value) as an aggregate to measure how relevant all retrieved top 10 titles are. BM25 scores are not normalized, so using the sum instead of the average seemed reasonable.</li>
</ul>

<h3 id="neural-search-result-features">Neural search result features</h3>

<ul>
  <li>Maximum semantic score: The maximum semantic score of the retrieved top 10 documents. This is the score we receive for a neural query based on the query’s similarity to the title.</li>
  <li>Average semantic score: In contrast to BM25 scores, the semantic scores are normalized and in the range of 0 to 1. Using the average score seems more reasonable than attempting to calculate the sum.</li>
</ul>

<h2 id="feature-engineering">Feature engineering</h2>

<p>We used the output of the global hybrid search optimizer as training data. As part of this process, we ran every query 66 times: once per hybrid search configuration. For each query we calculated the search metrics, so we know which pipeline worked best per query and thus also which “neuralness” (neural search weight) worked best. We used the best NDCG@10 value per query as the metric to decide the ideal “neuralness.”</p>

<p>That leaves us with 250 queries (small query set) or 5,000 queries (large query set) together with their “neuralness” values for which they achieved the best NDCG@10 values. Next, we engineered the nine features for each query. This constitutes the training and test data.</p>

<h2 id="model-training-and-evaluation-findings">Model training and evaluation findings</h2>

<p>With the appropriate data at hand, we explored different algorithms and experimented with different model fitting settings to identify patterns and evaluate whether our approach was suitable.<br />
We used two relatively simple algorithms: linear regression and random forest regression.<br />
We applied cross-validation, regularization, and tried out all different feature combinations. This resulted in interesting findings that are summarized in the following section.</p>

<p><strong>Dataset size matters</strong>: Working with the differently sized datasets revealed that the amount of data matters when training and evaluating the models. The larger dataset reported a smaller Root Mean Squared Error compared to the smaller dataset. The larger dataset also showed less variation of the RMSE scores within the cross-validation runs (that is, when comparing the RMSE scores within one cross-validation run for one feature combination).</p>

<p><strong>Model performance differs among the different algorithms</strong>: The best RMSE score for the random forest regressor was 0.18 compared to 0.22 for the best linear regression model (large dataset)—both with different feature combinations, though. The more complex model (random forest) performs better. However, better performance comes with the trade-off of longer training times for this more complex model.</p>

<p><strong>Feature combinations of all groups have the lowest RMSE</strong>: The lowest error scores can be achieved when combining features from all three feature groups (query, lexical search result, and neural search result). Looking at RMSE scores for feature combinations within the feature groups shows that working with lexical search result feature combinations serves as the best alternative.</p>

<p>This is particularly interesting when thinking about productionizing this: putting an approach like this in production means that features need to be calculated per query during query time. Getting lexical search result features and neural search result features requires running these queries, which would add significant latency to the overall query even prior to inference time.</p>

<p>The following image shows the distribution of RMSE scores within one cross-validation run when fitting random forest regression models with feature combinations within one group (blue: neural search features, red: lexical result features, green: query features) and across the groups (purple: features from all groups). The feature mix (purple) scores lowest (best), followed by training on lexical search result features only (red).
<img src="/assets/media/blog-images/2024-12-xx-optimizing-hybrid-search/2_random_forest_best_feature_combinations.png" alt="Box plot showing the distribution of RMSE scores within one cross validation run when fitting the random forest regression model" class="center" style="width: 100%; max-width: 800px; height: auto; text-align: center" /></p>

<p>The overall picture does not change when looking at the numbers for the linear model.
<img src="/assets/media/blog-images/2024-12-xx-optimizing-hybrid-search/3_linear_model_best_feature_combinations.png" alt="Box plot showing the distribution of RMSE scores within one cross validation run when fitting the linear regression model" class="center" /></p>

<h2 id="model-testing">Model testing</h2>

<p>Let’s look at how the trained models perform when applying them dynamically to our test set.<br />
For each query of the test set we engineer the features and let the model make the inference for the “neuralness” values between 0.0 and 1.0 because “neuralness” is also a feature that we pass into the model. We then take the “neuralness” value that resulted in the highest prediction, which is the best NDCG value. By knowing the “neuralness” we can calculate the “lexicalness” by subtracting the “neuralness” from 1.</p>

<p>We again use the l2 norm and arithmetic mean as our hybrid search normalization and combination parameter values because they scored best in the global hybrid search optimizer experiment. With that, we build the hybrid query, execute it, retrieve the results, and calculate the search metrics like with the baseline and global hybrid search optimizer.</p>

<p>The following are the metrics for the small dataset.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">Metric</th>
      <th style="text-align: center">Baseline BM25</th>
      <th style="text-align: center">Global Hybrid Search Optimizer</th>
      <th style="text-align: center">Dynamic Hybrid Search Optimizer – Linear Model</th>
      <th style="text-align: center">Dynamic Hybrid Search Optimizer – Random Forest Model</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">DCG@10</td>
      <td style="text-align: center">9.65</td>
      <td style="text-align: center">9.99</td>
      <td style="text-align: center">10.92</td>
      <td style="text-align: center">10.92</td>
    </tr>
    <tr>
      <td style="text-align: center">NDCG@10</td>
      <td style="text-align: center">0.24</td>
      <td style="text-align: center">0.26</td>
      <td style="text-align: center">0.28</td>
      <td style="text-align: center">0.28</td>
    </tr>
    <tr>
      <td style="text-align: center">Precision@10</td>
      <td style="text-align: center">0.27</td>
      <td style="text-align: center">0.29</td>
      <td style="text-align: center">0.32</td>
      <td style="text-align: center">0.32</td>
    </tr>
  </tbody>
</table>

<p>The following are the metrics for the large dataset.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">Metric</th>
      <th style="text-align: center">Baseline BM25</th>
      <th style="text-align: center">Global Hybrid Search Optimizer</th>
      <th style="text-align: center">Dynamic Hybrid Search Optimizer – Linear Model</th>
      <th style="text-align: center">Dynamic Hybrid Search Optimizer – Random Forest Model</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">DCG@10</td>
      <td style="text-align: center">8.82</td>
      <td style="text-align: center">9.30</td>
      <td style="text-align: center">10.13</td>
      <td style="text-align: center">10.13</td>
    </tr>
    <tr>
      <td style="text-align: center">NDCG@10</td>
      <td style="text-align: center">0.23</td>
      <td style="text-align: center">0.25</td>
      <td style="text-align: center">0.27</td>
      <td style="text-align: center">0.27</td>
    </tr>
    <tr>
      <td style="text-align: center">Precision@10</td>
      <td style="text-align: center">0.24</td>
      <td style="text-align: center">0.27</td>
      <td style="text-align: center">0.29</td>
      <td style="text-align: center">0.29</td>
    </tr>
  </tbody>
</table>

<p>Looking at these numbers shows us a steady positive trend starting from the baseline and going all the way to the dynamic predictions of “lexicalness” and “neuralness” per query. The large dataset shows a DCG increase of 8.9%, rising from 9.3 to 10.13, and the small dataset shows an increase of 9.3%. The other metrics increase as well: NDCG shows an improvement of 7.4% for the large dataset and 10.3% for the small dataset, and Precision shows an improvement of 8% for the large dataset and 7.7% for the small dataset.</p>

<p>Interestingly, both models score exactly equally. The reason for this is that while they both predict different NDCG values, they predict the best ones with the same “neuralness” as an input feature. So while the models may differ in RMSE scores during the evaluation phase, they provide equal results when applied to the test set.</p>

<p>Despite the low judgement coverage, we see improvements for all metrics. This gives us confidence that this approach can provide value not only for search systems switching from lexical to hybrid search but also for those that are already are in production but have never used any systematic process to evaluate and identify the best settings.</p>

<h1 id="conclusion">Conclusion</h1>

<p>We provide a systematic approach to optimizing hybrid search in OpenSearch based on its current state and capabilities (normalization and combination techniques). The results look promising, especially given the low judgment coverage provided by the ESCI dataset.</p>

<p>We encourage everyone to adopt the approach and explore its usefulness with their dataset. We look forward to hearing the community’s feedback on the provided approach on the <a href="https://forum.opensearch.org/">OpenSearch forum</a>.</p>

<h1 id="future-work">Future work</h1>

<p>The currently planned next steps include replicating the approach with a dataset that has higher judgment coverage and covers a different domain in order to determine its generalizability.</p>

<p>Optimizing hybrid search is not typically the first step in search result quality optimization. Optimizing lexical search results first is especially important because the lexical search query is part of the hybrid search query. Bayesian optimization is an efficient technique for efficiently identifying the best set of fields and field weights, sometimes also referred to as “learning to boost.”</p>

<p>The straightforward approach of trying out 66 different combinations can be performed more elegantly by applying a technique like Bayesian optimization as well. In particular, we expect this to result in a performance improvement for large search indexes and large numbers of queries.</p>

<p>Reciprocal rank fusion, currently under active development, is another way of combining lexical search and neural search:</p>

<ul>
  <li><a href="https://github.com/opensearch-project/neural-search/issues/865">https://github.com/opensearch-project/neural-search/issues/865</a></li>
  <li><a href="https://github.com/opensearch-project/neural-search/issues/659">https://github.com/opensearch-project/neural-search/issues/659</a></li>
</ul>

<p>We also plan to include this technique and to identify the best way of running hybrid search dynamically per query.</p>]]></content><author><name>dwrigley</name></author><category term="technical-posts" /><category term="community" /><summary type="html"><![CDATA[Introduction]]></summary></entry><entry><title type="html">Streamline OpenSearch Dashboards development with VS Code</title><link href="https://opensearch.org/blog/vscode-osd-setup/" rel="alternate" type="text/html" title="Streamline OpenSearch Dashboards development with VS Code" /><published>2024-12-27T00:00:00+00:00</published><updated>2025-01-09T20:49:28+00:00</updated><id>https://opensearch.org/blog/vscode-osd-setup</id><content type="html" xml:base="https://opensearch.org/blog/vscode-osd-setup/"><![CDATA[<p>Developing OpenSearch Dashboards (OSD) can feel overwhelming, whether you’re setting up your environment for the first time or starting work on a significant feature. This blog post introduces developer tools and workflows in <a href="https://code.visualstudio.com/">VS Code</a> that make OSD development more manageable and efficient.</p>

<p>VS Code provides many built-in capabilities, such as IntelliSense, code search, and Git integration. In this post, you’ll learn how to configure the following:</p>

<ul>
  <li>Unit tests that are easy to run and debug at the individual test level.</li>
  <li>A linter that runs automatically on save using OSD rules, eliminating the need to run the linter at commit time and recommit changes (though some linting errors will still require manual fixes).</li>
  <li>A one-click OSD server startup and server-side debugging.</li>
  <li>Multi-root workspaces for development on <em>both</em> individual plugins <em>and</em> OSD Core.</li>
</ul>

<h2 id="setting-up-jest-integration">Setting up Jest integration</h2>

<p>OSD uses <a href="https://jestjs.io/">Jest</a> as the testing framework. With over 2,000 test suites in OSD alone, finding and running specific tests can be a hassle.</p>

<p>The <a href="https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest">vscode-jest</a> extension provides a quick and easy Jest integration in VS Code, as shown in the following image.</p>

<p><img src="/assets/media/blog-images/2024-12-17-vscode-osd-setup/jest-integration.gif" alt="jest-integration" /></p>

<p>Here are a few Jest features:</p>

<ul>
  <li>A graphical interface used to select which tests to run: you can run all project tests, tests inside a specific directory, specific test files, individual test suites, or even individual test methods.
<img src="/assets/media/blog-images/2024-12-17-vscode-osd-setup/test-suites.png" alt="test-suites" /></li>
  <li>A UI icon that displays passed or failed tests.</li>
  <li>An inline <code class="language-plaintext highlighter-rouge">Test Run</code> button in your test files so you don’t have to run tests in the command line.
<img src="/assets/media/blog-images/2024-12-17-vscode-osd-setup/inline-tests.png" alt="inline-tests" /></li>
  <li>The ability to run a test with debug options.
<img src="/assets/media/blog-images/2024-12-17-vscode-osd-setup/debug-tests.png" alt="debug-tests" /></li>
</ul>

<p><strong>Note:</strong> Do not run all tests under <code class="language-plaintext highlighter-rouge">src/plugins*</code>. There are over 2,000 test cases, so running all tests will consume lots of resources. Instead, run tests on demand for specific subdirectories.</p>

<h3 id="configuring-jest">Configuring Jest</h3>

<ol>
  <li>Install the <code class="language-plaintext highlighter-rouge">vscode-jest</code> extension.</li>
  <li>Open your workspace settings by pressing <code class="language-plaintext highlighter-rouge">Cmd</code> (or <code class="language-plaintext highlighter-rouge">Ctrl</code>) + <code class="language-plaintext highlighter-rouge">Shift</code> + <code class="language-plaintext highlighter-rouge">P</code> and selecting <strong>Open Workspace Settings (JSON)</strong>. If you want the settings to persist globally across all your projects, select <strong>Open User Settings (JSON)</strong>. Because the following step will only apply to the <code class="language-plaintext highlighter-rouge">OpenSearch-Dashboards</code> repo, we recommend making these changes a <strong>Workspace Setting</strong> (internally, VS Code will create a <code class="language-plaintext highlighter-rouge">.vscode</code> directory in the workspace root and place a <code class="language-plaintext highlighter-rouge">settings.json</code> file inside <code class="language-plaintext highlighter-rouge">.vscode</code>).</li>
  <li>
    <p>Add the following settings to the <code class="language-plaintext highlighter-rouge">settings.json</code> file:</p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w"> </span><span class="p">{</span><span class="w">
   </span><span class="nl">"jest.jestCommandLine"</span><span class="p">:</span><span class="w"> </span><span class="s2">"yarn test:jest"</span><span class="p">,</span><span class="w">
   </span><span class="nl">"jest.runMode"</span><span class="p">:</span><span class="w"> </span><span class="s2">"on-demand"</span><span class="w">
 </span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
  </li>
</ol>

<p>The first line will use the custom <code class="language-plaintext highlighter-rouge">jest.js</code> OSD script to load the custom <code class="language-plaintext highlighter-rouge">config.js</code> file and pass any arguments directly to <code class="language-plaintext highlighter-rouge">jest</code>. The second line prevents the test runner from running every test on file save, which helps to save computing resources.</p>

<p>After performing these steps, you should see the <strong>Test Explorer</strong> (<img src="/assets/media/blog-images/2024-12-17-vscode-osd-setup/test-icon.png" style="border: none; padding: 0; box-shadow: none; width:1.8rem;" alt="flask icon"/>) icon in the <strong>Extensions</strong> sidebar. Select this icon to view all detected test files.</p>

<h2 id="configuring-prettier-and-eslint-integration">Configuring Prettier and ESLint integration</h2>

<p>The OSD repo includes <a href="https://typicode.github.io/husky/">Husky</a>, a precommit hook that runs scripts before commits are made (scripts may include linting or running unit tests). However, rerunning the linter and recommitting your changes may still be time-consuming. VS Code provides the ability to lint on save, but OSD has specific linting rules that may not work out of the box.</p>

<p>You can use the <a href="https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode">Prettier</a> and <a href="https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint">ESLint</a> VS Code plugins to lint files automatically on save. Note that some rules that cannot be fixed automatically will still require manual corrections. The Prettier plugin is configured using the <code class="language-plaintext highlighter-rouge">.prettierrc</code> config file and will execute the Prettier rules in VS Code. ESLint is configured using the workspace’s ESLInt configuration to enforce its rules. The linting integration is shown in the following image.</p>

<p><img src="/assets/media/blog-images/2024-12-17-vscode-osd-setup/linting-integration.gif" alt="linting-integration" /></p>

<h3 id="prerequisites">Prerequisites</h3>

<p>Before you start, configure linting on save by pressing <code class="language-plaintext highlighter-rouge">Cmd</code> (or <code class="language-plaintext highlighter-rouge">Ctrl</code>) + <code class="language-plaintext highlighter-rouge">Shift</code> + <code class="language-plaintext highlighter-rouge">P</code> and selecting <strong>Open User Settings (JSON)</strong> so you can save this setting across all your projects:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
    </span><span class="nl">"editor.formatOnSave"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="setup">Setup</h3>

<ol>
  <li>Install the Prettier and ESLint extensions.</li>
  <li>Press <code class="language-plaintext highlighter-rouge">Cmd</code> (or <code class="language-plaintext highlighter-rouge">Ctrl</code>) + <code class="language-plaintext highlighter-rouge">Shift</code> + <code class="language-plaintext highlighter-rouge">P</code> and select <strong>Open Workspace Settings (JSON)</strong>.</li>
  <li>
    <p>Add the following settings to the <code class="language-plaintext highlighter-rouge">settings.json</code> file:</p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w"> </span><span class="p">{</span><span class="w">
   </span><span class="nl">"prettier.configPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">".prettierrc"</span><span class="p">,</span><span class="w">
   </span><span class="nl">"editor.defaultFormatter"</span><span class="p">:</span><span class="w"> </span><span class="s2">"esbenp.prettier-vscode"</span><span class="p">,</span><span class="w">
   </span><span class="nl">"eslint.autoFixOnSave"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
   </span><span class="nl">"editor.codeActionsOnSave"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
     </span><span class="nl">"source.fixAll.eslint"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
   </span><span class="p">},</span><span class="w">
 </span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
    <p><strong>Note</strong>: If you already have some settings in the <code class="language-plaintext highlighter-rouge">editor.codeActionsOnSave</code> setting, append <code class="language-plaintext highlighter-rouge">"source.fixAll.eslint": true</code> to the existing settings.</p>
  </li>
</ol>

<p>Now, when you save a file, the file is linted automatically.</p>

<h2 id="configuring-osd-server-run-tasks">Configuring OSD server run tasks</h2>

<p>Starting the OSD server typically requires running commands in two terminals:</p>

<ul>
  <li>In the first terminal, run <code class="language-plaintext highlighter-rouge">yarn opensearch snapshot</code>.</li>
  <li>After several seconds, in the second terminal, run <code class="language-plaintext highlighter-rouge">yarn run start --no-base-path</code>.</li>
</ul>

<p>This approach works for many use cases but doesn’t allow you to debug server-side changes. To simplify this process, you can set up a series of launch tasks, turning server startup into a one-click operation:</p>

<ol>
  <li>Start the OpenSearch server.</li>
  <li>Start the Dashboards server. Starting the Dashboards server requires a wait period while the server boots up; this can be accomplished with a VS Code task that specifies to wait for a certain period of time.</li>
</ol>

<p>Making this task a VS Code run configuration provides the following benefits:</p>

<ul>
  <li>One-click OSD development server startup, eliminating the need to repeatedly use the CLI.</li>
  <li>The ability to debug server-side code (for client-side code [public], you can use your preferred browser’s developer tools).</li>
</ul>

<p>The run configuration is presented in the following image.</p>

<p><img src="/assets/media/blog-images/2024-12-17-vscode-osd-setup/run-configurations.gif" alt="run-configurations" /></p>

<h3 id="prerequisites-1">Prerequisites</h3>

<p>Make sure <a href="https://github.com/nvm-sh/nvm">NVM</a> and Node 18.9.0 are installed on your system:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># With Homebrew</span>
brew <span class="nb">install </span>nvm

<span class="c"># Normal</span>
/bin/bash <span class="nt">-c</span> <span class="s2">"</span><span class="si">$(</span>curl <span class="nt">-fsSL</span> https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh<span class="si">)</span><span class="s2">"</span>

<span class="c"># Install 18.9.0</span>
nvm <span class="nb">install </span>18.9.0
</code></pre></div></div>

<h3 id="setup-1">Setup</h3>

<ol>
  <li>
    <p>If the <code class="language-plaintext highlighter-rouge">launch.json</code> file does not already exist in the <code class="language-plaintext highlighter-rouge">.vscode</code> directory at the project root, create it and add the following configuration:</p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w"> </span><span class="p">{</span><span class="w">
   </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.2.0"</span><span class="p">,</span><span class="w">
   </span><span class="nl">"configurations"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
   </span><span class="err">/**</span><span class="w">
   </span><span class="err">*</span><span class="w"> </span><span class="err">This</span><span class="w"> </span><span class="err">starts</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">Dashboards</span><span class="w"> </span><span class="err">server</span><span class="w">
   </span><span class="err">*</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">Will</span><span class="w"> </span><span class="err">wait</span><span class="w"> </span><span class="mi">13</span><span class="w"> </span><span class="err">seconds</span><span class="w"> </span><span class="err">before</span><span class="w"> </span><span class="err">starting</span><span class="w"> </span><span class="err">so</span><span class="w"> </span><span class="err">OpenSearch</span><span class="w"> </span><span class="err">server</span><span class="w"> </span><span class="err">can</span><span class="w"> </span><span class="err">run</span><span class="w">
   </span><span class="err">*/</span><span class="w">
     </span><span class="p">{</span><span class="w">
       </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Start Dashboards Server"</span><span class="p">,</span><span class="w">
       </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"node"</span><span class="p">,</span><span class="w">
       </span><span class="nl">"request"</span><span class="p">:</span><span class="w"> </span><span class="s2">"launch"</span><span class="p">,</span><span class="w">
       </span><span class="err">//</span><span class="w"> </span><span class="err">Alternatively</span><span class="p">,</span><span class="w"> </span><span class="err">run</span><span class="w"> </span><span class="s2">"which yarn"</span><span class="w"> </span><span class="err">and</span><span class="w"> </span><span class="err">change</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">path</span><span class="w"> </span><span class="err">of</span><span class="w"> </span><span class="err">yarn</span><span class="w">
       </span><span class="nl">"program"</span><span class="p">:</span><span class="w"> </span><span class="s2">"~/.nvm/versions/node/v18.19.0/bin/yarn"</span><span class="p">,</span><span class="w">
       </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"run"</span><span class="p">,</span><span class="w"> </span><span class="s2">"start"</span><span class="p">,</span><span class="w"> </span><span class="s2">"--no-base-path"</span><span class="p">],</span><span class="w">
       </span><span class="nl">"cwd"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}"</span><span class="p">,</span><span class="w">
       </span><span class="nl">"runtimeExecutable"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
       </span><span class="nl">"runtimeArgs"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
       </span><span class="nl">"env"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
         </span><span class="nl">"NODE_ENV"</span><span class="p">:</span><span class="w"> </span><span class="s2">"development"</span><span class="w">
       </span><span class="p">},</span><span class="w">
       </span><span class="nl">"console"</span><span class="p">:</span><span class="w"> </span><span class="s2">"integratedTerminal"</span><span class="p">,</span><span class="w">
       </span><span class="nl">"internalConsoleOptions"</span><span class="p">:</span><span class="w"> </span><span class="s2">"openOnSessionStart"</span><span class="p">,</span><span class="w">
       </span><span class="nl">"preLaunchTask"</span><span class="p">:</span><span class="w"> </span><span class="s2">"13 Second Delay Command"</span><span class="w">
     </span><span class="p">},</span><span class="w">
   </span><span class="err">/**</span><span class="w">
   </span><span class="err">*</span><span class="w"> </span><span class="err">This</span><span class="w"> </span><span class="err">starts</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">OpenSearch</span><span class="w"> </span><span class="err">snapshot</span><span class="w"> </span><span class="err">server</span><span class="w">
   </span><span class="err">*</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">This</span><span class="w"> </span><span class="err">will</span><span class="w"> </span><span class="err">be</span><span class="w"> </span><span class="err">ran</span><span class="w"> </span><span class="err">first</span><span class="w">
   </span><span class="err">*</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">Everytime</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">server</span><span class="w"> </span><span class="err">is</span><span class="w"> </span><span class="err">stopped</span><span class="p">,</span><span class="w"> </span><span class="err">data</span><span class="w"> </span><span class="err">will</span><span class="w"> </span><span class="err">NOT</span><span class="w"> </span><span class="err">be</span><span class="w"> </span><span class="err">persisted.</span><span class="w"> </span><span class="err">Thus</span><span class="p">,</span><span class="w"> </span><span class="err">if</span><span class="w"> </span><span class="err">you</span><span class="w"> </span><span class="err">need</span><span class="w"> </span><span class="err">to</span><span class="w"> </span><span class="err">persist</span><span class="w"> </span><span class="err">data</span><span class="p">,</span><span class="w"> </span><span class="err">you</span><span class="w"> </span><span class="err">can</span><span class="w"> </span><span class="err">configure</span><span class="w"> </span><span class="err">this</span><span class="w"> </span><span class="err">to</span><span class="w"> </span><span class="err">run</span><span class="w"> </span><span class="err">your</span><span class="w"> </span><span class="err">own</span><span class="w"> </span><span class="err">local</span><span class="w"> </span><span class="err">OpenSearch</span><span class="w"> </span><span class="err">server</span><span class="w">
   </span><span class="err">*/</span><span class="w">
     </span><span class="p">{</span><span class="w">
       </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Start OpenSearch Snapshot"</span><span class="p">,</span><span class="w">
       </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"node"</span><span class="p">,</span><span class="w">
       </span><span class="nl">"request"</span><span class="p">:</span><span class="w"> </span><span class="s2">"launch"</span><span class="p">,</span><span class="w">
       </span><span class="err">//</span><span class="w"> </span><span class="err">Alternatively</span><span class="p">,</span><span class="w"> </span><span class="err">run</span><span class="w"> </span><span class="s2">"which yarn"</span><span class="w"> </span><span class="err">and</span><span class="w"> </span><span class="err">change</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">path</span><span class="w"> </span><span class="err">of</span><span class="w"> </span><span class="err">yarn</span><span class="w">
       </span><span class="nl">"program"</span><span class="p">:</span><span class="w"> </span><span class="s2">"~/.nvm/versions/node/v18.19.0/bin/yarn"</span><span class="p">,</span><span class="w">
       </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"run"</span><span class="p">,</span><span class="w"> </span><span class="s2">"opensearch"</span><span class="p">,</span><span class="w"> </span><span class="s2">"snapshot"</span><span class="p">],</span><span class="w">
       </span><span class="nl">"cwd"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}"</span><span class="p">,</span><span class="w">
       </span><span class="nl">"runtimeExecutable"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
       </span><span class="nl">"runtimeArgs"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
       </span><span class="nl">"env"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
         </span><span class="nl">"NODE_ENV"</span><span class="p">:</span><span class="w"> </span><span class="s2">"development"</span><span class="w">
       </span><span class="p">},</span><span class="w">
       </span><span class="nl">"console"</span><span class="p">:</span><span class="w"> </span><span class="s2">"integratedTerminal"</span><span class="p">,</span><span class="w">
       </span><span class="nl">"internalConsoleOptions"</span><span class="p">:</span><span class="w"> </span><span class="s2">"openOnSessionStart"</span><span class="w">
     </span><span class="p">}</span><span class="w">
   </span><span class="p">],</span><span class="w">
   </span><span class="nl">"compounds"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
   </span><span class="err">/**</span><span class="w">
   </span><span class="err">*</span><span class="w"> </span><span class="err">This</span><span class="w"> </span><span class="err">is</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">run</span><span class="w"> </span><span class="err">configuration</span><span class="w"> </span><span class="err">to</span><span class="w"> </span><span class="err">startup</span><span class="w"> </span><span class="err">OSD</span><span class="w">
   </span><span class="err">*</span><span class="w"> </span><span class="mi">1</span><span class="err">.</span><span class="w"> </span><span class="err">Starts</span><span class="w"> </span><span class="err">up</span><span class="w"> </span><span class="err">OpenSearch</span><span class="w"> </span><span class="err">server</span><span class="w">
   </span><span class="err">*</span><span class="w"> </span><span class="mi">2</span><span class="err">.</span><span class="w"> </span><span class="err">Waits</span><span class="w"> </span><span class="mi">13</span><span class="w"> </span><span class="err">seconds</span><span class="w">
   </span><span class="err">*</span><span class="w"> </span><span class="mi">3</span><span class="err">.</span><span class="w"> </span><span class="err">Starts</span><span class="w"> </span><span class="err">up</span><span class="w"> </span><span class="err">Dashboards</span><span class="w"> </span><span class="err">server</span><span class="w">
   </span><span class="err">*/</span><span class="w">
     </span><span class="p">{</span><span class="w">
       </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Start Dashboards"</span><span class="p">,</span><span class="w">
       </span><span class="nl">"configurations"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"Start OpenSearch Snapshot"</span><span class="p">,</span><span class="w"> </span><span class="s2">"Start Dashboards Server"</span><span class="p">],</span><span class="w">
       </span><span class="err">//</span><span class="w"> </span><span class="err">If</span><span class="w"> </span><span class="err">either</span><span class="w"> </span><span class="err">Dashboards</span><span class="w"> </span><span class="err">or</span><span class="w"> </span><span class="err">OpenSearch</span><span class="w"> </span><span class="err">server</span><span class="w"> </span><span class="err">is</span><span class="w"> </span><span class="err">stopped</span><span class="p">,</span><span class="w"> </span><span class="err">both</span><span class="w"> </span><span class="err">servers</span><span class="w"> </span><span class="err">will</span><span class="w"> </span><span class="err">be</span><span class="w"> </span><span class="err">stopped</span><span class="w"> </span><span class="err">(set</span><span class="w"> </span><span class="err">this</span><span class="w"> </span><span class="err">to</span><span class="w"> </span><span class="kc">false</span><span class="w"> </span><span class="err">to</span><span class="w"> </span><span class="err">individually</span><span class="w"> </span><span class="err">turn</span><span class="w"> </span><span class="err">off</span><span class="w"> </span><span class="err">a</span><span class="w"> </span><span class="err">server</span><span class="w"> </span><span class="err">without</span><span class="w"> </span><span class="err">turning</span><span class="w"> </span><span class="err">all</span><span class="w"> </span><span class="err">off)</span><span class="w">
       </span><span class="nl">"stopAll"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
     </span><span class="p">}</span><span class="w">
   </span><span class="p">]</span><span class="w">
 </span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>
    <p>If the <code class="language-plaintext highlighter-rouge">tasks.json</code> file does not already exist in the <code class="language-plaintext highlighter-rouge">.vscode</code> directory at the project root, create it and add the following configuration:</p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w"> </span><span class="p">{</span><span class="w">
   </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.0.0"</span><span class="p">,</span><span class="w">
   </span><span class="nl">"tasks"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
   </span><span class="err">//</span><span class="w"> </span><span class="err">Silent</span><span class="w"> </span><span class="err">task</span><span class="w"> </span><span class="err">to</span><span class="w"> </span><span class="err">sleep</span><span class="w"> </span><span class="err">for</span><span class="w"> </span><span class="mi">13</span><span class="w"> </span><span class="err">seconds;</span><span class="w"> </span><span class="err">this</span><span class="w"> </span><span class="err">is</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">upper</span><span class="w"> </span><span class="err">limit</span><span class="w"> </span><span class="err">on</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">time</span><span class="w"> </span><span class="err">taken</span><span class="w"> </span><span class="err">for</span><span class="w"> </span><span class="err">OpenSearch</span><span class="w"> </span><span class="err">to</span><span class="w"> </span><span class="err">boot</span><span class="w"> </span><span class="err">up</span><span class="w">
     </span><span class="p">{</span><span class="w">
       </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"13 Second Delay Command"</span><span class="p">,</span><span class="w">
       </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"shell"</span><span class="p">,</span><span class="w">
       </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sleep 13"</span><span class="p">,</span><span class="w">
       </span><span class="nl">"group"</span><span class="p">:</span><span class="w"> </span><span class="s2">"none"</span><span class="p">,</span><span class="w">
       </span><span class="nl">"presentation"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
         </span><span class="nl">"reveal"</span><span class="p">:</span><span class="w"> </span><span class="s2">"silent"</span><span class="p">,</span><span class="w">
         </span><span class="nl">"panel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"new"</span><span class="p">,</span><span class="w">
         </span><span class="nl">"close"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
       </span><span class="p">}</span><span class="w">
     </span><span class="p">}</span><span class="w">
   </span><span class="p">]</span><span class="w">
 </span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>
    <p>On the <strong>Run and Debug</strong> tab, select <strong>Start Dashboards</strong> from the dropdown menu and press the play icon. OSD should start after a period of time.</p>
  </li>
</ol>

<p>Now you can set any breakpoint in <code class="language-plaintext highlighter-rouge">server</code> code.</p>

<p>The <strong>Debug</strong> toolbar should appear under the <strong>CALL STACK</strong> in a specific worker node, as shown in the following image.</p>

<p><img src="/assets/media/blog-images/2024-12-17-vscode-osd-setup/debug-toolbar.png" alt="debug-toolbar" /></p>

<p>Using this toolbar, you can resume breakpoints, step into code, and execute the next instruction.</p>

<h2 id="configuring-multi-root-workspace-integration">Configuring multi-root workspace integration</h2>

<p>For most development scenarios, working within the OSD Core should be sufficient. However, if you need to develop code for a plugin, the <a href="https://code.visualstudio.com/docs/editor/multi-root-workspaces">multi-root workspaces feature</a> is a useful option. The setup for Jest, linting, and run configurations for plugins is similar to that of the OSD Core, so we won’t cover it in detail here.</p>

<p>In summary, in order for OSD to recognize plugins during development, the plugin’s project root must be located within the <code class="language-plaintext highlighter-rouge">OpenSearch-Dashboards/plugins/</code> directory. The following image shows multi-root workspace integration.</p>

<p><img src="/assets/media/blog-images/2024-12-17-vscode-osd-setup/multi-root-workspaces.gif" alt="multi-root-workspaces" /></p>

<h3 id="setup-2">Setup</h3>

<p>In this example, assume you’re developing code for the <a href="https://github.com/opensearch-project/anomaly-detection-dashboards-plugin">anomaly-detection-dashboards-plugin</a> and that the project has been checked out in the <code class="language-plaintext highlighter-rouge">plugins</code> directory.</p>

<ol>
  <li>
    <p>Navigate to one directory above the <code class="language-plaintext highlighter-rouge">OpenSearch-Dashboards</code> project folder and create a file called <code class="language-plaintext highlighter-rouge">OpenSearch-Dashboards.code-workspace</code>. Add the following configuration to this file:</p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w"> </span><span class="p">{</span><span class="w">
   </span><span class="nl">"folders"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
     </span><span class="p">{</span><span class="w">
       </span><span class="err">//</span><span class="w"> </span><span class="err">Names</span><span class="w"> </span><span class="err">are</span><span class="w"> </span><span class="err">configurable;</span><span class="w"> </span><span class="err">they</span><span class="w"> </span><span class="err">will</span><span class="w"> </span><span class="err">show</span><span class="w"> </span><span class="err">up</span><span class="w"> </span><span class="err">in</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">EXPLORER</span><span class="w"> </span><span class="err">tab</span><span class="w">
       </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"OSD Core"</span><span class="p">,</span><span class="w">
     </span><span class="err">//</span><span class="w"> </span><span class="err">Path</span><span class="w"> </span><span class="err">to</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">project</span><span class="w"> </span><span class="err">root</span><span class="w">
       </span><span class="nl">"path"</span><span class="p">:</span><span class="w"> </span><span class="s2">"OpenSearch-Dashboards"</span><span class="w">
     </span><span class="p">},</span><span class="w">
     </span><span class="p">{</span><span class="w">
       </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Anomaly Detection Plugin"</span><span class="p">,</span><span class="w">
       </span><span class="nl">"path"</span><span class="p">:</span><span class="w"> </span><span class="s2">"OpenSearch-Dashboards/plugins/anomaly-detection-dashboards-plugin"</span><span class="w">
     </span><span class="p">}</span><span class="w">
   </span><span class="p">],</span><span class="w">
   </span><span class="err">//</span><span class="w"> </span><span class="err">Define</span><span class="w"> </span><span class="err">workspace-specific</span><span class="w"> </span><span class="err">settings</span><span class="w"> </span><span class="err">here</span><span class="w">
   </span><span class="nl">"settings"</span><span class="p">:</span><span class="w"> </span><span class="p">{},</span><span class="w">
   </span><span class="nl">"launch"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
     </span><span class="err">//</span><span class="w"> </span><span class="err">Specify</span><span class="w"> </span><span class="err">workspace-specific</span><span class="w"> </span><span class="err">launch</span><span class="w"> </span><span class="err">configurations</span><span class="w">
     </span><span class="nl">"configurations"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
     </span><span class="err">//</span><span class="w"> </span><span class="err">Specify</span><span class="w"> </span><span class="err">workspace-specific</span><span class="w"> </span><span class="err">launch</span><span class="w"> </span><span class="err">compounds</span><span class="w">
     </span><span class="nl">"compounds"</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
   </span><span class="p">}</span><span class="w">
 </span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>
    <p>Select the <strong>Open Workspace</strong> button to view your new workspace.</p>
  </li>
</ol>

<p>In this workspace, you will have access to launch configurations, Jest test suites, code search, file search, and many other features.</p>

<h2 id="wrapping-up">Wrapping up</h2>

<p>While these tools aren’t a replacement for a thorough understanding of the OSD codebase, they can help to streamline your development workflow. By automating tasks like server startup, debugging, and linting, they reduce time spent on configuration and allow you to focus on writing code. These tools make the development and PR process more efficient, saving you time and boosting productivity.</p>]]></content><author><name>wronghuy</name></author><category term="technical-post" /><summary type="html"><![CDATA[OpenSearch Dashboards can be a challenge to set up. This blog post shows you how to use VS Code with OpenSearch Dashboards to make development easier.]]></summary></entry><entry><title type="html">OpenSearch simplified: The power of Application-Based Configuration (ABC) templates</title><link href="https://opensearch.org/blog/OpenSearch-Simplified-The-Power-of-Application-Based-Templates/" rel="alternate" type="text/html" title="OpenSearch simplified: The power of Application-Based Configuration (ABC) templates" /><published>2024-12-23T00:00:00+00:00</published><updated>2025-01-09T20:49:28+00:00</updated><id>https://opensearch.org/blog/OpenSearch-Simplified-The-Power-of-Application-Based-Templates</id><content type="html" xml:base="https://opensearch.org/blog/OpenSearch-Simplified-The-Power-of-Application-Based-Templates/"><![CDATA[<p>OpenSearch supports a wide variety of use cases—such as <a href="https://opensearch.org/docs/latest/install-and-configure/configuring-opensearch/logs/">logs</a>, <a href="https://opensearch.org/docs/latest/monitoring-your-cluster/metrics/getting-started/">metrics</a>, <a href="https://opensearch.org/docs/latest/data-prepper/common-use-cases/trace-analytics/">traces</a>, <a href="https://opensearch.org/docs/latest/search-plugins/">website search</a>, and <a href="https://opensearch.org/docs/latest/search-plugins/vector-search/">vectors</a>—and enables you to build solutions for various applications based on your use case. As the use cases for OpenSearch continue to grow, managing indexes and configuring them with the right settings can become a daunting task, both for experienced and new users. OpenSearch provides numerous settings that you can use to fine-tune indexes for various performance and usability dimensions, such as throughput, latency, and disk utilization. However, for new users, finding the optimal configuration often requires extensive experimentation and developer effort, creating friction during the onboarding process. As new features are developed and released, experienced OpenSearch users may overlook them as well.</p>

<p>To address this challenge, <a href="https://opensearch.org/blog/introducing-opensearch-2-17/">OpenSearch 2.17</a> introduced the concept of <em>Application-Based Configuration (ABC) templates</em> as an experimental feature. This feature allows you to easily configure your indexes based on your specific use case, reducing the need for manual updates while promoting a seamless onboarding experience and managing the lifecycle of the index and its settings and mappings as new features are introduced.</p>

<h2 id="what-are-abc-templates">What are ABC templates?</h2>

<p>In OpenSearch, ABC templates are predefined system templates designed to simplify the process of configuring indexes for various use cases by providing predefined settings and configurations out of the box. These templates encapsulate various optimized settings, mappings, and configurations tailored for different use cases, eliminating the need to manually handle each setting individually.</p>

<p>ABC templates can be applied to your indexes or index templates by providing the <code class="language-plaintext highlighter-rouge">context</code> field in a request when creating an index or index template. This associates the index or index template with the specified use case (for example, logs or metrics), automatically applying the corresponding settings and mappings from the ABC template to the index or index template.</p>

<p>ABC templates make the indexes use-case-aware and apply the best applicable configuration in terms of performance and end-to-end support at all stages, from index creation to data visualization (for example, Simple Schema for Observability). You can rely on composable index templates and component templates for providing the basic building blocks for the applicable configurations. ABC templates use component templates as a resource in order to expose use-case-specific configurations. The solution is designed to work well with existing index definitions, while the available optimizations are provided straight out of the box.</p>

<h2 id="why-use-abc-templates">Why use ABC templates?</h2>

<p>OpenSearch ABC templates offer the following advantages:</p>

<ul>
  <li><strong>Simplified configuration</strong>: ABC templates eliminate the need to navigate through numerous settings, reducing the complexity of index configuration.</li>
  <li><strong>Optimized performance</strong>: The predefined settings and mappings in the templates are optimized for specific use cases, ensuring better performance out of the box.</li>
  <li><strong>Automatic updates</strong>: As OpenSearch introduces new optimizations and features, they are added to the ABC templates, and you can start using them for newly created indexes with minimal effort.</li>
</ul>

<p>To understand the optimized ABC template settings and their performance benefits, we performed a comparison exercise using an index that relies on the <code class="language-plaintext highlighter-rouge">logs</code> template, which uses the HTTP logs dataset (at the same refresh interval), and found the following:</p>

<ul>
  <li>Storage improved by 20% (when using <code class="language-plaintext highlighter-rouge">zstd_no_dict</code> compared to the default <code class="language-plaintext highlighter-rouge">qat_lz4</code> compression for stored fields).</li>
  <li>Indexing p99 latency improved by 6% because the generated segments are smaller and thus require fewer merges.</li>
  <li>The template requires the index to use the <code class="language-plaintext highlighter-rouge">log_byte_size</code> merge policy, thus ensuring that data ingested together is kept together even after segment merges, resulting in improved performance of time range filter queries.</li>
</ul>

<p>The following diagram compares storage and p99 latency performance.</p>

<p><img src="/assets/media/blog-images/2024-12-20-OpenSearch-Simplified-The-Power-of-Application-Based-Templates/perf-comparison.png" alt="Application-based template performance comparison" /></p>

<h2 id="how-to-use-abc-templates">How to use ABC templates</h2>

<p>To get started with ABC templates, see <a href="https://opensearch.org/docs/latest/im-plugin/index-context/">Index context</a>.</p>

<p>The following animation illustrates creating an index using an ABC template. The template sets the required index mappings and settings. Additionally, it configures direct integration with OpenSearch Dashboards for subsequently ingested data.</p>

<p><img src="/assets/media/blog-images/2024-12-20-OpenSearch-Simplified-The-Power-of-Application-Based-Templates/demo.gif" alt="Illustration of an index using ABC templates" /></p>

<h3 id="available-templates">Available templates</h3>

<p>In the first version of <code class="language-plaintext highlighter-rouge">opensearch-system-templates</code>, the following templates are available to use in the <code class="language-plaintext highlighter-rouge">context</code> parameter as of OpenSearch 2.17:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">logs</code></li>
  <li><code class="language-plaintext highlighter-rouge">metrics</code></li>
  <li><code class="language-plaintext highlighter-rouge">nginx-logs</code></li>
  <li><code class="language-plaintext highlighter-rouge">amazon-cloudtrail-logs</code></li>
  <li><code class="language-plaintext highlighter-rouge">amazon-elb-logs</code></li>
  <li><code class="language-plaintext highlighter-rouge">amazon-s3-logs</code></li>
  <li><code class="language-plaintext highlighter-rouge">apache-web-logs</code></li>
  <li><code class="language-plaintext highlighter-rouge">k8s-logs</code></li>
</ul>

<h3 id="limitations">Limitations</h3>

<p>While providing a lot of benefits, ABC templates have the following limitations:</p>

<ul>
  <li>When using ABC templates for an index, you cannot include any settings defined in the template that supports the index context during its creation or during dynamic setting updates.</li>
  <li>Once configured for an index, the context becomes permanent and cannot be removed.</li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>As OpenSearch continues to evolve, you can expect to benefit from new optimizations and features seamlessly integrated into your existing ABC templates, ensuring a consistent and optimized experience across various use cases. By providing predefined, optimized settings and configurations out of the box, this feature reduces the friction and developer effort required during the onboarding process. We look forward to your feedback on the <a href="https://forum.opensearch.org/">OpenSearch forum</a> and to working together to make OpenSearch easier to use with every version release.</p>]]></content><author><name>mgodwani</name></author><category term="technical-post" /><summary type="html"><![CDATA[OpenSearch supports a wide variety of use cases—such as logs, metrics, traces, website search, and vectors—and enables you to build solutions for various applications based on your use case. As the use cases for OpenSearch continue to grow, managing indexes and configuring them with the right settings can become a daunting task, both for experienced and new users. OpenSearch provides numerous settings that you can use to fine-tune indexes for various performance and usability dimensions, such as throughput, latency, and disk utilization. However, for new users, finding the optimal configuration often requires extensive experimentation and developer effort, creating friction during the onboarding process. As new features are developed and released, experienced OpenSearch users may overlook them as well.]]></summary></entry><entry><title type="html">Deep dive: Enabling pluggable storage in the OpenSearch vector engine</title><link href="https://opensearch.org/blog/enable-pluggable-storage-in-opensearch-vectordb/" rel="alternate" type="text/html" title="Deep dive: Enabling pluggable storage in the OpenSearch vector engine" /><published>2024-12-18T00:00:00+00:00</published><updated>2025-01-09T20:49:28+00:00</updated><id>https://opensearch.org/blog/enable-pluggable-storage-in-opensearch-vectordb</id><content type="html" xml:base="https://opensearch.org/blog/enable-pluggable-storage-in-opensearch-vectordb/"><![CDATA[<p>In 2019, OpenSearch introduced the Vector Engine, which supports three native engines: Non-Metric Space Library (NMSLIB), Facebook AI Similarity Search (Faiss), and Lucene. Unlike Lucene, which is Java based, Faiss and NMSLIB are C++ libraries that OpenSearch accesses through a lightweight Java Native Interface (JNI) layer. However, these native engines handle I/O using file-based APIs, with Faiss relying on <code class="language-plaintext highlighter-rouge">FILE</code> pointers and NMSLIB using <code class="language-plaintext highlighter-rouge">std::fstream</code> to manage graph indexes.</p>

<p>This blog post explains how we addressed these limitations by introducing an abstraction layer for loading data into native engines without compromising performance. We’ll start with an overview of k-NN search, discuss the challenges of file API dependencies, and explain the solution we implemented. Finally, we’ll explore how these changes support searchable snapshots of vector indexes, which involves running approximate k-NN search on remote snapshots using native engines.</p>

<h2 id="what-is-k-nn-search">What is k-NN search?</h2>

<p>The k-nearest neighbors (k-NN) search algorithm identifies the k closest vectors to a given query vector. It uses a distance metric, such as cosine similarity, to measure similarity between vectors, with closer points considered to be more similar.</p>

<p>In the OpenSearch vector database, you can choose from different vector search algorithms. A popular algorithm for approximate nearest neighbor (ANN) search in high-dimensional spaces is Hierarchical Navigable Small World (HNSW). HNSW organizes data points into a multi-layer graph in which each layer contains connections for efficient data navigation. Inspired by skip lists, HNSW graph layers have varying densities that increase proportionally with depth. This helps narrow the search space from broader to more specific regions—similar to locating an address by starting with a country and then narrowing down to a state, city, and street.</p>

<p>For more information about building a k-NN similarity search engine with OpenSearch, see our <a href="https://opensearch.org/docs/latest/search-plugins/knn/index/">documentation</a>.</p>

<h2 id="challenges-with-file-based-apis">Challenges with file-based APIs</h2>

<p>Native vector engines, such as Faiss and NMSLIB, offer high performance and predictable latencies. However, their reliance on file-based APIs makes them difficult to integrate with storage that is not file system based.</p>

<p>Lucene uses the Java-based <strong>Directory</strong> abstraction for reading and writing files. The <strong>Directory</strong> class abstracts file storage, enabling operations like reading, writing, and managing file metadata across diverse storage systems. This abstraction allows the Lucene vector engine to store files independently of the underlying OpenSearch storage.</p>

<p>Unlike Lucene, native engines tightly couple their operations to file-based I/O. To address these limitations, we applied Lucene’s principles to native engines. By abstracting the I/O layer, we eliminated the engines’ tight coupling to specific file APIs. This enhancement enables integration with any OpenSearch directory implementation, making vector search compatible with a broader range of storage systems.</p>

<h2 id="introducing-the-loading-layer">Introducing the loading layer</h2>

<p>Both Faiss and NMSLIB load graph-based vector indexes from storage into memory. During this process, they use <code class="language-plaintext highlighter-rouge">fread</code> to fetch the bytes needed to reconstruct the graph.</p>

<p>To improve flexibility, we replaced <code class="language-plaintext highlighter-rouge">fread</code> with a read interface. Faiss provides an <strong>IOReader</strong> interface for reading index data from various storage systems. For NMSLIB, we introduced a similar read interface called <strong>NmslibIOReader</strong>. These interfaces allow the native engines to read data through an abstraction layer, making integration possible with OpenSearch’s directory implementations.</p>

<p>Because k-NN search is conducted after the graph is loaded into memory, this change does not impact average search performance.</p>

<p>The following diagram provides a high-level overview of the loading layer in native engines.</p>

<p><img src="/assets/media/blog-images/2024-12-04-enable-pluggable-storage-in-opensearch-vectordb/loading_layer_high_level.png" alt="High-level overview of the loading layer in native engines" style="width: 100%; max-width: 800px;" /></p>

<h2 id="performance-benchmarks">Performance benchmarks</h2>

<p>The following sections present performance benchmark results.</p>

<h3 id="benchmarking-environment">Benchmarking environment</h3>

<p>We ran benchmarking tests in an environment with the following configuration.</p>

<table>
  <tbody>
    <tr>
      <td>OpenSearch version</td>
      <td>2.18</td>
    </tr>
    <tr>
      <td>vCPUs</td>
      <td>48</td>
    </tr>
    <tr>
      <td>Physical memory</td>
      <td>128 GB</td>
    </tr>
    <tr>
      <td>Storage type</td>
      <td>Amazon Elastic Block Store (Amazon EBS)</td>
    </tr>
    <tr>
      <td>JVM</td>
      <td>63 GB</td>
    </tr>
    <tr>
      <td>Total number of vectors</td>
      <td>1M</td>
    </tr>
    <tr>
      <td>Dimensions</td>
      <td>128</td>
    </tr>
  </tbody>
</table>

<h3 id="benchmarking-results">Benchmarking results</h3>

<p>During benchmarking, we observed that introducing the loading layer resulted in identical search performance compared to the baseline. Additionally, there were no differences in system metrics or JVM GC metrics when introducing the loading layer.</p>

<p>Based on these findings, we concluded that we successfully replaced the tight coupling of the File API with Lucene’s <strong>IndexInput</strong>. This change maintained the same search performance. Additionally, with this change you can integrate a custom <strong>Directory</strong> in OpenSearch and save a vector index in your preferred storage system.</p>

<p>The following table presents our benchmarking results, comparing query latency with the loading layer (candidate) to the baseline.</p>

<table>
  <thead>
    <tr>
      <th>Engine</th>
      <th>Metric</th>
      <th>Description</th>
      <th>Baseline</th>
      <th>Candidate</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Faiss</td>
      <td>Average query latency</td>
      <td>The time taken to process a vector search query.</td>
      <td>3.5832 ms</td>
      <td>3.83349 ms</td>
    </tr>
    <tr>
      <td>Faiss</td>
      <td>p99 query latency</td>
      <td>The p99 latency for processing a vector search query.</td>
      <td>22.1628 ms</td>
      <td>23.8439 ms</td>
    </tr>
    <tr>
      <td>Faiss</td>
      <td>Total Young Gen JVM GC time</td>
      <td>The time spent on Young GC in the JVM.</td>
      <td>0.338 sec</td>
      <td>0.342 sec</td>
    </tr>
  </tbody>
</table>

<p>The results demonstrate that replacing the file-based API with Lucene’s <strong>IndexInput</strong> maintains search performance while enabling broader storage compatibility.</p>

<h2 id="configuring-searchable-snapshots-for-vector-search">Configuring searchable snapshots for vector search</h2>

<p>With the loading layer in place, you can now perform vector searches directly on remote snapshots. At a high level, you’ll create a vector index, take a snapshot of the index, and run a vector search on the snapshot. The following diagram illustrates these steps.</p>

<p><img src="/assets/media/blog-images/2024-12-04-enable-pluggable-storage-in-opensearch-vectordb/searchable_snapshots_overview.png" alt="Overview of searchable snapshots" style="width: 100%; max-width: 600px;" /></p>

<p>Follow these steps to configure searchable snapshots.</p>

<h3 id="prerequisites">Prerequisites</h3>

<p>Configure your cluster for searchable snapshots. For detailed steps, see <a href="https://opensearch.org/docs/latest/tuning-your-cluster/availability-and-recovery/snapshots/searchable_snapshot/#configuring-a-node-to-use-searchable-snapshots">Configuring a node to use searchable snapshots</a>.</p>

<h3 id="step-1-create-a-local-index">Step 1: Create a local index</h3>

<p>Use the following request to create a local vector index:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">PUT</span><span class="w"> </span><span class="err">/knn-index/</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"settings"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"index"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"knn"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"mappings"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"properties"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"my_vector"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"knn_vector"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"dimension"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="step-2-ingest-data">Step 2: Ingest data</h3>

<p>Ingest some data into the index:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">POST</span><span class="w"> </span><span class="err">_bulk?refresh</span><span class="w">
</span><span class="p">{</span><span class="w"> </span><span class="nl">"index"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"_index"</span><span class="p">:</span><span class="w"> </span><span class="s2">"knn-index"</span><span class="p">,</span><span class="w"> </span><span class="nl">"_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">{</span><span class="w"> </span><span class="nl">"my_vector"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">1.5</span><span class="p">,</span><span class="w"> </span><span class="mf">2.5</span><span class="p">],</span><span class="w"> </span><span class="nl">"price"</span><span class="p">:</span><span class="w"> </span><span class="mf">12.2</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">{</span><span class="w"> </span><span class="nl">"index"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"_index"</span><span class="p">:</span><span class="w"> </span><span class="s2">"knn-index"</span><span class="p">,</span><span class="w"> </span><span class="nl">"_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">{</span><span class="w"> </span><span class="nl">"my_vector"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">2.5</span><span class="p">,</span><span class="w"> </span><span class="mf">3.5</span><span class="p">],</span><span class="w"> </span><span class="nl">"price"</span><span class="p">:</span><span class="w"> </span><span class="mf">7.1</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">{</span><span class="w"> </span><span class="nl">"index"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"_index"</span><span class="p">:</span><span class="w"> </span><span class="s2">"knn-index"</span><span class="p">,</span><span class="w"> </span><span class="nl">"_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">{</span><span class="w"> </span><span class="nl">"my_vector"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">3.5</span><span class="p">,</span><span class="w"> </span><span class="mf">4.5</span><span class="p">],</span><span class="w"> </span><span class="nl">"price"</span><span class="p">:</span><span class="w"> </span><span class="mf">12.9</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">{</span><span class="w"> </span><span class="nl">"index"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"_index"</span><span class="p">:</span><span class="w"> </span><span class="s2">"knn-index"</span><span class="p">,</span><span class="w"> </span><span class="nl">"_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"4"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">{</span><span class="w"> </span><span class="nl">"my_vector"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">5.5</span><span class="p">,</span><span class="w"> </span><span class="mf">6.5</span><span class="p">],</span><span class="w"> </span><span class="nl">"price"</span><span class="p">:</span><span class="w"> </span><span class="mf">1.2</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">{</span><span class="w"> </span><span class="nl">"index"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"_index"</span><span class="p">:</span><span class="w"> </span><span class="s2">"knn-index"</span><span class="p">,</span><span class="w"> </span><span class="nl">"_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"5"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">{</span><span class="w"> </span><span class="nl">"my_vector"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">4.5</span><span class="p">,</span><span class="w"> </span><span class="mf">5.5</span><span class="p">],</span><span class="w"> </span><span class="nl">"price"</span><span class="p">:</span><span class="w"> </span><span class="mf">3.7</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="step-3-query-the-local-index">Step 3: Query the local index</h3>

<p>Query the local index to ensure that it’s configured correctly:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">POST</span><span class="w"> </span><span class="err">knn-index/_search</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"query"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"knn"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"my_vector"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"vector"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">],</span><span class="w">
        </span><span class="nl">"k"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The response returns the vectors closest to the query vector:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"took"</span><span class="p">:</span><span class="w"> </span><span class="mi">16</span><span class="p">,</span><span class="w">
  </span><span class="nl">"timed_out"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
  </span><span class="nl">"_shards"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"total"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
    </span><span class="nl">"successful"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
    </span><span class="nl">"skipped"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"failed"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"hits"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"total"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
      </span><span class="nl">"relation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"eq"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"max_score"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.6666667</span><span class="p">,</span><span class="w">
    </span><span class="nl">"hits"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="p">{</span><span class="w">
        </span><span class="nl">"_index"</span><span class="p">:</span><span class="w"> </span><span class="s2">"knn-index"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"_score"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.6666667</span><span class="p">,</span><span class="w">
        </span><span class="nl">"_source"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"my_vector"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
            </span><span class="mf">1.5</span><span class="p">,</span><span class="w">
            </span><span class="mf">2.5</span><span class="w">
          </span><span class="p">],</span><span class="w">
          </span><span class="nl">"price"</span><span class="p">:</span><span class="w"> </span><span class="mf">12.2</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="p">{</span><span class="w">
        </span><span class="nl">"_index"</span><span class="p">:</span><span class="w"> </span><span class="s2">"knn-index"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"_score"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.6666667</span><span class="p">,</span><span class="w">
        </span><span class="nl">"_source"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"my_vector"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
            </span><span class="mf">2.5</span><span class="p">,</span><span class="w">
            </span><span class="mf">3.5</span><span class="w">
          </span><span class="p">],</span><span class="w">
          </span><span class="nl">"price"</span><span class="p">:</span><span class="w"> </span><span class="mf">7.1</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">]</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="step-4-take-a-snapshot">Step 4: Take a snapshot</h3>

<p>Take a snapshot of the index. For detailed steps, see <a href="https://opensearch.org/docs/latest/tuning-your-cluster/availability-and-recovery/snapshots/snapshot-restore/">Take and restore snapshots</a>. After taking the snapshot, delete the <code class="language-plaintext highlighter-rouge">knn-index</code> so that it is no longer available locally.</p>

<h3 id="step-5-create-a-searchable-snapshot-index-from-the-snapshot">Step 5: Create a searchable snapshot index from the snapshot</h3>

<p>Use the following request to restore the original index from the snapshot in order to create a searchable snapshot index</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">POST</span><span class="w"> </span><span class="err">_snapshot/&lt;SNAPSHOT_REPO&gt;/&lt;SNAPSHOT_NAME&gt;/_restore</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"storage_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"remote_snapshot"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"indices"</span><span class="p">:</span><span class="w"> </span><span class="s2">"knn-index"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>To verify the successful creation of the searchable snapshot index, use the following request:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">GET</span><span class="w"> </span><span class="err">/_cat/indices</span><span class="w">
</span></code></pre></div></div>
<p>For more information, see <a href="https://opensearch.org/docs/latest/tuning-your-cluster/availability-and-recovery/snapshots/searchable_snapshot/#create-a-searchable-snapshot-index">Create a searchable snapshot index</a>.</p>

<h3 id="step-6-run-a-vector-search-query">Step 6: Run a vector search query</h3>

<p>Run a vector search query on the searchable snapshot index:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">POST</span><span class="w"> </span><span class="err">knn-index/_search</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"query"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"knn"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"my_vector"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"vector"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">],</span><span class="w">
        </span><span class="nl">"k"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The query returns the same results as the local index query in <a href="#step-3-query-the-local-index">Step 3</a>.</p>

<h2 id="conclusion">Conclusion</h2>

<p>By introducing an I/O layer that uses Lucene’s <strong>Directory</strong> abstraction, we eliminated the native engines’ dependency on file-based APIs that limit storage to local file systems. This change allows the vector engine to read graph data structures from any storage system supported by OpenSearch’s <strong>Directory</strong> implementation. Our extensive benchmarking tests confirmed that this change maintains the search performance of the original file-API-based approach. Notably, we observed no regression in search times after the graphs were loaded into memory (graph loading is a one-time operation for a properly scaled cluster).</p>

<p>With this new read interface, you can now use vector indexes with any OpenSearch <strong>Directory</strong> implementation. This added flexibility makes it possible to store vector data in remote storage solutions like Amazon Simple Storage Service (Amazon S3).</p>

<h2 id="next-steps">Next steps</h2>

<p>In version 2.18, we introduced the ability to use vector search queries with Lucene’s <strong>Directory</strong> and <strong>IndexInput</strong> classes. Looking ahead, version 2.19 will expand this functionality to the native index creation process. Specifically, the k-NN plugin will begin using the <strong>IndexOutput</strong> class to write graph files directly to segments. For more information, see <a href="https://github.com/opensearch-project/k-NN/issues/2033">this GitHub issue</a>.</p>

<p>Additionally, the k-NN plugin now having the ability to stream vector data structure files presents an opportunity for partial loading of these files. This enhancement will reduce memory pressure on the cluster and deliver better price-performance, especially under high-stress conditions. For more information, see <a href="https://github.com/opensearch-project/k-NN/issues/1693">this GitHub issue</a>.</p>]]></content><author><name>kdooyong</name></author><category term="technical-posts" /><category term="community" /><summary type="html"><![CDATA[In 2019, OpenSearch introduced the Vector Engine, which supports three native engines: Non-Metric Space Library (NMSLIB), Facebook AI Similarity Search (Faiss), and Lucene. Unlike Lucene, which is Java based, Faiss and NMSLIB are C++ libraries that OpenSearch accesses through a lightweight Java Native Interface (JNI) layer. However, these native engines handle I/O using file-based APIs, with Faiss relying on FILE pointers and NMSLIB using std::fstream to manage graph indexes.]]></summary></entry><entry><title type="html">OpenSearchCon 2024: North America – The blog post</title><link href="https://opensearch.org/blog/OpenSearchCon-2024-NA-Blog/" rel="alternate" type="text/html" title="OpenSearchCon 2024: North America – The blog post" /><published>2024-12-09T00:00:00+00:00</published><updated>2025-01-09T20:49:28+00:00</updated><id>https://opensearch.org/blog/OpenSearchCon-2024-NA-Blog</id><content type="html" xml:base="https://opensearch.org/blog/OpenSearchCon-2024-NA-Blog/"><![CDATA[<p>OpenSearch, the community-driven, open-source search and analytics suite, has been gaining momentum in the data-driven application landscape. The recent <a href="https://opensearch.org/events/opensearchcon/2024/north-america/index.html">OpenSearchCon 2024: North America</a> event held in San Francisco, which attracted over 350 attendees from around the world, was a testament to the growing influence of this powerful platform. In this blog post, we’ll explore the key highlights, insightful sessions, and exciting announcements that emerged from this year’s conference.</p>

<h2 id="keynote-the-future-of-opensearch">Keynote: The future of OpenSearch</h2>

<p>The event kicked off with an inspiring keynote from Eli Fisher, Sr. Manager, Product Management at OpenSearch, who laid out the vision for the future of OpenSearch. Some key announcements included:</p>

<ul>
  <li>OpenSearch 2.x and unlocking AI potential: Eli shared details about the OpenSearch 2.x release, including features like the OpenSearch AI toolkit, a built-in retrieval-augmented generation (RAG) processor, and concurrent segment search, unlocking new possibilities for AI-powered applications.</li>
  <li>OpenSearch generative AI use cases: Anupam Chaturvedi, Adobe Engineering Manager, demonstrated how Adobe is leveraging OpenSearch for its Acrobat generative AI assistant, showcasing the practical applications of OpenSearch in the generative AI space.</li>
  <li>Community growth: Jim Zemlin, Executive Director, <a href="https://www.linuxfoundation.org/">The Linux Foundation</a>, spoke about welcoming OpenSearch to the Linux Foundation. By opening up project governance and moving to a neutral home, the OpenSearch Project is taking concrete steps to further the project’s founding principle of being for the community and by the community.</li>
</ul>

<h2 id="important-sessions-and-workshops">Important sessions and workshops</h2>
<p>The conference featured several informative sessions and workshops, including:</p>

<ul>
  <li>Session: <a href="https://www.youtube.com/watch?v=kpWQ55hVF90">Airbnb Embedding Platform</a>
  This session, led by Moutupsi Paul, Xiaotang Wang, and Amulya Sharma, focused on how Airbnb is leveraging OpenSearch as a vector database for its embedding platform. The session provided an overview of how Airbnb is using vector embeddings to solve guest, host, and marketplace challenges, followed by specific use cases that led Airbnb to adopt OpenSearch as its vector database. The session shared lessons learned and provided insights into strategies for optimizing OpenSearch as a vector database.</li>
  <li>Session: <a href="https://youtu.be/-_mXJqvPw0o?si=7Lb1Y__uAZ7ZCc66">Lucene And Beyond - Core Storage Extension In OpenSearch</a>
  OpenSearch is tightly bound to the Lucene core APIs that facilitate encoding, transactions, merges, and search. This session, led by Samuel Herman, focused on how OpenSearch storage encoding can be extended to popular formats (for example, Parquet, Avro) that are readable by public big data systems such as Apache Spark.</li>
  <li>Session: <a href="https://youtu.be/iZrFVqvTD9s?si=loButiXx8zm2PHob">Bringing cloud native innovation from search platforms at Uber and Slack into OpenSearch</a>
  This session, led by Karthikeyan Ramasamy and Bryan Burkholder, showcased how their organizations’ unique requirements of low-latency, high-throughput workloads led them to develop their own search and log analytics platforms, making it easy to operate, cost effective, and able to scale to petabytes of data. The session covered how Uber and Slack are collaborating with the OpenSearch Project to bring some of their learnings and innovations from their platforms into OpenSearch, with a special focus on a modular cloud-native architecture featuring isolation between readers and writers and a performant messaging protocol for communication between services.</li>
  <li>Session: <a href="https://www.youtube.com/watch?v=JPo_uF4Fw4M">A deep dive into OpenSearch Serverless</a>
  This session, led by Rohit Nair and Milav Shah, dives deep into how Amazon OpenSearch Serverless works, including architecture details, understanding performance and operational characteristics, and the future roadmap.</li>
</ul>

<h2 id="closing-remarks-whats-next-for-opensearch">Closing remarks: What’s next for OpenSearch?</h2>

<p>The event was not just about technical sessions—it was also a fantastic opportunity to network with peers in the open-source search space. The community lounge was buzzing with activity, and there were plenty of informal meetups where attendees could connect with OpenSearch engineers, contributors, and advocates.</p>

<p>As the OpenSearchCon event drew to a close, it was clear that the conference was a significant milestone for the OpenSearch community. The announcements about new features, improvements in observability, and the ongoing commitment to open-source principles left attendees excited about the future of the project.</p>

<p>The OpenSearch roadmap for 2024 highlights key themes around which contributors are innovating, including stability, availability, and resiliency (StAR); cost, performance, and scale (CoPS); ease of use; observability and log analytics; search and machine learning (ML); security; and a modular architecture.</p>

<p>As the OpenSearch Project continues to grow, the community is streamlining the process of creating a roadmap that includes ideas and projects provided directly by the community. It’s clear that the project is poised to make an even bigger impact in the world of AI/ML, search, and observability. We will be back in 2025 with three different OpenSearchCon events, including in Europe, India, and North America. You can bookmark the <a href="https://opensearch.org/events/">Upcoming Events</a> page and consider attending one of the project’s bi-weekly <a href="https://www.meetup.com/opensearch/">meetups</a> or an in-person <a href="https://www.meetup.com/pro/opensearchproject/">user group</a>. We encourage you to try out OpenSearch, engage with the community, and stay tuned for the exciting developments to come.</p>]]></content><author><name>prthsh</name></author><category term="community-updates" /><category term="community" /><category term="updates" /><category term="update" /><category term="partners" /><summary type="html"><![CDATA[OpenSearch, the community-driven, open-source search and analytics suite, has been gaining momentum in the data-driven application landscape. The recent OpenSearchCon North America 2024 event held in San Francisco, which attracted over 350 attendees from around the world, was a testament to the growing influence of this powerful platform. In this blog post, we'll explore the key highlights, insightful sessions, and exciting announcements that emerged from this year's conference.]]></summary></entry><entry><title type="html">Give back and go forward: Driving community contributions from vendor led to vendor neutral</title><link href="https://opensearch.org/blog/driving-community-contributions/" rel="alternate" type="text/html" title="Give back and go forward: Driving community contributions from vendor led to vendor neutral" /><published>2024-11-29T00:00:00+00:00</published><updated>2025-01-09T20:49:28+00:00</updated><id>https://opensearch.org/blog/driving-community-contributions</id><content type="html" xml:base="https://opensearch.org/blog/driving-community-contributions/"><![CDATA[<p>When Amazon Web Services (AWS) launched the OpenSearch Project, it sought to inspire a community of contributors to help drive innovation for a strategically important open-source project. Working toward that goal called for new domain knowledge and a fresh look at company culture to help build trust across internal teams and, more importantly, with external contributors and organizations.</p>

<p>In <a href="https://www.youtube.com/watch?v=9gwTMW901ew&amp;t=4142">this keynote address</a>, I shared the journey OpenSearch has embarked on with the invaluable support of our community, highlighting our efforts to empower and collaboratively shape the project’s future.</p>

<p><img src="/assets/media/blog-images/2024-11-27-driving-community-contributions/LF_Member_Summit_Keynote_journey.png" alt="OpenSearch Project Linux Foundation infographic" class="center" style="width: 100%; max-width: 800px; height: auto; text-align: center" /></p>

<p>For those unfamiliar with OpenSearch, it is a community-driven, open-source platform for search, analytics, and vector databases. It includes integrated tools for observability, security, visualization, and AI-powered applications, all available under the Apache 2.0 license. OpenSearch is now a project under the Linux Foundation. At the Open Source Summit Europe in Vienna two months ago, we launched the <a href="https://foundation.opensearch.org/">OpenSearch Software Foundation</a> to establish open governance for the project. Since then, we’ve formed a governing board and a technical steering committee, which I have the honor of leading, to guide the project’s technical direction.</p>

<p>OpenSearch originated as a fork of Elasticsearch in early 2021 after Elastic changed its licensing to a more restrictive source-available model. Users still desired an open-source alternative, prompting AWS to release a fork. This phase of our journey was both exciting and challenging. The fork involved substantial effort: 650 pull requests, around 56,000 files, and over 4.5 million lines of code to modify. By July, we delivered a feature-complete, stable version 1.0. At that time, OpenSearch was one of the few fully open-source search and analytics engines available. Our primary focus was to launch a stable, interoperable 1.x line that met our community’s needs.</p>

<p>With a stable product in place, 2022 was dedicated to listening and learning—a lot of learning. As we invited and encouraged a community of users and contributors, both the OpenSearch team and our sponsors at AWS spent the year understanding how to enable a community independently. This might seem surprising, but AWS had not previously led an open-source project of OpenSearch’s scope and trajectory. We had to learn the best ways to build trust with internal AWS teams and, more importantly, with external contributors and organizations. To support this, we engaged through various communication channels—forums, blog posts, and numerous requests for comments.</p>

<p>In 2023, I joined AWS to lead engineering for open-source OpenSearch. I quickly realized that while OpenSearch had achieved significant success, we were still working to rebuild the trust lost when Elastic changed its license. I also saw the immense potential of our dedicated community to drive innovation and accelerate growth.</p>

<p>Early in 2023, the rise of generative AI placed OpenSearch at the forefront, thanks to our early innovations in k-NN algorithms as a vector database. It became crucial to invest in and enhance core search performance and vector database use cases, including hybrid search. This led to a cultural transformation within AWS, OpenSearch, and our relationship with our community, focusing on deeper innovations in the search domain. Initially, there were challenges, but the AWS team and the broader community embraced the change. Together, we pushed for more open governance and trust building. We established a leadership committee comprising diverse stakeholders to promote open and transparent governance. And we saw results!</p>

<p>By the end of 2023, OpenSearch had made significant strides in:</p>

<ul>
  <li><strong>Search and generative AI innovation</strong>: We made notable advancements in search performance, hybrid search, and vector database functionality.</li>
  <li><strong>Community growth</strong>: Contributions from outside of AWS surged, and we added maintainers from over 25 organizations. Our user forum traffic grew to 500,000 views monthly, and <a href="http://opensearch.org/">opensearch.org</a> reached over 1 million monthly visitors.</li>
  <li><strong>Broad participation</strong>: A slew of new contributors joined the effort, while long-time contributors deepened their commitments. Companies like Aryn, SAP, and Bytedance made significant contributions, while Intel drove substantial performance improvements with new codecs based on Zstandard compression.</li>
</ul>

<p>This momentum carried into 2024 as our technical roadmap grew ambitiously. Companies like Uber and Slack/Salesforce began innovating in OpenSearch. This year marked a critical mass for the OpenSearch community. We completed the journey to becoming a truly open-source OpenSearch when we committed to transition the project to the Linux Foundation. This move reinforces our dedication to maintaining OpenSearch as an open-source and neutral project for the long term, and the response from the community has been overwhelming.</p>

<p>Today the OpenSearch Software Foundation has 14 member organizations, including premier members AWS, SAP, and Uber and general members Aiven, Aryn, Atlassian, Canonical, DataStax, Digital Ocean, DTEX, Eliatra, Graylog, Instaclustr by NetApp, and Portal26. Since the launch of the OpenSearch Software Foundation in September, we’ve confirmed a governing board with leaders from AWS, Aryn, SAP, and Uber. Our Technical Steering Committee is already driving key technology decisions with committee members from SAP, Bytedance, Oracle, Logz.io, Aiven, Aryn, Uber, and Slack/Salesforce.</p>

<p>With a more engaged community than ever before, we’re well positioned to build on the foundation we’ve forged together and continue to innovate. While the OpenSearch Software Foundation represents a culmination of four years of community-focused collaboration and growth, in many ways it feels like we’re at the beginning of a new and really exciting journey. As always, we will continue to look to the community for your valuable input as we move forward together on this journey.</p>

<!--
Copyright (c) 2020 Nathan Lam
https://github.com/nathancy/jekyll-embed-video
-->

<div class="embed-container">
    <iframe src="https://www.youtube.com/embed/9gwTMW901ew" width=" 640" height="385" frameborder="0" allowfullscreen="true" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture">
    </iframe>
  </div>]]></content><author><name>anandhi</name></author><category term="community" /><summary type="html"><![CDATA[When Amazon Web Services (AWS) launched the OpenSearch Project, it sought to inspire a community of contributors to help drive innovation for a strategically important open-source project. Working toward that goal called for new domain knowledge and a fresh look at company culture to help build trust across internal teams and, more importantly, with external contributors and organizations.]]></summary></entry><entry><title type="html">OpenSearch Project update: A look at performance progress through version 2.17</title><link href="https://opensearch.org/blog/opensearch-performance-2.17/" rel="alternate" type="text/html" title="OpenSearch Project update: A look at performance progress through version 2.17" /><published>2024-11-27T00:00:00+00:00</published><updated>2025-01-09T20:49:28+00:00</updated><id>https://opensearch.org/blog/opensearch-performance-2.17</id><content type="html" xml:base="https://opensearch.org/blog/opensearch-performance-2.17/"><![CDATA[<p>Our commitment to enhancing OpenSearch’s performance remains unwavering, and this blog post showcases the significant progress we’ve made. Recently, we’ve focused our investments on four key areas: text querying, vector storage and querying, ingestion and indexing, and storage efficiency. Additionally, we’ve published our search and performance roadmap, reaffirming that performance continues to be our top priority. In this blog post, we’ll bring you up to date on our continuing performance improvements through <a href="https://github.com/opensearch-project/opensearch-build/blob/main/release-notes/opensearch-release-notes-2.17.0.md">OpenSearch 2.17</a>.</p>

<p>OpenSearch 2.17 offers a remarkable <strong>6x performance boost</strong> compared to OpenSearch 1.3, enhancing key operations like text queries, terms aggregations, range queries, date histograms, and sorting. Additionally, the improvements in semantic vector search now allow for highly configurable settings, enabling you to balance response time, accuracy, and cost according to your needs. These advancements are a testament to the dedicated community whose contributions and collaboration propel OpenSearch forward.</p>

<p>The first section focuses on key query operations, including text queries, terms aggregations, range queries, date histograms, and sorting. These improvements were evaluated using the <a href="https://github.com/opensearch-project/opensearch-benchmark-workloads/tree/main/big5">OpenSearch Big5 workload</a>, which represents common use cases in both search and analytics applications. The benchmarks provide a repeatable framework for measuring real-world performance enhancements. The next section reports on vector search improvements. Finally, we present our roadmap for 2025, where you’ll see that we’re making qualitative improvements in many areas, in addition to important incremental changes. We are improving query speed by processing data in real time. We are building a query planner that uses resources more efficiently. We are speeding up intra-cluster communications. And we’re adding efficient join operations to query domain-specific language (DSL), Piped Processing Language (PPL), and SQL. To follow our work in more detail, and to contribute comments or code, please participate on the <a href="https://forum.opensearch.org/">OpenSearch forum</a> as well as directly in our GitHub repos.</p>

<style>

.light-green-clr {
    background-color: #e3f8e3;
}

.gray-clr {
    background-color: #f5f7f7; 
}

.ylw-clr {
    background-color: #FFEFCC;
}

.border-btm {
    border-bottom: 2px solid #e6e6e6;
}

.bold {
    font-weight: 700;
}

.left {
    text-align: left;
}

table { 
    font-size: 16px; 
}

h3 {
    font-size: 22px;
}

th {
    background-color: #f5f7f7;
}​

</style>

<h2 id="performance-improvements-through-217">Performance improvements through 2.17</h2>

<p>Since its inception, OpenSearch has consistently improved performance, and version 2.17 continues this trend. Compared to earlier versions, OpenSearch 2.17 delivers improved performance, achieving a <strong>6x speed increase</strong> over OpenSearch 1.3 and reducing query latencies across various categories. The following graph shows the relative improvements by query category as 90th percentile latencies, with a baseline of OpenSearch 1.3.</p>

<p><img src="/assets/media/blog-images/2024-11-26-opensearch-performance-2.17/OS-PerformanceChart-ldc@2x.png" alt="OpenSearch performance improvements up to 2.17" class="center" style="width: 100%; max-width: 800px; height: auto; text-align: center" /></p>

<h3 id="key-highlights">Key highlights</h3>

<p>Based on our benchmarking, we’ve identified the following key highlights:</p>

<ul>
  <li><strong>Overall query performance</strong>: OpenSearch 2.17 delivers <strong>6x better performance</strong> than OpenSearch 1.3.</li>
  <li><strong>Text queries</strong>: Text search queries, fundamental to many OpenSearch use cases, are <strong>63% faster</strong> in 2.17 compared to the baseline of OpenSearch 1.3.</li>
  <li><strong>Terms aggregations</strong>: This critical query type for log analytics shows an <strong>81% improvement</strong> compared to OpenSearch 1.3, allowing for faster and more efficient data aggregation.</li>
  <li><strong>Date histograms</strong>: Date histogram performance has <strong>improved by 97%</strong> compared to OpenSearch 1.3, providing major speed improvements for time-series analysis.</li>
  <li><strong>Range queries</strong>: With an <strong>87% performance improvement</strong> compared to OpenSearch 1.3, range queries now execute more quickly while using fewer resources.</li>
  <li><strong>Sorting and filtering</strong>: OpenSearch 2.17 delivers faster sorting, with a <strong>59% improvement</strong> compared to OpenSearch 1.3, enhancing query performance for numeric and textual datasets.</li>
</ul>

<p>The following table summarizes performance improvements for the preceding query types.</p>

<table>
    <tbody>
        <tr>
            <th></th>
            <th>Query type</th>
            <th>OS 1.3.18</th>
            <th>OS <br />2.7</th>
            <th>OS 2.11</th>
            <th>OS 2.12</th>
            <th>OS 2.13</th>
            <th>OS 2.14</th>
            <th>OS 2.15</th>
            <th>OS 2.16</th>
            <th>OS 2.17</th>
        </tr>
        <tr>
            <td rowspan="5" class="bold left gray-clr">Big 5 areas mean latency, ms</td>
            <td class="left">Text queries</td>
            <td class="ylw-clr">59.51</td>
            <td>47.91</td>
            <td>41.05</td>
            <td>27.29</td>
            <td>27.61</td>
            <td>27.85</td>
            <td>27.39</td>
            <td>21.7</td>
            <td class="light-green-clr">21.77</td>
        </tr>
        <tr>
            <td class="left">Sorting</td>
            <td class="ylw-clr">17.73</td>
            <td>11.24</td>
            <td>8.14</td>
            <td>7.99</td>
            <td>7.53</td>
            <td>7.47</td>
            <td>7.78</td>
            <td>7.22</td>
            <td class="light-green-clr">7.26</td>
        </tr>
        <tr>
            <td class="left">Terms aggregations</td>
            <td class="ylw-clr">609.43</td>
            <td>1351</td>
            <td>1316</td>
            <td>1228</td>
            <td>291</td>
            <td>293</td>
            <td>113</td>
            <td>112</td>
            <td class="light-green-clr">113</td>
        </tr>
        <tr>
            <td class="left">Range queries</td>
            <td class="ylw-clr">26.08</td>
            <td>23.12</td>
            <td>16.91</td>
            <td>18.71</td>
            <td>17.33</td>
            <td>17.39</td>
            <td>18.51</td>
            <td>3.17</td>
            <td class="light-green-clr">3.17</td>
        </tr>
        <tr>
            <td class="left">Date histograms</td>
            <td class="ylw-clr">6068</td>
            <td>5249</td>
            <td>5168</td>
            <td>469</td>
            <td>357</td>
            <td>146</td>
            <td>157</td>
            <td>164</td>
            <td class="light-green-clr">160</td>
        </tr>
        <tr>
            <td colspan="2" class="bold left gray-clr">Aggregate (geo mean)</td>
            <td class="ylw-clr">159.04</td>
            <td>154.59</td>
            <td>130.9</td>
            <td>74.85</td>
            <td>51.84</td>
            <td>43.44</td>
            <td>37.07</td>
            <td>24.66</td>
            <td class="light-green-clr">24.63</td>
        </tr>
        <tr>
            <td colspan="2" class="bold left gray-clr">Speedup factor, compared to OS 1.3 (geo mean)</td>
            <td class="ylw-clr">1.0</td>
            <td>1.03</td>
            <td>1.21</td>
            <td>2.12</td>
            <td>3.07</td>
            <td>3.66</td>
            <td>4.29</td>
            <td>6.45</td>
            <td class="light-green-clr">6.46</td>
        </tr>
        <tr>
            <td colspan="2" class="bold left gray-clr">Relative latency, compared to OS 1.3 (geo mean)</td>
            <td class="ylw-clr">100%</td>
            <td>97.20%</td>
            <td>82.31%</td>
            <td>47.06%</td>
            <td>32.60%</td>
            <td>27.31%</td>
            <td>23.31%</td>
            <td>15.51%</td>
            <td class="light-green-clr">15.49%</td>
        </tr>
    </tbody>
</table>

<p>For a detailed benchmark analysis or to run your own benchmarks, see the <a href="#appendix-benchmarking-tests-and-results">Appendix</a>.</p>

<h2 id="queries">Queries</h2>

<p>OpenSearch now features the following query improvements.</p>

<h3 id="text-queries">Text queries</h3>

<p>Text queries are fundamental to effective text search, especially in applications requiring fast and accurate document retrieval. OpenSearch 2.12 introduced the <strong>match_only_text</strong> field to address specific needs in analytics and applications prioritizing 100% recall or customized ranking strategies. This field type dramatically reduced index sizes and accelerated query execution by removing the complexity of relevance-based scoring. As a result, <strong>text queries performed 47% faster compared to OpenSearch 2.11 and 57% faster compared to OpenSearch 1.3</strong>.</p>

<p>With OpenSearch 2.17, we further amplified these performance gains. Building on the foundation of the <strong>match_only_text</strong> field, OpenSearch 2.17 optimizes text queries, achieving <strong>21% faster performance compared to 2.14</strong> and <strong>63% faster performance compared to 1.3</strong>. These improvements stem from continued enhancements to query execution and index optimization. Applications relying on text search for analytics or high-recall use cases can now achieve faster results with reduced resource usage, making OpenSearch 2.17 an even more powerful choice for modern text search workloads.</p>

<h3 id="terms-and-multi-terms-aggregations">Terms and multi-terms aggregations</h3>

<p>Terms aggregations are crucial for slicing large datasets based on multiple criteria, making them important query operations for data analytics use cases. Building on prior advancements, OpenSearch 2.17 enhances the efficiency of global terms aggregations, using term frequency optimizations to handle large immutable collections, such as log data, with unprecedented speed.</p>

<p>Performance benchmarks demonstrate a <strong>61% performance improvement compared to OpenSearch 2.14</strong> and an overall <strong>81% reduction in query latency compared to OpenSearch 1.3</strong>, while <strong>multi-terms aggregation queries demonstrate up to a 20% reduction in latency</strong>. Additionally, memory efficiency is improved dramatically, with a <strong>50–60% reduction in memory footprint for short-lived objects</strong> because new byte array allocations for composite key storage are not needed.</p>

<p>OpenSearch 2.17 also introduced support for the <strong><a href="https://github.com/opensearch-project/OpenSearch/pull/13461">wildcard field type</a></strong>, enabling highly efficient execution of wildcard, prefix, and regular expression queries. This new field type uses trigrams (or bigrams and individual characters) to match patterns before applying a post-filtering step to evaluate the original field, resulting in faster and more efficient query execution.</p>

<p>These advancements make OpenSearch 2.17 a powerful tool for analytics use cases, from large-scale log processing to complex query scenarios, continuing the mission of delivering speed, efficiency, and scalability to your data workflows.</p>

<h3 id="date-histograms">Date histograms</h3>

<p>Date histograms are fundamental to time-based data analysis, underpinning OpenSearch Dashboards visualizations such as time-series charts. In OpenSearch 2.17, date histogram queries now execute <strong>55% faster compared to OpenSearch 2.13</strong> and <strong>97% faster compared to OpenSearch 1.3</strong>, significantly improving the performance of time-series aggregations. This enhancement is particularly impactful for queries without subaggregations and has also been extended to <strong>range aggregations</strong>, further optimizing time-based analyses.</p>

<p>Additionally, <strong>cardinality aggregation</strong>—a critical tool for counting distinct values, such as unique visitors, event types, or products—has received a major performance boost. OpenSearch 2.17 introduced an <a href="https://github.com/opensearch-project/OpenSearch/pull/13821">optimization</a> that dynamically prunes documents containing distinct values that have already been collected, significantly reducing redundant processing. For <strong>low-cardinality requests</strong>, this leads to notable performance gains, while <strong>high-cardinality requests</strong> see improvements of up to <strong>20%</strong>, streamlining the handling of even the most demanding datasets.</p>

<p>These enhancements make OpenSearch 2.17 an essential upgrade for managing time-based or high-volume datasets, ensuring faster and more efficient query execution for diverse analytics needs.</p>

<h3 id="range-queries-and-numeric-fields">Range queries and numeric fields</h3>

<p><strong>Range queries</strong>, commonly used to filter data within specific numerical or date ranges, have undergone significant performance improvements in OpenSearch 2.17. These queries are now <strong>81% faster compared to OpenSearch 2.14</strong> and <strong>87% faster compared to OpenSearch 1.3</strong> because of advancements and optimizations in range filter processing.</p>

<p>At search time, OpenSearch evaluates whether a query can be rewritten from its <em>original query</em> to an <em>approximate query</em>, which executes faster and uses fewer resources. This <a href="https://github.com/opensearch-project/OpenSearch/pull/13788">approximate range optimization</a> ensures that most queries deliver equivalent results with reduced computational overhead, maintaining accuracy while improving performance. These enhancements make OpenSearch 2.17 an excellent choice for applications requiring high-performance range filtering, such as analytics dashboards, monitoring systems, and time-series data exploration.</p>

<h3 id="sorting-and-filtering">Sorting and filtering</h3>

<p><strong>Sorting performance</strong> has been a focus throughout the OpenSearch 2.x series, with OpenSearch 2.17 showing minor improvements compared to OpenSearch 2.14 but a <strong>59% performance improvement compared to OpenSearch 1.3</strong>. These refinements enable faster query results, particularly for datasets requiring extensive sorting by numeric or textual fields.</p>

<p>Filtering has also seen a significant advancement with the introduction of <strong><a href="https://github.com/opensearch-project/OpenSearch/pull/14774">roaring bitmap encoding</a></strong> for handling large filter lists. This approach minimizes memory and network overhead by compressing filter data into efficient bitmap structures. It is particularly effective in high-cardinality scenarios, such as when filtering a product catalog by items owned by a specific customer. The bitmap-based filters can be stored and seamlessly joined with the main index at query time. Tests demonstrate that this method maintains <strong>low latency</strong> even with numerous filters, making it a scalable and high-performing alternative to traditional terms queries or lookup strategies. These improvements ensure that OpenSearch 2.17 delivers faster and more efficient sorting and filtering for diverse use cases, from search to large-scale analytics.</p>

<h2 id="vector-search">Vector search</h2>

<p><strong>Disk-optimized vector search</strong>: The OpenSearch vector engine continues to prioritize cost savings in the 2.17 release. This release introduced disk-optimized vector search, allowing you to use the full potential of vector workloads, even in low-memory environments. Disk-optimized vector search is designed to provide out-of-the-box <strong>32x compression</strong> when using binary quantization, a powerful compression technique. Additionally, you have the flexibility to fine-tune costs, response time, and accuracy to your unique needs through configurable parameters such as compression rate, sampling, and rescoring. According to internal benchmarks, OpenSearch’s disk-optimized vector search can deliver cost savings of up to 70% while maintaining p90 latencies of around 200 ms and recall of over 0.9. For more information, see <a href="https://opensearch.org/docs/latest/search-plugins/knn/disk-based-vector-search/">Disk-based vector search</a>.</p>

<p><strong>Cost improvements by reducing memory footprint</strong>: Vector search capabilities in native engines (Faiss and NMSLIB) received a significant boost in OpenSearch 2.17. In this version, OpenSearch’s byte compression technique is extended to the Faiss Engine <a href="https://github.com/opensearch-project/k-NN/pull/1823">HSNW</a> and <a href="https://github.com/opensearch-project/k-NN/pull/2002">IVF</a> algorithms to further reduce memory footprint by up to 75% for vectors within byte range ([-128, 127]). These provide an additional 25% memory footprint savings compared to OpenSearch 2.14 with <a href="https://opensearch.org/blog/optimizing-opensearch-with-fp16-quantization/">FP16 quantization</a> and an overall savings of up to 85% compared to OpenSearch 1.3.</p>

<p><strong>Vector index build improvements</strong>: In 2024, the Vector Engine team made significant <a href="https://github.com/opensearch-project/k-NN/issues/1599">investments</a> in improving the performance of the OpenSearch vector engine. These included adding <a href="https://github.com/opensearch-project/k-NN/issues/2056">AVX512 SIMD support</a>, fixing <a href="https://github.com/opensearch-project/k-NN/issues/2277">some bugs</a> related to segment replication with vector indexes, <a href="https://github.com/opensearch-project/k-NN/issues/1853">transitioning to the more efficient KNNVectorsFormat</a>, and <a href="https://github.com/opensearch-project/k-NN/issues/1938">employing incremental graph builds during merges to reduce memory footprint</a>. With incremental graph builds, the native memory footprint during indexing has been significantly reduced because the full dataset is loaded into memory at once. This improvement supports HNSW graph builds in low-memory environments and reduces overall build time by approximately 30% compared to OpenSearch 1.3.</p>

<p><strong>Exact search improvements</strong>: In OpenSearch 2.15, <a href="https://opensearch.org/blog/boosting-k-nn-exact-search/">SIMD optimizations</a> were added to the k-NN plugin’s script scoring, resulting in significant performance gains for CPUs with SIMD support, such as AVX2 or AVX512 on x86 or NEON on ARM. Further improvements in OpenSearch 2.17 introduced Lucene’s new vector format, which includes optimized memory-mapped file access. Together, these enhancements significantly reduce search latency for exact k-NN searches on supported hardware.</p>

<h2 id="roadmap-for-2025">Roadmap for 2025</h2>

<p>The following improvements are included in the OpenSearch roadmap for 2025.</p>

<h3 id="core-search-engine">Core search engine</h3>

<p>In 2025, we will push the boundaries of OpenSearch’s query engine with several key initiatives aimed at improving performance, scalability, and efficiency:</p>

<ul>
  <li><strong><a href="https://github.com/opensearch-project/OpenSearch/issues/16679">Streaming architecture</a></strong>: We’re moving from a request/response model to streaming, processing, and delivering data in real time, thereby reducing memory overhead and improving query speed.</li>
  <li><strong><a href="https://github.com/opensearch-project/OpenSearch/issues/15185">Native join support</a></strong>: We’re introducing efficient join operations across indexes that will be natively supported and fully integrated with OpenSearch’s query DSL, PPL, and SQL.</li>
  <li><strong>Native vectorized processing</strong>: By using modern CPU SIMD operations and native code, we’re optimizing the processing of data streams to eliminate Java’s garbage collection bottlenecks.</li>
  <li><strong><a href="https://github.com/opensearch-project/OpenSearch/issues/12390">Smarter query planning</a></strong>: Optimizing where and how computations run will ensure reduced unnecessary data transfer and improve performance for parallel query execution.</li>
  <li><strong><a href="https://github.com/opensearch-project/OpenSearch/issues/15190">gRPC-based Search API</a></strong>: We’re enhancing client-server communication with Protobuf and gRPC, accelerating search by reducing overhead.</li>
  <li><strong><a href="https://github.com/orgs/opensearch-project/projects/153">Query performance optimization</a></strong>: Improving performance remains our consistent priority, and several key initiatives, such as docID encoding and query approximation, will reduce index size and enhance the performance of large-range queries.</li>
  <li><strong><a href="https://github.com/opensearch-project/OpenSearch/issues/12498">Star-tree indexing</a></strong>: Precomputing aggregations using star-tree indexing will ensure faster, more predictable performance for aggregation-heavy queries.</li>
</ul>

<h3 id="vector-search-1">Vector search</h3>

<p>In 2025, we will continue to invest in the following key initiatives aimed at performance improvements and cost savings:</p>

<ul>
  <li><strong>Index build acceleration with GPUs and SIMD</strong>: k-NN performance can be enhanced by using libraries with GPU support. Because vector distance calculations are compute-heavy, GPUs can speed up computations and reduce index build and search query times.</li>
  <li><strong>Autotuning k-NN indexes</strong>: OpenSearch’s vector database offers a toolkit of algorithms tailored for diverse workloads. In 2025, our goal is to enhance the out-of-the-box experience by autotuning hyperparameters and settings based on access patterns and hardware resources.</li>
  <li><strong>Cold-warm tiering</strong>: In version 2.18, we added support for enabling vector search on remote snapshots. We will continue focusing on decoupling index read/write operations to extend vector indexes to different storage systems in order to reduce storage and compute costs.</li>
  <li><strong>Memory footprint reduction</strong>: We will continue to aggressively reduce the memory footprint of vector indexes. One of our goals is to support the ability to partially load HNSW indexes into native engines. This complements our disk-optimized search and helps further reduce the operating costs of OpenSearch clusters.</li>
  <li><strong>Reduced disk storage using derived source</strong>: Currently, vector data is stored both in a doc-values-like format and in the stored <code class="language-plaintext highlighter-rouge">_source</code> field. The stored <code class="language-plaintext highlighter-rouge">_source</code> field can contribute more than 60% of the overall vector storage requirement. We plan to create a custom stored field format that will inject the vector fields into the source from the doc-values-like format, creating a derived source field. In addition to storage savings, this approach will improve indexing throughput, reduce shard size, and even accelerate search.</li>
</ul>

<h3 id="neural-search">Neural search</h3>

<p>Neural search uses machine learning models to understand the semantic meaning of search queries, going beyond traditional keyword matching. <strong>It encompasses dense vector search, sparse vector search, and hybrid search approaches that combine semantic understanding with lexical search</strong>. Since introducing neural search capabilities in OpenSearch 2.9, we’ve expanded the functionality to include text embedding, cross-encoder reranking, sparse encoding, and hybrid search.</p>

<p>Our 2025 roadmap emphasizes optimizing performance, enhancing functionality, and simplifying adoption. Key initiatives include:</p>

<ul>
  <li><strong>Improving hybrid query performance</strong>: Reduce latency by up to 25%.</li>
  <li><strong>Introducing explainability for hybrid queries</strong>: Provide insights into how each subquery result contributes to the final hybrid query result, enabling better debugging and performance analysis.</li>
  <li><strong>Supporting additional algorithms for combining hybrid query results</strong>: Support algorithms like reciprocal rank fusion (RRF), which improves hybrid search latency by avoiding costly score normalization because the scores are rank based.</li>
  <li><strong>Enhancing neural sparse pruning strategies</strong>: Apply techniques such as pruning by weight, by ratio with max weight, by top-k, and by alpha-mass to improve performance by 20%.</li>
  <li><strong>Optimizing inference calls during updates and reindexing</strong>: Reduce the number of inference calls required for neural and sparse ingestion pipelines, increasing throughput by 20% for these operations.</li>
  <li><strong>Consolidating multifield inference calls</strong>: Combine multiple field inference calls into a single operation for dense and sparse vector semantic search, reducing inference latency by 15% for multifield dense-vector-based semantic queries.</li>
  <li><strong>Reducing memory usage for resource-constrained systems</strong>: Introduce a new quantization processor to decrease memory usage by 20%, improving efficiency in environments with limited resources or connectivity.</li>
</ul>

<p>These advancements aim to enhance query performance, streamline operations, and expand usability across diverse workloads.</p>

<h2 id="conclusion">Conclusion</h2>

<p>OpenSearch continues to evolve, not only by expanding functionality but also by significantly enhancing performance, efficiency, and scalability across diverse workloads. OpenSearch 2.17 exemplifies the community’s commitment, delivering improvements in query speed, resource utilization, and memory efficiency across text queries, aggregations, range queries, and time-series analytics. These advancements underscore our dedication to optimizing OpenSearch for real-world use cases.</p>

<p>Key innovations like disk-optimized vector search and enhancements to terms and multi-terms aggregations demonstrate our focus on staying at the forefront of vector search and analytics technology. Additionally, OpenSearch 2.17’s improvements to hybrid and vector search, combined with roadmap plans for streaming architecture, gRPC APIs, and smarter query planning, highlight our forward-looking strategy for meeting the demands of modern workloads.</p>

<p>These achievements are made possible through collaboration with the broader OpenSearch community, whose contributions to testing, feedback, and development have been invaluable. Together, we are building a robust and efficient search and analytics engine capable of addressing current and future challenges.</p>

<p>Stay connected to our <a href="https://opensearch.org/blog">blog</a> and <a href="https://github.com/opensearch-project/OpenSearch">GitHub repos</a> for ongoing updates and insights as we continue this journey of innovation and share future plans.</p>

<h2 id="appendix-benchmarking-tests-and-results">Appendix: Benchmarking tests and results</h2>

<p>This section outlines the performance benchmarks and results achieved using OpenSearch Benchmark to evaluate the Big5 workload across various OpenSearch versions and configurations.</p>

<h3 id="benchmark-setup">Benchmark setup</h3>

<ul>
  <li><strong>Benchmarking tool</strong>: OpenSearch Benchmark, our standard benchmarking tool used in prior evaluations.</li>
  <li><strong>Instance type</strong>: <em>c5.2xlarge</em> (8 vCPU, 16 GB RAM), chosen as a mid-tier option to avoid masking resource efficiency gains with oversized instances.</li>
  <li><strong>Cluster configuration</strong>: A single-node cluster for reproducibility and ease of setup.</li>
  <li><strong>Index setup</strong>: A Big5 Index configured with one shard and no replicas (<code class="language-plaintext highlighter-rouge">--workload-params="number_of_replicas:0"</code>).</li>
  <li><strong>Corpus details</strong>: A 100 GB dataset with 116 million documents. The storage size after ingestion was 24 GB for the primary shard. After removing overheads like doc values, the RSS is expected to be around 8 GB, matching the JVM heap size for the instance. Keeping most of the data in memory ensures a more accurate evaluation of performance improvements.</li>
  <li><strong>Ingestion</strong>: Conducted with a single bulk indexing client to ensure that data is ingested in chronological order.</li>
  <li><strong>Merge policy</strong>:
    <ul>
      <li>LogByteSizeMergePolicy for OpenSearch 2.11.0 and later.</li>
      <li>TieredMergePolicy for OpenSearch 1.3.18 and 2.7.0.</li>
    </ul>
  </li>
  <li><strong>Test mode</strong>: Tests were run in <a href="https://opensearch.org/docs/latest/benchmark/user-guide/target-throughput/#benchmarking-mode">benchmarking mode</a> (<code class="language-plaintext highlighter-rouge">target-throughput</code> disabled) so that the OpenSearch client sent requests to the OpenSearch cluster as fast as possible.</li>
</ul>

<h3 id="results">Results</h3>

<p>The following table provides benchmarking test results.</p>

<table>
    <thead>
        <tr>
            <th>Buckets</th>
            <th>Query</th>
            <th>Order</th>
            <th>OS 1.3.18 p90 service time (ms)</th>
            <th>OS 2.7 p90 service time (ms)</th>
            <th>OS 2.11.1 p90 service time (ms)</th>
            <th>OS 2.12.0 p90 service time (ms)</th>
            <th>OS 2.13.0 p90 service time (ms)</th>
            <th>OS 2.14 p90 service time (ms)</th>
            <th>OS 2.15 p90 service time (ms)</th>
            <th>OS 2.16 p90 service time (ms)</th>
            <th>OS 2.17 p90 service time (ms)</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td rowspan="4" class="bold">Text queries</td>
            <td>query-string-on-message</td>
            <td>1</td>
            <td>332.75</td>
            <td>280</td>
            <td>276</td>
            <td>78.25</td>
            <td>80</td>
            <td>77.75</td>
            <td>77.25</td>
            <td>77.75</td>
            <td>78</td>
        </tr>
        <tr>
            <td>query-string-on-message-filtered</td>
            <td>2</td>
            <td>67.25</td>
            <td>47</td>
            <td>30.25</td>
            <td>46.5</td>
            <td>47.5</td>
            <td>46</td>
            <td>46.75</td>
            <td>29.5</td>
            <td>30</td>
        </tr>
        <tr>
            <td>query-string-on-message-filtered-sorted-num</td>
            <td>3</td>
            <td>125.25</td>
            <td>102</td>
            <td>85.5</td>
            <td>41</td>
            <td>41.25</td>
            <td>41</td>
            <td>40.75</td>
            <td>24</td>
            <td>24.5</td>
        </tr>
        <tr class="border-btm">
            <td>term</td>
            <td>4</td>
            <td>4</td>
            <td>3.75</td>
            <td>4</td>
            <td>4</td>
            <td>4</td>
            <td>4</td>
            <td>4</td>
            <td>4</td>
            <td>4</td>
        </tr>
        <tr>
            <td rowspan="14" class="bold">Sorting</td>
            <td>asc_sort_timestamp</td>
            <td>5</td>
            <td>9.75</td>
            <td>15.75</td>
            <td>7.5</td>
            <td>7</td>
            <td>7</td>
            <td>7</td>
            <td>7</td>
            <td>7</td>
            <td>7</td>
        </tr>
        <tr>
            <td>asc_sort_timestamp_can_match_shortcut</td>
            <td>6</td>
            <td>13.75</td>
            <td>7</td>
            <td>7</td>
            <td>6.75</td>
            <td>6</td>
            <td>6.25</td>
            <td>6.5</td>
            <td>6</td>
            <td>6.25</td>
        </tr>
        <tr>
            <td>asc_sort_timestamp_no_can_match_shortcut</td>
            <td>7</td>
            <td>13.5</td>
            <td>7</td>
            <td>7</td>
            <td>6.5</td>
            <td>6</td>
            <td>6</td>
            <td>6.5</td>
            <td>6</td>
            <td>6.25</td>
        </tr>
        <tr>
            <td>asc_sort_with_after_timestamp</td>
            <td>8</td>
            <td>35</td>
            <td>33.75</td>
            <td>238</td>
            <td>212</td>
            <td>197.5</td>
            <td>213.5</td>
            <td>204.25</td>
            <td>160.5</td>
            <td>185.25</td>
        </tr>
        <tr>
            <td>desc_sort_timestamp</td>
            <td>9</td>
            <td>12.25</td>
            <td>39.25</td>
            <td>6</td>
            <td>7</td>
            <td>5.75</td>
            <td>5.75</td>
            <td>5.75</td>
            <td>6</td>
            <td>6</td>
        </tr>
        <tr>
            <td>desc_sort_timestamp_can_match_shortcut</td>
            <td>10</td>
            <td>7</td>
            <td>120.5</td>
            <td>5</td>
            <td>5.5</td>
            <td>5</td>
            <td>4.75</td>
            <td>5</td>
            <td>5</td>
            <td>5</td>
        </tr>
        <tr>
            <td>desc_sort_timestamp_no_can_match_shortcut</td>
            <td>11</td>
            <td>6.75</td>
            <td>117</td>
            <td>5</td>
            <td>5</td>
            <td>4.75</td>
            <td>4.5</td>
            <td>4.75</td>
            <td>5</td>
            <td>5</td>
        </tr>
        <tr>
            <td>desc_sort_with_after_timestamp</td>
            <td>12</td>
            <td>487</td>
            <td>33.75</td>
            <td>325.75</td>
            <td>358</td>
            <td>361.5</td>
            <td>385.25</td>
            <td>378.25</td>
            <td>320.25</td>
            <td>329.5</td>
        </tr>
        <tr>
            <td>sort_keyword_can_match_shortcut</td>
            <td>13</td>
            <td>291</td>
            <td>3</td>
            <td>3</td>
            <td>3.25</td>
            <td>3.5</td>
            <td>3</td>
            <td>3</td>
            <td>3</td>
            <td>3</td>
        </tr>
        <tr>
            <td>sort_keyword_no_can_match_shortcut</td>
            <td>14</td>
            <td>290.75</td>
            <td>3.25</td>
            <td>3</td>
            <td>3.5</td>
            <td>3.25</td>
            <td>3</td>
            <td>3.75</td>
            <td>3</td>
            <td>3.25</td>
        </tr>
        <tr>
            <td>sort_numeric_asc</td>
            <td>15</td>
            <td>7.5</td>
            <td>4.5</td>
            <td>4.5</td>
            <td>4</td>
            <td>4</td>
            <td>4</td>
            <td>4</td>
            <td>4</td>
            <td>4</td>
        </tr>
        <tr>
            <td>sort_numeric_asc_with_match</td>
            <td>16</td>
            <td>2</td>
            <td>1.75</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>1.75</td>
            <td>2</td>
            <td>2</td>
        </tr>
        <tr>
            <td>sort_numeric_desc</td>
            <td>17</td>
            <td>8</td>
            <td>6</td>
            <td>6</td>
            <td>5.5</td>
            <td>4.75</td>
            <td>5</td>
            <td>4.75</td>
            <td>4.25</td>
            <td>4.5</td>
        </tr>
        <tr class="border-btm">
            <td>sort_numeric_desc_with_match</td>
            <td>18</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>1.75</td>
            <td>2</td>
            <td>2</td>
        </tr>
        <tr>
            <td rowspan="7" class="bold">Terms aggregations</td>
            <td>cardinality-agg-high</td>
            <td>19</td>
            <td>3075.75</td>
            <td>2432.25</td>
            <td>2506.25</td>
            <td>2246</td>
            <td>2284.5</td>
            <td>2202.25</td>
            <td>2323.75</td>
            <td>2337.25</td>
            <td>2408.75</td>
        </tr>
        <tr>
            <td>cardinality-agg-low</td>
            <td>20</td>
            <td>2925.5</td>
            <td>2295.5</td>
            <td>2383</td>
            <td>2126</td>
            <td>2245.25</td>
            <td>2159</td>
            <td>3</td>
            <td>3</td>
            <td>3</td>
        </tr>
        <tr>
            <td>composite_terms-keyword</td>
            <td>21</td>
            <td>466.75</td>
            <td>378.5</td>
            <td>407.75</td>
            <td>394.5</td>
            <td>353.5</td>
            <td>366</td>
            <td>350</td>
            <td>346.5</td>
            <td>350.25</td>
        </tr>
        <tr>
            <td>composite-terms</td>
            <td>22</td>
            <td>290</td>
            <td>242</td>
            <td>263</td>
            <td>252</td>
            <td>233</td>
            <td>228.75</td>
            <td>229</td>
            <td>223.75</td>
            <td>226</td>
        </tr>
        <tr>
            <td>keyword-terms</td>
            <td>23</td>
            <td>4695.25</td>
            <td>3478.75</td>
            <td>3557.5</td>
            <td>3220</td>
            <td>29.5</td>
            <td>26</td>
            <td>25.75</td>
            <td>26.25</td>
            <td>26.25</td>
        </tr>
        <tr>
            <td>keyword-terms-low-cardinality</td>
            <td>24</td>
            <td>4699.5</td>
            <td>3383</td>
            <td>3477.25</td>
            <td>3249.75</td>
            <td>25</td>
            <td>22</td>
            <td>21.75</td>
            <td>21.75</td>
            <td>21.75</td>
        </tr>
        <tr class="border-btm">
            <td>multi_terms-keyword</td>
            <td>25</td>
            <td>0*</td>
            <td>0*</td>
            <td>854.75</td>
            <td>817.25</td>
            <td>796.5</td>
            <td>748</td>
            <td>768.5</td>
            <td>746.75</td>
            <td>770</td>
        </tr>
        <tr>
            <td rowspan="9" class="bold">Range queries</td>
            <td>keyword-in-range</td>
            <td>26</td>
            <td>101.5</td>
            <td>100</td>
            <td>18</td>
            <td>22</td>
            <td>23.25</td>
            <td>26</td>
            <td>27.25</td>
            <td>18</td>
            <td>17.75</td>
        </tr>
        <tr>
            <td>range</td>
            <td>27</td>
            <td>85</td>
            <td>77</td>
            <td>14.5</td>
            <td>18.25</td>
            <td>20.25</td>
            <td>22.75</td>
            <td>24.25</td>
            <td>13.75</td>
            <td>14.25</td>
        </tr>
        <tr>
            <td>range_field_conjunction_big_range_big_term_query</td>
            <td>28</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
        </tr>
        <tr>
            <td>range_field_conjunction_small_range_big_term_query</td>
            <td>29</td>
            <td>2</td>
            <td>1.75</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>1.5</td>
            <td>2</td>
            <td>2</td>
        </tr>
        <tr>
            <td>range_field_conjunction_small_range_small_term_query</td>
            <td>30</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
        </tr>
        <tr>
            <td>range_field_disjunction_big_range_small_term_query</td>
            <td>31</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2.25</td>
        </tr>
        <tr>
            <td>range-agg-1</td>
            <td>32</td>
            <td>4641.25</td>
            <td>3810.75</td>
            <td>3745.75</td>
            <td>3578.75</td>
            <td>3477.5</td>
            <td>3328.75</td>
            <td>3318.75</td>
            <td>2</td>
            <td>2.25</td>
        </tr>
        <tr>
            <td>range-agg-2</td>
            <td>33</td>
            <td>4568</td>
            <td>3717.25</td>
            <td>3669.75</td>
            <td>3492.75</td>
            <td>3403.5</td>
            <td>3243.5</td>
            <td>3235</td>
            <td>2</td>
            <td>2.25</td>
        </tr>
        <tr class="border-btm">
            <td>range-numeric</td>
            <td>34</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
        </tr>
        <tr>
            <td rowspan="5" class="bold">Date histograms</td>
            <td>composite-date_histogram-daily</td>
            <td>35</td>
            <td>4828.75</td>
            <td>4055.5</td>
            <td>4051.25</td>
            <td>9</td>
            <td>3</td>
            <td>2.5</td>
            <td>3</td>
            <td>2.75</td>
            <td>2.75</td>
        </tr>
        <tr>
            <td>date_histogram_hourly_agg</td>
            <td>36</td>
            <td>4790.25</td>
            <td>4361</td>
            <td>4363.25</td>
            <td>12.5</td>
            <td>12.75</td>
            <td>6.25</td>
            <td>6</td>
            <td>6.25</td>
            <td>6.5</td>
        </tr>
        <tr>
            <td>date_histogram_minute_agg</td>
            <td>37</td>
            <td>1404.5</td>
            <td>1340.25</td>
            <td>1113.75</td>
            <td>1001.25</td>
            <td>923</td>
            <td>36</td>
            <td>32.75</td>
            <td>35.25</td>
            <td>39.75</td>
        </tr>
        <tr>
            <td>range-auto-date-histo</td>
            <td>38</td>
            <td>10373</td>
            <td>8686.75</td>
            <td>9940.25</td>
            <td>8696.75</td>
            <td>8199.75</td>
            <td>8214.75</td>
            <td>8278.75</td>
            <td>8306</td>
            <td>8293.75</td>
        </tr>
        <tr>
            <td>range-auto-date-histo-with-metrics</td>
            <td>39</td>
            <td>22988.5</td>
            <td>20438</td>
            <td>20108.25</td>
            <td>20392.75</td>
            <td>20117.25</td>
            <td>19656.5</td>
            <td>19959.25</td>
            <td>20364.75</td>
            <td>20147.5</td>
        </tr>
    </tbody>
</table>

<h3 id="notes-and-considerations">Notes and considerations</h3>

<ul>
  <li><strong>Additional queries</strong>: The Big5 workload was recently updated to include additional queries. These queries have been included in the results of this blog post.</li>
  <li><sup>*</sup> <strong><code class="language-plaintext highlighter-rouge">multi_terms-keyword</code> support</strong>: OpenSearch 1.3.18 and 2.7.0 recorded <code class="language-plaintext highlighter-rouge">0</code> ms service time for <code class="language-plaintext highlighter-rouge">multi_terms-keyword</code>. This is because <code class="language-plaintext highlighter-rouge">multi_terms-keyword</code> was not supported until OpenSearch 2.11.0. Mean latency calculations account for this by excluding <code class="language-plaintext highlighter-rouge">multi_terms-keyword</code> from the geometric mean computation for OpenSearch 1.3.18 and 2.7.0.</li>
</ul>]]></content><author><name>sisurab</name></author><category term="technical-posts" /><category term="community" /><summary type="html"><![CDATA[Learn more about the strategic enhancements and performance features that OpenSearch has delivered up to version 2.17.]]></summary></entry><entry><title type="html">Introducing byte vector support for Faiss in the OpenSearch vector engine</title><link href="https://opensearch.org/blog/faiss-byte-vector/" rel="alternate" type="text/html" title="Introducing byte vector support for Faiss in the OpenSearch vector engine" /><published>2024-11-26T00:00:00+00:00</published><updated>2025-01-09T20:49:28+00:00</updated><id>https://opensearch.org/blog/faiss-byte-vector</id><content type="html" xml:base="https://opensearch.org/blog/faiss-byte-vector/"><![CDATA[<p>The growing popularity of generative AI and large language models (LLMs) has led to an increased demand for efficient vector search and similarity operations. These models often rely on high-dimensional vector representations of text, images, or other data. Performing similarity searches or nearest neighbor queries on these vectors becomes computationally expensive, especially as vector databases grow in size. OpenSearch’s support for Faiss byte vectors offers a promising solution to these challenges.</p>

<p>Using byte vectors instead of float vectors for vector search provides significant improvements in memory efficiency and performance. This is especially beneficial for large-scale vector databases or environments with limited resources. Faiss byte vectors enable you to store quantized embeddings, significantly reducing memory consumption and lowering costs. This approach typically results in only minimal recall loss compared to using full-precision (float) vectors.</p>

<h2 id="how-to-use-a-faiss-byte-vector">How to use a Faiss byte vector</h2>

<p>A byte vector is a compact vector representation in which each dimension is a signed 8-bit integer ranging from -128 to 127. To use byte vectors, you must convert your input vectors, typically in <code class="language-plaintext highlighter-rouge">float</code> format, into the <code class="language-plaintext highlighter-rouge">byte</code> type before ingestion. This process requires quantization techniques, which compress float vectors while maintaining essential data characteristics. For more information, see <a href="https://opensearch.org/docs/latest/field-types/supported-field-types/knn-vector#quantization-techniques">Quantization techniques</a>.</p>

<p>To use a <code class="language-plaintext highlighter-rouge">byte</code> vector, set the <code class="language-plaintext highlighter-rouge">data_type</code> parameter to <code class="language-plaintext highlighter-rouge">byte</code> when creating a k-NN index (the default value of the <code class="language-plaintext highlighter-rouge">data_type</code> parameter is <code class="language-plaintext highlighter-rouge">float</code>):</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">PUT</span><span class="w"> </span><span class="err">test-index</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"settings"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"index"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"knn"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"mappings"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"properties"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"my_vector1"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"knn_vector"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"dimension"</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w">
        </span><span class="nl">"data_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"byte"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"method"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hnsw"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"space_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"l2"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"engine"</span><span class="p">:</span><span class="w"> </span><span class="s2">"faiss"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"parameters"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nl">"ef_construction"</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="p">,</span><span class="w">
            </span><span class="nl">"m"</span><span class="p">:</span><span class="w"> </span><span class="mi">16</span><span class="w">
          </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w"> 
</span></code></pre></div></div>

<p>During ingestion, make sure that each dimension of the vector is within the supported [-128, 127] range:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">PUT</span><span class="w"> </span><span class="err">test-index/_doc/</span><span class="mi">1</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"my_vector"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">-126</span><span class="p">,</span><span class="w"> </span><span class="mi">28</span><span class="p">,</span><span class="w"> </span><span class="mi">127</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="mi">-45</span><span class="p">,</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span><span class="w"> </span><span class="mi">-110</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w"> 
</span></code></pre></div></div>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">PUT</span><span class="w"> </span><span class="err">test-index/_doc/</span><span class="mi">2</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"my_vector"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">100</span><span class="p">,</span><span class="w"> </span><span class="mi">-25</span><span class="p">,</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="mi">-67</span><span class="p">,</span><span class="w"> </span><span class="mi">-2</span><span class="p">,</span><span class="w"> </span><span class="mi">127</span><span class="p">,</span><span class="w"> </span><span class="mi">99</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w"> 
</span></code></pre></div></div>

<p>During querying, make sure that the query vector is also within the byte range:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">GET</span><span class="w"> </span><span class="err">test-index/_search</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"size"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
  </span><span class="nl">"query"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"knn"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"my_vector1"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"vector"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">-1</span><span class="p">,</span><span class="w"> </span><span class="mi">45</span><span class="p">,</span><span class="w"> </span><span class="mi">-100</span><span class="p">,</span><span class="w"> </span><span class="mi">125</span><span class="p">,</span><span class="w"> </span><span class="mi">-128</span><span class="p">,</span><span class="w"> </span><span class="mi">-8</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w"> </span><span class="mi">10</span><span class="p">],</span><span class="w">
        </span><span class="nl">"k"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><strong>Note</strong>: When using <code class="language-plaintext highlighter-rouge">byte</code> vectors, expect some loss of recall precision as compared to using <code class="language-plaintext highlighter-rouge">float</code> vectors. Byte vectors are useful for large-scale applications and use cases that prioritize reducing memory usage in exchange for a minimal loss in recall.</p>

<h2 id="benchmarking-results">Benchmarking results</h2>

<p>We used OpenSearch Benchmark to run benchmarking tests on popular datasets to compare recall, indexing, and search performance between float vectors and byte vectors using Faiss HNSW.</p>

<p><strong>Note</strong>: Without single instruction, multiple data (SIMD) optimization (such as AVX2 or NEON) or when AVX2 is disabled (on x86 architectures), the quantization process introduces additional latency. For more information about AVX2-compatible processors, see <a href="https://en.wikipedia.org/wiki/Advanced_Vector_Extensions#CPUs_with_AVX2">CPUs with AVX2</a>. In an AWS environment, all community Amazon Machine Images (AMIs) with <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/virtualization_types.html">HVM</a> support AVX2 optimization.</p>

<p>These tests were conducted on a single-node cluster, except for the cohere-10m dataset, which used two <code class="language-plaintext highlighter-rouge">r5.2xlarge</code> instances.</p>

<h3 id="configuration">Configuration</h3>

<p>The following table lists the cluster configuration for the benchmarking tests.</p>

<table>
  <thead>
    <tr>
      <th><code class="language-plaintext highlighter-rouge">m</code></th>
      <th><code class="language-plaintext highlighter-rouge">ef_construction</code></th>
      <th><code class="language-plaintext highlighter-rouge">ef_search</code></th>
      <th>Replicas</th>
      <th>Primary shards</th>
      <th>Indexing clients</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>16</td>
      <td>100</td>
      <td>100</td>
      <td>0</td>
      <td>8</td>
      <td>16</td>
    </tr>
  </tbody>
</table>

<p>The following table lists the dataset configuration for the benchmarking tests.</p>

<table>
  <thead>
    <tr>
      <th>Dataset ID</th>
      <th>Dataset</th>
      <th>Vector dimension</th>
      <th>Data size</th>
      <th>Number of queries</th>
      <th>Training data range</th>
      <th>Query data range</th>
      <th>Space type</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Dataset 1</strong></td>
      <td>gist-960-euclidean</td>
      <td>960</td>
      <td>1,000,000</td>
      <td>1,000</td>
      <td>[0.0, 1.48]</td>
      <td>[0.0, 0.729]</td>
      <td>L2</td>
    </tr>
    <tr>
      <td><strong>Dataset 2</strong></td>
      <td>cohere-ip-10m</td>
      <td>768</td>
      <td>10,000,000</td>
      <td>10,000</td>
      <td>[-4.142334, 5.5211477]</td>
      <td>[-4.109505, 5.4809895]</td>
      <td>innerproduct</td>
    </tr>
    <tr>
      <td><strong>Dataset 3</strong></td>
      <td>cohere-ip-1m</td>
      <td>768</td>
      <td>1,000,000</td>
      <td>10,000</td>
      <td>[-4.1073565, 5.504557]</td>
      <td>[-4.109505, 5.4809895]</td>
      <td>innerproduct</td>
    </tr>
    <tr>
      <td><strong>Dataset 4</strong></td>
      <td>sift-128-euclidean</td>
      <td>128</td>
      <td>1,000,000</td>
      <td>10,000</td>
      <td>[0.0, 218.0]</td>
      <td>[0.0, 184.0]</td>
      <td>L2</td>
    </tr>
  </tbody>
</table>

<h3 id="recall-memory-and-indexing-results">Recall, memory, and indexing results</h3>

<table>
  <thead>
    <tr>
      <th>Dataset ID</th>
      <th>Faiss HNSW recall@100</th>
      <th>Faiss HNSW byte recall@100</th>
      <th>% Reduction in recall</th>
      <th>Faiss HNSW memory usage (GB)</th>
      <th>Faiss HNSW byte memory usage (GB)</th>
      <th>% Reduction in memory</th>
      <th>Faiss HNSW mean indexing throughput (docs/sec)</th>
      <th>Faiss HNSW byte mean indexing throughput (docs/sec)</th>
      <th>% Gain in indexing throughput</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Dataset 1</strong></td>
      <td>0.91</td>
      <td>0.89</td>
      <td>2.20</td>
      <td>3.72</td>
      <td>1.04</td>
      <td>72.00</td>
      <td>4673</td>
      <td>9686</td>
      <td>107.28</td>
    </tr>
    <tr>
      <td><strong>Dataset 2</strong></td>
      <td>0.91</td>
      <td>0.83</td>
      <td>8.79</td>
      <td>30.03</td>
      <td>8.57</td>
      <td>71.46</td>
      <td>4911</td>
      <td>10207</td>
      <td>107.84</td>
    </tr>
    <tr>
      <td><strong>Dataset 3</strong></td>
      <td>0.94</td>
      <td>0.86</td>
      <td>8.51</td>
      <td>3.00</td>
      <td>0.86</td>
      <td>71.33</td>
      <td>6112</td>
      <td>11673</td>
      <td>90.98</td>
    </tr>
    <tr>
      <td><strong>Dataset 4</strong></td>
      <td>0.99</td>
      <td>0.98</td>
      <td>1.01</td>
      <td>0.62</td>
      <td>0.26</td>
      <td>58.06</td>
      <td>38273</td>
      <td>43267</td>
      <td>13.05</td>
    </tr>
  </tbody>
</table>

<h3 id="query-results">Query results</h3>

<table>
  <thead>
    <tr>
      <th>Dataset ID</th>
      <th>Query clients</th>
      <th>Faiss HNSW p90 (ms)</th>
      <th>Faiss HNSW byte p90 (ms)</th>
      <th>Faiss HNSW p99 (ms)</th>
      <th>Faiss HNSW byte p99 (ms)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Dataset 1</strong></td>
      <td><strong>1</strong></td>
      <td>5.35</td>
      <td>5.34</td>
      <td>5.95</td>
      <td>5.59</td>
    </tr>
    <tr>
      <td><strong>Dataset 1</strong></td>
      <td><strong>8</strong></td>
      <td>6.68</td>
      <td>6.64</td>
      <td>10.23</td>
      <td>9.14</td>
    </tr>
    <tr>
      <td><strong>Dataset 1</strong></td>
      <td><strong>16</strong></td>
      <td>10.59</td>
      <td>7.38</td>
      <td>12.94</td>
      <td>11.47</td>
    </tr>
    <tr>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td><strong>Dataset 2</strong></td>
      <td><strong>1</strong></td>
      <td>7.39</td>
      <td>7.14</td>
      <td>8.35</td>
      <td>7.59</td>
    </tr>
    <tr>
      <td><strong>Dataset 2</strong></td>
      <td><strong>8</strong></td>
      <td>15.47</td>
      <td>14.83</td>
      <td>21.38</td>
      <td>16.20</td>
    </tr>
    <tr>
      <td><strong>Dataset 2</strong></td>
      <td><strong>16</strong></td>
      <td>25.01</td>
      <td>25.32</td>
      <td>31.98</td>
      <td>29.42</td>
    </tr>
    <tr>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td><strong>Dataset 3</strong></td>
      <td><strong>1</strong></td>
      <td>4.97</td>
      <td>4.72</td>
      <td>5.62</td>
      <td>5.02</td>
    </tr>
    <tr>
      <td><strong>Dataset 3</strong></td>
      <td><strong>8</strong></td>
      <td>6.75</td>
      <td>5.98</td>
      <td>7.69</td>
      <td>7.7</td>
    </tr>
    <tr>
      <td><strong>Dataset 3</strong></td>
      <td><strong>16</strong></td>
      <td>10.51</td>
      <td>6.94</td>
      <td>13.87</td>
      <td>12.4</td>
    </tr>
    <tr>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td><strong>Dataset 4</strong></td>
      <td><strong>1</strong></td>
      <td>2.91</td>
      <td>3.03</td>
      <td>3.16</td>
      <td>3.15</td>
    </tr>
    <tr>
      <td><strong>Dataset 4</strong></td>
      <td><strong>8</strong></td>
      <td>3.38</td>
      <td>3.30</td>
      <td>6.30</td>
      <td>4.75</td>
    </tr>
    <tr>
      <td><strong>Dataset 4</strong></td>
      <td><strong>16</strong></td>
      <td>4.35</td>
      <td>3.80</td>
      <td>8.76</td>
      <td>8.83</td>
    </tr>
  </tbody>
</table>

<h3 id="key-findings">Key findings</h3>

<p>The following are the key findings derived from comparing the benchmarking results:</p>

<ul>
  <li><strong>Memory savings</strong>: Byte vectors reduced memory usage by up to <strong>72%</strong>, with higher-dimensional vectors achieving greater reductions.</li>
  <li><strong>Indexing performance</strong>: The mean indexing throughput for byte vectors was <strong>2x to 107.84%</strong> higher than for float vectors, especially with larger vector dimensions.</li>
  <li><strong>Search performance</strong>: Search latencies were similar, with byte vectors occasionally performing better.</li>
  <li><strong>Recall</strong>: For byte vectors, there was a slight (up to <strong>8.8%</strong>) reduction in recall as compared to float vectors, depending on the dataset and the quantization technique used.</li>
</ul>

<h2 id="how-does-faiss-work-with-byte-vectors-internally">How does Faiss work with byte vectors internally?</h2>

<p>Faiss doesn’t directly support the <code class="language-plaintext highlighter-rouge">byte</code> data type for vector storage. To achieve this, OpenSearch uses a  <a href="https://faiss.ai/cpp_api/struct/structfaiss_1_1ScalarQuantizer.html"><code class="language-plaintext highlighter-rouge">QT_8bit_direct_signed</code> scalar quantizer</a>. This quantizer accepts float vectors within the signed 8-bit value range and encodes them as unsigned 8-bit integer vectors. During indexing and search, these encoded unsigned 8-bit integer vectors are decoded back into the original signed 8-bit vectors for distance computation.</p>

<p>This quantization approach reduces the memory footprint by a factor of four. However, encoding and decoding during scalar quantization introduce additional latency. To mitigate this, you can use <a href="https://opensearch.org/docs/latest/search-plugins/knn/knn-index#simd-optimization-for-the-faiss-engine">SIMD optimization</a> with the <code class="language-plaintext highlighter-rouge">QT_8bit_direct_signed</code> quantizer to reduce search latencies and improve indexing throughput.</p>

<h3 id="example">Example</h3>

<p>The following example shows how an input vector is encoded and decoded using the <code class="language-plaintext highlighter-rouge">QT_8bit_direct_signed</code> scalar quantizer:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Input vector:</span>
<span class="p">[</span><span class="o">-</span><span class="mi">126</span><span class="p">,</span> <span class="mi">28</span><span class="p">,</span> <span class="mi">127</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="o">-</span><span class="mi">45</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="o">-</span><span class="mi">110</span><span class="p">]</span>

<span class="c1">// Encoded vector generated by adding 128 to each dimension of the input vector to convert signed int8 to unsigned int8:</span>
<span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mi">156</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">128</span><span class="p">,</span> <span class="mi">138</span><span class="p">,</span> <span class="mi">83</span><span class="p">,</span> <span class="mi">140</span><span class="p">,</span> <span class="mi">18</span><span class="p">]</span>

<span class="c1">// Encoded vector is decoded back into the original signed int8 vector by subtracting 128 from each dimension for distance computation:</span>
<span class="p">[</span><span class="o">-</span><span class="mi">126</span><span class="p">,</span> <span class="mi">28</span><span class="p">,</span> <span class="mi">127</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="o">-</span><span class="mi">45</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="o">-</span><span class="mi">110</span><span class="p">]</span>
</code></pre></div></div>

<h2 id="future-enhancements">Future enhancements</h2>

<p>In future versions, we plan to enhance this feature by adding an <code class="language-plaintext highlighter-rouge">on_disk</code> mode with a <code class="language-plaintext highlighter-rouge">4x</code> Faiss compression level. This mode will accept <code class="language-plaintext highlighter-rouge">fp32</code> vectors as input, perform online training, and quantize the data into byte-sized vectors, eliminating the need to perform external quantization.</p>

<h2 id="conclusion">Conclusion</h2>

<p>OpenSearch 2.17 introduced support for Faiss byte vectors, allowing you to efficiently store quantized byte vector embeddings. This reduces memory consumption by up to 75%, lowers costs, and maintains high performance. These advantages make byte vectors an excellent choice for large-scale similarity search applications, especially when memory resources are limited, and applications that handle large volumes of data within the signed byte value range.</p>

<h2 id="references">References</h2>

<ul>
  <li><a href="https://github.com/erikbern/ann-benchmarks?tab=readme-ov-file#data-sets">Benchmarking datasets</a></li>
  <li><a href="https://huggingface.co/datasets/Cohere/wikipedia-22-12-simple-embeddings">Cohere/wikipedia-22-12-simple-embeddings</a></li>
  <li>Matthijs Douze, Alexandr Guzhva, Chengqi Deng, Jeff Johnson, Gergely Szilvasy, Pierre-Emmanuel Mazar’e, Maria Lomeli, Lucas Hosseini and Herv’e J’egou. The Faiss library. <a href="https://arxiv.org/abs/2401.08281">https://arxiv.org/abs/2401.08281</a></li>
</ul>]]></content><author><name>naveen</name></author><category term="technical-posts" /><summary type="html"><![CDATA[The growing popularity of generative AI and large language models (LLMs) has led to an increased demand for efficient vector search and similarity operations. These models often rely on high-dimensional vector representations of text, images, or other data. Performing similarity searches or nearest neighbor queries on these vectors becomes computationally expensive, especially as vector databases grow in size. OpenSearch’s support for Faiss byte vectors offers a promising solution to these challenges.]]></summary></entry></feed>