不当使用scroll导致elasticsearch内存暴涨的问题

举报
超级马里奥 发表于 2020/08/20 19:15:12 2020/08/20
【摘要】 某天发现es集群堆内存暴涨,持续在90%以上,并且一直降不下去,持续告警。这种问题首先就是怀疑内存泄漏,但是没有定位出来哪里泄漏了,用jmap dump了一个20G的内存文件下来,使用MAT打开问题分析可以看到SearchService里的ConcurrentHashMap占了78%的内存,打开SearchService的源码,只能是这个activeContexts了:private fin...

某天发现es集群堆内存暴涨,持续在90%以上,并且一直降不下去,持续告警。

这种问题首先就是怀疑内存泄漏,但是没有定位出来哪里泄漏了,用jmap dump了一个20G的内存文件下来,使用MAT打开

问题分析

可以看到SearchService里的ConcurrentHashMap占了78%的内存,打开SearchService的源码,只能是这个activeContexts了:

private final ConcurrentMapLong<SearchContext> activeContexts = ConcurrentCollections.newConcurrentMapLongWithAggressiveConcurrency();


问题已经有一点眉目了,是用户执行了某些查询导致的内存使用率上升。


首先大致了解一下es的search流程,search可以简单理解为分成两个阶段:query和fetch。

  1. client节点接收到search请求后,根据查询的索引的shard所在的节点,将search请求异步转发给每个shard

  2. query阶段在每个shard上执行查询,将这个shard上满足条件的doc id和评分的topN返回给client节点,这样client实际拿到了 topN * shard个数 条记录

  3. client汇总每个shard返回的doc id和评分,把这些topN再次排序获取到整个查询的topN

  4. 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


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。