Elasticsearch 中增加分片数量,聚合一定会变快吗?
在一次聚合测过程中,我们希望通过增加分片数量的方式,让聚合计算过程更快完成。因此准备了一个索引,该索引有2.6亿 条 doc,大小为70GB,有2个分片。命名为 index2,然后将其 split 为40个分片,生成一个新索引,命名为 index40:
集群有2个节点,JVM 配置30GB,每个索引都经过了 forcemerge。让集群处于空闲状态,然后执行聚合测试。这次聚合测试是为了验证在高基数的数据样本中,bucket 聚合的速度,并了解一下执行原理,看看可以优化的点。为了模拟产生大量 bucket,测试使用深度嵌套聚合:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
{ "aggs": { "sip": { "terms": { "field": "sip", "size": 10000 }, "aggs": { "dip": { "terms": { "field": "dip", "size": 10000 }, "aggs": { "proto": { "terms": { "field": "proto", "size": 10000 } } } } } } } } |
由于 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 对比热点线程,定位到了最可疑的调用链:
1 2 3 4 5 |
at org.apache.lucene.store.ByteBufferIndexInput.buildSlice(ByteBufferIndexInput.java:277) .... org.elasticsearch.search.aggregations.LeafBucketCollector#collect(int, long) |
翻一下 buildSlice 的代码,发现每次都要new 一个ByteBuffer出来:
1 2 3 |
final ByteBuffer slices[] = new ByteBuffer[endIndex - startIndex + 1]; |
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 ,请勿用于任何商业用途)
2 thoughts on “Elasticsearch 中增加分片数量,聚合一定会变快吗?”
这是单机数据吧,记得有文章测试和分片数是 n型曲线关系,集群也是
是单机的,不过集群也是一样的道理