作者:Adrien Grand
Elasticsearch 8.9 通过支持动态修剪(dynamic pruning)引入了基数聚合加速。 这种优化需要满足特定的条件才能生效,但一旦实现,通常会产生惊人的结果。 我们观察到,通过此更改,一些基数聚合的运行速度提高了 1,000 倍。
例如,计算由 Elastic Kubernetes 集成监控的 Kubernetes 部署的唯一值数量可受益于此优化:
POST metrics-*/_search
{
"query": { // giving an example query, but any query would work
"bool": {
"filter": [
{ "range": { "@timestamp": { "gte": "now-1d/h" } } },
{ "match": { "data_stream.dataset": "kubernetes.pod" } }
]
}
},
"size": 0,
"track_total_hits": false,
"aggs": {
"deployment_count": {
"cardinality": {
"field": "kubernetes.deployment.name"
}
}
}
}
它是如何工作的?
动态修剪是使用索引结构动态减少运行查询时需要评估的匹配集的过程。 例如,如果你查询按时间戳降序排序的前 10 个事件,开始评估匹配项,并找到 10 个时间戳在过去一小时内的命中,那么你可以在时间戳字段上动态引入过滤器以忽略超过的事件一小时前:他们没有机会进入前十名。
对基数聚合的优化遵循类似的想法:一旦看到一个值,以后就没有必要再次查看该值,因为它不会影响该字段的唯一值的计数。 因此,在查询评估期间,基数集成会自动在 disjunctive 查询上引入一个过滤器,该过滤器仅匹配迄今为止尚未见过的值。 当收集具有新值的文档时,这些值将从析取中删除。
例如,假设你正在计算具有两个唯一值的字段的基数:a 和 b。 下表列出了查询中的所有匹配项,其中第一列中与查询匹配的 Lucene 文档 ID 以及第二列中与此文档 ID 关联的值。
Doc ID | Value |
3 | b |
10 | b |
12 | a |
19 | b |
30 | a |
当开始评估查询时,Elasticsearch 会隐式地将 a OR b 上的过滤器添加到主查询中。 看到第一个匹配项后,文档 ID 3,值 b 不需要再次看到,因此过滤器将转变为仅匹配值 a 的更具选择性的过滤器。 这有助于节省对 doc ID 10 的评估,因为它也有 b 作为值,并直接跳转到下一个以 a 为值的文档:doc ID 12。此时,a 已从过滤器中删除,Elasticsearch 知道评估更多匹配是没有意义的,因为它已经看到了该字段的所有唯一值。 这有助于节省评估文档 ID 19 和 30。
此优化的第一阶段(动态引入过滤器)已经有助于显着减少查询需要评估的文档数量,从而加快查询评估速度。 但是,当查询在看到所有唯一值时退出时,第二阶段会触发最惊人的加速,因为它可以帮助跳过索引的大多数文档。 请注意,第二阶段并不总是发生,具体取决于查询 —— 某些值可能只存在于与查询不匹配的文档中。
什么时候开始生效?
Disjunctive 查询不能很好地随着子句数量的变化而扩展,因此这种优化的主要限制是它只能在基数相对较小的字段上工作。 因此,Elasticsearch 仅对唯一值不超过 1,024 个的段(segments)启用此优化。
此外,这种优化仅支持关键字字段,以利用它们使用倒排索引进行索引的事实,并且它们的文档值给我们每个段的唯一值的数量。
最后,基数聚合必须是唯一的聚合,并且位于聚合树的顶层。
结论
此优化针对 Elastic Kubernetes 集成的仪表板进行了评估,它显着加快了仪表板加载时间,尤其是在处理大量数据时。 特别是,本博客介绍中共享的示例查询的延迟减少了 90%。 我们希望您能享受加速带来的乐趣!
Elastic 8.9 中还有哪些新功能? 查看 8.9 公告帖子以了解更多信息。
原文:Achieve faster cardinality aggregations via dynamic pruning | Elastic Blog