Elasticsearch:快速近似 ES|QL - 第一部分

张开发
2026/6/6 6:44:16 15 分钟阅读
Elasticsearch:快速近似 ES|QL - 第一部分
作者来自 Elastic Jan Kuipers 及 Thomas Veasey通过 Elasticsearch 实操深入了解我们在 Elasticsearch Labs 仓库中的示例 notebooks开始免费云试用或者现在就在你的本地机器上试用 Elastic。分析工作负载通常涉及将大量数据汇总为数量少得多的统计结果。Elasticsearch 查询语言 ES|QL 通过 STATS 命令实现这一能力。这允许你选择各种聚合函数并将其应用于先前的查询结果同时还可以按照一个或多个 ES|QL 表达式对结果进行分组。这是一个灵活的操作结合 ES|QL 查询能力可以对存储在 Elasticsearch 索引集合中的数据执行 MapReduce。良好用户体验的一个关键要求是这些操作能够快速完成。基于大语言模型 LLM 的 agents 也引入了新的更高带宽和推测性查询模式这些模式可能从不同的优化策略中受益。在这个由两部分组成的博客系列中我们讨论了一种我们将在 Elasticsearch 和 Elastic Stack 的 9.4 版本中引入到 ES|QL 的优化方法该方法利用了对问题的一种放宽处理。我们不再尝试获得聚合的精确值而是允许返回近似值并附带一些误差特征描述。近似的一个关键好处是它打破了性能与数据集大小之间的依赖关系查询可以达到的近似精度并不取决于原始数据集的大小而主要取决于其数据特征以及查询本身。正如我们稍后将看到的这使我们能够实现显著的性能提升。在下一篇博客中我们将讨论该方法背后的理论以及我们对其统计特性所做的验证。在这里我们将介绍语法并说明如何通过标准 ES|QL 和查询重写来实现。你可以在流行的 ClickBench 基准测试的一个子集上探索其性能。最后我们将讨论一些在使用查询近似时值得了解的限制和注意事项。语法和行为那么你实际上该如何使用它SET approximationtrue; // The query you want to approximate FROM data | commands | STATS xagg(...) | commands就是这样。你只需引入一行新的 SET approximationtrue; 然后像往常一样编写你的 STATS 查询管道。下面我们将讨论一些高级配置选项以及围绕 agg(...) 和命令的一些限制。不过本质上我们选择了一些默认值使其通常能够在实现显著加速的同时提供有用的近似结果。通过这一更改你会在查询结果中看到一些差异。让我们通过一个具体示例来说明这一点。假设原始查询如下FROM sales | WHERE timestamp NOW()-1w | STATS count COUNT() BY item_category | SORT count DESC | LIMIT 5结果可能看起来像这样item_category | count --------------------------- Household Essentials | 5165 Kitchen | 2132 Storage | 1121 Home Decor | 877 Furniture | 357对这个查询进行近似会为每个被估计的量引入一些额外的列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] | truecount 列现在包含一个估计值你会看到它与上面的精确值有一些差异。_approximation_confidence_interval(count) 列默认表示 count 估计值的中心 90% 置信区间而 _approximation_certified(count) 列表示我们是否高度确信结果及其置信区间是可靠的。概括来说置信区间是一个区间我们期望它以较高概率 0.9 包含被估计量的真实值。certified 列表示近似的分布是否按我们的预期运行。当结果未被 certified 时它通常仍然是准确的但我们对其分布特性的测试尚未能够确认这一点。这些内容将在我们的第二篇文章中进行更详细的讨论。实现在查询执行之前近似查询会通过随机采样和外推进行重写。让我们来看一下上一节中的查询。重写后的查询中负责获得最佳估计的部分如下FROM sales | SAMPLE probability | WHERE timestamp NOW()-1w | STATS count TO_LONG(COUNT() / probability) BY item_category | SORT count DESC | LIMIT 5该查询对数据的一部分进行采样因此最终的 count 需要通过使用采样概率的倒数进行放大来外推。外推显然依赖于底层的聚合函数我们会针对所有支持的函数进行适当处理。为了获得采样概率我们设置了一个由 STATS 命令处理的固定 number_of_rows。在这种情况下概率计算如下FROM sales | WHERE timestamp NOW()-1w | STATS total_row_count COUNT() | EVAL probability number_of_rows / total_row_count该查询在最终近似查询执行之前执行。除了这个最佳估计之外还需要计算置信区间以及用于验证数值分布是否按预期表现的统计检验。区间是通过一种偏差校正并加速的 bootstrap 置信区间 BCa 方法的变体来计算的。因此需要将数据划分为 B 个桶并依次使用这些桶来计算区间。省略一些实现细节后这个近似查询看起来如下FROM sales | SAMPLE p | WHERE timestamp NOW()-1w | EVAL bucketId RANDOM(B) // B is the number of buckets | STATS count TO_LONG(COUNT() / p) count_0 TO_LONG(COUNT() / p) WHERE bucketId0 (...) count_B-1 TO_LONG(COUNT() / p) WHERE bucketIdB-1 BY item_category | WHERE count 10 | SORT count DESC | LIMIT 5 | EVAL ci CONFIDENCE_INTERVAL(count, count_0, ..., count_B-1), certified CERTIFIED(count, count_0, ..., count_B-1) | DROP bucketId, count_0, ..., count_B-1为了对估计值和置信区间进行认证需要有足够的数据并且分桶后的数值分布应当趋近于正态分布。某些查询可以仅使用索引中维护的汇总统计信息来高效计算。为了正确处理这些情况在这些情况下采样反而更慢且不准确我们更新了物理查询规划器因为检测这类情况需要仅在数据所在位置才能获得的信息。当规划器检测到可以这样处理时它会直接按正常方式执行查询。这类查询通常本身就很快因此实际上没有副作用所以在使用近似功能时不需要担心这一点不过你会看到这类查询的置信区间长度始终为 0表示结果是精确的。结果为了探索性能提升我们使用 ClickBench。这是一个用于数据库管理系统 DBMS 分析工作负载的基准测试。它包含大约 1 亿行数据重点涵盖点击流与流量分析、Web 分析、机器生成数据、结构化日志以及事件数据。该基准还定义了 43 个典型的即席分析与实时仪表盘查询。其中一些查询不适合近似计算。例如我们不支持对分类值的 unique count 进行近似也不支持计算度量值的最小值和最大值。我们同样不关注仅针对搜索的查询因为 Elasticsearch 在这类场景下本身性能已经非常优秀。因此我们在评估中排除了这些类型的查询。最后我们还希望测试一些额外的聚合函数比如 percentiles这些函数在原始查询集中覆盖不足因此我们额外添加了一些指标查询的变体来补充评估。基准中的查询使用标准 SQL 编写因此需要转换为 ES|QL 语法。这个转换相当直接。下面是一个示例SELECT SUM(AdvEngineID), COUNT(*), AVG(ResolutionWidth) FROM hits变为FROM hits | STATS s SUM(AdvEngineID), c COUNT(*), a AVG(ResolutionWidth)当用 ES|QL 重写时。在运行所有基准测试时我们使用一个 Elastic Cloud Hosted 实例配置为 870GB 磁盘、29GB 内存和 4 个 vCPU本质上等同于一个 Amazon Elastic Compute Cloud EC2 i3.xlarge 实例。在下面的结果中我们只是比较开启和关闭查询近似的 ES|QL 表现。关于不同硬件配置和数据存储的更广泛结果可以在这里找到。即使在显著受限的测试硬件上与最小配置的 vCPU 数匹配我们的近似方法仍然能够在性能上与更大的系统竞争。我们将每个查询及其近似版本以随机顺序各运行五次并在每次运行之间清空查询缓存。我们报告五次运行的平均执行时间。虽然清空缓存应该足以避免 “第二次运行更快” 的大多数优势但我们仍然希望避免任何可能的预热效应因此选择交替执行。结果分为四类使用索引汇总统计信息重写的查询3 个查询。执行表现良好的查询13 个查询。高基数分区的查询7 个查询。过滤条件较严格的查询12 个查询。大致来说对于这四类近似查询分别是等价1更快且准确2更快但不可靠3以及相比精确查询略慢4。对于第 1 类规划器会自动检测到可以使用汇总统计信息执行查询因此最终执行方式与原始查询相同。为了做到这一点我们需要仅在数据节点上才能获得的信息因此只有在估计采样概率之后才进行重写。由于这一过程非常高效开销很小约 10–15%。在两种情况下结果都是精确的。第 2 类查询在平均情况下如果估计数值并计算置信区间会快约 23×如果只进行数值估计则快约 72×你可以通过以下方式选择后者SET approximation{confidence_level:null}。这些总体数字掩盖了近似对性能影响的显著差异。下表展示了我们观察到的不同加速范围中的部分查询示例查询基线 / 毫秒带 CI 的近似 / 毫秒不带 CI 的近似 / 毫秒31725145151043401721561332912610638212146739328421392225250564785019对应的查询如下// Query 3 FROM hits | STATS s SUM(AdvEngineID), c COUNT(*), a AVG(ResolutionWidth) // Query 10 FROM hits | STATS s SUM(AdvEngineID), c COUNT(*), a AVG(ResolutionWidth) BY RegionID | SORT c DESC | LIMIT 10 // Query 13 FROM hits | WHERE SearchPhrase ! | STATS c COUNT(*) BY SearchPhrase | SORT c DESC | LIMIT 10 // Query 21 FROM hits | WHERE URL ! | STATS l AVG(LENGTH(URL)), c COUNT(*) BY CounterID | WHERE c 100000 | SORT l DESC | LIMIT 25 // Query 22 FROM hits | WHERE Referer ! | GROK Referer %{URIPROTO}://(?:www\.)?%{URIHOST:k} | WHERE k IS NOT NULL | STATS l AVG(LENGTH(Referer)), c COUNT(*) BY k | WHERE c 100000 | SORT l DESC | LIMIT 25我们将在下一篇博客中回到近似的准确性问题但为了让你有一个直观感受下面我们展示了查询 13 在一次运行中的精确值与近似值对于第 3 类我们平均获得约 11× 的加速。然而该类别中的查询结果可能会遗漏一些分区并且往往存在较大的估计误差。在这种情况下近似仍然是有价值的尤其是在 agents 工作流的上下文中但如果对准确性要求较高则需要比默认值更大的采样规模。正如我们在下一节中讨论的我们提供了一个 API 来显式控制采样大小。如果源数据集足够大可以增加采样规模同时近似仍然能够带来显著的性能提升。下表展示了该类别中的一些查询示例查询基线 / 毫秒带 CI 的近似 / 毫秒不带 CI 的近似 / 毫秒158256118712417706412109982对应的查询如下// Query 15 FROM hits | STATS c COUNT(*) BY UserID, SearchPhrase | SORT c DESC | LIMIT 10 // Query 17 FROM hits | EVAL m DATE_EXTRACT(minute_of_hour, EventTime) | STATS c COUNT(*) BY UserID, m, SearchPhrase | SORT c DESC | LIMIT 10最后第 4 类查询使用选择性过滤条件并最终会被精确执行但由于查询重写阶段的额外工作它们会稍微变慢。通常这些查询本身执行就很快因此绝对的性能下降很小。在我们的测试环境中平均比 “不使用采样” 的情况慢约 14%或 370ms。限制与最佳实践这里有必要明确指出一些限制。特别是目前以下查询不受支持使用 TS source 命令的查询。使用 FORK 或 JOIN 处理命令的查询。使用两个或以上 STATS 命令的 pipeline。ABSENT、PRESENT、DISTINCT_COUNT、MIN、MAX、TOP、ST_CENTROID_AGG 和 ST_EXTENT_AGG 聚合函数。我们计划在未来版本中解除部分限制例如支持对使用 TS、FORK 和 JOIN 的查询进行近似处理但其中一些限制是本质性的。例如尽管已有一些关于估计指标数据集的最小值和最大值或分类数据集中唯一值数量的方法例如这篇论文但它们需要显式或隐式地对分布做出某些假设。总之我们认为自动提供这些统计量的估计值过于容易被误用。对于高级用户我们提供另一种方式ES|QL 支持直接使用 SAMPLE 命令。这允许用户对任意查询获得“点估计”但不会对采样影响进行修正也不会量化误差。例如FROM data | SAMPLE 0.01 | STATS DISTINCT_COUNT(value)计算 value 字段在大约 1/100 数据集样本上的唯一值计数。可以调整采样概率从而观察该估计如何逐渐收敛或者使用更复杂的估计方法通过 STATS COUNT() BY value 来估计数据的频率分布。在某些情况下采样会更有问题。如果查询中应用了非常严格的过滤条件那么采样的价值就很小因为匹配的行本来就很少。在这种情况下我们会发现需要采样过大比例的数据行才能在重写阶段完成估计此时系统会回退为不使用采样执行查询其结果是精确的。然而用于确定采样比例的搜索过程本身也会带来额外开销。因此用户仍然需要付出一定的性能成本虽然低于原始查询成本但却无法获得收益。如果你事先知道查询预计只会匹配较少的数据行最好直接不使用近似执行。第二种情况仅在对某个表达式进行 STATS 分组时出现。如果该表达式的基数非常高那么即使扫描了大量行每个统计分组可能仍然只基于少量数据计算。一些情况比其他情况更难处理。例如按 count 升序排序即寻找最稀有的分区如果“热门分区”需要采样几乎整个数据集才能发现那么在单次查询中可能无法估计。对于这种情况可以先估计高频分区并通过更新查询将其有效排除。总体而言低频分区可能在采样过程中丢失其统计估计误差也可能较大。需要注意的是对于样本数量少于 10 的统计结果我们不会进行估计而是直接从结果集中移除。在极高基数的 BY 子句情况下例如某个字段在每一行都是唯一值这意味着查询可能返回空结果。如果你发现近似查询结果不够准确可以选择增加采样规模。默认情况下STATS 使用的采样规模为 1,000,000当涉及分组时否则为 100,000。目前这一点需要手动配置我们提供如下 APISET approximation{rows:12345678}; FROM data | commands | STATS xagg(...) | commands有时一些函数会显著改变其所作用量的分布特性。一个人为构造的例子如下FROM data | STATS sl SUM(length) | EVAL csl COS(sl)如果估计值 sl 的变化远大于 2π那么我们预计 csl 的分布在区间 [-1, 1] 内主要是平坦的并且在两个端点附近会出现峰值。在这种情况下中心置信区间的概念可能并不特别有用因为分布的众数几乎完全落在大多数中心置信区间之外。无论如何仅通过观察 csl 的样本我们的标准置信区间机制无法可靠刻画该分布并且会低估 csl 的变异性。然而我们的统计检验应该能够检测到这个问题因此结果不会被认证certified。最后需要指出的是Elasticsearch 实现了一些查询优化策略而这些策略理想情况下需要考虑采样正在发生这一事实。这些优化会在 Lucene 层对查询进行重写而这种预处理本身可能相对昂贵。在需要处理每一行数据时通过构建合适的数据结构来加速昂贵的字符串匹配操作是合理的但如果只处理其中很小一部分数据那么这种权衡就不同了。这一点我们计划在未来进行改进。结论在这篇博客中我们介绍了一种我们正在引入 ES|QL 的新型查询优化方式它通过放宽结果必须精确这一约束实现了显著更快的查询。我们在 ClickBench 上发现相较于精确计算我们能够在最多快 100 倍的情况下仍然准确估计查询值及其置信区间而仅计算数值本身时最多可快 250 倍。此外我们预计随着数据集规模增长这种优势还会进一步扩大因为近似的精度与数据集大小无关。该功能支持 ES|QL 的多种特性只需在查询前加上 SET approximationtrue; 即可启用。除了提供点估计之外我们还会估计置信区间并指示我们认为用于计算这些区间的基本假设是否成立。如果结果可靠我们可以对其进行认证certify。我们将在下一篇文章中解释该特性的理论基础并讨论其准确性的测试方法。原文https://www.elastic.co/search-labs/blog/fast-approximate-esql-part-1

更多文章