当前位置: 首页 > news >正文

图解 Elasticsearch 的 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的清理算法,但是官方还是建议你进行手工清理

97b89e055c4e51e3a361dc9d0fb4989a.png

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

2、fielddata 内存构成

根据官方文档中的信息,我这边做了这么一个汇总。

034a78218c0da5bcc9744579f7ddb705.png

其中 bucket aggregations 特定包括我们常用的 terms 以及 composite/diversified_sampler/significant_terms

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

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

3、Text 的 fielddata 使用

Text 类型结合分词器的使用产生的倒排索引,可以满足用户进行数据检索的需求。

但是倒排索引是不支持排序聚合以及脚本编辑处理的这些需求的,而这些需求在日常业务场景下又是不避免的,那怎么办呢?

ES 做了 fielddata 这个属性来满足这一部分需求。

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

a8d655cfcae76aaa1aa09eb12407de93.png

比起在检索查询直接访问写入时计算好的倒排索引数据结构,排序聚合等查询则需要在数据重载到内存中计算出数据结构。

这样的使用方式让 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 是这样的

e789f776ce4a85a4af99545b190f1da4.png

有了 doc values 后

88ad3ed9ba01e22e9e9b085278e317e8.png

不仅可以在数据写入时就产生列存结构数据结果,同时减少了 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 类,欢迎指正

7915f056b6ff6bde453d85eb5da3af51.png

鉴于每个 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 冗余。

444253378fa9f16fe2dc51b195b74a4c.png

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 属性。

2b79cc1d5fdf135d93fddf87721c3397.png

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、 参考资料

DocValues 存储格式及压缩实现

https://mp.weixin.qq.com/s/kP5Pza2xRtBlcJs5WYvgjA

Disk-Based Field Data a.k.a. Doc Values

https://www.elastic.co/cn/blog/disk-based-field-data-a-k-a-doc-values

Doc Values and Fielddata

https://www.elastic.co/guide/en/elasticsearch/guide/current/docvalues-and-fielddata.html

更多推荐

  1. Elasticsearch 使用误区之一——将 Elasticsearch 视为关系数据库!

  2.   Elasticsearch 使用误区之二——频繁更新文档

  3.  Elasticsearch 使用误区之三——分片设置不合理

  4. Elasticsearch 使用误区之四——不合理的使用 track_total_hits

  5. Elasticsearch Filter 缓存加速检索的细节,你知道吗?

  6. 深入解析 Elasticsearch IK 分词器:ik_smart 和 ik_max_word 的区别与应用场景

  7.    《一本书讲透 Elasticsearch》读者群的创新之路

616001d8fe06d66bee662dc2effbc402.png

更短时间更快习得更多干货!

和全球2000+ Elastic 爱好者一起精进!

elastic6.cn——ElasticStack进阶助手

121eddb0bb1e69440374a5d0ab1cae32.gif

抢先一步学习进阶干货!

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【HuggingFace Transformers】BertIntermediate 和 BertPooler源码解析
  • 使用 OpenCV 组合和缩放多张图像
  • 【网络基础】DNS协议详解:从背景到解析过程及`dig`工具的使用
  • Java核心概念之(线程、进程、同步、互斥)
  • (十二)Flink Table API
  • 给自闭症孩子家长的建议:携手同行,共筑爱的桥梁
  • Docker常见命令和参数
  • Cmake相关概念
  • HikariCP源码分析之源码环境搭建
  • 优化MySQL性能的方法
  • C#知识|语法拾遗:分支结构、循环
  • spark-sql 写入paimon主键表报错 Cannot write nullable values to non-null column
  • MegaCLI和H750阵列卡不匹配导致的服务夯死
  • 第二证券:两融账户开通需要的条件?证券两融开户?
  • Android中几种线程交互方式
  • Android开源项目规范总结
  • Codepen 每日精选(2018-3-25)
  • k8s 面向应用开发者的基础命令
  • log4j2输出到kafka
  • Mysql数据库的条件查询语句
  • RxJS: 简单入门
  • Transformer-XL: Unleashing the Potential of Attention Models
  • vue从创建到完整的饿了么(11)组件的使用(svg图标及watch的简单使用)
  • 不用申请服务号就可以开发微信支付/支付宝/QQ钱包支付!附:直接可用的代码+demo...
  • 大主子表关联的性能优化方法
  • 简单实现一个textarea自适应高度
  • 力扣(LeetCode)22
  • 人脸识别最新开发经验demo
  • 体验javascript之美-第五课 匿名函数自执行和闭包是一回事儿吗?
  • 小程序开发之路(一)
  • 异常机制详解
  • 职业生涯 一个六年开发经验的女程序员的心声。
  • 自定义函数
  • 2017年360最后一道编程题
  • Java性能优化之JVM GC(垃圾回收机制)
  • mysql 慢查询分析工具:pt-query-digest 在mac 上的安装使用 ...
  • ​​​​​​​ubuntu16.04 fastreid训练过程
  • ​【已解决】npm install​卡主不动的情况
  • ​软考-高级-系统架构设计师教程(清华第2版)【第9章 软件可靠性基础知识(P320~344)-思维导图】​
  • #Datawhale AI夏令营第4期#AIGC方向 文生图 Task2
  • #前后端分离# 头条发布系统
  • #我与Java虚拟机的故事#连载03:面试过的百度,滴滴,快手都问了这些问题
  • #我与Java虚拟机的故事#连载05:Java虚拟机的修炼之道
  • (13)Hive调优——动态分区导致的小文件问题
  • (52)只出现一次的数字III
  • (AngularJS)Angular 控制器之间通信初探
  • (C++17) std算法之执行策略 execution
  • (安卓)跳转应用市场APP详情页的方式
  • (差分)胡桃爱原石
  • (附源码)ssm户外用品商城 毕业设计 112346
  • (蓝桥杯每日一题)love
  • (七)c52学习之旅-中断
  • (转)大道至简,职场上做人做事做管理
  • **PHP分步表单提交思路(分页表单提交)
  • **python多态