<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[Basics - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Basics - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/search-labs/blog/category/basics</link>
    </image>
    <link>https://www.elastic.co/search-labs/blog/category/basics</link>
    <atom:link href="https://www.elastic.co/search-labs/rss/category/basics.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[en]]></language>
    <lastBuildDate>Sat, 24 Oct 2026 10:12:03 GMT</lastBuildDate>
  <item>
    <title><![CDATA[How to compare two Elasticsearch indices and find missing documents]]></title>
    <description><![CDATA[Exploring approaches for comparing two Elasticsearch indices and finding missing documents.]]></description>
    <content:encoded><![CDATA[<p>When managing Elasticsearch indices, you may need to verify that all documents present in one index also exist in another, such as after a reindex operation, a migration, or a data pipeline. Elasticsearch doesn't provide a built-in "diff" command for this, but the right approach depends on one key question: <strong>Are your document IDs stable between the two indices?</strong></p><h2>The problem</h2><p>Imagine you have two indices, <code>index-a</code> (source) and <code>index-b</code> (target), and you want to find all documents that exist in <code>index-a</code> but are missing from <code>index-b</code>.</p><p>A naive approach, querying both indices and comparing results in memory, won't scale. Elasticsearch is designed to handle millions of documents, and loading them all at once isn’t practical.</p><p>There are two scenarios:</p><ol><li><p><strong>IDs are stable</strong>: Both indices use the same <code>_id</code> for the same document (for example, <code>emp_no</code> as the document ID). This is the easy case.</p></li><li><p><strong>IDs are generated</strong>: Documents were ingested through different pipelines that assigned random or sequential IDs. You can't compare by <code>_id</code>; you need to match on content.</p></li></ol><p>Let's walk through both.</p><h2>Step 0 — A lighter CLI for Elasticsearch</h2><p>All the examples in this post use <a href="https://github.com/Anaethelion/escli-rs">escli</a>, a small Rust command line interface (CLI) that wraps the Elasticsearch REST API. It reads your cluster URL and credentials from environment variables, so you don’t have to repeat authentication headers on every command.</p><p>To see why that matters, here's a typical <code>_search</code> call with raw <code>curl</code>:</p>curl -X GET \
  -H "Authorization: ApiKey $ELASTIC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":{"term":{"user.id":"kimchy"}}}' \
  "$ELASTICSEARCH_URL/my-index-000001/_search"<p>With <code>escli</code>, the same request becomes:</p>./escli search --index my-index-000001 &lt;&lt;&lt; '{"query":{"term":{"user.id":"kimchy"}}}'<p>The credentials live in a <code>.env</code> file that escli sources automatically — no <code>-H "Authorization: ..."</code> on every call, no risk of leaking secrets in shell history. The request body is passed via stdin (<code>&lt;&lt;&lt;</code>), which makes it easy to pipe in multi-line JSON built dynamically with <code>jq</code>.</p><h2>Step 1 — Count documents in both indices</h2><p>Before doing a full scan, get a quick count of each index. If the counts match, the indices are likely in sync, and there’s no need to scan at all.</p>./escli count --index index-a
./escli count --index index-b<p>The <code>_count</code> API returns:</p>{ "count": 1000000 }<p>If the counts differ, proceed to the full comparison.</p><h2>Step 2 — When IDs mean something: Use op_type=create</h2><p>If both indices use the same <code>_id</code> for the same document, for example, because you indexed documents using a functional business key like <code>emp_no</code> rather than a generated UUID, you can find and fix missing documents in a single <code>_reindex</code> call.</p><h3>Why functional IDs matter</h3><p>Using a meaningful field as <code>_id</code> (instead of a random UUID) is a best practice when the data has a natural key. It means:</p><ul><li><p>The same document always gets the same <code>_id</code>, regardless of which pipeline ingested it.</p></li><li><p>You can easily update or delete documents by ID.</p></li><li><p>You can use <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-index#operation-index-op_type"><code>op_type=create</code></a> to skip documents that already exist in the target.</p></li><li><p>No client-side scanning or comparison is needed.</p></li></ul><h3>The op_type=create trick</h3><p><code>_reindex</code> with <code>op_type=create</code> tries to create each document from the source in the target. If a document with the same <code>_id</code> already exists, Elasticsearch reports it as a <code>version_conflict</code> and moves on. It <strong>doesn’t</strong> overwrite the existing document. Setting <code>conflicts=proceed</code> tells the API to continue instead of aborting on the first conflict.</p>./escli reindex &lt;&lt;&lt; '{
  "source": { "index": "index-a" },
  "dest":   { "index": "index-b", "op_type": "create" },
  "conflicts": "proceed"
}'<p>The response tells you exactly what happened:</p>{
  "total": 1000000,
  "created": 49594,
  "version_conflicts": 950406,
  "failures": []
}<ul><li><p><code>created</code>: Documents that were missing from <code>index-b</code> and have now been added.</p></li><li><p><code>version_conflicts</code>: Documents that already existed in <code>index-b</code> and were left untouched.</p></li></ul><p><strong>No scanning, no client-side comparison, no intermediate file.</strong> Everything happens server-side in about six seconds on a 1M-document dataset.</p><h2>Step 3 — When IDs are not stable: Business-key comparison</h2><p>Sometimes you can't rely on <code>_id</code>. A document pipeline that generates IDs at ingestion time will assign a different <code>_id</code> each time the same record is processed. If <code>index-a</code> and <code>index-b</code> were populated by two such pipelines, the same employee record might have <code>_id: "abc123"</code> in one index and <code>_id: "xyz789"</code> in the other, even though the underlying data is identical.</p><p>In this case, you need to match documents by content rather than by ID. The key is to identify a set of fields that together form a unique business key.</p><p>For an employee dataset, a reasonable business key is <code>(first_name, last_name, birth_date)</code>. A document in <code>index-a</code> is "missing" from <code>index-b</code> if no document in <code>index-b</code> has the same combination of those three fields.</p><h3>3a — Scan the source with PIT + search_after</h3><p>Open a <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-open-point-in-time">point in time (PIT)</a> on the source index to get a consistent snapshot, and then paginate through it, fetching only the business-key fields:</p>./escli open_point_in_time index-a 5m
# → { "id": "46ToAwMDaWR..." }./escli search &lt;&lt;&lt; '{
  "size": 10000,
  "_source": ["first_name", "last_name", "birth_date"],
  "pit": { "id": "46ToAwMDaWR...", "keep_alive": "5m" },
  "sort": [{ "_shard_doc": "asc" }]
}'<p>The sort key <code>_shard_doc</code> is the most efficient sort for full-index pagination: it uses the internal Lucene document order with no overhead. Repeat with <code>search_after</code> until the response contains zero hits. Always close the PIT when done:</p>./escli close_point_in_time &lt;&lt;&lt; '{"id": "46ToAwMDaWR..."}'<h3>3b — Check each page against the target via _msearch</h3><p>For each page of source documents, build one <code>_msearch</code> request with one subquery per document. Each subquery uses a <code>bool/must</code> on the three business-key fields and requests <code>size: 0</code>; we only need to know whether a match exists, we don’t need to retrieve the document itself.</p>./escli msearch &lt;&lt; 'EOF'
{"index": "index-b"}
{"size":0,"query":{"bool":{"must":[{"term":{"first_name.keyword":"Alice1"}},{"term":{"last_name.keyword":"Smith"}},{"term":{"birth_date":"1985-03-12"}}]}}}
{"index": "index-b"}
{"size":0,"query":{"bool":{"must":[{"term":{"first_name.keyword":"Bob2"}},{"term":{"last_name.keyword":"Jones"}},{"term":{"birth_date":"1990-07-24"}}]}}}
EOF<p>The response contains one entry per subquery, in the same order:</p>{
  "responses": [
    { "hits": { "total": { "value": 1 } } },
    { "hits": { "total": { "value": 0 } } }
  ]
}<p><code>total.value == 0</code> means no document in <code>index-b</code> matches that business key; the document is missing. Collect the corresponding <code>_id</code> from the source page.</p><strong>Note on</strong> <strong><code>.keyword</code></strong> <strong>subfields</strong>: <code>term</code> queries require exact (keyword) matching. The <code>first_name</code> and <code>last_name</code> fields must have a <code>.keyword</code> subfield in the index mapping. The demo's <code>mapping.json</code> includes this.<h3>3c — Speed it up with split-by-date</h3><p>If the business key includes a date field, you can partition the source into date slices and run each slice as an independent job. Each slice opens its own PIT with a <code>range</code> filter on <code>birth_date</code>, runs its own msearch loop, and writes its results to a separate file. The parent script launches all slices in parallel and aggregates the results when they’re all done.</p><p>But depending on your use case, you might want to partition by a different field; for example, if you have a <code>team</code> field, you could run one slice per team. The key is to find a field that allows you to split the data into reasonably even chunks that can be processed in parallel.</p>[compare] Launching 5 slices in parallel...

  → Slice 1: 1960-01-01 → 1969-12-31 ✅ — 244408 checked, 12207 missing
  → Slice 2: 1970-01-01 → 1979-12-31 ✅ — 243624 checked, 12212 missing
  → Slice 3: 1980-01-01 → 1989-12-31 ✅ — 243551 checked, 11921 missing
  → Slice 4: 1990-01-01 → 1999-12-31 ✅ — 243895 checked, 11991 missing
  → Slice 5: 2000-01-01 → 2009-12-31 ✅ — 24522 checked, 1263 missing<h2>Performance on a 1M dataset</h2><p>To validate the approaches, the demo generates 1,000,000 documents in <code>index-a</code> and deliberately skips ~5% in <code>index-b</code> (49,594 missing documents), and then runs the full compare → reindex cycle.</p><p>Results on a MacBook M3 Pro:</p><p><strong>Comparison</strong> (<code>compare-indices.sh</code>):</p><p>Strategy</p><p>Compare</p><p>Reindex</p><p>Total</p><p>How it works</p><p>op_type</p><p></p><p>6s</p><p>6s</p><p>Full _reindex server-side, skips existing</p><p>business-key</p><p>1m 38s</p><p>4s</p><p>1m 42s</p><p>PIT scan + _msearch by business key</p><p>split-by-date</p><p>32s</p><p>4s</p><p>36s</p><p>Same as business-key, 5 slices in parallel</p><p>The <code>op_type=create</code> approach is fastest because everything is server-side and requires no client-side scanning. The <code>split-by-date</code> strategy cuts the <code>business-key</code> duration from 1m 38s down to 36s through parallelism: not bad for a comparison across two 1M-document indices.</p><h2>Decision tree</h2>Are _id values stable between both indices?
