深入分析 Elasticsearch 聚合的性能
Elasticsearch 在不知不觉中为提升性能做了很多工作,我们还能做些什么来进一步优化它呢?在研究我们使用的一些大型聚合操作的性能时,我一直在思考这个问题。在本文中,我将对 Elasticsearch 中的缓存机制进行基本解释,并通过两个实验来验证缓存和查询之间的交互作用。
Elasticsearch是如何缓存的?
Elasticsearch 拥有不同级别的缓存机制,它们协同工作,确保尽可能快速地响应数据。所有缓存级别都具有相同的目标:近乎实时的响应。这意味着您获得的响应既快速,又与索引中当前存在的数据完全匹配(或几乎完全匹配)。
请求缓存
Elasticsearch 拥有自己的智能请求缓存。它会根据底层索引的更新来更新此缓存,从而确保缓存始终准确。当然,也存在一些需要注意的地方,例如:
即使在索引设置中启用了请求缓存,大小大于 0 的请求也不会被缓存。
请求缓存失效的其他原因还包括响应中包含每次请求都会改变的值。例如,如果响应包含当前日期或随机生成的数字,则该响应将无法缓存。
如果您想了解更多关于如何调整请求缓存的信息,请查看文档。
查询缓存
从更深层次来看,过滤型查询的结果可以缓存为一种称为位集的二进制表示形式。与请求缓存类似,每当索引中的相关内容更新时,此缓存都会自动更新。Elasticsearch 仅缓存适用于大量文档的查询:
只有包含超过 10,000 个文档(或占总文档数的 3%,以较大者为准)的段才会缓存位集。
这样做是因为对于较小的数据段,执行查询可能更快。Elasticsearch 在没有缓存的情况下已经对性能进行了大幅优化,因此添加缓存很容易导致速度变慢。您可以在这里找到更多关于查询缓存的信息。
字段数据缓存
字段数据缓存对于聚合操作非常重要。正如文档中所述:
它将所有字段值加载到内存中,以便提供基于文档的快速访问这些值。
字段数据缓存的内存不足会导致聚合速度变慢。您可以监控字段数据缓存的使用情况并根据需要进行调整。点击此处了解更多信息。
提取常用查询元素是否有帮助?
查询缓存似乎对很多实际应用中的聚合操作都非常有益。对索引的过滤子集执行聚合操作是很常见的。Elasticsearch 在这种情况下能否重用过滤结果?或者我们能否帮助它做到这一点?
让我们比较一下以下查询的性能。第一个查询对两个聚合分别指定了相同的筛选条件:
{
"size": 0,
"aggregations": {
"1": {
"filter": {
"match": {
"search_field": "text"
}
},
"aggregations": {
"items": {
"top_hits": {
"size": 100,
"_source": {
"includes": "field1"
}
}
}
}
},
"2": {
"filter": {
"match": {
"search_field": "text"
}
},
"aggregations": {
"items": {
"top_hits": {
"size": 100,
"_source": {
"includes": "field2"
}
}
}
}
}
}
}
第二个查询将此筛选条件提取到更高层级,这样聚合结果应该能够保持一致。我们需要将筛选条件包装在bool.filter中,以确保评分标准相同:
{
"query": {
"bool": {
"filter": [
{
"match": {
"search_field": "text"
}
}
]
}
},
"size": 0,
"aggregations": {
"1": {
"top_hits": {
"size": 100,
"_source": {
"includes": "field1"
}
}
},
"2": {
"top_hits": {
"size": 100,
"_source": {
"includes": "field2"
}
}
}
}
}
本次测试中我们禁用了请求缓存,但查询缓存和字段数据缓存仍然可以正常工作。我们已确保筛选查询的段实际上大于 10,000 个文档。这意味着查询缓存应该会生效,并且这两个查询的查询时间应该没有区别。实际情况也正是如此:
这两种方案的性能没有区别。查询缓存似乎运行良好。考虑到查询缓存的要求,您可能仍然会更倾向于第二种方案。字段数据缓存在两种方案中的表现也同样出色。
聚合操作是否并行运行?
在日常工作中,我经常看到我们在单个查询中放入大量聚合操作。这让我不禁思考,聚合操作真的可以并行执行吗?或者,我们是否可以通过例如每次查询都执行一个聚合操作的多重搜索来提高响应速度?
在这个测试中,我们运行与之前相同的查询。我们分别测试在单个查询中包含 1、2、5 和 10 个聚合的情况。然后,我们将此结果与将聚合拆分成多个查询(每个聚合都使用单独的查询)的情况进行比较。
当我们为每个聚合分配单独的查询并执行msearch时,性能显著提升。在 10 个聚合的情况下,速度提升接近 2 倍。在此测试中,Elasticsearch 实例运行在配备 2 个可用 CPU 的 Docker 容器中,因此这种速度提升已接近预期最佳水平。
很明显,聚合操作默认情况下不会并行运行。因此,如果想要提升响应速度,将聚合操作拆分成多个查询可能是一个不错的选择。但这仅适用于 CPU 不是瓶颈的情况,因为拆分查询会增加总的 CPU 时间。
结论
那么,提取常用查询元素有用吗?一般来说,应该这样做。但并非必须,因为 Elasticsearch 可以针对这些情况进行优化。如果你的过滤器不符合查询缓存的条件,将常用查询元素在聚合中向上移动可能仍然会略微提升性能。
聚合操作是并行执行的吗?默认情况下不是。如果 CPU 资源尚未达到瓶颈,使用msearch将它们拆分可能是一个明智的选择。在尚未充分利用的集群上,这可以显著提升响应速度。
文章来源:https://dev.to/raoulmeyer/diving-into-performance-of-elasticsearch-aggregations-3gbi

