<?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[Jan Kuipers - 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[Jan Kuipers - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/search-labs/author/jan-kuipers</link>
    </image>
    <link>https://www.elastic.co/search-labs/author/jan-kuipers</link>
    <atom:link href="https://www.elastic.co/search-labs/rss/author/jan-kuipers.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[en]]></language>
    <lastBuildDate>Sat, 26 Sep 2026 05:44:33 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Fast approximate Elasticsearch ES|QL - part II]]></title>
    <description><![CDATA[Explaining the approach we use to obtain fast approximate Elasticsearch ES|QL queries and the testing we did of error estimation.]]></description>
    <content:encoded><![CDATA[<p>As we discussed in our <a href="https://www.elastic.co/search-labs/blog/fast-approximate-esql-part-1">previous blog</a>, we’re introducing fast approximate <a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/esql">ES|QL</a> <code>STATS</code> queries, which will be available in version 9.4 of Elasticsearch and the Elastic Stack. This feature allows users to estimate an expensive analytics query, often orders of magnitude faster than running the full query, by relaxing the constraint that it returns the exact value. We believe this has many uses; for example, we’re planning to integrate it into Kibana to obtain fast chart previews where possible.</p><p>In order for you to be able to trust our estimates, we provide error estimates. Furthermore, since there are edge cases in error estimation, we certify when the estimated value and error are trustworthy. In this blog post, we’ll dive into the theory for approximating and estimating the error in such queries, as well as discuss the testing we’ve done.</p><h3>Background</h3><p>In order to estimate ES|QL <code>STATS</code> queries efficiently, we make use of a property that’s shared by many statistics: Their estimates computed from a large number of independent samples from a dataset approach their true value. In the case of an index with some field  we can think of the true value of a statistic as its value computed for a random variable with uniform discrete distribution on . In the following we denote this quantity ; it can be things like <code>AVG</code>, <code>MEDIAN</code>, and so on. If we make  independent draws from , denoted , such that each value is selected with probability , we have  independent copies of this random variable. The property we rely on means that a sample statistic value  computed from  approaches  as  becomes large. For example, if  is the mean of some metric values then  as  becomes large. Indeed, for many statistics the limiting error distribution is known to be normal. Furthermore, it only depends on the distribution of , the size of the sample  and the type of the aggregation . This means supported <code>STATS</code> queries can be approximated with fixed accuracy independent of the index size .</p><p>It is easy to pick values at random from a Lucene index: create a filter that takes exponentially distributed jumps through the dataset, where the expected jump size is controlled by the desired sample probability. The AND of this filter and any other Lucene query can be performed extremely efficiently, since AND’ing filter queries is one of the things for which it is well optimized. In our other post, we discussed some real-world query examples to give a sense of the speedup we obtain for different levels of accuracy.</p><p>So far, we've only discussed obtaining an estimate of a query. While such a point estimator can be useful, without knowing anything about its error those uses are limited. We found that ES|QL has existing capabilities that make it relatively easy to incorporate cheap, flexible, and accurate error estimation at the same time. We'll discuss this next.</p><h3>Error estimates</h3><p>We view providing an accurate understanding of the uncertainty in our estimates as crucial for users to be able to trust the approximation. While having the option to quickly estimate an ES|QL query alone can be useful in certain situations, we wanted to provide a richer feature that allows clients to make intelligent choices. For example, if an approximate query is being used to preview a chart and the error is only a couple of pixels, there’s little point in running another expensive query to redraw it.</p><p>The way we've chosen to represent error is by a confidence interval: the -central confidence interval, to be precise. This can be expressed in terms of the <a href="https://en.wikipedia.org/wiki/Cumulative_distribution_function">cumulative density</a>, , of the statistic being estimated. Specifically, it's the interval which contains the true value of the statistic with probability  whose endpoints are  and . Confidence interval calculations are surprisingly subtle. There are also important constraints for our use case that make standard approaches undesirable. Next, we’ll take a look in more detail at the motivation and the design for the approach we’ve adopted.</p><p>A key requirement of the whole project is to dramatically accelerate expensive analytics queries. It’s therefore vital that the overhead of estimating uncertainty isn’t too large compared to estimating the query result itself. We also want the feature to be as general as possible, but “isolated” within the language. In other words, ES|QL is a flexible language, and we want estimation to work with as much of it as possible. At the same time, we don’t want to introduce a cross-cutting feature that incurs development costs on every new feature we ship.</p><p>With these considerations in mind, we chose to estimate confidence intervals by partitioning the sample set and computing the query output on each subsample. This is reminiscent of bootstrap; however, since we ensure that each partition receives a disjoint random subset of the sample data, we know that they comprise true estimates of the statistic distribution. To achieve the best possible estimate of the statistic itself, we still compute its value on the full sample. For example, to estimate the mean and its distribution the process can be expressed as follows:</p><p>This introduces a complication to account for the discrepancy between the count of values used to estimate a query statistic and used to sample its distribution. This is a downside; however, there are some significant advantages.</p><p>Most of the work in analytic queries resides in computing the aggregate statistics: post-processing after a <code>STATS</code> reduction acts on a far smaller table, and the cost is often relatively small. In this scheme, every row in the input data to the <code>STATS</code> command is processed exactly twice compared to just estimating the statistic. Therefore, roughly speaking we pay a fixed overhead that's the same order of magnitude as the cost of estimating the query in order to estimate its uncertainty. Since we often achieve multiple orders of magnitude speedup on the exact query, this is acceptable.</p><p>Because this process uses a plain old table, with extra columns for the distribution samples, we can pass the whole table through any ES|QL pipeline and compute confidence intervals on the final results. For example, if we include <code>EVAL square_avg = avg * avg</code> in the pipeline above, we'd have exactly the same <code>square_avg</code>, <code>square_avg_0</code>, …, <code>square_avg_B-1</code> extra values. At the end of the pipeline, we have samples from the distribution of the original statistics and all quantities that are computed using them. Therefore, we can apply our standard confidence interval machinery to reduce the table and convert samples into confidence intervals for derived quantities as well. This whole process is essentially transparent to the rest of the ES|QL language, and as we showed above, can be achieved by query rewriting.</p><h3>The confidence interval calculation</h3><p>We have independent samples of the statistic distribution . However, they're computed with fewer values than our estimate . We also have a relatively small number of distribution samples, to avoid the count discrepancy being too large, and so we don’t inflate the table too much. We therefore prefer a parametric approach for estimating confidence intervals.</p><p>The errors in the statistics for which we support estimation tend to normal distributions in the limit they're computed from many values. So a natural choice, the standard interval, is to estimate the mean and standard deviation from the samples and report the corresponding normal confidence intervals . Here,  denotes the standard normal distribution function. For heavy-tailed data and statistical functions that are sensitive to outliers, such as <code>STD_DEV</code>, convergence to normality can be slow, resulting in poorly calibrated intervals.</p><p>Briefly, in order to assess the quality of the intervals, one can examine their calibration. Specifically, one computes a quantity called the <a href="https://en.wikipedia.org/wiki/Coverage_probability">coverage</a>. For a central confidence interval, it should contain the true statistic value roughly  times for  trials. In fact, since we seek the central confidence interval, we can make the stronger statement that the true value should be above, or below, the confidence interval endpoints in roughly  out  trials. The empirical coverage is this fraction computed for a large number of trials. It allows us to compare alternative approaches by simulation. We return to this when we report our test results.</p><p>In order to obtain better confidence intervals, we tried a couple of different approaches: the <a href="https://en.wikipedia.org/wiki/Cornish%E2%80%93Fisher_expansion">Cornish-Fisher</a> correction of quantiles and an adaptation of <a href="https://en.wikipedia.org/wiki/Bootstrapping_(statistics)#Deriving_confidence_intervals_from_the_bootstrap_distribution">bias-corrected accelerated</a> (BCa) confidence intervals. Simulation showed BCa provided more robust calibration across a range of confidences, so this is the approach we selected. The basic idea, which was introduced by Efron, is to assume that there exists a monotonic transformation of the underlying statistic  which, when applied to a distribution sample normalizes its distribution:</p><p>Here, ,  and  is the standard normal random variable. This is clearly a relaxation of the assumption that the statistic itself is normally distributed, which is used to derive the standard interval. In fact, this family includes many distributions, since  is only constrained to be monotonic. (You can think of  as a first-order Taylor expansion of the case that the variance is an arbitrary function of the true parameter value. This further relaxes the assumption that the normalizing transformation also stabilizes the variance.) The nice thing about this ansatz is that  never needs to be explicitly computed, and there exist standard approaches for estimating the parameters  and  from the distribution samples.</p><p>To handle  one simply arranges for the estimate to land at the median of transformed distribution. If we assume the cumulative distribution function in theta space is  then , where  is the estimated statistic value, and as before  is the standard normal distribution function. Typically,  is approximated by the empirical distribution function, computed indirectly by bootstrap. However, somewhat surprisingly, extensive simulation showed that we obtained better calibrated intervals using a normal approximation to our sample values, i.e.  with  and  their empirical mean and standard deviation, respectively.</p><p>To complete the procedure, one can rearrange (1) to derive  quantiles for  as follows:</p><p>where  is the standard normal z-score for quantile . Typically, one uses the inverse empirical cumulative density estimate of  to convert quantiles back to a confidence interval. However, because we have a mismatch between the count of values used to compute distribution samples and the query estimate, we need to do some sort of scaling. Exploring options by simulation, we again found it best to use a normal approximation, , where  is the number of distribution samples we use. This is just applying the usual scaling of variance by .</p><p>Efron showed that in the case  is distributed as , i.e. that it depends only on the true value , then the acceleration  can be estimated without any knowledge of . In particular, . By assumption, our statistics tend to normal distributions with mean . Since skew is translation and scale invariant, this gives that , i.e. one sixth of the skew of our distribution samples. One thing this glosses over is the dependence of skew, and therefore acceleration, on sample size. We know it tends to zero as the count increases. In fact, skew also asymptotes to zero as  and so we also adjust acceleration to be  to account for the count mismatch between the samples  and estimate .</p><p>Although we significantly improve the calibration of confidence intervals by using a better methodology, we still see issues in the case that the underlying distribution has very heavy tails for some of the supported <code>STATS</code> functions. Therefore, we introduce some additional guard rails we discuss next.</p><h3>Guard rails</h3><p>To avoid the user having to understand too much about edge cases, we provide additional safeguards that surface when we've been unable to confirm  that the distribution samples behave as we expect. This typically happens when the statistic isn’t computed from a sufficient number of values given the metric distribution. It's exacerbated by very skewed metric data and certain aggregation functions, such as the <code>STD_DEV</code>, which are sensitive to outliers.</p><p>We have some global constraints on the minimum count of values used to estimate a statistic for which we'll certify it. For example, if any bucket is empty, then we can’t rely on the distribution samples. This is because ES|QL allows mixing approximate statistics, which treat empty buckets differently. For example, consider the following query:</p><p>There is no self-contained way of correctly assigning a value to <code>mix</code> for empty buckets, since summing requires that we treat them as zero, in which case we bias our estimate of <code>avg</code>. Alternatively, ignoring empty buckets introduces bias in the <code>sum</code>. There is also a global minimum count of values for which we’ve verified our certification method is sufficiently reliable; this is 10.</p><p>We explored a variety of additional tests to certify the results. These were based on both tests of the underlying data distribution, specifically <a href="https://en.wikipedia.org/wiki/Heavy-tailed_distribution#Hill.27s_tail-index_estimator">Hill’s estimator</a>, as well as the statistic’s distribution properties. If the true distribution of the statistic is sufficiently normal, then our estimate and confidence interval calculation behaves as we expect: The interval is well calibrated and the interval width is representative of the actual error. Therefore, in the end, we chose to use a test based on the p-value for distribution samples’ <a href="https://en.wikipedia.org/wiki/Skewness">skewness</a> and <a href="https://en.wikipedia.org/wiki/Kurtosis">kurtosis</a> versus a normal distribution null hypothesis. To certify a result, we require that the two tail p-values are greater than 0.05 for both tests. As we show below, we found this test was well aligned to our actual needs: to distinguish results for which the estimate and its confidence interval are more and less reliable.</p><p>There's a simple trick we can use to boost the accuracy of the accuracy of the test: Create multiple independent distribution samples and use a vote. Given a test to certify results with a failure rate , the distribution of the count of  failures for  tests is  for the case the null hypothesis, that the estimate is trustworthy, is true. For example, for the majority vote assuming  and  then the significance of the test is , i.e. we fail to certify fewer than 1% of trustworthy results. Note that we can compute multiple trials relatively easily using different seeds for the <code>RANDOM</code> bucket identifier.</p><p>This additional check allows us to certify that we trust our estimates and their errors. We surface this information in the approximate query results. When we can’t certify results, they won’t necessarily be inaccurate, but they should be treated with more caution.</p><h3>Testing</h3><p>The two main aims of the testing we discuss here were to understand the calibration of the confidence intervals and to see how well they characterize the statistics' estimation errors. The count function is particularly well behaved, its error distribution is binomial, so the majority of our testing focused on metric aggregations. We study smooth distributions but make sure we cover a range of tail behaviors. The presence of outliers is the key factor that reduces the accuracy of estimated statistics. For example, if an outlier isn’t sampled at all, it can significantly affect the value of some statistics.</p><p>We explored a range of light-tailed distributions, such as uniform and normal, and skewed and heavy-tailed distributions, such as exponential, log-normal, Cauchy, and Pareto. For each family of distribution, we used multiple parameterizations, focusing primarily on varying the scale parameter. In total, we had 24 distinct data distributions. Figure 1 shows some example sample distributions from this set. Note that we’ve truncated the charts to remove extreme outliers, which are present for both the Cauchy and log-normal distributions.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6e7b7df23a7e1faa/6a170ca0c1e8a505d7f88312/fb088c17f9755c0d1b3173fb917f0af2c0f83847-1712x950.png" alt="" /><p>For each data distribution, we evaluated 14 different sample sizes, ranging from 1000 to 500000. Then, for each sample set, we evaluated <code>AVG</code>, <code>COUNT</code>, <code>MEDIAN_ABSOLUTE_DEVIATION</code>, <code>MEDIAN</code>, <code>PERCENTILE([25, 75, 90, 95, 99])</code>, <code>SUM</code> and <code>STD_DEV</code> at two levels of confidence, 50% and 90%. In total, we have around 7500 distinct experiments. For each experiment, we assessed the interval calibration using 100 runs and counting the number of times the true statistic lands in the confidence interval. This gives us a binomially distributed estimate for the true confidence interval coverage. The variation we expect in the estimated coverage changes slightly with the level of confidence; for example, at 50% we expect to see values mainly between 0.44 and 0.56, and for 90% we expect to see values mainly between 0.86 and 0.94 using 100 trials.</p><p>Figure 2 shows <a href="https://en.wikipedia.org/wiki/Box_plot">box plots</a> for the empirical coverage for the two confidence levels computed from all experiments. In all cases, the confidence intervals are reasonably well calibrated. Extreme percentiles are biased for small sample sizes, which leads to increased outlier counts for small sample sizes. As a rule of thumb, you’d want roughly  samples to ensure that you have enough samples in the appropriate tail.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte33fb7bc54844a83/6a170ca2839dfa7a19dcff38/02a5375025e811ba18c4e823e1d984261bbf6f42-631x763.png" alt="" /><p>Next, we examine the degree to which the confidence intervals capture the typical size of the estimate error. To do this, we examine the distribution of the ratio of the estimated statistics' error and half the confidence interval width for all certified results. The higher the confidence, the wider the interval, so different confidence levels shift the mean of this distribution. Figure 3 shows this distribution computed for the 90% confidence interval. As expected, the distribution is roughly normal, albeit with a tail of some larger errors. We see in all cases the confidence interval width gives the order of magnitude of the estimated statistics' actual errors.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3d4bc25885b66945/6a170ca360084b472d3c45b4/2d4ab88a07910edac7e8406ae4942694751f0090-1000x600.png" alt="" /><p>We’ve shown that certified results are nearly always reliable; however, we’d also like some insight into the proportion of results which we fail to certify that are actually reliable, to confirm that the test aligns with our objective. We use <em>reliable</em> here in the fairly strong sense that the confidence interval is well calibrated. Specifically, for the 50% and 90% confidence intervals, we count the proportion of uncertified results for which the confidence interval empirical calibration has an acceptable margin of error, given the number of trials used to estimate it. Using this procedure, the false positive rate across all experiments is around 1%. This agrees well with the failure rate we expect by chance, given our test parameters, and confirms the assumption underlying the test.</p><p>Finally, to better understand the difference between certified and uncertified results, Figure 4 shows the error distribution of the ratio of the estimated statistics' errors and half the 90% confidence interval for the reliable and unreliable results separately. Note that we truncated the range for uncertified intervals.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0c4f729eb4c1127c/6a170ca5961e69c63cc4cf66/c0cd6d42f5d061aac15767539209a7c443ed1acd-1000x600.png" alt="" /><h3>Wrapping up</h3><p>In this post, we present the background behind our approach for quickly estimating ES|QL queries and providing an indication of their errors. To do this, we developed an effective confidence interval mechanism that allows us to provide error estimates. Our approach also allows us to estimate confidence intervals for quantities derived from sampled statistics via other pipeline operations. Quantifying the error comes with a relatively small overhead compared to just estimating the query. Finally, we developed a statistical test to certify results we return. Values that aren’t certified can still be accurate, but we’re less confident in them.</p><p>As well as testing the feature on a range of real-world use cases, which we discuss in <a href="https://www.elastic.co/search-labs/blog/fast-approximate-esql-part-1">our companion post</a>, we tested the error estimation by extensive simulation across a range of data characteristics, sample sizes, aggregation functions, and confidence levels. This showed confidence intervals are well calibrated, and the interval itself provides a good approximation of the actual error we observe in the estimates. Finally, we showed that we were able to certify intervals with a low false negative rate.</p><p>We’re planning to integrate this feature into other stack capabilities in the future, so stay tuned.

</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/fast-approximate-esql-part-2</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/fast-approximate-esql-part-2</guid>
    <category><![CDATA[ES|QL]]></category>
    <dc:creator><![CDATA[Thomas Veasey,Jan Kuipers]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0c4f729eb4c1127c/6a170ca5961e69c63cc4cf66/c0cd6d42f5d061aac15767539209a7c443ed1acd-1000x600.png" length="0" type="image/png"/>
    <pubDate>Fri, 17 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Fast approximate Elasticsearch ES|QL - part I]]></title>
    <description><![CDATA[Introducing the work we've done on a fast approximate querying mode for Elasticsearch ES|QL. In many cases, it allows us to achieve orders of magnitude latency reductions while providing accurate estimates.]]></description>
    <content:encoded><![CDATA[<p>Analytics workloads typically involve summarizing large volumes of data into a much smaller number of statistics. The Elasticsearch Query Language (ES|QL) implements this capability using the <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/stats-by">STATS command</a>. This allows you to select various aggregation functions and apply them to the previous query results, as well as grouping the results by one or more ES|QL expressions. This is a flexible operation that, coupled with ES|QL querying capabilities, allows one to perform <a href="https://en.wikipedia.org/wiki/MapReduce">MapReduce</a> on data stored in collections of Elasticsearch indices.</p><p>One of the key requirements for a pleasant user experience is that these operations are performed quickly. Large language model–based (LLM) agents also introduce new <a href="https://arxiv.org/pdf/2509.00997">higher bandwidth and speculative query patterns</a> that can potentially benefit from different optimization strategies.</p><p>In this two-part blog series, we discuss an optimization approach we’re introducing to ES|QL in version 9.4 of Elasticsearch and the Elastic Stack, which exploits a relaxation of the problem. Rather than trying to get exact values for aggregates, we allow ourselves to return approximate values, together with some characterization of their error. A key benefit of approximation is that it breaks the dependency between performance and dataset size: The accuracy with which one can approximate a query doesn’t depend on the original dataset size but, principally, its data characteristics and the query itself. As we’ll see later, this allows us to achieve some dramatic performance improvements.</p><p>In our<a href="https://www.elastic.co/search-labs/blog/fast-approximate-esql-part-2"> next blog post</a>, we will discuss the theory behind our approach and the validation we’ve done of its statistical properties. Here, we introduce the syntax and give a sense of how it’s achieved using standard ES|QL and query rewriting. You can explore its performance on a subset of the popular <a href="https://github.com/ClickHouse/ClickBench">ClickBench</a> benchmark. Finally, we discuss some limitations and gotchas that are worth understanding when you use query approximation.</p><h3>Syntax and behavior</h3><p>So how do you actually use it?</p><p>That’s it. You simply introduce the new line <code>SET approximation=true;</code> and write your <code>STATS</code> query pipeline as usual. Below, we discuss some advanced configuration options and some limitations around the <code>agg(...)</code> and <code>commands</code>. However, essentially, we choose defaults so that this will typically provide useful approximations while achieving significant speedups.</p><p>With this change, you’ll see some differences in the query results. Let’s look at a concrete example to illustrate this. Suppose the raw query is as follows:</p><p>The results might look something like this:</p>item_category        | count
---------------------+------
Household Essentials | 5165
Kitchen              | 2132
Storage              | 1121
Home Decor           | 877
Furniture            | 357<p>Approximating this query introduces some extra columns for each quantity that’s estimated:</p>item_category | count | _approximation_confidence_interval(count) | _approximation_certified(count)
--------------+-------+-------------------------------------------+--------------------------------
Essentials    | 5150  | [5100, 5250]                              | true
Kitchen       | 2150  | [2100, 2200]                              | true
Storage       | 1120  | [1100, 1150]                              | true
Home Decor    | 880   | [860, 900]                                | true
Furniture     | 330   | [310, 350]                                | true<p>The count column now contains an estimate, and you’ll see it’s somewhat different from the exact values above. The <code>_approximation_confidence_interval(count)</code> column defaults to the central 90% confidence interval for the <code>count</code> estimate and the <code>_approximation_certified(count)</code> column indicates if we’re highly confident that the results and their confidence interval are trustworthy. In outline, the <em>confidence interval</em> is an interval we expect has a high probability (0.9) of containing the true value for the quantity being estimated. The <em>certified column</em> indicates the distribution of the approximation is behaving as we expect. When the result isn’t certified, it’s often still accurate, but our test of the properties of its distribution hasn’t been able to confirm this. These quantities are discussed in more detail in our second post.</p><h3>Implementation</h3><p>An approximate query is rewritten before query execution using random sampling and extrapolation. Let’s take a look at the query of the previous section. The part of the rewritten query responsible for obtaining the best estimate looks like:</p><p>The query samples a fraction of the data, and therefore the final count has to be extrapolated by scaling up with the inverse of the sample probability. Extrapolation clearly depends on the underlying aggregation function, and we handle this appropriately for all functions we support.</p><p>To obtain the sample probability, we're setting a fixed <code>number_of_rows</code> to be processed by the <code>STATS</code> command. In this case, the probability is calculated as follows:</p><p>This query is executed before the final approximate query is executed.</p><p>As well as this best estimate, confidence intervals and a statistical test used to certify that the value distribution is behaving as we expect also need to be computed. The intervals are computed using a variant of the <a href="https://blogs.sas.com/content/iml/2017/07/12/bootstrap-bca-interval.html">bias-corrected and accelerated bootstrap confidence interval</a> (BCa) method. Therefore, the data needs to be partitioned into B buckets, which are used in turn to compute the intervals. Omitting some implementation details, this approximate query looks like:</p><p>To certify the estimate and confidence interval, there should be enough data, and the distribution of the bucket values should tend to normality.</p><p>Some queries can be efficiently computed using only summary statistics maintained in the index. To handle these correctly, where sampling is both slower and inaccurate, we updated the physical query planner, since detecting this case requires information that’s only available where the data resides. When the planner detects this is possible, it simply executes the query as normal. Such queries are typically fast anyway, and there’s no real side effect, so you don’t need to worry about this when using approximation; however, you’ll see that confidence intervals for such queries always have zero length, indicating the results are exact.</p><h3>Results</h3><p>To explore the performance improvements, we use <a href="https://github.com/ClickHouse/ClickBench">ClickBench</a>. This is a benchmark for analytics workloads for database management systems (DBMS). It comprises approximately 100 million rows, with a focus on clickstream and traffic analysis, web analytics, machine-generated data, structured logs, and events data. The benchmark also defines 43 queries that are typical of ad-hoc analytics and real-time dashboards.</p><p>Some of the queries aren’t suitable for approximation. For example, we don’t support approximating the unique count of a categorical value or computing the minimum and maximum of a metric value. We also don’t care about queries targeting search alone, for which Elasticsearch has excellent performance in any case. We therefore exclude these types of query from our evaluation. Finally, we also want to test a few additional aggregation functions, such as percentiles, which are not well represented in the original query set, so add some variants of the original metric queries to this end.</p><p>Queries in the benchmark are written using standard SQL and so need porting to use ES|QL syntax. This translation is fairly straightforward. Here’s an example:</p>SELECT SUM(AdvEngineID), COUNT(*), AVG(ResolutionWidth) FROM hits<p>becomes:</p><p>when rewritten in ES|QL.</p><p>For running all benchmarks, we use an Elastic Cloud Hosted instance with 870GB disk, 29GB Ram, and 4 vCPUs, in effect, an Amazon Elastic Compute Cloud (EC2) i3.xlarge instance. In the following results, we simply compare ES|QL with and without query approximation. Extensive results on a range of different hardware setups and datastores can be found <a href="https://benchmark.clickhouse.com/">here</a>. Even with significantly constrained test hardware (matching the vCPUs of the smallest setup), our approximation approach achieves competitive results against much larger systems.</p><p>We run each query and its approximation five times in a random order, clearing the <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-clear-cache">query cache</a> between each run. We report the average run time over all five runs. While clearing the cache should be sufficient to avoid most of the advantage of running second, we wanted to avoid any possible accidental prewarming effects, which is why we alternate.</p><p>The results break down into four categories:</p><ol><li><p>Queries which are rewritten to use index summary statistics (three queries).</p></li><li><p>Queries that perform well (13 queries).</p></li><li><p>Queries with high cardinality partitioning (seven queries).</p></li><li><p>Queries with restrictive filters (12 queries).</p></li></ol><p>Roughly speaking, for these four categories, approximate querying is: equivalent (1); faster and accurate (2); faster but unreliable (3); and slightly slower (4), compared to exact querying, respectively.</p><p>For category 1, the planner automatically detects that we’re able to perform the query using summary statistics, and we end up executing the queries in the same way. To do this, we need information that’s only available on the data nodes, so we perform the rewrite only after we've estimated the sample probability. Because we're able to do this very efficiently, the overhead is small (around 10–15%). In both cases, the results are exact.</p><p>Queries in category 2 run on average 23 faster if estimating the values and computing confidence intervals and 72 faster if just estimating the values, which you can select as follows: <code>SET approximation={"confidence_level":null}</code>. These headline figures hide quite some variation in the impact of approximation on performance. The table below shows some queries sampled from the range of speedups we see:</p><p>Query</p><p>Baseline / ms</p><p>Approximate with CI / ms</p><p>Approximate without CI / ms</p><p>3</p><p>1725</p><p>145</p><p>15</p><p>10</p><p>4340</p><p>1721</p><p>56</p><p>13</p><p>32912</p><p>6106</p><p>3821</p><p>21</p><p>46739</p><p>3284</p><p>2139</p><p>22</p><p>252505</p><p>6478</p><p>5019</p><p>Here are the corresponding queries:</p><p>We'll return to the accuracy of the approximation in the next blog post, but to give a sense of this, we plot below the exact and approximate values for a sample run for query 13:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f6acbc3a2c9e58f/6a170ee34a531bbd2c36aa17/9ab83c13f42f88253a242d78339356f4a7c48700-2094x1358.png" alt="approximation-of-clickbench-query-13" /><p>For category 3, we get an average speedup of . However, the results of queries in this category can miss some partitions and often have large estimation errors. Approximation can still be valuable for such queries, particularly in the context of agentic workflows, but requires larger sample sizes than out default if accuracy is important. As we discuss in the next section, we provide an API to explicitly control the sample size. If the source dataset is sufficiently large, this can be increased and approximation will still yield significant performance improvements. The table below shows a couple of query examples for this category:</p><p>Query</p><p>Baseline / ms</p><p>Approximate with CI / ms</p><p>Approximate without CI / ms</p><p>15</p><p>8256</p><p>1187</p><p>124</p><p>17</p><p>70641</p><p>2109</p><p>982</p><p>Here are the corresponding queries:</p><p>Finally, category 4 queries use selective filters and end up being executed exactly, but they run slightly slower because of the work done in the query rewrite stage. Typically, all these queries run fast anyway, so the absolute slowdown is small. On average, they run approximately 14% or 370ms slower than the “without” sampling for our test setup.</p><h3>Limitations and best practices</h3><p>It’s worth explicitly mentioning some limitations. In particular, the following queries are not currently supported:</p><ol><li><p>Queries using the <code>TS</code> source command.</p></li><li><p>Queries using the <code>FORK</code> or <code>JOIN</code> processing command.</p></li><li><p>Pipelines which use two or more <code>STATS</code> commands.</p></li><li><p>The <code>ABSENT</code>, <code>PRESENT</code>, <code>DISTINCT_COUNT</code>, <code>MIN</code>, <code>MAX</code>, <code>TOP</code>, <code>ST_CENTROID_AGG</code> and <code>ST_EXTENT_AGG</code> aggregation functions.</p></li></ol><p>We plan to lift some of these restrictions in future releases, such as approximating queries using <code>TS</code>, <code>FORK</code> and <code>JOIN</code>; however, some are intrinsic. For example, while there’s prior art for estimating the <a href="https://en.wikipedia.org/wiki/Generalized_extreme_value_distribution">minimum and maximum</a> of a metric dataset or the count of unique values of a categorical dataset (see, for example, <a href="https://arxiv.org/pdf/2202.02800">this</a> paper), they require making certain distributional assumptions, either explicitly or implicitly. In summary, we view trying to automatically provide estimates of these statistics as being too open to accidental misuse.</p><p>For the expert user, we provide another route: ES|QL supports using the <code>SAMPLE</code> command directly. This allows one to obtain “point estimates” of any query, albeit with no attempt to correct for the impact of sampling or quantify error. For example:</p><p>computes the unique count of the value field on a sample of roughly 1/100th of the dataset. The sample probability can be adjusted to get a sense of how this is asymptoting, or more sophisticated estimation procedures can use <code>STATS COUNT() BY value</code> to estimate the frequency profile of the data.</p><p>There are a couple of cases that are more problematic for sampling. If a very restrictive filter is applied in the query, then sampling is of little value, since few rows match anyway. In such cases, we discover that we’d have to sample too large a proportion of the rows to estimate the query in the rewrite phase. In this case, we revert to running the query without sampling and its result is exact. However, the search procedure to determine the fraction of rows to sample comes with some overhead. One therefore pays a penalty, albeit less than the original query cost, for no benefit. If you know in advance that the query is expected to match relatively few rows, it's best to run it without approximation.</p><p>The second case only applies when computing <code>STATS</code> partitioned by some expression. If the cardinality of this expression is very high, then even if many rows are searched, individual statistics may be computed from a small number of rows. Some cases are more problematic than others. Sorting by ascending count, that is, finding the rarest partitions, can be impossible to estimate in a single query if heavy hitters would require us to sample most of the dataset to find them. For this particular case, heavy hitting partitions can be estimated first and sometimes efficiently excluded by updating the query. In general, infrequent partitions may be lost in the sampling process, and their statistics' estimation errors can be high. It’s worth noting that we won’t attempt to estimate any statistic for which we have fewer than 10 samples, and we simply drop them from the result set. In the case of very high cardinality <code>BY</code> clause, for example, a field whose value is unique for every row, this means the query can return no results. If you find approximate query results are too inaccurate, you have the option to increase the sample size, which by default is 1,000,000 for <code>STATS</code>, which uses grouping and 100,000 otherwise. Currently, this needs to be done manually, and we provide the following API for this:</p><p>Occasionally, functions significantly alter the distribution characteristics of the quantities they act on. A contrived example is the following:</p><p>If the variation in the estimate <code>sl</code> is much larger than  we expect the distribution of <code>csl</code> to be mainly flat in the interval  with peaks near both endpoints. In this particular case, it’s not clear that the central confidence interval is a particularly useful concept, since the modes of the distribution lie outside almost all central confidence intervals. In any case, just observing the samples of <code>csl</code>, our standard confidence interval machinery won’t reliably characterize this distribution and it will underestimate the variability of <code>csl</code>. However, our statistical test should detect this problem, and the result won’t be certified.</p><p>Finally, we note that Elasticsearch implements some query optimization strategies that ideally <a href="https://github.com/elastic/elasticsearch/issues/138151">need to account for the fact that sampling is taking place</a>. These rewrite the query at the Lucene level and the preprocessing involved in this rewrite can be relatively expensive. Accelerating an expensive string matching operation by first building a suitable data structure makes sense if the query needs to process every row, but if it processes only a small fraction of them, the trade-off is different. This is something we plan to enhance in future.</p><h3>Conclusions</h3><p>In this blog post, we introduced a new form of query optimization we’re bringing to ES|QL that enables dramatically faster querying by relaxing the constraint that the results are exact. We found on ClickBench that we were able to accurately estimate query values and their confidence intervals up to 100 times faster and values alone up to 250 times faster than we can compute them exactly. Furthermore, we expect this advantage to grow as the dataset size increases, because the approximation accuracy is independent of the dataset size. This feature works with many features of the ES|QL language and is enabled by simply prepending <code>SET approximation=true;</code> to the query to estimate.</p><p>As well as providing a point estimate, we also estimate confidence intervals and indicate whether we think that the underlying assumptions used to compute these are satisfied. This allows us to certify the results if the results are reliable. We explain the theory behind this feature and discuss the testing of its accuracy in our <a href="https://www.elastic.co/search-labs/blog/fast-approximate-esql-part-2">next post</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/fast-approximate-esql-part-1</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/fast-approximate-esql-part-1</guid>
    <category><![CDATA[ES|QL]]></category>
    <dc:creator><![CDATA[Jan Kuipers,Thomas Veasey]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltba968f10a7cac60c/6a170ee50c48571b5301ab34/17afc59be8a46957a341faec1f44c9cb0a221894-1918x1176.png" length="0" type="image/png"/>
    <pubDate>Thu, 16 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>