当我们使用聚合分析,对某个的字段进行分析时,如果该字段是可分词的类型,比如text这种,就会抛出异常:
"caused_by": {
"type": "illegal_argument_exception",
"reason": "Fielddata is disabled on text fields by default. Set fielddata=true on [test_field] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory."
}
上面报错大概的意思是说:必须要打开fielddata
,然后将正排索引数据加载到内存中,才可以对分词field执行聚合操作,并且很消耗内存。
所以,我们需要先改变字段的mapping,将fielddata
置为true:
POST /test_index/_mapping
{
"properties": {
"test_field": {
"type": "text",
"fielddata": true
}
}
}
一、fielddata原理
当开启fielddata: true
后,Elasticsearch会在执行聚合操作时,实时地将field对应的数据建立一份fielddata正排索引,索引会被加载到JVM内存中,然后基于内存中的索引执行分词field的聚合操作。
所以,如果doc数量非常多,这个过程会非常消耗内存,因为分词的field需要按照term进行聚合,其中涉及很多复杂的算法和操作,Elasticsearch为了提升性能,对于这些操作全部是基于JVM内存进行的。
1.1 懒加载
fielddata是通过懒加载的方式加载到内存中的,所以只有对一个analzyed field执行聚合操作时,才会执行加载。这种懒加载的方式更加降低了性能。
1.2 内存限制
Elasticsearch可以通过配置indices.fielddata.cache.size
参数来限制fielddata对内存的使用。超出限制,就会清除内存已有的fielddata数据,但是一旦限制内存使用,又会导致频繁的evict和reload,产生大量内存碎片,同时降低IO性能。
1.3 circuit breaker
如果一次query 操作加载的feilddata数据量大小超过了总内存,就会发生内存溢出,circuit breaker会估算query要加载的fielddata大小,如果超出总内存就短路,query直接失败。可以通过以下参数进行设置:
indices.breaker.fielddata.limit:fielddata的内存限制,默认60%
indices.breaker.request.limit:执行聚合的内存限制,默认40%
indices.breaker.total.limit:综合上面两个,限制在70%以内
二、fielddata优化
一般来讲,不会对text类型的字段执行聚合操作,也不建议做这个操作。如果确实有这个需求,需要对fielddata做些优化,以提升性能。
2.1 fielddata预加载
第一个优化点是预加载 (与默认的 延迟加载相对)。随着新segment的创建(通过刷新、写入或合并等方式), 启动字段预加载可以使那些对搜索不可见的分段里的 fielddata 提前 加载。这就意味着首次命中分段的查询不需要促发 fielddata 的加载,因为 fielddata 已经被载入到内存,避免了用户遇到搜索卡顿的情形。
预加载是按字段启用的,所以我们可以控制具体哪个字段可以预先加载:
POST /test_index/_mapping
{
"properties": {
"test_field": {
"type": "string",
"fielddata": {
"loading" : "eager"
}
}
}
}
预加载只是简单的将载入 fielddata 的代价转移到索引刷新的时候,而不是查询时,从而大大提高了搜索体验。
2.2 全局序号
另一种用来降低 fielddata 内存使用的技术叫做 全局序号(Global Ordinals)。
设想我们有十亿文档,每个文档都有自己的 status
状态字段,状态总共有三种: status_pending
、 status_published
、 status_deleted
。如果我们为每个文档都保留其状态的完整字符串形式,那么每个文档就需要使用 14 到 16 字节,或总共 15 GB。
取而代之的是,我们可以指定三个不同的字符串对其排序、编号:0,1,2。
Ordinal | Term
-------------------
0 | status_deleted
1 | status_pending
2 | status_published
序号字符串在序号列表中只存储一次,每个文档只要使用数值编号的序号来替代它原始的值。
Doc | Ordinal
-------------------------
0 | 1 # pending
1 | 1 # pending
2 | 2 # published
3 | 0 # deleted
这样可以将内存使用从 15 GB 降到 1 GB 以下!
全局序号是一个构建在 fielddata 之上的数据结构,它只占用少量内存。唯一值是 跨所有分段 识别的,然后将它们存入一个序号列表中,terms
聚合可以对全局序号进行聚合操作,将序号转换成真实字符串值的过程只会在聚合结束时发生一次。这会将聚合(和排序)的性能提高三到四倍。
三、总结
本章,我介绍了Elasticsearch中的fielddata
的作用和优化方式。一般来讲,我们最好不要对string、text等可分词类型的字段进行聚合操作,因为即使进行了优化,性能开销也会非常大。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。