不当使用scroll导致elasticsearch内存暴涨的问题
某天发现es集群堆内存暴涨,持续在90%以上,并且一直降不下去,持续告警。
这种问题首先就是怀疑内存泄漏,但是没有定位出来哪里泄漏了,用jmap dump了一个20G的内存文件下来,使用MAT打开
问题分析
可以看到SearchService里的ConcurrentHashMap占了78%的内存,打开SearchService的源码,只能是这个activeContexts了:
private final ConcurrentMapLong<SearchContext> activeContexts = ConcurrentCollections.newConcurrentMapLongWithAggressiveConcurrency();
问题已经有一点眉目了,是用户执行了某些查询导致的内存使用率上升。
首先大致了解一下es的search流程,search可以简单理解为分成两个阶段:query和fetch。
client节点接收到search请求后,根据查询的索引的shard所在的节点,将search请求异步转发给每个shard
query阶段在每个shard上执行查询,将这个shard上满足条件的doc id和评分的topN返回给client节点,这样client实际拿到了 topN * shard个数 条记录
client汇总每个shard返回的doc id和评分,把这些topN再次排序获取到整个查询的topN
client再将 这些topN的doc id发送给对应的shard节点去获取原文,这个过程是fetch
(elasticsearch 源码解析与优化实战)
ES在query阶段会构造SearchContext,里面保存着这次查询的query语句、排序字段,以及查询过程中需要用到的IndexReader、IndexSearcher等信息。
因为fetch阶段也需要通过IndexReader来读取doc id对应的原文,所以在执行完query节点后,searchContext并不会马上释放,而是被放到了 内存中的 activeContexts,也就是造成客户节点内存暴涨的这个ConcurrentHashMap。
那这些放在activeContexts中的SearchContext是在什么时候释放的呢?
正常情况下是执行完fetch阶段以后就会释放。
有两种特殊的情况:
第一种: 在执行完query以后,client节点就挂了,后面的fetch就没执行,那不是就没机会释放了么?
这么简单的异常处理es肯定是有的,在SearchService的构造中就 启动了一个Reaper(收割机)来清理过期残留的SearchContext
this.keepAliveReaper = threadPool.scheduleWithFixedDelay(new Reaper(), keepAliveInterval, Names.SAME);
在Reaper里面把已经超过keepalive的时间还没有使用的SearchContextfree掉
这个keepalive的时间可以通过 search.default_keep_alive 设置,默认值5分钟
第二种特殊情况 是scroll,如果查询是个scroll查询,那在fetch阶段执行完成后也不能释放SearchContext,因为Scroll还会有下一次fetch来获取下一页的内容,那Scroll查询的SearchContext是在什么时候释放的呢?
有两种方式,第一种用户在scroll完了以后主动调用delete接口释放:
DELETE /_search/scroll { "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==" }
第二种方式是等待scroll超时,scroll超时时间是通过发送的scroll请求来决定的,如下面的这个例子,超时时间设置成了1分钟:
POST /twitter/_search?scroll=1m { "size": 100, "query": { "match" : { "title" : "elasticsearch" } } }
通过以上的分析,activeContexts大量未释放的SearchContext只能有两种可能:
1. es社区代码就有bug,在某种特定场景下有泄漏。
2. 客户在执行scroll,并且将超时时间设置的巨长。
第一点不太好找,先看第二点吧,我们已经dump了堆内存了,未释放SearchContext都能看到,那不就好办了么。
首先看到SearchContext里的ScrollContext不是Null,好的,确实是个scroll请求:
接下来得看下超时时间了,240分钟!!整整四个小时。
问题结论:
到这,问题基本已经清楚了,es里并没有内存泄漏,而是客户在使用scroll的时候将超时时间设置成了四个小时,并且在scroll完了以后并没有主动删除context,导致大量SearchContext无法释放。
进一步思考:
这么容易触发es服务端故障的问题es竟然防不住,有点说不过去啊,新版本是否有改进呢?找了找社区的issue,果然有。
1. 增加一个正在执行的scroll context的限制,虽然有点强盗逻辑,但是至少能防住这个问题
https://github.com/elastic/elasticsearch/issues/25244
2. 在scroll完了以后自动将SearchContext释放,不过目前这个还在讨论中,暂未实现,好几年没动静了 ==||
https://github.com/elastic/elasticsearch/issues/23269
- 点赞
- 收藏
- 关注作者
评论(0)