问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

图解 Elasticsearch 的 Fielddata Cache 使用与优化

创作时间:
2025-03-24 06:05:47
作者:
@小白创作中心

图解 Elasticsearch 的 Fielddata Cache 使用与优化

引用
CSDN
1.
https://blog.csdn.net/laoyang360/article/details/141406861

Elasticsearch中的Fielddata Cache是一个复杂且重要的内存缓存机制,它与Query Cache和Request Cache一样,不受GC控制。本文将深入探讨Fielddata Cache的使用场景、内存构成、以及如何进行优化和监控。

1. 难搞的 fielddata cache

在ES使用的几个内存缓存中,fielddata cache算是一个让人头疼的家伙。作为和query cache和request cache一样不受GC控制的内存使用者,fielddata cache虽然也有indices.fielddata.cache.size的设置来阻止过度使用,但是默认是不限制的

并且,当fielddata cache达到indices.fielddata.cache.size设定值的时候,虽然有类似LRU的清理算法,但是官方还是建议你进行手工清理

那么fielddata cache是被什么内容使用了呢?它的作用是什么呢?我们结合官方的一些资料扒一扒。

2. fielddata 内存构成

根据官方文档中的信息,我这边做了这么一个汇总。其中bucket aggregations特定包括我们常用的terms以及composite/diversified_sampler/significant_terms

使用的涉及面还不少,不仅有倒排字段,还有聚合查询甚至还扯上了不常见的join关联字段类型。感觉这类缓存的使用很复杂。

下面,我们先来看看Text的fielddata。

3. Text 的 fielddata 使用

Text类型结合分词器的使用产生的倒排索引,可以满足用户进行数据检索的需求。但是倒排索引是不支持排序聚合以及脚本编辑处理的这些需求的,而这些需求在日常业务场景下又是不避免的,那怎么办呢?

ES做了fielddata这个属性来满足这一部分需求。简单来说,开启text字段的fielddata后,ES将把所有字段的值(即分词后的token)装载进内存,然后对数据进行重新计算实现适合排序聚合等需求的内存数据结构。

比起在检索查询直接访问写入时计算好的倒排索引数据结构,排序聚合等查询则需要在数据重载到内存中计算出数据结构。这样的使用方式让jvm内的fielddata cache承担了较大的空间使用,动不动好几GB。

并且在ES古早的版本中,由于没有docvalue的设计,排序聚合等场景可能需要在text上开启fielddata得以实现,不仅使用麻烦,OOM的风险也增加了。

4. global ordinals

除了text字段上的fielddata,另一个使用fielddata cache的global ordinals也占据了fielddata cache的很多使用场景。要想了解global ordinals的产生,我们先聊聊doc values。

4.1 doc values 是什么

Doc values是lucene 4版本开始的一个特性,它以列存的方式将字段值存储在磁盘,这样既能利用了堆外的系统缓存,同时又实现排序查询或者scripting所需要的数据结构,完成了fielddata一样的任务。

原来的写入流程和排序scripting是这样的
有了doc values后
不仅可以在数据写入时就产生列存结构数据结果,同时减少了jvm内存的使用

当然,如果只是为了把数据计算提前到写入时和减少对堆内内存的使用,这对于ES来说显然是不够的。doc values实际有多种存储结构,都实现了快速随机访问与数据压缩的均衡。

  • NumericDocValuesWriter(数值类型,ES使用这个类型存储_seq_no等元数据字段)
  • SortedNumericDocValuesWriter(多值内部排序的数值类型,被存储的字段是数值类型的数组,ES使用这种方式存储用户定义的数值类型)
  • SortedDocValuesWriter(排序的字符类型,保存原始值及hash位置)
  • SortedSetDocValuesWriter(排序的字符数组类型,保存原始值及hash位置)

对其底层实现有兴趣的同学可以查看这篇博客:https://mp.weixin.qq.com/s/kP5Pza2xRtBlcJs5WYvgjA

4.2 global ordinals 让 doc values 聚合

现在我们再去理解global ordinals会更简单一些,下面是官网对其的定义:

To support aggregations and other operations that require looking up field values on a per-document basis, Elasticsearch uses a data structure called doc values. Term-based field types such as keyword store their doc values using an ordinal mapping for a more compact representation. This mapping works by assigning each term an incremental integer or ordinal based on its lexicographic order. The field’s doc values store only the ordinals for each document instead of the original terms, with a separate lookup structure to convert between ordinals and terms.

When used during aggregations, ordinals can greatly improve performance. As an example, the terms aggregation relies only on ordinals to collect documents into buckets at the shard-level, then converts the ordinals back to their original term values when combining results across shards.

Each index segment defines its own ordinal mapping, but aggregations collect data across an entire shard. So to be able to use ordinals for shard-level operations like aggregations, Elasticsearch creates a unified mapping called global ordinals. The global ordinal mapping is built on top of segment ordinals, and works by maintaining a map from global ordinal to the local ordinal for each segment.

