Elasticsearch 中增加分片数量,聚合一定会变快吗?

Elasticsearch 中增加分片数量,聚合一定会变快吗?

在一次聚合测过程中,我们希望通过增加分片数量的方式,让聚合计算过程更快完成。因此准备了一个索引,该索引有2.6亿 条 doc,大小为70GB,有2个分片。命名为 index2,然后将其 split 为40个分片,生成一个新索引,命名为 index40:

集群有2个节点,JVM 配置30GB,每个索引都经过了 forcemerge。让集群处于空闲状态,然后执行聚合测试。这次聚合测试是为了验证在高基数的数据样本中,bucket 聚合的速度,并了解一下执行原理,看看可以优化的点。为了模拟产生大量 bucket,测试使用深度嵌套聚合:

由于 ES 在单个节点的查询并发限制为5,为了提升并行度,加参数 max_concurrent_shard_requests 进行并发控制,本来预期的是让所有分片同时执行聚合,会大幅加快聚合速度,结果却很意外,增加聚合并行度后查询延迟并没有很大区别,而 CPU 利用率却上升了很多!

索引 分片数 max_concurrent_shard_requests 耗时(秒) 平均 CPU 利用率(24core)
index2 2 5 33 13.9%
index40 40 5 42 39.3%
index40 40 10 33 61.9%
index40 40 20 28 74.0%
index40 40 40 33 76.6%

加大聚合并行度,为什么没有提升执行速度?

index2的聚合延迟与index40的40并发聚合延迟基本相同,而 CPU 利用率却大幅增加,多出来的 CPU 干什么去了?打 hot_threads,jstack 等只看到在执行聚合,没什么特别的,profile 也没有看到问题。聚合过程中磁盘基本没有 io。

感觉聚合的速度与分片大小没有关系,为了验证这个想法,聚合请求加参数 preference=_shards:0让他只使用0分片的数据聚合,结果3秒就返回结果,参与聚合的分片越多,聚合返回越慢。因此推翻了这个想法。但同时也观察到CPU 利用率奇怪变化,如图,使用4个分片聚合时,起初如左图,4个 core 占满,符合预期,接下来很多其他 core 的利用率就会上升,系统在干什么?

聚合只会在 search 线程池执行,单个分片执行聚合时也不会并发执行,多出来的其他 core 在忙些什么?多次打 jsatck 对比热点线程,定位到了最可疑的调用链:

翻一下 buildSlice 的代码,发现每次都要new 一个ByteBuffer出来:

collect 每收集只收集一条数据,整个聚合过程中仅在此处就要动态申请大量 ByteBuffer,内存一定有问题,jstat 查看对 index40,max_concurrent_shard_requests=40聚合过程中的 gc 情况,果然非常频繁,部分截图如下:

在整个聚合过程中,有大约9秒的时间在执行 GC,而 hot_thread 和 jstack 都无法直接定位到 GC 线程热点,也因此绕了弯路。结合 GC 日志,确认多余的 core 是在忙于并发 GC。

对比 index2d 的聚合过程,YGC 只执行了2次,GCT 消耗时间低于1秒。

问题至此定位完毕。

###总结

由此产生一些思考:

  • 聚合计算一条条collect,效率太低,如今大部分计算引擎都使用向量化执行,每次处理一批数据。
  • 聚合过程中动态申请内存过于频繁,生成了大量临时对象,给YGC造成较大压力。
  • 增加分片,提升聚合并行度不一定能加快聚合速度,要考虑业务的聚合语句对内存的压力有多大,像今天的例子中,40个分片如果散布在更多的节点中,GC 就不是问题,整体聚合速度就应该快很多。类似的,如果聚合产生的 bucket 少一些的时候,增加聚合并行度可以明显提升整体聚合速度。

总之,聚合要考虑对节点内存的压力,但是这不太好量化出来。目前在高基数的数据上做 bucket 聚合不太适合,业务线如果有此类聚合,建议提前做好压力测试再上到生产环境。

(转载请注明作者和出处 easyice.cn ,请勿用于任何商业用途)

1 Star2 Stars3 Stars4 Stars5 Stars (欢迎评分)
Loading...

One thought on “Elasticsearch 中增加分片数量,聚合一定会变快吗?

发表评论

电子邮件地址不会被公开。 必填项已用*标注