内存溢出
Easysearch 内核完善之 OOM 内存溢出优化案例一则
Easysearch • liaosy 发表了文章 • 0 个评论 • 1627 次浏览 • 2024-03-15 10:37
最近某客户在使用 Easysearch 做聚合时,报出 OOM 导致掉节点的问题,当时直接让客户试着调整 indices.breaker.request.limit
,但是不起作用,于是又看了下 Easysearch 在断路器相关的代码,并自己测试了下。
断路器的种类和作用
Easysearch 内部有个 Circuit breaker 机制,目的是防止各种请求的负载过大导致 OutOfMemoryError
,比较常用的断路器有 7 种,分别是:
- Parent circuit breaker 父断路器
- Field data circuit breaker fielddata 断路器
- Request circuit breaker 请求断路器
- In flight requests circuit breaker 传输请求断路器
- Accounting requests circuit breaker lucene 内存占用断路器
- Script compilation circuit breaker 脚本编译断路器
- Regex circuit breaker 正则表达式断路器
其中在执行消耗内存较多的聚合查询时,Request circuit breaker 用得最多。
复现测试
我在模拟客户场景测试聚合查询时,发现断路器并没有覆盖查询的整个流程,仍然会有 OOM 的风险。我测试了一个高基数 5 百万的 Terms aggregation,就没有触发断路,而是在等待了 1 分多钟后直接 OOM 了。我的测试环境是单节点 内存配置为 -Xmx1g
,测试索引只有 1 个 shard。
测试语句如下:
curl -X GET "localhost:9211/leader-01/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 1,
"aggs": {
"a": {
"terms": { "field": "agent.id.keyword", "size": 5000000 }
}
}
}' > a.txt
Easysearch OOM 日志:
内存泄漏分析
使用 MemoryAnalyzer 分析生成的 jvm 堆转储文件:
最大的内存占用来自 Java 线程java.lang.Thread @ 0x7c8bb1d00
。这个线程浅层(Shallow)保留的对象占用了 112.8MB 内存。但该线程实际保留(Retained)的对象内存占用高达 851 MB,成为整个内存占用的绝对大头。
进一步查看 Leak Suspects:
非常明确的给出了具体的内存泄露的对象:StringTerms$Bucket[7500010]
数组长度达到了七百五十万,占用内存:731,001,720 字节(占总内存的 68.65%)。
按照提示的GlobalOrdinalsStringTermsAggregator.java:586
行,去查看代码,实际上是将收集完的OrdBucket
转换为 StringTerms.Bucket
,并且有一个 copy BytesRef
的操作。
至此,原因和解决办法都清楚了,只要在转换之前预估一下将要增长的内存并调用断路器检测一下内存,一旦超出允许范围就快速触发 CircuitBreakingException
,避免长时间等待后 OOM 引起的节点宕机了。
最新版 Elasticsearch 对比
作为对比,我又测试了下 Elasticsearch 最新版本 8.12.2,同样的测试环境和测试方法,结果依然是 OOM:
从这里可以看出 Elasticsearch 即使是最新版的断路器机制也还有很多改进的余地,比如增加对有 OOM 风险查询的覆盖率,还有就是在触发 GC 时,对 GC 堆内存回收的判断过于简单。
Easysearch 最新版本的改进
Easysearch 刚刚发布的 1.7.1 版本已经增加了上面的改进,后面也会持续改进查询聚合操作的内存控制,最新版本的跨集群复制(CCR)也增加了对 source_reuse 索引的支持,能更好的满足客户降本增效的需求,欢迎大家下载试用。
附官网下载链接:https://www.infinilabs.com/download/?product=easysearch
关于 Easysearch
INFINI Easysearch 是一个分布式的近实时搜索与分析引擎,核心引擎基于开源的 Apache Lucene。Easysearch 的目标是提供一个轻量级的 Elasticsearch 可替代版本,并继续完善和支持更多的企业级功能。 与 Elasticsearch 相比,Easysearch 更关注在搜索业务场景的优化和继续保持其产品的简洁与易用性。
官网文档:https://www.infinilabs.com/docs/latest/easysearch
index.cache.field.type在ES5.5.0中如何设置
回复Elasticsearch • LXJ 发起了问题 • 1 人关注 • 0 个回复 • 2214 次浏览 • 2018-08-10 18:19
ES5.3聚合内存溢出bug
Elasticsearch • yayg2008 发表了文章 • 1 个评论 • 4992 次浏览 • 2018-06-13 20:48
有以下DSL
{
"size" : 0,
"query" : { },
"_source" : false,
"aggregations" : {
"aggData" : {
"terms" : {
"field" : "url",
"size" : 200,
"min_doc_count" : 1,
"shard_min_doc_count" : 0,
"show_term_doc_count_error" : false,
"order" : [
{
"PV" : "desc"
}
]
},
"aggregations" : {
"PV" : {
"cardinality" : {
"field" : "userssid"
}
}
}
}
}
}
目的是对用户访问的URL进行分组统计,按独立用户数来排序。 执行后,data节点频繁FGC,内存无法回收,随即OOM,然后data节点脱离,集群变为red。 最初以为是cardinality精度问题导致内存使用过多,随即将precision_threshold设置为100,再次执行,内存使用量确实少了很多,但是还是用到GB级别。为了确认是否是cardinality问题,去掉外层聚合,直接执行
"aggregations" : {
"PV" : {
"cardinality" : {
"field" : "userssid"
}
}
}
发现响应非常快,而且内存占用只有KB级别。 再次单独执行外部聚合,发现也非常快,于是猜测是order导致,将order去掉,果然,如丝般顺滑,再也没有OOM。 为了解决这种OOM,首先想到的是熔断器。默认indices.breaker.request.limit配置是60%。改成10%后,触发熔断,集群正常,但是多点几次之后,data还是出现OOM了。 于是逐步调试,发现每执行1次,内存就增加一点,熔断返回后并没有被回收,直到OOM。基本确定是这里的order导致内存泄露了。 就在此时,同事反馈在5.6不会有这个问题,于是去查release note,果然在5.5的版本发现fix了这个问题。问题描述。 这个bug的根本原因是:
terms aggregations at the root level use the global_ordinals execution hint by default.
When all sub-aggregators can be run in breadth_first mode the collected buckets for these sub-aggs are dense (remapped after the initial pruning).
But if a sub-aggregator is not deferrable and needs to collect all buckets before pruning we don't remap global ords and the aggregator needs to deal with sparse buckets.
Most (if not all) aggregators expect dense buckets and uses this information to allocate memories.
This change forces the remap of the global ordinals but only when there is at least one sub-aggregator that cannot be deferred.
解决方案: 1,升级到5.5以上版本;
2,DSL增加"execution_hint":"map",属性。
{
"size" : 0,
"query" : { },
"_source" : false,
"aggregations" : {
"aggData" : {
"terms" : {
"field" : "url",
"size" : 200,
"execution_hint":"map",
"min_doc_count" : 1,
"shard_min_doc_count" : 0,
"show_term_doc_count_error" : false,
"order" : [
{
"PV" : "desc"
}
]
},
"aggregations" : {
"PV" : {
"cardinality" : {
"field" : "userssid"
}
}
}
}
}
}
ES2.1.1频繁GC,且内存溢出
Elasticsearch • code4j 回复了问题 • 8 人关注 • 4 个回复 • 6462 次浏览 • 2018-05-24 11:18
es集群主节点会因为短时间内大量查询导致OOM吗?之后生成的.hprof文件该怎么处理?
Elasticsearch • medcl 回复了问题 • 4 人关注 • 3 个回复 • 7992 次浏览 • 2018-04-11 09:59
20w条数据频繁出现内存溢出??
Elasticsearch • laoyang360 回复了问题 • 2 人关注 • 1 个回复 • 4402 次浏览 • 2018-01-29 07:52
index.cache.field.type在ES5.5.0中如何设置
回复Elasticsearch • LXJ 发起了问题 • 1 人关注 • 0 个回复 • 2214 次浏览 • 2018-08-10 18:19
es集群主节点会因为短时间内大量查询导致OOM吗?之后生成的.hprof文件该怎么处理?
回复Elasticsearch • medcl 回复了问题 • 4 人关注 • 3 个回复 • 7992 次浏览 • 2018-04-11 09:59
Easysearch 内核完善之 OOM 内存溢出优化案例一则
Easysearch • liaosy 发表了文章 • 0 个评论 • 1627 次浏览 • 2024-03-15 10:37
最近某客户在使用 Easysearch 做聚合时,报出 OOM 导致掉节点的问题,当时直接让客户试着调整 indices.breaker.request.limit
,但是不起作用,于是又看了下 Easysearch 在断路器相关的代码,并自己测试了下。
断路器的种类和作用
Easysearch 内部有个 Circuit breaker 机制,目的是防止各种请求的负载过大导致 OutOfMemoryError
,比较常用的断路器有 7 种,分别是:
- Parent circuit breaker 父断路器
- Field data circuit breaker fielddata 断路器
- Request circuit breaker 请求断路器
- In flight requests circuit breaker 传输请求断路器
- Accounting requests circuit breaker lucene 内存占用断路器
- Script compilation circuit breaker 脚本编译断路器
- Regex circuit breaker 正则表达式断路器
其中在执行消耗内存较多的聚合查询时,Request circuit breaker 用得最多。
复现测试
我在模拟客户场景测试聚合查询时,发现断路器并没有覆盖查询的整个流程,仍然会有 OOM 的风险。我测试了一个高基数 5 百万的 Terms aggregation,就没有触发断路,而是在等待了 1 分多钟后直接 OOM 了。我的测试环境是单节点 内存配置为 -Xmx1g
,测试索引只有 1 个 shard。
测试语句如下:
curl -X GET "localhost:9211/leader-01/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 1,
"aggs": {
"a": {
"terms": { "field": "agent.id.keyword", "size": 5000000 }
}
}
}' > a.txt
Easysearch OOM 日志:
内存泄漏分析
使用 MemoryAnalyzer 分析生成的 jvm 堆转储文件:
最大的内存占用来自 Java 线程java.lang.Thread @ 0x7c8bb1d00
。这个线程浅层(Shallow)保留的对象占用了 112.8MB 内存。但该线程实际保留(Retained)的对象内存占用高达 851 MB,成为整个内存占用的绝对大头。
进一步查看 Leak Suspects:
非常明确的给出了具体的内存泄露的对象:StringTerms$Bucket[7500010]
数组长度达到了七百五十万,占用内存:731,001,720 字节(占总内存的 68.65%)。
按照提示的GlobalOrdinalsStringTermsAggregator.java:586
行,去查看代码,实际上是将收集完的OrdBucket
转换为 StringTerms.Bucket
,并且有一个 copy BytesRef
的操作。
至此,原因和解决办法都清楚了,只要在转换之前预估一下将要增长的内存并调用断路器检测一下内存,一旦超出允许范围就快速触发 CircuitBreakingException
,避免长时间等待后 OOM 引起的节点宕机了。
最新版 Elasticsearch 对比
作为对比,我又测试了下 Elasticsearch 最新版本 8.12.2,同样的测试环境和测试方法,结果依然是 OOM:
从这里可以看出 Elasticsearch 即使是最新版的断路器机制也还有很多改进的余地,比如增加对有 OOM 风险查询的覆盖率,还有就是在触发 GC 时,对 GC 堆内存回收的判断过于简单。
Easysearch 最新版本的改进
Easysearch 刚刚发布的 1.7.1 版本已经增加了上面的改进,后面也会持续改进查询聚合操作的内存控制,最新版本的跨集群复制(CCR)也增加了对 source_reuse 索引的支持,能更好的满足客户降本增效的需求,欢迎大家下载试用。
附官网下载链接:https://www.infinilabs.com/download/?product=easysearch
关于 Easysearch
INFINI Easysearch 是一个分布式的近实时搜索与分析引擎,核心引擎基于开源的 Apache Lucene。Easysearch 的目标是提供一个轻量级的 Elasticsearch 可替代版本,并继续完善和支持更多的企业级功能。 与 Elasticsearch 相比,Easysearch 更关注在搜索业务场景的优化和继续保持其产品的简洁与易用性。
官网文档:https://www.infinilabs.com/docs/latest/easysearch
ES5.3聚合内存溢出bug
Elasticsearch • yayg2008 发表了文章 • 1 个评论 • 4992 次浏览 • 2018-06-13 20:48
有以下DSL
{
"size" : 0,
"query" : { },
"_source" : false,
"aggregations" : {
"aggData" : {
"terms" : {
"field" : "url",
"size" : 200,
"min_doc_count" : 1,
"shard_min_doc_count" : 0,
"show_term_doc_count_error" : false,
"order" : [
{
"PV" : "desc"
}
]
},
"aggregations" : {
"PV" : {
"cardinality" : {
"field" : "userssid"
}
}
}
}
}
}
目的是对用户访问的URL进行分组统计,按独立用户数来排序。 执行后,data节点频繁FGC,内存无法回收,随即OOM,然后data节点脱离,集群变为red。 最初以为是cardinality精度问题导致内存使用过多,随即将precision_threshold设置为100,再次执行,内存使用量确实少了很多,但是还是用到GB级别。为了确认是否是cardinality问题,去掉外层聚合,直接执行
"aggregations" : {
"PV" : {
"cardinality" : {
"field" : "userssid"
}
}
}
发现响应非常快,而且内存占用只有KB级别。 再次单独执行外部聚合,发现也非常快,于是猜测是order导致,将order去掉,果然,如丝般顺滑,再也没有OOM。 为了解决这种OOM,首先想到的是熔断器。默认indices.breaker.request.limit配置是60%。改成10%后,触发熔断,集群正常,但是多点几次之后,data还是出现OOM了。 于是逐步调试,发现每执行1次,内存就增加一点,熔断返回后并没有被回收,直到OOM。基本确定是这里的order导致内存泄露了。 就在此时,同事反馈在5.6不会有这个问题,于是去查release note,果然在5.5的版本发现fix了这个问题。问题描述。 这个bug的根本原因是:
terms aggregations at the root level use the global_ordinals execution hint by default.
When all sub-aggregators can be run in breadth_first mode the collected buckets for these sub-aggs are dense (remapped after the initial pruning).
But if a sub-aggregator is not deferrable and needs to collect all buckets before pruning we don't remap global ords and the aggregator needs to deal with sparse buckets.
Most (if not all) aggregators expect dense buckets and uses this information to allocate memories.
This change forces the remap of the global ordinals but only when there is at least one sub-aggregator that cannot be deferred.
解决方案: 1,升级到5.5以上版本;
2,DSL增加"execution_hint":"map",属性。
{
"size" : 0,
"query" : { },
"_source" : false,
"aggregations" : {
"aggData" : {
"terms" : {
"field" : "url",
"size" : 200,
"execution_hint":"map",
"min_doc_count" : 1,
"shard_min_doc_count" : 0,
"show_term_doc_count_error" : false,
"order" : [
{
"PV" : "desc"
}
]
},
"aggregations" : {
"PV" : {
"cardinality" : {
"field" : "userssid"
}
}
}
}
}
}