Global ordinals are used if a search contains any of the following components:
- Certain bucket aggregations on keyword, ip, and flattened fields. This includes terms aggregations as mentioned above, as well as composite, diversified_sampler, and significant_terms.
- Bucket aggregations on text fields that require fielddata to be enabled.
- Operations on parent and child documents from a join field, including has_child queries and parent aggregations.
The global ordinal mapping uses heap memory as part of the field data cache. Aggregations on high cardinality fields can use a lot of memory and trigger the field data circuit breaker.

翻译一下:

  1. 聚合涉及每个分片每个segment数据的汇总计算。
  2. 以词项数据为主的字段类型(或者说字符串数据,比如keyword类型)的doc values使用一种紧凑的数据结构ordinals来映射原始terms的值,ordinals能加速聚合查询
  3. global ordinal是对每个segment oridnals的map映射
  4. global ordinal mapping使用了fielddata cache的内存。

注意:这里只说明是Term-based field types,涉及的是keyword/ip/flattened这三个类型。其他类型比如long/int等使用聚合则并不会使用global ordinals。

有兴趣的大佬可以验证lucene的org.apache.lucene.index.OrdinalMap类,欢迎指正。

鉴于每个segment上的ordinal mapping已经压缩映射了原本的字段数据,而global ordinals是每个ordinals的一个map。不难看出,global ordinal在fielddata cache中的占用会比简单的text开启fielddata装载数据到内存减少很多

虽然global ordinals已经做了不少内存使用优化,但是高基数的数据聚合会占用大量的内存,这里的高基数是指一个字段包含很大比例的唯一值。

5. fielddata内存监控与清理

5.1 设置限制

这里主要有两个限制可以控制fielddata cache的使用:indices.fielddata.cache.size和indices.breaker.fielddata.limit。

indices.fielddata.cache.size
(Static) The max size of the field data cache, eg 38% of node heap space, or an absolute value, eg 12GB. Defaults to unbounded. If you choose to set it, it should be smaller than Field data circuit breaker limit.
indices.breaker.fielddata.limit
(Dynamic) Limit for fielddata breaker. Defaults to 40% of JVM heap.

indices.fielddata.cache.size的设置要比indices.breaker.fielddata.limit小。一旦超出了breaker的使用量,那就会触发内存的熔断了。

这里做个假设吧,一个ES进程实例申请jvm内存10GB,那么indices.breaker.fielddata.limit就默认为heap的40%,也就是4GB,这样indices.fielddata.cache.size的设定则要小于40%或者4GB,比如35%或者3.5GB,那么就会有0.5GB的indices.breaker.fielddata.limit冗余。

5.2 监控与清理

监控命令

GET _cat/fielddata?v
#查看索引的fielddata
GET _stats/fielddata?level=indices
#查看主机级别
GET _nodes/stats/indices/fielddata?fields=*
#查看主机上index级别字段级别
GET _nodes/stats/indices/fielddata?level=indices&fields=*

为了避免监控数据膨胀,一般是做到索引级别的使用监控,实际定位问题去使用字段级别的维度去人工定位。

清理命令

POST /my-index-000001/_cache/clear?fielddata=true

6. 小结和建议

不难看出,doc values其实是对倒排索引的fielddata使用的变迁和优化,以一种列存的文件模式实现了聚合排序场景的需求使用。

Text开启fielddata的实现方式相对比较粗糙,简单的申请jvm内存进行多个场景的数据构建计算,不仅需要不少的内存资源,也无法利用更多的计算资源和空间。

Doc values则经过各种列存数据文件的精细设计,即满足了fielddata的需求场景,也降低了jvm内存的使用,又在数据压缩和快速读取上实现了最大化,替代fielddata大部分的使用场景。

但对于text文本字符串长文中的单字聚合场景,比如:实现text字段的词云效果,还必须使用fielddata enable属性。

Kibana 8.X如何做出靠谱的词云图?

其实不管是fielddata还是doc values,ES使用fielddata cache几乎都是满足聚合排序等查询需求(除join字段)。但是jvm heap内存资源是紧张且宝贵的,为了安全使用ES,加强堆内的资源进行有效的利用,在此总结了以下几个建议:

  • 合理设置indices.fielddata.cache.size
  • 避免对text直接使用fielddata,利用doc values替代其使用场景。
  • 建立模型,清洗数据,利用合适的字段进行排序聚合。比如数字类型数据双字段,聚合使用long,查询匹配使用keyword。
  • terms/sampler/significant_terms这些聚合方法中将execution_hint设置为map,一般基数在百万以下的改为map方式会更优(字段值特别长的例外)。
  • 可以考虑对没有更新的历史数据进行forcemerge,将segment数量减少到最小。
  • 尽量减少ES执行聚合的复杂度,超大数据体量多层次的聚合还是对系统资源的一次考验
  • 做好对fielddata内存的监控和及时的处理,有必要时直接清理

7. 参考资料

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号