├── Yes → _reindex with op_type=create          (6s, server-side)
└── No  → Do you have a reliable business key?
          ├── Yes, simple scan is fast enough → business-key   (1m 42s)
          └── Yes, and you need more speed    → split-by-date  (36s, parallel)<h2>Conclusion</h2><p>Elasticsearch doesn't offer a native index diff command, but the right strategy depends on your data model:</p><ul><li><p><strong>Use functional</strong> <strong><code>_id</code></strong><strong>s</strong> (a natural business key like <code>emp_no</code>) whenever possible. It unlocks the simplest and fastest approach: <code>_reindex</code> with <code>op_type=create</code> finds and fills gaps in one server-side call.</p></li><li><p><strong>When IDs are unstable</strong>, match by business key using PIT + <code>_msearch</code>. Partition by a field and run slices in parallel to recover most of the performance. If you find yourself doing this regularly, consider computing a hash of your business key fields and using it as <code>_id</code> at ingestion time. You get the best of both worlds: stable IDs and efficient lookups.</p></li></ul><p>The complete demo, including dataset generation, comparison scripts, and reindex scripts, is available at <a href="https://github.com/dadoonet/blog-compare-indices/">https://github.com/dadoonet/blog-compare-indices/</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-index-comparison</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-index-comparison</guid>
    <category><![CDATA[Basics]]></category>
    <dc:creator><![CDATA[David Pilato]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt87c19debc74944e3/6a170e09286714495393e3ae/099abf465250360ab741a5aa13931fa8884ded34-1376x768.png" length="0" type="image/png"/>
    <pubDate>Mon, 06 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Testing Elasticsearch. It just got simpler.]]></title>
    <description><![CDATA[Explaining how Elasticsearch integration tests have become simpler thanks to improvements in Elasticsearch 9.x, the modern Java client, and Testcontainers 2.x.]]></description>
    <content:encoded><![CDATA[<p>When I first wrote about <a href="https://www.elastic.co/search-labs/blog/series/integration-tests-using-elasticsearch">testing Elasticsearch</a> with Testcontainers for Java, the focus was very pragmatic: if you care about correctness, you should test against a real node; if you care about confidence, your integration tests should resemble production as closely as possible; and if you care about maintainability, your setup shouldn’t turn into a maze of mocks and assumptions.</p><p>That philosophy hasn’t changed.</p><p>What has changed, however, is how little effort it now takes to achieve that goal. With Elasticsearch 9.x, the modern Java client, and Testcontainers 2.x, the experience of writing integration tests feels noticeably smoother, as if a layer of incidental complexity has quietly been removed.</p><p>The example accompanying this article is intentionally modest and can be found <a href="https://github.com/pioorg/elasticsearch9-testcontainers2/blob/main/src/test/java/testing_elasticsearch/ES9TC2DemoTest.java">here</a>.</p><p>It doesn’t attempt to demonstrate sophisticated indexing strategies or elaborate data pipelines; instead, it concentrates on the essentials, because the essentials are precisely where the improvements are most visible.</p><h2>When the tooling stops getting in the way</h2><p>Anyone who has maintained a test suite for a few years will recognize the pattern: You introduce a new library, a transitive dependency pulls something unexpected, and before long, you’re negotiating between versions of testing engines rather than writing tests.</p><p>With Testcontainers 2.x, that negotiation largely disappears. The dependency structure is clearer, the modules are more explicit, and the accidental coupling to older testing frameworks no longer sneaks in behind your back. In practical terms, adding Elasticsearch support to your tests is now as straightforward as declaring:</p>&lt;dependency&gt;
  &lt;groupId&gt;org.testcontainers&lt;/groupId&gt;
  &lt;artifactId&gt;testcontainers-elasticsearch&lt;/artifactId&gt;
  &lt;version&gt;2.0.3&lt;/version&gt;
  &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;<p>And, if you’re using JUnit Jupiter integration:</p>&lt;dependency&gt;
  &lt;groupId&gt;org.testcontainers&lt;/groupId&gt;
  &lt;artifactId&gt;testcontainers-junit-jupiter&lt;/artifactId&gt;
  &lt;version&gt;2.0.3&lt;/version&gt;
  &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;<p>There are no exclusions to sprinkle in, no legacy engines to silence, and no uneasy feeling that something hidden might surface during the next upgrade. The configuration becomes almost unremarkable, which, in the context of build tooling, is a compliment.</p><h2>A real Elasticsearch node, with security intact</h2><p>In the demo test, we use the official Elasticsearch 9.3.1 Docker image:</p>var container =
    new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:9.3.1");

container.start();<p>At first glance, this may look similar to older examples, yet the subtle difference lies in what we no longer need to do. <strong>We don’t disable security.</strong> <strong>We don’t bypass SSL.</strong> We don’t simplify the environment just to make the test convenient.</p><p>Instead, once the container is started, we construct a client that uses the REST API and authenticates properly:</p>try (var client = ElasticsearchClient.of(c -&gt; c
     .host("https://" + container.getHttpHostAddress())
     .usernameAndPassword("elastic", ElasticsearchContainer.ELASTICSEARCH_DEFAULT_PASSWORD)
     .sslContext(container.createSslContextFromCa())
)) {<p>What deserves special mention here is how neat the client construction itself has become. In earlier iterations, creating an Elasticsearch client often meant juggling multiple intermediate objects, configuring transport layers explicitly, wrapping low-level clients, and dedicating some amount of code to what was essentially plumbing. Now, the signal-to-noise ratio is refreshingly high. The builder encapsulates the necessary details, the container provides what the client needs, and the resulting configuration fits comfortably within a few readable lines.</p><p>Just as importantly, the <code>ElasticsearchClient</code> is <code>AutoCloseable</code>, which means it integrates naturally with try-with-resources, ensuring proper cleanup without additional ceremony. The lifecycle is explicit, concise, and self-contained, which is exactly what you want in integration tests that should focus on behavior rather than infrastructure management.</p><p>The container exposes everything required to build a legitimate, secure connection, and the client integrates with it naturally, which means the test environment mirrors production in all the aspects that matter, without imposing additional mental overhead from the developer.</p><p>This alignment between realism and simplicity is, perhaps, one of the most meaningful improvements.</p><h2>Typed APIs change the character of tests</h2><p>The evolution of the Elasticsearch Java client has also reshaped how integration tests read and feel. Where older approaches often involved parsing JSON responses or navigating loosely typed structures, the modern client offers a builder-based, strongly typed API that guides you through valid request shapes at compile time.</p><p>In the demo, we perform a simple cluster health check:</p>var health = client.cluster().health();

Assertions.assertEquals("docker-cluster", health.clusterName());
Assertions.assertEquals(HealthStatus.Green, health.status());<p>What’s striking here is not the complexity of the operation, but the absence of friction. There’s no manual extraction from maps, no assertions built on untyped string values, and no detour into low-level response handling. The test code looks indistinguishable from application code, which subtly reinforces the idea that integration tests aren’t a special category of code with different rules, but simply another consumer of the same APIs.</p><p>When the boundary between production code and test code becomes thinner, confidence increases almost by default.</p><h2>Reading the test as a story</h2><p>If you take a look at the full test case:</p>@Test
void newClientTest() throws IOException {
    try (var container =
             new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:9.3.1")) {
        
        container.start();
        
        try (
            var client = ElasticsearchClient.of(c -&gt;
                c.host("https://" + container.getHttpHostAddress())
                    .usernameAndPassword("elastic", ElasticsearchContainer.ELASTICSEARCH_DEFAULT_PASSWORD)
                    .sslContext(container.createSslContextFromCa()))) {

            HealthResponse health = client.cluster().health();

            Assertions.assertEquals("docker-cluster", health.clusterName());
            Assertions.assertEquals(HealthStatus.Green, health.status());
        }
    }
}<p>you’ll notice that it reads less like a configuration script and more like a short narrative:</p><ul><li><p>We define the container.</p></li><li><p>We start the container.</p></li><li><p>We build a client.</p></li><li><p>We call a real API.</p></li><li><p>We assert the outcome.</p></li></ul><p>The supporting infrastructure fades into the background, leaving the intent of the test clearly visible. That clarity isn’t accidental; it’s the cumulative effect of incremental improvements across Testcontainers and the Elasticsearch client.</p><h2>The advanced patterns still apply</h2><p>None of the more advanced techniques discussed in earlier articles, <a href="https://www.elastic.co/search-labs/blog/elasticsearch-integration-tests-faster">Faster integration tests with real Elasticsearch</a> and <a href="https://www.elastic.co/search-labs/blog/elasticsearch-improve-performance-integration-tests">Advanced integration tests with real Elasticsearch</a>, have become obsolete. Reusing containers to speed up large test suites, customizing cluster settings, preloading indices, or testing role-based access scenarios remain entirely valid and, in many cases, essential.</p><p>What has improved is the baseline experience. The simplest possible integration test, the one that merely needs a real node and a real client, no longer requires defensive configuration or dependency gymnastics. It’s concise, expressive, and production-like by default.</p><h2>Progress without drama</h2><p>There was no dramatic rewrite of the ecosystem, no disruptive migration guide that forced a rethinking of everything. Instead, there has been a steady refinement of APIs and dependencies, each release smoothing a rough edge here and removing a surprise there.</p><p>The result isn’t flashy, yet it’s tangible. Writing integration tests against Elasticsearch now feels less like assembling a test harness and more like exercising a real system in miniature.</p><p>Sometimes progress announces itself loudly. Sometimes it arrives quietly, in the form of code that simply reads better and requires less explanation. In this case, it’s the latter, and for those of us who care about clean, reliable integration tests, that’s more than enough.</p><p>And what if we could do something similar with Kibana? Sounds appealing? Stay tuned!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-integration-tests</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-integration-tests</guid>
    <category><![CDATA[Java]]></category>
    <category><![CDATA[Basics]]></category>
    <dc:creator><![CDATA[Piotr Przybyl]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc31c3bf0d453d846/6a170e12cdacbfdf6a7d2a7e/3ae41b1f2876d2ad11c8e2b79bbf79955d6902aa-1440x840.png" length="0" type="image/png"/>
    <pubDate>Thu, 19 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch Serverless pricing demystified: VCUs and ECUs explained]]></title>
    <description><![CDATA[Learn how Elasticsearch Serverless pricing works for Elastic’s fully-managed deployment offering. We explain VCUs (Search, Ingest, ML) and ECUs, detailing how consumption is based on actual allocated resources, workload complexity, and Search Power.]]></description>
    <content:encoded><![CDATA[<p><em>Navigating Elasticsearch Serverless pricing is simple... you pay for the resources you use. Getting a handle on VCUs, ECUs, and the factors that drive your consumption is key to making informed decisions about your usage. In this blog, we'll break down exactly how Elasticsearch Serverless pricing works so you can plan, monitor, and optimize your spend.</em></p><p>When we built Elasticsearch Serverless, we had to decide how to bill our users. While a charge per query may have been easier to reason about from a consumption perspective, it would be a lot harder to reason about from a resource perspective. Instead, we implemented a simple pricing scheme comprising three dimensions for compute: search, ingest, and machine learning VCUs. This means we charge users for the actual resources we allocate to fulfill your requested workloads.</p><h2>VCU, ECU, and other terms</h2><p>Let's start by defining a few terms that will keep coming back throughout this post.</p><h3>VCU</h3><p>A VCU is a <a href="https://www.elastic.co/docs/deploy-manage/cloud-organization/billing/elasticsearch-billing-dimensions#elasticsearch-billing-information-about-the-vcu-types-search-ingest-and-ml">Virtual Compute Unit</a>, representing a fraction of RAM, CPU, and local disk for caching. We separate compute by the workloads they support, so we have three flavors of VCU:</p><ol><li><p>Search VCU</p></li><li><p>Ingest VCU</p></li><li><p>Machine Learning (ML) VCU</p></li></ol><p>VCU’s are charged by the hour.</p><h3>Regional pricing</h3><p>We have different prices for different regions and different cloud providers. You can find a full list of prices <a href="https://cloud.elastic.co/cloud-pricing-table?productType=serverless">on this page</a>.</p><h3>ECU</h3><p>An ECU is an <a href="https://www.elastic.co/docs/deploy-manage/cloud-organization/billing/ecu">Elastic Consumption Unit</a>, which is the unit we bill you in. The nominal value of an ECU is $1.00 USD. All of the different components of consumption are charged at a specific rate of ECUs per time unit. For example, one Gigabyte of storage might cost 0.047 ECU per month, so 100 GB of storage will cost you 4.7 ECU = $4.70 for one month. Similarly, if your search workload consumed 10 VCUs in a day and the Search VCU rate in your region is 0.09 ECU, your cost for that day would be $0.90.</p><h3>Interactive Dataset Size</h3><p>The amount of data in your project has a direct influence on your costs. We make the distinction of “interactive dataset” primarily for time-series data, as this relates to the amount of data in the Boost Window. For non-time-series data, this is simply the amount of data in the project.</p><p></p><h2>Project settings</h2><p>We have three <a href="https://www.elastic.co/docs/deploy-manage/deploy/elastic-cloud/project-settings">project settings</a> that allow you to control your project's usage.</p><h3>Search power</h3><p>Search Power controls the speed of searches against your data. With Search Power, you can improve search performance by adding more resources for querying, or you can reduce provisioned resources to cut costs. Choose from three Search Power settings:</p><p><strong>On-demand</strong>: Autoscales based on data and search load, with a lower minimum baseline for resource use. This flexibility results in more variable query latency and reduced maximum throughput.</p><p><strong>Performant</strong>: Delivers consistently low latency and autoscales to accommodate moderately high query throughput.</p><p><strong>High-availability</strong>: Optimized for high-throughput scenarios, autoscaling to maintain query latency even at very high query volumes.</p><h3>Boost window</h3><p>For time series use cases, the boost window is the number of days of data that constitutes your interactive dataset size. The interactive dataset is the portion of your data that we keep cached, and that we use to determine how to scale the Search tier for your project. By default, the boost window is seven days.</p><h3>Data retention</h3><p>You can set the number of days of data that are retained in your project, which will affect the amount of storage we need. You can do this on a per-data stream basis in your project.</p><h2>Price components</h2><p>Serverless Elasticsearch contains a few different pricing components. For most use cases, the components you will care most about are Search, Ingest, and ML VCUs, as well as the Elastic Inference Service's token consumption.</p><h3>Search VCUs</h3><p>Search VCU consumption is the most complex part of pricing. We make this simple for you by automatically determining the right amount of VCUs that are needed to fulfill your workloads. For more details on how our autoscaling logic works, see <a href="https://www.elastic.co/search-labs/blog/elasticsearch-serverless-tier-autoscaling">our earlier blog on the topic</a>.</p><h4>Search VCU inputs</h4><p>Search VCUs are allocated based on a few factors, but mainly, we can boil it down to three inputs: the interactive dataset size, the search load on the system, and Search Power.</p><p>For traditional search use cases, the interactive dataset size will generally be your entire dataset. For time series use cases, it will be the portion of your dataset that fits inside the Boost Window.</p><p>Search load measures the amount of load being placed on the system by currently active searches. The main contributing factors are the number of searches per second, the complexity of the searches (the more that needs to be computed, the higher the load), and the size of the dataset that needs to be searched to fulfill the result. If we can get you the right number of results by scanning 10% of the dataset, then the load will be much lower than if we need to scan the full dataset.</p><p>Finally, <a href="https://www.elastic.co/docs/deploy-manage/deploy/elastic-cloud/project-settings">Search Power</a> influences the number of VCUs we allocate. Each Search Power setting defines the baseline capacity of the search tier.</p><p>In short: the larger the dataset size and the higher the search load, the more VCUs we need to fulfill your search requests. Search Power allows you to tune to what extent we will scale up and down.</p><h4>Minimum VCUs</h4><p>Elasticsearch Serverless is designed to align infrastructure costs directly with your application's demand. </p><p>For smaller workloads, the search infrastructure can scale down to zero VCUs during periods of inactivity. If the system detects fifteen minutes of total inactivity, the associated hardware resources are deprovisioned. This makes the platform highly cost-effective for development environments, bursty workloads, or applications with intermittent usage. Note that inactivity means actual inactivity: no user-initiated searches whatsoever. As soon as we need to serve a search of any kind, we need to allocate hardware resources to execute that search.</p><p>As your interactive dataset grows, the system eventually reaches a storage threshold where a baseline level of resources is required to maintain data availability and indexing readiness. A minimum VCU allocation is maintained to ensure your data remains "warm" and queryable, even if no active searches are occurring.</p><h4>VCU consumption is not linear</h4><p>Because our hardware is allocated in steps, consumption of VCUs does not necessarily scale linearly with workload size. Each scaling step can contain a wide range of workloads, and if your workload is at the bottom of that range, it may have a lot of room to grow before we need to jump to the next scaling step.</p><p>This can make estimating based on a non-representative workload hard. For example, you may be consuming 2 VCUs per hour on a small workload. It's entirely possible that you could increase your workload size by a factor of 100 and still fit in that 2 VCU per hour load before we need to start increasing the amount of VCUs we allocate to serve your workload.</p><p>We know this makes estimating your cost a little harder, and we are working on ways to make that easier for you. If you need more help estimating your likely price, you can always talk to our customer team and get more personalized assistance.</p><h2>Ingest VCUs</h2><p>Ingest VCUs are much simpler than Search VCUs.</p><h4>Ingest VCU Inputs</h4><p>Ingest VCUs have essentially three inputs: the number of indices, the ingest rate, and the ingest complexity. We need to allocate a little bit of memory for every index in your system, which is why the number of indices matters. Read indices in data streams do not count for this calculation.</p><p>The faster you ingest, the more CPU we will need to process that ingestion. And the more complex your ingest requests, the more CPU we will need. Some factors that make ingest requests more expensive to execute are complicated field mappings or a lot of post-processing.</p><h4>Minimum Ingest VCUs</h4><p>We do not have a minimum number of VCUs we allocate to your ingest. If you do not ingest data, we do not need to allocate any VCUs to processing ingestion. There is an exception for a large number of indices (think: thousands of indices), where we do need to keep some resources allocated to be responsive when indexing requests come in.</p><h4>VCU consumption is not linear</h4><p>As with Search VCUs, we allocate Ingest VCUs based on step functions. Each step can contain a wide range of workloads: it's entirely possible that if you have a minimal amount of ingest, you could increase your ingest rate by a factor of 100 and still fit in the same step, thus not actually increasing your cost.</p><h2>AI workloads</h2><p>When running machine learning tasks in Serverless, we give you three options:</p><ol><li><p>You use our Elastic Inference Service (EIS) to run your inference and completion workloads. We take care of everything, and you are charged per token.</p></li><li><p>You use traditional Elasticsearch Machine Learning capabilities to run your workloads. These use our Trained Models capabilities. We will scale up and down based on your machine learning workload requirements.</p></li><li><p>You do it yourself, outside of our systems, and just bring your vectors or other inference results to store and search in Elasticsearch.</p></li></ol><h4>EIS</h4><p>The pricing for EIS is <a href="https://cloud.elastic.co/cloud-pricing-table?productType=serverless">quite straightforward</a>: you get charged a rate per one million consumed tokens. Token consumption is generally easy to predict for inference workloads. For LLM-based tasks, particularly agentic ones, this can be more complex, and some experimentation and trial runs may be useful to determine how many tokens your workloads typically consume.</p><h4>ML VCUs</h4><p>Machine Learning VCUs work on one simple input: machine learning workloads. The more inference you require, the more VCUs we will consume. Once you stop performing inference, we will scale down. We will keep a trained model in memory for about 24 hours after you last used it so that we can be responsive, which means that the minimal amount of VCU required to keep that model available will remain up for 24 hours before scaling down entirely.</p><p>We generally recommend our customers use EIS instead of our Machine Learning nodes for inference, particularly if your usage is periodic. By switching to EIS, you will not have to wait for machine learning nodes to spin up, and we won't charge you for unused ML node time before scaling down. EIS charges on a per token basis.</p><h2>Storage</h2><p>We charge storage per gigabyte per month. Storage does serve as an input into other parts of our system, particularly Search VCUs (see Search VCU above), but the pricing for storage itself is <a href="https://cloud.elastic.co/cloud-pricing-table?productType=serverless">quite straightforward</a>.</p><h2>Data Out (egress)</h2><p>We charge you for the data you take out of the system.</p><p>To minimize your egress costs, we recommend a few optimizations on your queries:</p><ol><li><p>Do not return vectors in your query responses. We <a href="https://www.elastic.co/search-labs/blog/elasticsearch-exclude-vectors-from-source">do this by default</a> for indices created after October 2025. You can always return vectors in your responses explicitly if necessary.</p></li><li><p>Return only the fields needed for your application. You can <a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index">do this</a> by using the <code>fields</code> and <code>_source</code> parameters.</p></li></ol><h2>Support</h2><p>We charge <a href="https://www.elastic.co/pricing/serverless-search">support</a> as a percentage of your total ECU usage. We currently have four levels of support:</p><ol><li><p>Limited support</p></li><li><p>Base support</p></li><li><p>Enhanced support</p></li><li><p>Premium support</p></li></ol><h2>Project subtype profiles</h2><p>We currently offer two project subtypes for Serverless Elasticsearch, referred to as “General Purpose” and “Vector Optimized”. All Serverless Elasticsearch projects created through the cloud console UI will be created using the “General Purpose” option. You may create a “Vector Optimized” by calling the API directly with the <code>optimized_for</code> parameter (see <a href="https://www.elastic.co/docs/api/doc/elastic-cloud-serverless/operation/operation-createelasticsearchproject">documentation</a> for all options).</p><p>The difference between the two options is the allocation of resources. We allocate approximately four times more resources (aka VCUs) to the “Vector Optimized” profile, which will result in your costs being up to four times higher. This is why we recommend starting on the “General Purpose” profile and only using the “Vector Optimized” profile when your use case demands the use of uncompressed dense vectors with high dimensionality, and quantization and <a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction">DiskBBQ</a> will not serve your needs.</p><p>When Serverless Elasticsearch was envisioned years ago, we thought that vector workloads would require much more resources to remain performant. However, with innovations like <code>semantic_text</code>, <code>sparse_vector</code> models, and <a href="https://www.elastic.co/search-labs/blog/elasticsearch-9-1-bbq-acorn-vector-search">Better Binary Quantization</a> (BBQ), we’ve found that many vector workloads perform well on the “General Purpose” profile at a fraction of the cost. Therefore, don’t let the “Vector Optimized” label fool you…you can get excellent price <em>and</em> performance for vector workloads on the “General Purpose” profile.</p><h2>Monitoring costs</h2><p>We recognize that keeping track of your costs, especially when you are new to Elasticsearch Serverless, is important to you. We built a few tools just for this purpose, and continue to improve them for even greater visibility.</p><h2>Cloud console billing usage</h2><p>The <a href="https://www.elastic.co/docs/deploy-manage/cloud-organization/billing/view-billing-history">Elastic Cloud Console</a> provides billing details for your cloud account, across all cloud-based resources, including Elasticsearch Serverless. There, you can find a breakdown of all the price components described in this article. Filters allow you to zoom in on specific time periods and resources.</p><p>To further monitor your costs, you can also configure custom <a href="https://www.elastic.co/docs/deploy-manage/cloud-organization/billing/manage-billing-notifications">budget alerts </a>from the Budgets and notifications tab under the Billing and subscriptions page.</p><h2>AutoOps monitoring</h2><p>We’re bringing <a href="https://www.elastic.co/docs/deploy-manage/monitor/autoops/autoops-for-serverless">AutoOps to Serverless</a>! One of the key value propositions of Elasticsearch Serverless is that we ensure everything runs smoothly, but that also means you have limited observability into the infrastructure. AutoOps for Serverless gives users visibility into what is driving usage, and, therefore, costs.</p><p>AutoOps is rolled out in new Serverless regions regularly, and we're always working to add new monitoring tools. Make sure to check out the <a href="https://www.elastic.co/docs/deploy-manage/monitor/autoops/ec-autoops-regions#autoops-for-serverless-full-regions">region coverage</a> and future planned monitoring tools.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-serverless-pricing-vcus-ecus</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-serverless-pricing-vcus-ecus</guid>
    <category><![CDATA[Basics]]></category>
    <category><![CDATA[Inside Elastic]]></category>
    <dc:creator><![CDATA[Sander Philipse,Pete Galeotti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3b8542204a8988dc/6a170bed0e2e49cd2641a12e/46f1e3c09e17cb8aa2a1cca64624bf533e55fe1d-1746x1096.png" length="0" type="image/png"/>
    <pubDate>Fri, 19 Dec 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Parsing JSON fields in Elasticsearch]]></title>
    <description><![CDATA[Learn how to parse JSON fields in Elasticsearch using an ingest pipeline to efficiently index, query, and aggregate JSON data.]]></description>
    <content:encoded><![CDATA[<p>In this article, we will discuss how to parse JSON fields in Elasticsearch, which is a common requirement when dealing with log data or other structured data formats. We will cover the following topics:</p><ol><li><p>Ingesting JSON data into Elasticsearch</p></li><li><p>Using an Ingest Pipeline to parse JSON fields</p></li><li><p>Querying and aggregating JSON fields</p></li></ol><h2>1. Ingesting JSON data into Elasticsearch</h2><p>When ingesting JSON data into Elasticsearch, it is essential to ensure that the data is properly formatted and structured. Elasticsearch can automatically detect and map JSON fields, but it is recommended to define an explicit mapping for better control over the indexing process.</p><p>To create an index with a custom mapping, you can use the following API call:</p>PUT /my_index
{
 "mappings": {
   "properties": {
     "message": {
       "type": "keyword"
     },
     "json_field": {
       "properties": {
         "field1": {
           "type": "keyword"
         },
         "field2": {
           "type": "integer"
         }
       }
     }
   }
 }
}<p>In this example, we create an index called <code>my_index</code> with a custom mapping for a JSON field named <code>json_field</code>.</p><h2>2. Using an Ingest Pipeline to parse JSON fields</h2><p>If your JSON data is stored as a string within a field, you can use the Ingest Pipeline feature in Elasticsearch to parse the JSON string and extract the relevant fields. The Ingest Pipeline provides a set of built-in processors, including the <code>json</code> processor, which can be used to parse JSON data.</p><p>To create an ingest pipeline with the <code>json</code> processor, use the following API call:</p>PUT _ingest/pipeline/json_parser
{
 "description": "Parse JSON field",
 "processors": [
   {
     "json": {
       "field": "message",
       "target_field": "json_field"
     }
   }
 ]
}<p>In this example, we create an ingest pipeline called <code>json_parser</code> that parses the JSON string stored in the <code>message</code> field and stores the resulting JSON object in a new field called <code>json_field</code>.</p><p>To index a document using this pipeline, use the following API call:</p>POST /my_index/_doc?pipeline=json_parser
{
 "message": "{\"field1\": \"value1\", \"field2\": 42}"
}<p>The document will be indexed with the parsed JSON fields:</p>{
 "_index": "my_index",
 "_type": "_doc",
 "_id": "1",
 "_source": {
   "message": "{\"field1\": \"value1\", \"field2\": 42}",
   "json_field": {
     "field1": "value1",
     "field2": 42
   }
 }
}<h2>3. Querying and aggregating JSON fields</h2><p>Once the JSON fields are indexed, you can query and aggregate them using the Elasticsearch Query DSL. For example, to search for documents with a specific value in the <code>field1</code> subfield, you can use the following query:</p>POST /my_index/_search
{
 "query": {
       "term": {
         "json_field.field1": "value1"
       }
 }
}<p>To aggregate the values of the <code>field2</code> subfield, you can use the following aggregation:</p>POST /my_index/_search
{
 "size": 0,
 "aggs": {
   "field2_sum": {
         "sum": {
           "field": "json_field.field2"
         }
 }
}<h2>Bonus: How to deal with unparsed JSON data?</h2><p>If you are in the situation where you have already ingested unparsed JSON data into a text/keyword field, there’s a way to extract the JSON data without having to reindex everything from scratch.</p><p>You can leverage the <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-update-by-query#operation-update-by-query-pipeline">Update by Query API</a> with the ingest pipeline developed in section 2. But before running that update, you’ll first need to update your index mapping similarly to what we did in section 1 to add the <code>json_field</code> mapping, by running the command below:</p>PUT /my_index/_mapping
{
  "properties": {
    "json_field": {
      "properties": {
        "field1": {
          "type": "keyword"
        },
        "field2": {
          "type": "integer"
        }
      }
    }
  }
}<p>When done, you can simply run the command below, which will iterate over all documents in your index, extract the JSON from the <code>message</code> field and index the parsed JSON data into the <code>json_field</code> object.</p>POST /my_index/_update_by_query?pipeline=json_parser<h2>Conclusion</h2><p>In conclusion, parsing JSON fields in Elasticsearch can be achieved using custom mappings, the Ingest Pipeline feature, and the Elasticsearch Query DSL. By following these steps, you can efficiently index, query, and aggregate JSON data in your Elasticsearch cluster.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-parse-json-field-ingest-pipeline</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-parse-json-field-ingest-pipeline</guid>
    <category><![CDATA[Basics]]></category>
    <dc:creator><![CDATA[Valentin Crettaz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6d8bd040a15da207/6a17e422414c640e67945116/ef9edded97edd7c919e617e648e62016155cde56-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 17 Oct 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch plugin for UBI: Analyze user data in Kibana]]></title>
    <description><![CDATA[Discover how to capture user behavior data using the Elasticsearch plugin for UBI and build a custom dashboard in Kibana to analyze it. ]]></description>
    <content:encoded><![CDATA[<p>In this article, we’ll show you how to capture and analyze user analytics data using the <strong>UBI</strong> <em>(User Behavior Insights)</em> standard in Elasticsearch.</p><p><em>You can learn more about UBI in </em><a href="https://www.elastic.co/search-labs/blog/elasticsearch-plugin-user-behavior-insights"><em>this article</em></a><em>.</em></p><p>Data collected with the UBI collector can be used on Kibana to build dashboards that open the window to users’ behavior in our application. In this blog, we will explore how to analyze UBI data in Kibana to gain insights into how our app is being used.</p><h2>Demo set up</h2><p>We can easily reproduce the demo in this blog following these steps:</p><p>1. Clone the repository</p>git clone https://github.com/Alex1795/ubi-dashboard-elasticsearch_blog.git 
cd ubi-dashboard-elasticsearch_blog<p>2. Install required libraries:</p>pip install -r requirements.txt<p>3. Run the setup script. Make sure to have the following environment variables set beforehand</p><ol><li><p>ES_HOST</p></li><li><p>API_KEY</p></li><li><p>KIBANA_HOST</p></li></ol>python setup.py<p>That’s all you need to do. If everything went well, you should see this output from the script execution:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0c568b39867bd357/6a170e3360084be8393c45ff/947a67ef7210fa76f62324a3eadb62a3e10bb887-1600x633.png" alt="" /><p>As we can see the script:</p><ul><li><p>Created two indices with the appropriate mappings</p></li><li><p>Indexed 23 documents to these indices</p></li><li><p>Uploaded some saved objects to Kibana</p></li></ul><p>Now, let’s take a look at what exactly this script did behind the scenes.</p><h2>Understanding the uploaded data</h2><p>First, we put some data in Elasticsearch before creating our visualizations.</p><p>You can reproduce the process manually in Kibana DevTools, copying the mappings and sample data and using the <strong>PUT &lt;index&gt;</strong> and <strong>PUT _bulk</strong> APIs, respectively.</p><h3>Ubi_events index</h3><p>User action data, documents are generated for every click (in this case), and it includes:</p><ul><li><p><strong>application</strong>: The client application that generated the event ("search-ui")</p></li><li><p><strong>action_name</strong>: Type of user action performed ("click")</p></li><li><p><strong>query_id</strong>: Links this event to the corresponding search query session</p></li><li><p><strong>client_id</strong>: A generated, unique ID that represents a user or session without revealing personal data. It is generated instead of using identifiable data like email addresses or usernames. This approach allows us to have privacy advantages such as safe analytics capabilities and secure data sharing without exposing PII, while still having important functionality like session continuity, behavioral analysis, or A/B testing.</p></li><li><p><strong>timestamp</strong>: ISO 8601 formatted timestamp when the event occurred</p></li><li><p><strong>message_type</strong>: Category of the event for processing ("CLICK_THROUGH")</p></li><li><p><strong>message</strong>: Human-readable description of what happened ("Clicked Fahrenheit 451")</p></li><li><p><strong>user_query</strong>: The original search term that led to this event ("fahrenheit")</p></li><li><p><strong>event_attributes</strong>: Nested object containing detailed event context:</p><ul><li><p><strong>object.device</strong>: Device type used by the user ("mobile")</p></li><li><p><strong>object.object_id</strong>: Unique identifier of the clicked item</p></li><li><p><strong>object.description</strong>: Details about the clicked item (book title, date, author)</p></li><li><p><strong>object.position.ordinal</strong>: Ranking position of the item in search results (1st)</p></li><li><p><strong>object.position.page_depth</strong>: Which page of results the item appeared on (1st page)</p></li><li><p><strong>object.user.ip</strong>: User's IP address</p></li><li><p><strong>object.user.city/region/country</strong>: Geographic location data</p></li><li><p><strong>object.user.location</strong>: Precise latitude/longitude coordinates</p></li></ul></li></ul><p>Sample document:</p>       {
         "application": "search-ui",
         "action_name": "click",
         "query_id": "2dd48446-7ca8-4510-89f4-2ebb67ed240b",
         "client_id": "8c1915fe-8ee0-4487-b801-3b1d67c25cf6",
         "timestamp": "2025-07-30T14:25:52.698Z",
         "message_type": "CLICK_THROUGH",
         "message": "Clicked Fahrenheit 451",
         "user_query": "fahrenheit",
         "event_attributes": {
           "object": {
             "device": "mobile",
             "object_id": "ZwoTM5gBPJ218VOaBpj4",
             "description": "Fahrenheit 451(1953-10-15) by Ray Bradbury",
             "position": {
               "ordinal": 1,
               "page_depth": 1
             },
             "user": {
               "ip": "192.168.1.100",
               "city": "New York",
               "region": "New York",
               "country": "United States",
               "location": {
                 "lat": 40.7128,
                 "lon": -74.006
               }
             }
           }
         }
       }<p>You can download the index mappings <a href="https://github.com/Alex1795/ubi-dashboard-elasticsearch_blog/blob/main/index_mappings/ubi_events-mappings.json">here</a></p><h3>Ubi_queries index</h3><p>Search data includes data relevant to each search executed by the users:</p><ul><li><p><strong>query_response_id</strong>: Unique identifier for this specific query response instance</p></li><li><p><strong>user_query</strong>: The original search term entered by the user ("fahrenheit")</p></li><li><p><strong>query_id</strong>: Unique identifier for the search query session</p></li><li><p><strong>query_response_object_ids</strong>: Array of object IDs that were returned as search results (["3", "9"])</p></li><li><p><strong>query</strong>: The complete Elasticsearch query object in JSON format, including search parameters, fields to search, result size, sorting, and metadata</p></li><li><p><strong>client_id</strong>: A generated unique ID that represents a user or session without revealing personal data. It is generated instead of using identifiable data like email addresses or usernames. This approach allows us to have privacy advantages such as safe analytics capabilities and secure data sharing without exposing PII, while still having important functionality like session continuity, behavioral analysis, or A/B testing.</p></li><li><p><strong>timestamp</strong>: Unix timestamp in milliseconds when the query was executed (1753885225098)</p></li></ul><p>Sample document:</p>    {
         "query_response_id": "03e8af3e-8725-49d9-99ad-36bf2a8e96d1",
         "user_query": "fahrenheit",
         "query_id": "f8b2f5bc-cb3c-49d4-86bc-19212a782ba7",
         "query_response_object_ids": [
           "3",
           "9"
         ],
         "query": """{"from":0,"size":20,"query":{"multi_match":{"query":"fahrenheit","fields":["author^1.0","name^1.0"]}},"_source":{"includes":["name","author","image_url","url","price","release_date"],"excludes":[]},"sort":[{"_score":{"order":"desc"}}],"ext":{"query_id":"f8b2f5bc-cb3c-49d4-86bc-19212a782ba7","user_query":"fahrenheit","client_id":"8c1915fe-8ee0-4487-b801-3b1d67c25cf6","object_id_field":null,"query_attributes":{}}}""",
         "client_id": "8c1915fe-8ee0-4487-b801-3b1d67c25cf6",
         "timestamp": 1753885225098
       }<p>You can download the index mappings <a href="https://github.com/Alex1795/ubi-dashboard-elasticsearch_blog/blob/main/index_mappings/ubi_queries-mappings.json">here</a><strong>.</strong></p><h3>Sample data</h3><p>We can use the <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-bulk">_bulk API</a> to index <a href="https://github.com/Alex1795/ubi-dashboard-elasticsearch_blog/blob/main/sample_documents/bulk_index.ndjson">some sample</a> data in both indices</p><p>This will create 6 documents in the <strong>ubi_queries </strong>index and 16 in the <strong>ubi_events</strong> index.</p><h3>Dashboard object</h3><p>Before going into details of the visualizations used in this example <a href="https://github.com/Alex1795/ubi-dashboard-elasticsearch_blog/blob/main/dashboards/web_analytics_dashboard.ndjson">here</a>, you can download the Saved Object of the full example dashboard and <a href="https://www.elastic.co/docs/explore-analyze/find-and-organize/saved-objects#saved-objects-import">import</a> it into your Kibana instance. This dashboard explores the most searched terms, when searches and events took place, and where they come from (in a map).</p><h2>Visualize Insights</h2><p>We are going to create a Kibana dashboard to analyze the most common metrics leveraging <a href="https://www.elastic.co/docs/explore-analyze/visualize/lens">Kibana Lens</a>. For a reference on available visualizations, visit <a href="https://www.elastic.co/docs/explore-analyze/visualize/supported-chart-types">this</a> page.</p><h3>Ubi_events</h3><p>We will start with some simple Metric visualizations created with Lens: <strong>Total events:</strong> Counts how many events were triggered in the timeframe. Uses a simple count of the documents in the index, denoted by <strong># Records</strong> in the field list.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt69d672ece8a13874/6a170e35286714284693e3be/2d06ff89f2cf43ee4102e9e01079ff63754e99fd-502x182.png" alt="" /><p><strong>Event actions: </strong>Counts actions by <code>action_name</code>. This is a simple count of documents split by <code>action_name.keyword</code>. In our sample data, we have two types of actions:</p><ul><li><p>click: Generated when a user clicks in the book link</p></li><li><p>search_input: Generated when a user enters text in the search box (debounce 300ms)</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltda49e8d513f0166c/6a170e366f7f04c66c9148fb/3735206d066f62990159c0777243f8b3d0703b6b-1188x186.png" alt="" /><p>Now on table visualizations:</p><p><strong>Top clicks: </strong>A table with a count of the number of events split by the query they come from. It uses a Top values function on the <code>user_query.keyword</code>. This can give us visibility on which queries generate more interactions on our webpage.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt81925167886ba5e3/6a170e37b339d58be776a06c/87aba3bbb9cf32aefa2aa126ba8edfb6bb456ae4-223x296.png" alt="" /><p>Finally, some other visualizations:</p><p><strong>Device types:</strong> This visualization breaks down the percentage of events by the device they come from. The device can be one of three categories: Desktop, mobile, or tablet. This visualization is a pie that uses the top values of <code>event_attributes.object.device.keyword,</code> and can give us insights into which type of devices our users have. This can generate alerts if we detect an unexpected, sudden fall of events on a specific type of device, as this might indicate that a recent change in our app resulted in errors when accessing it from a device.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt695213931fbb528c/6a170e397d8d6741e770e7c2/b9e8d1a07fcebf73b3107bcbc728b77a5d30e6a6-846x484.png" alt="" /><p><strong>Events map:</strong> A <a href="https://www.elastic.co/docs/explore-analyze/visualize/maps/maps-getting-started">map visualization</a> that shows where the events are coming from, which allows us to see the geographical distribution of our users. Right now, this shows where individual documents come from, but this can also be used to see the density of users with a heatmap, for example.</p><p>This particular visualization can provide very interesting insights when used with different filters. For example, we can see where different search terms are coming from or where most of our clicks are originating. This can be useful information for making decisions on localization efforts or establishing differences across local markets. The map uses the location at <code>event_attributes.object.user.location</code>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1fbe510ac15fc06a/6a170e3b4a531b59a036a9fb/ca87fe7d5f84b9c38d9787c899c55bd48f2af9f5-1600x759.png" alt="" /><p><strong>UBI Events: </strong>A saved search with the latest UBI events documents</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0101bc86e2a11639/6a170e3dd7c022575bde6545/73c94c7f0d238149851e066b4b53d16dff9e2e74-1309x379.png" alt="" /><h3>Ubi_queries</h3><p>Here we have visualizations from this index:</p><p><strong>Total queries:</strong> A simple document count of the index to show how many queries have been received in total. This shows the big picture and answers the question of how many total queries we had in the selected time window.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2f0e83ff0e87e104/6a170e3e286714171893e3c2/10af8288eaa278aff04625dab4d4a2c86c9ebf6d-218x90.png" alt="" /><p><strong>Unique clients: </strong>A <code>unique_count</code> of the field <code>client_id</code> to show how many different clients have used our website.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt30a15247e689a3c3/6a170e3f509168aec9e1bb82/baf7b29670f9392619dffef4800cb50efb3a0578-249x95.png" alt="" /><p><strong>Top queries (tag cloud):</strong> A Tag cloud of the top 5 most searched terms. This visualization uses the field <code>user_query.keyword</code> and allows us to easily see the main terms that our users are looking for.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt31218b03715ea15c/6a170e41e8fbce688139fd0b/833844afba1a5120ba2e83eade8f83954c34ba82-790x327.png" alt="" /><p><strong>Queries over time: </strong>A line chart of queries per hour, which uses a simple count metric in a horizontal axis of the field timestamp</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3c0731a036283fad/6a170e42a6c2b9839ce79798/01d79d29cd63bbc3be273ed3f55f49cc01859f64-873x182.png" alt="" /><p><strong>Query terms over time:</strong> Similar to the last one, but broken down by the <code>user_query.keyword</code>. This chart can show how many different terms are searched over time.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcb263c69b0e87ecb/6a170e4467045b7cac45c288/afb1c9c19919ca9117b5dc26c6f938c425261ae9-844x209.png" alt="" /><p><strong>Top queries:</strong> A Top values table showing how many times a term was searched. It uses the <code>user_query.keyword</code> field.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfb8f5d3bf9cb7943/6a170e45a292995c17d010b6/19854feaa749b528e328e1e427aa285b2abd16b6-384x295.png" alt="" /><p><strong>Client queries:</strong> A Top values table of the <code>client_id</code> field that counts the total queries and unique queries per client.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9201d14e53e3666a/6a170e4760084b756b3c4608/af18679364d92263913febf59c792957e41e5292-382x291.png" alt="" /><p><strong>No result queries:</strong> A Top value table that shows the top <code>query_terms</code> that didn’t match any document, and a Unique Count of the field client_id. This can be very useful to determine what products our website is lacking. For example, in an e-commerce book store, seeing regular searches for a particular book title could lead us to buy copies to sell. Alternatively, it can also indicate shortcomings in our search implementation, for example, if people are using question-based searches that align better with semantic search approaches.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc59e01f67e2899a1/6a170e48dc55de5d75e00e72/75dc6908767eb86ef2e2b8ac7e25c57b55f722ee-746x574.png" alt="" /><p>Here you can see the full dashboard:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4e197f27fb09906b/6a170e4a0e2e49c69541a1b3/39a00886ed753e12e8f2966b509b08afc45b4389-1600x913.png" alt="" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt057ec5248cd60aca/6a170e4c8b73cb5b3b18a0ce/2eb980f0123d6464b94599bc01f61de542f8b8f9-1600x412.png" alt="" /><h2>Analysis of sample data</h2><p>In our dashboard, we can get some insights:</p><ul><li><p>Traffic is coming from 3 different cities in the US</p></li><li><p>Most of our users access our website from a desktop device, but we have a sizable number of users using a mobile device and even some using a tablet.</p></li><li><p>We can see the top query is “asimov,” but at the same time, we do not have any results. This might be a good indicator of what products should be prioritized for stock acquisition.</p></li></ul><p>To further this analysis, we could use Kibana’s Machine Learning capabilities to understand and predict behaviours on our website. Going even one step further, we can create alerts based on these behaviors using the different available connectors.</p><p>From a search relevance perspective, user behavior is a useful input for relevance engineering tools like <a href="https://www.elastic.co/search-labs/blog/elasticsearch-learning-to-rank-introduction">LTR</a>.</p><h2>Conclusion</h2><p>Data collected by the UBI collector can be easily used to have a better understanding of our users. The resulting dashboard becomes a live pulse of what our users are searching for and can point to data gaps to drive improvements in our search engine.</p><p><strong>Note:</strong> The o19s User Behavior Insights (UBI) plugin mentioned in this article is a third-party, community-maintained plugin and is not officially supported by Elastic. For questions or issues related to this plugin, please refer to the o19s UBI project repository at <a href="https://github.com/o19s/ubi">https://github.com/o19s/ubi</a>. </p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-plugin-user-behavior-data-kibana</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-plugin-user-behavior-data-kibana</guid>
    <category><![CDATA[Basics]]></category>
    <dc:creator><![CDATA[Eduard Martin,Alexander Dávila]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d7e4a5bbd9427c2/6a170e4d0e2e4905b641a1b7/04f1738a38cead88c9a67b0f863171b4b43010ab-1600x913.png" length="0" type="image/png"/>
    <pubDate>Fri, 26 Sep 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Failure store: see what didn’t make it]]></title>
    <description><![CDATA[Learn about failure store, a new feature in the Elastic Stack that captures and indexes previously lost events. ]]></description>
    <content:encoded><![CDATA[<p>If a tree falls in the woods and no one is around, does it make a sound? Yes, it does. Just like if a log message is emitted but fails to process into your observability platform, <strong>that log message really did happen</strong>—and, assuming it’s something important, you’ll almost certainly hear about it eventually.</p><p>Elastic is built to adapt to all kinds of data your systems emit: logs, metrics, traces, custom telemetry, and more. But when that data doesn’t match the expected shape due to a schema change, a misconfigured agent, or a rogue service emitting unexpected fields, it can fail to process and silently disappear.</p><p>That absence is a signal. But it’s also hard to detect, hard to debug, and hard to report on. And worse, it puts the burden on the client to figure out what happened.</p><p>That’s why we built <strong>failure store</strong>: a new way to capture, debug, and analyze failed events directly in the Elastic Stack. In this blog, we’ll go over Elastic’s failure store and explain how it provides visibility into data ingestion issues, helps debug schema changes, and enables teams to monitor data quality and pinpoint failure patterns.</p><h2>About failure store</h2><p><em>Failure store</em> gives you visibility into failed events that were previously only visible to the client sending the data and to dead letter queues. It works by capturing and indexing failed documents into dedicated `::failures` indices that live in your data stream alongside your production data. You can enable it per data stream or across multiple data streams with a single cluster setting.</p><h2>Why it matters</h2><p>Teams are often downstream from the source of truth. They don’t write the code; they just keep it all running. When upstream teams ship changes that break mappings or introduce unexpected fields, failures happen. But without access to the original failed data itself, debugging becomes guesswork.</p><p>Even worse, when data fails to be indexed, it doesn’t exist in your indexes—which means it’s much harder to measure the impact. You can’t track which streams are failing most often. You can’t quantify how broken your pipelines are. And you certainly can’t alert on what’s missing if the platform never saw it (except for alerts when data goes missing).</p><p>The failure store allows a developer to understand which data failed indexing and why, giving observability engineers the tools needed to quickly understand and fix ingestion failures. Triage also happens quickly since failures are stored in Elasticsearch, with no need to gather information from remote clients or shippers.</p><h2>Get started</h2><p><strong>Set up for new data streams…</strong></p>PUT _index_template/my-index-template
{
  "index_patterns": ["my-datastream-*"],
  "data_stream": { },
  "template": {
    "data_stream_options": {
      "failure_store": { // ✨
        "enabled": true 
      } 
    }
  }
}<p><strong>…or enable for existing data streams</strong></p><p>Enable failure store for individual data streams in stack management in Kibana or leverage the _data_stream API:</p>PUT _data_stream/my-existing-datastream/_options
{
  "failure_store": {
    "enabled": true
  }
}<p><strong>Enable failure store via cluster setting</strong></p><p>If you have a large number of existing data streams, you may want to enable their failure stores in one place. Instead of updating each of their options individually, set data_streams.failure_store.enabled to a list of index patterns in the cluster settings. Any data streams that match one of these patterns will operate with their failure store enabled.</p>PUT _cluster/settings
{
  "persistent" : {
    "data_streams.failure_store.enabled" : [ "my-datastream-*", "logs-*" ]
  }
}<p><strong>A failing document response</strong></p><p>After enabling the failure store, requests that previously would fail are now processed differently. The client now receives a <code>201 created</code> instead of a <code>400 Bad Request</code>. Especially if you’re using custom applications or our <a href="https://www.elastic.co/docs/reference/elasticsearch-clients">language clients</a>, make sure to update your code accordingly. When a document goes to the failure store, the response will contain the <code>failure_store: used</code> attribute.</p>{
  "_index": ".fs-logs-generic.otel-default-2025.07.31-000010",
  "_id": "2K9IYpgBfukt97YIaUPG",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 0,
  "_primary_term": 1,
  "failure_store": "used"
}<p><strong>Search and filter failure data</strong> just like any other logs, with support for ES|QL and Kibana tools:</p>  FROM logs-generic.otel-default::failures<p>Data in the failure store comes with all the context to make debugging simple. Each ::failures index contains information about which pipeline failed, along with specific error messages, stack traces, and error types that help you identify patterns.</p><p>Are you getting lots of errors and aren't sure where to start? Use ES|QL and ML functions. With the data exposed in ES|QL, errors can be analyzed with ML capabilities such as <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/grouping-functions#esql-categorize">CATEGORIZE</a> to help parse errors and extract patterns. Read more about data remediation techniques in our <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/failure-store-recipes#failure-store-examples-remediation">documentation</a>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta20404a812af7309/6a170deda929cf3114ae0a5d/9a7c92485859ee13e971466c074bd001dbad12ba-1506x596.png" alt="Elastic failure store - example of query failures with ES|QL" /><p><strong>Control costs and retention</strong> using the same data stream lifecycle you're already using for your other data. Absent a custom retention, failure store data will stick around for 30 days.</p><p><strong>Monitor data quality over time</strong> with failure metrics and sortable dashboards by failure percentage to find new problem areas that require investigation. Read more about data quality monitoring in the <a href="https://www.elastic.co/docs/solutions/observability/data-set-quality-monitoring">documentation</a>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcc5a3f8e6bfc435d/6a170def7d8d67042370e7a4/f6790c267346481ba3b54f203c0f30224215855b-1600x818.png" alt="Elastic failure store - example for data quality and failure summaries" /><h2>Learn more</h2><p>Failure store is available starting in Elastic 9.1 and 8.19 and will be enabled by default on <strong>logs-*-*</strong> indexes in an upcoming release. To learn more, check the <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/failure-store">documentation</a> for setup instructions and best practices.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elastic-failure-store</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elastic-failure-store</guid>
    <category><![CDATA[Basics]]></category>
    <dc:creator><![CDATA[James Baiera,Graham Hudgins]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a02efdbda3b403b/6a170df1961e6963e4c4cf8d/9ad9833bbf5e1fd93376b955500d6cbc70e19ec0-1200x628.png" length="0" type="image/png"/>
    <pubDate>Wed, 13 Aug 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch new semantic_text mapping: Simplifying semantic search]]></title>
    <description><![CDATA[Learn how to use the new semantic_text field type and semantic query for simplifying semantic search in Elasticsearch.]]></description>
    <content:encoded><![CDATA[<h2>semantic_text - You know, for semantic search!</h2><p>Do you want to start using semantic search for your data, but focus on your model and results instead of on the technical details? We’ve introduced the <code>semantic_text</code> field type that will take care of the details and infrastructure that you need.</p><p><a href="https://www.elastic.co/what-is/semantic-search">Semantic search</a> is a sophisticated technique designed to enhance the relevance of search results by utilizing <a href="https://www.elastic.co/elasticsearch/machine-learning">machine learning models</a>. Unlike traditional keyword-based search, semantic search focuses on understanding the meaning of words and the context in which they are used. This is achieved through the application of machine learning models that provide a deeper semantic understanding of the text.</p><p>These models generate <a href="https://www.elastic.co/what-is/vector-embedding">vector embeddings</a>, which are numeric representations capturing the text meaning. These embeddings are stored alongside your document data, enabling <a href="https://www.elastic.co/what-is/vector-search">vector search techniques</a> that take into account the word meaning and context instead of pure lexical matches.</p><h2>How to perform semantic search</h2><p>To perform semantic search, you need to go through the following steps:</p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text#choosing-an-inference-model">Choose an inference mode</a>l to create embeddings, both for indexing documents and performing queries.</p></li><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text#creating-your-index-mapping">Create your index mapping</a> to store the inference results, so they can be efficiently searched afterwards.</p></li><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text#setting-up-indexing">Setting up indexing</a> so inference results are calculated for new documents added to your index.</p></li><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text#automatically-handling-long-text-passages">Automatically handle long text documents</a>, so search can be accurate and cover the entire document.</p></li><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text#querying-your-data">Querying your data</a> to retrieve results.</p></li></ul><p>Configuring semantic search from the ground up can be complex. It requires setting up mappings, ingestion pipelines, and queries tailored to your chosen inference model. Each step offers opportunities for fine-tuning and optimization, but also demands careful configuration to ensure all components work together seamlessly.</p><p>While this offers a great degree of control, it makes using semantic search a detailed and deliberate process, requiring you to configure separate pieces that are all related to each other and to the inference model.</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-text.html"><code>semantic_text</code></a> simplifies this process by focusing on what matters: the inference model. Once you have selected the inference model, <code>semantic_text</code> will make it easy to start using semantic search by providing sensible defaults, so you can focus on your search and not on how to index, generate, or query your embeddings.</p><p>Let's take a look at each of these steps, and how <code>semantic_text</code> simplifies this setup.</p><h3>Choosing an inference model</h3><p>The inference model will generate embeddings for your documents and queries. Different models have different tradeoffs in terms of:</p><ul><li><p>Accuracy and relevance of the results</p></li><li><p>Scalability and performance</p></li><li><p>Language and multilingual support</p></li><li><p>Cost</p></li></ul><p>Elasticsearch supports both internal and external inference services:</p><ul><li><p>Internal services are deployed in the Elasticsearch cluster. You can use already included models like <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">ELSER</a> and <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-e5.html">E5</a>, or import <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-model-ref.html#ml-nlp-model-ref-text-embedding">external models</a> into the cluster using <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-import-model.html">eland</a>.</p></li><li><p>External services are deployed by model providers. Elasticsearch supports the following:   </p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-cohere-embeddings-support">Cohere</a></p></li><li><p><a href="https://www.elastic.co/search-labs/integrations/hugging-face">Hugging Face</a></p></li><li><p><a href="https://www.elastic.co/search-labs/integrations/mistral">Mistral</a></p></li><li><p><a href="https://www.elastic.co/search-labs/integrations/open-ai">OpenAI</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-azure-ai-studio-support">Azure AI Studio</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-azure-openai-embeddings-support">Azure OpenAI</a></p></li><li><p>Google AI Studio</p></li></ul></li></ul><p>Once you have chosen the inference mode, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/put-inference-api.html">create an inference endpoint</a> for it. The inference endpoint identifier will be the only configuration detail that you will need to set up <code>semantic_text</code>.</p>PUT _inference/sparse_embedding/my-elser-endpoint
{
  "service": "elser",
  "service_settings": {
    "num_allocations": 1,
    "num_threads": 1
  }
}
<h3>Creating your index mapping</h3><p>Elasticsearch will need to index the embeddings generated by the model so they can be efficiently queried later.</p><p>Before semantic_text, you needed to understand about the two main field types used for storing embeddings information:</p><ul><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/sparse-vector.html"><code>sparse_vector</code></a>: It indexes sparse vector embeddings, like the ones generated by ELSER. Each embedding consists of pairs of tokens and weights. There is a small number of tokens generated per embedding.</p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html"><code>dense_vector</code></a>: It indexes vectors of numbers, which contains the embedding information. A model produces vectors of a fixed size, called the vector dimension.</p></li></ul><p>The field type to use is conditioned by the model you have chosen. If using dense vectors, you will need to <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html#dense-vector-params">configure</a> the field to include the dimension count, the similarity function used to calculate vectors proximity, and storage customizations like quantization or the specific data type used for each element.</p><p>Now, if you're using semantic_text, you define a <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-text.html">semantic_text field mapping</a> by just specifying the inference endpoint identifier for your model:</p>PUT test-index
{
  "mappings": {
    "properties": {
      "infer_field": {
        "type": "semantic_text",
        "inference_id": "my-elser-endpoint"
      }
    }
  }
}
<p>That's it. No need for you to define other mapping options, or to understand which field type you need to use.</p><h3>Setting up indexing</h3><p>Once your index is ready to store the embeddings, it's time to generate them.</p><p>Before <code>semantic_text</code>, to generate embeddings automatically on document ingestion you needed to set up an <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ingest.html">ingestion pipeline</a>.</p><p>Ingestion pipelines are used to automatically enrich or transform documents when ingested into an index, or when explicitly specified as part of the ingestion process.</p><p>You need to use the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/inference-processor.html">inference processor</a> to generate embeddings for your fields. The processor needs to be configured using:</p><ul><li><p>The text fields from which to generate the embeddings</p></li><li><p>The output fields where the generated embeddings will be added</p></li><li><p>Specific inference configuration for text embeddings or sparse embeddings, depending on the model type</p></li></ul><p>With <code>semantic_text</code>, you simply add documents to your index. semantic_text fields will automatically calculate the embeddings using the specified inference endpoint.</p><p>This means there's no need to create an inference pipeline to generate the embeddings. Using bulk, index, or update APIs will do that for you automatically:</p>PUT test-index/_doc/doc1
{
  "infer_field": "These are not the droids you're looking for. He's free to go around"
}
<p>Inference requests in <code>semantic_text</code> fields are also batched. If you have 10 documents in a bulk API request, and each document contains 2 <code>semantic_text</code> fields, then that request will perform a single inference request with 20 texts to your inference service in one go, instead of making 10 separate inference requests of 2 texts each.</p><h3>Automatically handling long text passages</h3><p>Part of the challenge of selecting a model is the number of tokens that the model can generate embeddings for. Models have a limited number of tokens they can process. This is referred to as the model’s context window.</p><p>If the text you need to work with is longer than the model’s context window, you may <strong>truncate</strong> the text and use just part of it to generate embeddings. This is not ideal as you'll lose information; the resulting embeddings will not capture the full context of the input text.</p><p>Even if you have a long context window, having a long text means a lot of content will be reduced to a single embedding, making it an inaccurate representation.</p><p>Also, returning a long text will be difficult for the users to understand, as they will have to scan the text to check it's what they are looking for. Using smaller snippets would be preferable instead.</p><p>Another option is to use <strong>chunking</strong> to divide long texts into smaller fragments. These smaller chunks are added to each document to provide a better representation of the complete text. You can then use a nested query to search over all the individual fragments and retrieve the documents that contain the best-scoring chunks.</p><p>Before <code>semantic_text</code>, chunking was not done out of the box - the inference processor did not support chunking. If you needed to use chunking, you needed to do it before ingesting your documents or use the script processor to perform the chunking in Elasticsearch.</p><p>Using semantic_text means that chunking will be done on your behalf when indexing. Long documents will be split into 250-word sections with a 100-word overlap so that each section shares 100 words with the previous section. This overlap ensures continuity and prevents vital contextual information in the input text from being lost by a hard break.</p><p>If the model and inference service support batching the chunked inputs are automatically batched together into as few requests as possible, each optimally sized for the Inference Service. The resulting chunks will be stored in a nested object structure so you can check the text contained in each chunk.</p><h3>Querying your data</h3><p>Now that the documents and their embeddings are indexed in Elasticsearch, it's time to do some queries!</p><p>Before <code>semantic_text</code>, you needed to use a different query depending on the type of embeddings the model generates (dense or sparse). A <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-sparse-vector-query.html">sparse vector query</a> is needed to query sparse_vector field types, and either a <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">knn search</a> or a <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-knn-query.html">knn query</a> can be used to search dense_vector field types.</p><p>The query process can be further customized for performance and relevance. For example, sparse vector queries can define <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-sparse-vector-query.html#sparse-vector-query-with-pruning-config-and-rescore-example">token pruning</a> to avoid considering irrelevant tokens. Knn queries can specify the number of candidates to consider and the top k results to be returned from each shard.</p><p>You don't need to deal with those details when using <code>semantic_text</code>. You use a <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-semantic-query.html">single query type</a> to search your documents:</p>GET test-index/_search
{
  "query": {
    "semantic": {
      "field": "infer_field",
      "query": "robots you're searching for"
    }
  }
}
<p>Just include the field and the query text. There’s no need to decide between sparse vector and knn queries, semantic text does this for you.</p><p>Compare this with using a specific <code>knn</code> search with all its configuration parameters:</p>{
  "knn": {
    "field": "infer_field",
    "k": 10,
    "num_candidates": 100,
    "query_vector_builder": {
      "text_embedding": { 
        "model_id": "my-dense-vector-embedding-model", 
        "model_text": "robots you're searching for" 
      }
    }
  }
}
<h2>Under the hood: How <code>semantic_text</code> works</h2><p>To understand how <code>semantic_text</code> works, you can create a <code>semantic_text</code> index and check what happens when you ingest a document. When the first document is ingested, the inference endpoint calculates the embeddings. When indexed, you will notice changes in the index mapping:</p>GET test-index
{
  "test-index": {
    "mappings": {
      "properties": {
        "infer_field": {
          "type": "semantic_text",
          "inference_id": "my-elser-endpoint",
          "model_settings": {
            "task_type": "sparse_embedding"
          }
        }
      }
    }
  }
}
<p>Now there is additional information about the model settings. Text embedding models will also include information like the number of dimensions or the similarity function for the model.</p><p>You can check the document already includes the embedding results:</p>GET test-index/_doc/doc1
{
  "_index": "test-sparse",
  "_id": "doc1",
  "_source": {
    "infer_field": {
      "text": "these are not the droids you're looking for. He's free to go around",
      "inference": {
        "inference_id": "my-elser-endpoint",
        "model_settings": {
          "task_type": "sparse_embedding"
        },
        "chunks": [
          {
            "text": "these are not the droids you're looking for. He's free to go around",
            "embeddings": {
              "##oid": 1.9103845,
              "##oids": 1.768872,
              "free": 1.693662,
              "dr": 1.6103356,
              "around": 1.4376559,
              "these": 1.1396849

              …
            }
          }
        ]
      }
    }
  }
}
<p>The field does not just contain the input text, but also a structure storing the original text, the model settings, and information for each chunk the input text has been divided into.</p><p>This structure consists of an object with two elements:</p><ul><li><p><em>text</em>: Contains the original input text</p></li><li><p><em>inference</em>: Inference information added by the inference endpoint, that consists of: </p><ul><li><p><em>inference_id</em> of the inference endpoint</p></li><li><p><em>model_settings</em> that contain model properties</p></li><li><p><em>chunks</em>: Nested object that contains an element for each chunk that has been created from the input text. Each chunk contains:</p><ul><li><p>The <em>text</em> for the chunk</p></li><li><p>The calculated <em>embeddings</em> for the chunk text</p></li></ul></li></ul></li></ul><h2>Customizing <code>semantic_text</code></h2><p><code>semantic_text</code> simplifies semantic search by making default decisions about indexing and querying your data:</p><ul><li><p>uses <code>sparse_vector</code> or <code>dense_vector</code> field types depending on the inference model type</p></li><li><p>Automatically defines the number of dimensions and similarity according to the inference results</p></li><li><p>Uses <code>int8_hnsw</code> index type for dense vector field types to leverage <a href="https://www.elastic.co/search-labs/blog/evaluating-scalar-quantization">scalar quantization</a>.</p></li><li><p>Uses query defaults. No token pruning is applied for <code>sparse_vector</code> queries, nor custom <code>k</code> and <code>num_candidates</code> are set for knn queries.</p></li></ul><p>Those are sensible defaults and allow you to quickly and easily start working with semantic search. Over time, you may want to customize your queries and data types to optimize search relevance, index and query performance, and index storage.</p><h3>Query customization</h3><p>There are no customization options - yet - for semantic queries. If you want to customize queries against <code>semantic_text</code> fields, you can perform <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-semantic-query.html#advanced-search">advanced semantic_text search</a> using explicit knn and sparse vector queries.</p><p>We're planning to add <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/retrievers-overview.html">retrievers support</a> for <code>semantic_text</code>, and adding configuration options to the <code>semantic_text</code> field so they won't be needed at query time. Stay tuned!</p><h3>Data type customization</h3><p>If you need deeper customization for the data indexing, you can use the <code>sparse_vector</code> or <code>dense_vector</code> field types. These field types give you full control over how embeddings are generated, indexed, and queried.</p><p>You need to create an ingest pipeline with an inference processor to generate the embeddings. <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-search-inference.html">This tutorial</a> walks you through the process.</p><h2>What's next with <code>semantic_text</code>?</h2><p>We're just getting started with <code>semantic_text</code>! There are quite a few enhancements that we will keep working on, including:</p><ul><li><p>Better inference error handling</p></li><li><p>Customize the chunking strategy</p></li><li><p>Hiding embeddings in _source by default, to avoid cluttering the search responses</p></li><li><p>Inner hits support, to retrieve the relevant chunks of information for a query</p></li><li><p>Filtering and retrievers support</p></li><li><p>Kibana support</p></li></ul><h2>Try it out!</h2><p><code>semantic_text</code>is available on <a href="https://www.elastic.co/elasticsearch/serverless">Elasticsearch Serverless</a> now! It will be available soon on Elasticsearch 8.15 version for <a href="https://www.elastic.co/cloud">Elastic Cloud</a> and on <a href="https://www.elastic.co/downloads/elasticsearch">Elasticsearch downloads</a>.</p><p>If you already have an Elasticsearch serverless cluster, you can see a complete example for testing semantic search using <code>semantic_text</code> in <a href="https://www.elastic.co/search-labs/blog/elasticsearch-cohere-rerank">this tutorial</a>, or try it with <a href="https://colab.research.google.com/github/elastic/elasticsearch-labs/blob/main/notebooks/search/09-semantic-text.ipynb">this notebook</a>.</p><p>We'd love to hear about your experience with <code>semantic_text</code>! Let us know what you think in the <a href="https://www.elastic.co/community">forums</a>, or open an issue in the <a href="https://github.com/elastic/elasticsearch">GitHub repository</a>. Let's make semantic search easier together!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text</guid>
    <category><![CDATA[Vector Database]]></category>
    <category><![CDATA[Basics]]></category>
    <dc:creator><![CDATA[Carlos Delgado,Mike Pellegrini]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt31515f5dc8f12092/6a170c170c48570fa101aabd/dc08f5c15b12a0e686b8922ad8d2b997ca1227d7-1024x1024.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 24 Jun 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Automatically updating your Elasticsearch index using Node.js and an Azure Function App]]></title>
    <description><![CDATA[Learn how to update your Elasticsearch index automatically using Node.js and an Azure Function App. Follow these steps to ensure your index stays current.]]></description>
    <content:encoded><![CDATA[<p>Maintaining an up-to-date Elasticsearch index is crucial, especially when dealing with frequently changing dynamic datasets. This blog post will guide you through automatically updating your Elasticsearch index using Node.js and an Azure Function App.</p><p>First, we'll load the data using Node.js and ensure it remains current through regular updates. Then, we'll leverage the capabilities of <a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview?pivots=programming-language-javascript">Azure Function Apps</a> to automate these updates, thereby ensuring your index is always fresh and reliable.</p><p>For this blog post, we will be using the <a href="https://data.nasa.gov/Space-Science/Asteroids-NeoWs-API/73uw-d9i8/about_data">Near Earth Object Web Service (NeoWs</a>), a RESTful web service offering detailed information about near-earth asteroids. By integrating NeoWs with Node.js services integrated as Azure serverless functions, this example will provide you with a robust framework to handle the complexities of managing dynamic data effectively. This approach will help you minimize the risks of working with outdated information and maximize the accuracy and usefulness of your data.</p><h2>Prerequisites</h2><ul><li><p>This example uses Elasticsearch version 8.13; if you are new to Elasticsearch, check out our Quick Start on <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/getting-started.html">Elasticsearch</a>. Any 8.0 version should work for this blog post.</p></li><li><p>Download the latest <a href="https://docs.npmjs.com/downloading-and-installing-node-js-and-npm">NPM and Node.js version</a>. This tutorial uses Node v21.6.1 and npm 10.5.0.</p></li><li><p><a href="https://api.nasa.gov/">An API key</a> for NASA's APIs.</p></li><li><p>An active <a href="https://azure.microsoft.com/en-us/">Azure account</a> with access to create a Function App.</p></li><li><p>Access to the <a href="https://azure.microsoft.com/en-us/get-started/azure-portal">Azure portal</a> or <a href="https://learn.microsoft.com/en-us/cli/azure/">Azure CLI</a></p></li></ul><h2>Setting up locally</h2><p>Before you begin indexing and loading your data locally, setting up your environment is essential. First, create a directory and initialize it. Then, download the necessary packages and create a <code>.env</code> file to store your configuration settings. This preliminary setup ensures your local environment is prepared to handle the data efficiently.</p>mkdir Introduction-to-Data-Loading-in-Elasticsearch-with-Nodejs
cd Introduction-to-Data-Loading-in-Elasticsearch-with-Nodejs
npm init
<p>You will be using the <a href="https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html">Elasticsearch node client</a> to connect to Elastic, <a href="https://www.npmjs.com/package/axios">Axios</a> to connect to the NASA APIs and <a href="https://www.npmjs.com/package/dotenv">dotenv</a> to parse your secrets. You will want to download the required packages running the following commands:</p>npm install @elastic/elasticsearch axios dotenv
<p>After downloading the required packages, you can create a .<code>env</code> file at the root of the project directory. The .<code>env</code> file allows you to keep your credentials secure locally. Check out the <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/automatically-updating-your-index-nodejs-azure/env.example">example .env file</a> to learn more. To learn more about connecting to Elasticsearch, be sure to take a look at the <a href="https://docs.npmjs.com/downloading-and-installing-node-js-and-npm">documentation on the subject</a>.</p><p>To create a <code>.env</code> file, you can use this command at the root of your project:</p>touch .env
<p>In your <code>.env </code>, be sure to have the following entered in. Be sure to add your complete endpoint:</p>ELASTICSEARCH_ENDPOINT="https://...."
ELASTICSEARCH_API_KEY="YOUR_ELASTICSEARCh_API_KEY"
NASA_API_KEY="YOUR_NASA_API_KEY"
<p>You will also want to create a new JavaScript file as well:</p>touch loading_data_into_a_index.js
<h2>Creating your index and loading your data in</h2><p>Now that you have set up the proper file structure and downloaded the required packages, you are ready to create a script that creates an index and loads data into the index. If you get stuck along the way be sure to check out the <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/automatically-updating-your-index-nodejs-azure/loading_data_into_a_index.js">full version of the file</a> you are creating in this section.</p><p>In the file <code>loading_data_into_a_index.js,</code> configure the <a href="https://www.npmjs.com/package/dotenv">dotenv</a> package to use the keys and tokens stored in your .<code>env </code>file. You should also import the <a href="https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html">Elasticsearch client</a> to connect to Elasticsearch and <a href="https://www.npmjs.com/package/axios">Axios</a> and make HTTP requests.</p>require('dotenv').config();

const { Client } = require('@elastic/elasticsearch');
const axios = require('axios');
<p>Since your keys and tokens are currently stored as environment variables, you will want to retrieve them and create a client to authenticate to Elasticsearch.</p>const elasticsearchEndpoint = process.env.ELASTICSEARCH_ENDPOINT;
const elasticsearchApiKey = process.env.ELASTICSEARCH_API_KEY;
const nasaApiKey = process.env.NASA_API_KEY;

const client = new Client({
  node: elasticsearchEndpoint,
  auth: {
    apiKey: elasticsearchApiKey
  }
});
<p>You can develop a function to retrieve data from NASA's NEO (Near Earth Object) Web Service asynchronously. You will first configure the base URL for the NASA API request and create date objects for today and the previous week to establish the query period. After you format these dates in the YYYY-MM-DD format required for the API request, set up the dates as query parameters and execute the GET request to the NASA API. Additionally, the function includes error-handling mechanisms to aid debugging should any issues arise.</p>async function fetchNasaData() {
  const url = "https://api.nasa.gov/neo/rest/v1/feed";
  const today = new Date();
  const lastWeek = new Date(today);
  lastWeek.setDate(today.getDate() - 7);

  const startDate = lastWeek.toISOString().split('T')[0];
  const endDate = today.toISOString().split('T')[0];
  const params = {
    api_key: nasaApiKey,
    start_date: startDate,
    end_date: endDate,
  };

  try {
    const response = await axios.get(url, { params });
    return response.data;
  } catch (error) {
    console.error('Error fetching data from NASA:', error);
    return null;
  }
}
<p>Now, you can create a function to transform the raw data from the NASA API into a structured format. Since the data you get back is currently nested in a complex JSON response. A more straightforward array of objects makes handling data easier.</p>function createStructuredData(response) {
  const allObjects = [];
  const nearEarthObjects = response.near_earth_objects;

  Object.keys(nearEarthObjects).forEach(date =&gt; {
    nearEarthObjects[date].forEach(obj =&gt; {
      const simplifiedObject = {
        close_approach_date: date,
        name: obj.name,
        id: obj.id,
        miss_distance_km: obj.close_approach_data.length &gt; 0 ? obj.close_approach_data[0].miss_distance.kilometers : null,
      };

      allObjects.push(simplifiedObject);
    });
  });

  return allObjects;
}
<p>You will want to create an index to store the data from the API. An <a href="https://www.elastic.co/blog/what-is-an-elasticsearch-index">index</a> inside Elasticsearch is where you can store your data in documents. In this function, you will check to see if an index exists and create a new one if needed. You will also specify the proper <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html">mapping</a> of fields for your index. This function also loads the data into the index as documents and maps the id field from the NASA data to the<code> _id</code> field in Elasticsearch.</p>async function indexDataIntoElasticsearch(data) {
  const indexExists = await client.indices.exists({ index: 'nasa-node-js' });
  if (!indexExists.body) {
    await client.indices.create({
      index: 'nasa-node-js',
      body: {
        mappings: {
          properties: {
            close_approach_date: { type: 'date' },
            name: { type: 'text' },
            miss_distance_km: { type: 'float' },
          },
        },
      },
    });
  }

  const body = data.flatMap(doc =&gt; [{ index: { _index: 'nasa-node-js', _id: doc.id } }, doc]);
  await client.bulk({ refresh: false, body });
}
<p>You will want to create a main function to fetch, structure, and index the data. This function will also print out the number of records being uploaded and log whether the data is indexed, whether there is no data to index, or whether it failed to get data back from the NASA API. After creating the <code>run</code> function, you will want to call the function and catch any errors that may come up.</p>async function run() {
  const rawData = await fetchNasaData();
  if (rawData) {
    const structuredData = createStructuredData(rawData);
    console.log(`Number of records being uploaded: ${structuredData.length}`);
    if (structuredData.length &gt; 0) {
      await indexDataIntoElasticsearch(structuredData);
      console.log('Data indexed successfully.');
    } else {
      console.log('No data to index.');
    }
  } else {
    console.log('Failed to fetch data from NASA.');
  }
}

run().catch(console.error);
<p>You can now run the file from your command line by running the following:</p>node loading_data_into_a_index.js
<p>To confirm that your index has been successfully loaded, you can check in the Elastic Dev Tools by executing the following API call:</p>GET /nasa-node-js/_search
<h2>Keeping your index updated with an Azure Function App</h2><p>Now that you've successfully loaded your data into your index locally, this data can quickly become outdated. To ensure your information remains current, you can set up an Azure Function App to automatically fetch new data daily and upload it to your Elasticsearch index.</p><p>The first step is to configure your Function app in Azure Portal. A helpful resource for getting started is the <a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-create-function-app-portal?pivots=programming-language-javascript">Azure quick start guide</a>.</p><p>After you've set up your function, you can ensure that you have environment variables set up for <code>ELASTICSEARCH_ENDPOINT</code>, <code>ELASTICSEARCH_API_KEY</code>, and <code>NASA_API_KEY</code>. In Function Apps, environment variables are called Application settings. Inside your function app, click on the "Configuration" option in the left panel under "Settings." Under" the "Application settings" tab, click on "+ New application setting."</p><p>You will want to make sure the required libraries are installed as well. If you go to your terminal on the Azure Portal, you can install the necessary packages by entering the following:</p>npm install @elastic/elasticsearch axios
<p>The packages you are installing should look very similar to the previous install, except you will be using the moment to parse dates, and you no longer need to load an env file since you just set your secrets to be Application settings.</p><p>You can click where it says create to create a new function inside your Function App select the template entitled “Timer trigger”. You will now have a file called function.json set for you. You will want to adjust it to look as follows to run this application every day at 10 am.</p>{
    "bindings": [
      {
        "name": "myTimer",
        "type": "timerTrigger",
        "direction": "in",
        "schedule": "0 0 10 * * *"
      }
    ]
  }
<p>You'll also want to upload your <code>package.json</code> file and ensure it appears as follows:</p>{
  "name": "introduction-to-data-loading-in-elasticsearch-with-nodejs",
  "version": "1.0.0",
  "description": "A simple script for loading data in Elasticsearch",
  "main": "loading_data_into_a_index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" &amp;&amp; exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/JessicaGarson/Introduction-to-Data-Loading-in-Elasticsearch-with-Nodejs.git"
  },
  "author": "Jessica Garson",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/JessicaGarson/Introduction-to-Data-Loading-in-Elasticsearch-with-Nodejs/issues"
  },
  "homepage": "https://github.com/JessicaGarson/Introduction-to-Data-Loading-in-Elasticsearch-with-Nodejs#readme",
  "dependencies": {
    "@elastic/elasticsearch": "^8.12.0",
    "axios": "^0.21.1"
  }
}
<p>The next step is to create a <code>index.js</code> file. This script is designed to automatically update the data daily. It accomplishes this by systematically fetching and parsing new data each day and then seamlessly updating the dataset accordingly. Elasticsearch can use the same method to ingest time series or immutable data, such as webhook responses. This method ensures the information remains current and accurate, reflecting the latest available data.You can can check out the <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/automatically-updating-your-index-nodejs-azure/loading_data_into_a_index.js">full code</a> as well.</p><p>The main differences between the script you run locally and this one are as follows:</p><ul><li><p>You will no longer need to load a <code>.env</code> file, since you have already set your environment variables</p></li><li><p>There is also different logging designed more towards creating a more sustainable script</p></li><li><p>You keep your index updated based on the most recent <code>close approach date</code></p></li><li><p>There is an entry point for an Azure Function App</p></li></ul><p>You will first want to set up your libraries and authenticate to Elasticsearch as follows:</p>const elasticsearchEndpoint = process.env.ELASTICSEARCH_ENDPOINT;
const elasticsearchApiKey = process.env.ELASTICSEARCH_API_KEY;
const nasaApiKey = process.env.NASA_API_KEY;

const client = new Client({
 node: elasticsearchEndpoint,
 auth: {
   apiKey: elasticsearchApiKey
 }
});
<p>Afterward, you will want to obtain the last date update date from Elasticsearch and configure a backup method to get data from the past day if anything goes wrong.</p>async function getLastUpdateDate() {
  try {
    const response = await client.search({
      index: 'nasa-node-js',
      body: {
        size: 1,
        sort: [{ close_approach_date: { order: 'desc' } }],
        _source: ['close_approach_date']
      }
    });

    if (response.body &amp;&amp; response.body.hits &amp;&amp; response.body.hits.hits.length &gt; 0) {
      return response.body.hits.hits[0]._source.close_approach_date;
    } else {
      // Default to one day ago if no records found
      const today = new Date();
      const lastWeek = new Date(today);
      lastWeek.setDate(today.getDate() - 1);
      return lastWeek.toISOString().split('T')[0];
    }
  } catch (error) {
    console.error('Error fetching last update date from Elasticsearch:', error);
    throw error;
  }
}
<p>The following function connects to NASA's NEO (Near Earth Object) Web Service to get the data to keep your index updated. There is also some additional error handling that can capture any API errors that might come up.</p>async function fetchNasaData(startDate) {

  const url = "https://api.nasa.gov/neo/rest/v1/feed";
  const today = new Date();

  const endDate = today.toISOString().split('T')[0];

  const params = {
    api_key: nasaApiKey,
    start_date: startDate,
    end_date: endDate,
  };

  try {
    // Perform the GET request to the NASA API with query parameters
    const response = await axios.get(url, { params });
    return response.data;
  } catch (error) {
    // Log any errors encountered during the request
    console.error('Error fetching data from NASA:', error);
    return null;
  }
}
<p>Now, you will want to create a function to organize your data by iterating over the objects of each date.</p>function createStructuredData(response) {
  const allObjects = [];
  const nearEarthObjects = response.near_earth_objects;

  Object.keys(nearEarthObjects).forEach(date =&gt; {
    nearEarthObjects[date].forEach(obj =&gt; {
      const simplifiedObject = {
        close_approach_date: date,
        name: obj.name,
        id: obj.id,
        miss_distance_km: obj.close_approach_data.length &gt; 0 ? obj.close_approach_data[0].miss_distance.kilometers : null,
      };

      allObjects.push(simplifiedObject);
    });
  });

  return allObjects;
}
<p>Now, you will want to load your data into Elasticsearch using the bulk indexing operation. This function should look similar to the one in the previous section.</p>async function indexDataIntoElasticsearch(data) {
  const body = data.flatMap(doc =&gt; [{ index: { _index: 'nasa-node-js', _id: doc.id } }, doc]);
  await client.bulk({ refresh: false, body });
}
<p>Finally, you will want to create an entry point for the function that will run according to the timer you set. This function is similar to a main function, as it calls the functions created previously in the file. There is also some additional logging, such as printing the number of records and informing you if the data was indexed correctly.</p>module.exports = async function (context, myTimer) {
  try {
    const lastUpdateDate = await getLastUpdateDate();
    context.log(`Last update date from Elasticsearch: ${lastUpdateDate}`);

    const rawData = await fetchNasaData(lastUpdateDate);
    if (rawData) {
      const structuredData = createStructuredData(rawData);
      context.log(`Number of records being uploaded: ${structuredData.length}`);
      
      if (structuredData.length &gt; 0) {

        const flatFileData = JSON.stringify(structuredData, null, 2);
        context.log('Flat file data:', flatFileData);

        await indexDataIntoElasticsearch(structuredData);
        context.log('Data indexed successfully.');
      } else {
        context.log('No data to index.');
      }
    } else {
      context.log('Failed to fetch data from NASA.');
    }
  } catch (error) {
    context.log('Error in run process:', error);
  }
<h2>Conclusion</h2><p>Using Node.js and <a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview?pivots=programming-language-javascript">Azure's Function App</a>, you should be able to ensure that your Elasticsearch index is updated regularly. By utilizing Node.js's capabilities in conjunction with Azure's Function App, you can efficiently maintain your index's regular updates. This powerful combination offers a streamlined, automated process, reducing the manual effort involved in keeping your index regularly updated. Full code for this example can be found on <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/automatically-updating-your-index-nodejs-azure">Search Labs GitHub</a>. Let us know if you built anything based on this blog or if you have questions on our <a href="https://discuss.elastic.co/">forums</a> and <a href="https://communityinviter.com/apps/elasticstack/elastic-community">the community Slack channel</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-index-node-js-automatic-updates</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-index-node-js-automatic-updates</guid>
    <category><![CDATA[Javascript]]></category>
    <category><![CDATA[Basics]]></category>
    <dc:creator><![CDATA[Jessica Garson]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt373f9290a1371dd7/6a17122e0c48579b2001abba/fd87bff40e296ebce871d631c86fd0245f11c796-1440x960.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 04 Jun 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Adding document level security (DLS) to your internal knowledge search]]></title>
    <description><![CDATA[Learn how to secure your internal knowledge lake and offer personalized search for your end-users using document level security (DLS).]]></description>
    <content:encoded><![CDATA[<p>There's a good chance that your enterprise is drowning in internal data.</p><p>You've got your issue-tracking, your note-taking, your meeting transcripts, your wiki pages, your video recordings, your chats and IMs and DMs. And don't forget the emails!</p><p>It's no wonder that so many enterprises are trying to create workplace search experiences - giving their employees a centralized, one-stop-shop for searching for internal information.</p><p>With Elastic's catalog of <a href="https://www.elastic.co/guide/en/enterprise-search/current/connectors.html">connectors</a>, this is relatively easy to do. But after you get all your data indexed and ready to be searched, how do you ensure that it is secured? After all, Tess (from Engineering) shouldn't be looking at Bob's (from HR) notes on performance reviews. How can you make sure that each separate user who comes to this unified search bar does gets their own unique view into only the data that they're authorized to view?</p><p>Enter, Document Level Security (DLS).</p><h2>Understanding document level security (DLS) in Elasticsearch</h2><p>Folks who've followed Elasticsearch for a while may already be aware that <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/document-level-security.html">DLS</a> has been an Elasticsearch feature for quite a long time. It's part of the larger theme of <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/authorization.html">user authorization</a>, and is really quite simple. You embed metadata in Elasticsearch documents, and then you craft an Elasticsearch query, filtering based on that document metadata, that describes the user's authorization. That query is used to create an <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.12/defining-roles.html">Elasticsearch Role</a>.</p><p>At query time, when the search user authenticates, their role(s) (if any) is identified, and the embedded query filter (if any) is applied to their searches.</p><p>Let's look at a simplistic example. Say we have two documents:</p>PUT example/_doc/1
{
  "my-data": true,
  "text": "This data is mine"
}

PUT example/_doc/2
{
  "my-data": false,
  "text": "This data belongs to someone else"
}
<p>A query that would fetch only <em>my</em> data would be:</p>GET example/_search
{
  "query": {
    "term": {
      "my-data": {
        "value": true
      }
    }
  }
}
<p>That query can be embedded into a Role, like:</p>POST /_security/role/my_role
{
  "indices": [
    {
      "names": [ "example" ],
      "privileges": ["read"],
      "query": {
        "term": {
          "my-data": {
            "value": true
          }
        }
      }
    }
  ]
}
<p>So if my user is assigned the role <code>my_role</code>, if I just do</p>GET example/_search
<p>I will only see document <code>1</code>, but not document <code>2</code>.</p><p>While this example is simple in theory, it has a relatively large number of moving pieces.</p><ul><li><p>you must ensure that the documents contain the relevant metadata (<code>"my-data": true</code> vs <code>"my-data": false</code>)</p></li><li><p>you must <em>trust</em> that the metadata on those documents is accurate</p></li><li><p>you must create a Role for every search user with a finely crafted Elasticsearch query</p></li><li><p>you must ensure that every role you create correctly maps to the right user at query time</p></li><li><p>you mush ensure that all of the above stays up to date.</p></li></ul><p>That last one is particularly difficult. When people in your enterprise join, leave, switch teams, or get promoted, that requires changes - potentially to both your (meta)data AND your Roles. And if you add in data sources that support sharing or access editing, you're definitely needing to make sure that your (meta)data stays up-to-date.</p><h2>DLS with Elastic connectors</h2><p><a href="https://www.elastic.co/guide/en/enterprise-search/current/dls.html">Connector document level security</a> builds off of the Elasticsearch DLS primitives. For many connectors, this includes syncing the relevant metadata and Role Descriptors to support DLS. This results in the documents in your content index automatically containing metadata (usually in a <code>_allow_access_control</code> field) to describe the people/groups who are authorized to search for this document, as well as documents in a special <code>.search-acl-filter-&lt;index-name&gt;</code> index that contain the Role Descriptor JSON necessary to build a concrete Role or an API key for a given search user.</p><p>You can find <a href="https://www.elastic.co/guide/en/enterprise-search/current/dls.html#dls-availability-prerequisites">which connectors have DLS available here</a>. For this blog, we're going to reference an example application which utilizes the Sharepoint Online connector. This was the first connector we enabled DLS on, but the example could be easily adapted to work with any DLS-enabled connector.</p><p>If your connector is eligible, and you have a Platinum+ Elasticsearch license, you can enable DLS through a toggle on the connector configuration page.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta4d4869b2ef7a27b/6a1711f7964cea84be08bce1/a8d709d6d559891d623d108484326201a7d048d0-1440x666.png" alt="enable-dls" /><p>From there, it's just a matter of running a Full sync and an Access Control sync, and Elasticsearch will have all the data it needs.</p><h2>DLS implementation example</h2><p>And then what?</p><p>Once Elasticsearch has Role Descriptors and document data with sufficient metadata for those role filters, you're ready to build a secure search experience.</p><p>We've built <a href="https://github.com/elastic/elasticsearch-labs/tree/main/example-apps/internal-knowledge-search">an example knowledge search app</a> that we'll use for this blog, and you're welcome to go take a look at its source code. However, we do want to stress that this is an example only - it is not ready to be run in production on its own. Please exercise good judgement and do not run code that you have not read or do not understand.</p><p>This application has a pretty simple architecture.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4ddcfc0dd7551129/6a1711f867045b814245c303/2ee525509acb672b89f36a2b05ad2b75872745d6-864x408.png" alt="dls-simple-architecture" /><p>It is composed of a <a href="https://github.com/elastic/elasticsearch-labs/blob/main/example-apps/internal-knowledge-search/api/app.py">Flask backend</a> and a <a href="https://github.com/elastic/elasticsearch-labs/tree/main/example-apps/internal-knowledge-search/app-ui">React frontend</a>. The backend is configured with environment variables to establish a connection with Elasticsearch.</p>export ELASTICSEARCH_URL=...
export ELASTIC_USERNAME=...
export ELASTIC_PASSWORD=...
<p>Using this connection, the backend provides three endpoints:</p><ol><li><p><code>GET /api/persona</code> This endpoint lists the identifiers for the "identities" or "personas" that the connector found during the Access Control sync. The frontend uses this list to populate a dropdown of personas so to demonstrate how search results change depending on the selected persona.</p></li><li><p><code>GET /api/indices</code> This endpoint lists which indices have been included in your Search Application. The frontend uses this list to allow you to choose which ones to search against.</p></li><li><p><code>GET /api/api_key?persona=&lt;persona&gt;</code> This endpoint creates and returns an Elasticsearch API key based off of a selected persona. In a production system, <code>persona</code> wouldn't be a request argument, but would be inferred from the authentication credentials. This API key is then used by the frontend to issue search requests to Elasticsearch.</p></li></ol><h3>Caveats</h3><p>As stated above, this example should not be used in production. Gaps include:</p><ul><li><p>It does not implement authentication. A production-ready app would need a way for users to authenticate, and have their identities verified, rather than selecting a user from a dropdown.</p></li><li><p>It does not utilze SSL/TLS. The backend currently transmits Elasticsearch API Keys to the frontend over HTTP, not HTTPS.</p></li><li><p>The frontend issues <code>/_search</code> requests directly to Elasticsearch. Depending on the production use case, you may not want to expose Elasticsearch to your end user like this. Instead, it may be advisable to issue requests from the frontend to your backend (again, with authentication implemented), and have the backend translate those requests to Elasticsearch queries.</p></li></ul><h3>Source Code</h3><p>Below we link to the critical pieces of code that are necessary to implement search with DLS.</p><h4>Creating the authenticated user's role descriptor</h4><p><a href="https://github.com/elastic/elasticsearch-labs/blob/ebd2e96de3dc8d56624e70248de4bbac35e2ec71/example-apps/internal-knowledge-search/api/app.py#L121-L166">Code link</a></p>
            identity = elasticsearch_client.get(
                index=identities_index, id=persona)
            permissions = identity["_source"]["query"]["template"]["params"][
                "access_control"
            ]
            role_descriptor = {
                "dls-role": {
                    "cluster": ["all"],
                    "indices": [
                        {
                            "names": [search_app_name],
                            "privileges": ["read"],
                            "query": {
                                "template": {
                                    "params": {"access_control": permissions},
                                    "source": """{
                                        "bool": {
                                            "should": [
                                                {
                                                    "bool": {
                                                        "must_not": {
                                                            "exists": {
                                                                "field": "_allow_access_control"
                                                            }
                                                        }
                                                    }
                                                },
                                                {
                                                    "terms": {
                                                        "_allow_access_control.enum": {{#toJson}}access_control{{/toJson}}
                                                    }
                                                }
                                            ]
                                        }
                                    }""",
                                }
                            },
                        }
                    ],
                    "restriction": {"workflows": ["search_application_query"]},
                }
            }
<p>You may notice that the query template in this role descriptor is significantly more complex than the simple example provided earlier in this blog. This query does several things:</p><ol><li><p>It uses a query template, instead of an explicit query. This makes it easier when reading to separate a long list of permissions from the query syntax.</p></li><li><p>It uses a <code>bool</code> query. This allows us to combine several logical checks.</p></li><li><p>It grants access to any documents that do not contain the <code>_allow_access_control</code> field</p></li><li><p>It grants access to documents where the <code>_allow_access_control</code> field contains a value found in this user's <code>permissions</code></p></li></ol><h4>Creating an API Key from that Role Descriptor</h4><p><a href="https://github.com/elastic/elasticsearch-labs/blob/ebd2e96de3dc8d56624e70248de4bbac35e2ec71/example-apps/internal-knowledge-search/api/app.py#L167-L169">Code link</a></p>
        api_key = elasticsearch_client.security.create_api_key(
            name=search_app_name+"-internal-knowledge-search-example-"+persona, expiration="1h", role_descriptors=role_descriptor)
        return {"api_key": api_key['encoded']}
<h4>Searching with the API Key</h4><p><a href="https://github.com/elastic/elasticsearch-labs/blob/ebd2e96de3dc8d56624e70248de4bbac35e2ec71/example-apps/internal-knowledge-search/app-ui/src/pages/SearchPage.tsx#L76-L107">Code link</a></p>
      const apiKey = searchPersonaAPIKey;

      const client = SearchApplicationClient(
        appName,
        searchEndpoint,
        apiKey,
        {
          facets: {
            description: {
              type: "text",
            },
          },
        },
        {
          disableCache: true,
        }
      );

      const sortArray = Object.values(sorts).map((sort) =&gt; ({
        [sort.title]: sort.sortDirection,
      }));

      const rawResults = await client()
        .query(query)
        .setSort(sortArray)
        .setPageSize(10)
        .addParameter("indices", indexFilter)
        .search();

      const searchResults = rawResults.hits.hits.map((hit: any) =&gt; {
        return mapHitToSearchResult(hit);
      });
]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/dls-internal-knowledge-search</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/dls-internal-knowledge-search</guid>
    <category><![CDATA[Basics]]></category>
    <dc:creator><![CDATA[Sean Story]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1a0bf009f00b1c80/6a1711fa4a531b3db636aa9f/c7c174d6408b23fca482664c608f9e8849243d96-1440x720.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 22 Jan 2024 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>