概述
继续跟中华石杉老师学习ES,第52篇
课程地址: https://www.roncoo.com/view/55
官网
中文指南(2.x):戳这里
fielddata核心原理
fielddata加载到内存的过程是lazy加载的,对一个analzyed field执行聚合时,才会加载,而且是field-level加载的.
一个index的一个field,所有doc都会被加载,而不是少数doc,不是index-time创建,是query-time创建
fielddata内存限制
indices.fielddata.cache.size: 20%,超出限制,清除内存已有fielddata数据
fielddata占用的内存超出了这个比例的限制,那么就清除掉内存中已有的fielddata数据.
默认无限制,限制内存使用,但是会导致频繁evict和reload,大量IO性能损耗,以及内存碎片和gc
查询
GET _cat/nodes?v&h=id,ip,port,r,ramPercent,ramCurrent,heapMax,heapCurrent,fielddataM
indices.fielddata.cache.size 控制为 fielddata 分配的堆空间大小。 当你发起一个查询,分析字符串的聚合将会被加载到 fielddata,如果这些字符串之前没有被加载过。如果结果中 fielddata 大小超过了指定 大小 ,其他的值将会被回收从而获得空间。
默认情况下,设置都是 unbounded ,Elasticsearch 永远都不会从 fielddata 中回收数据。
这个默认设置是刻意选择的:fielddata 不是临时缓存。它是驻留内存里的数据结构,必须可以快速执行访问,而且构建它的代价十分高昂。如果每个请求都重载数据,性能会十分糟糕。
设想我们正在对日志进行索引,每天使用一个新的索引。通常我们只对过去一两天的数据感兴趣,尽管我们会保留老的索引,但我们很少需要查询它们。不过如果采用默认设置,旧索引的 fielddata 永远不会从缓存中回收! fieldata 会保持增长直到 fielddata 发生断熔(请参阅 断路器),这样我们就无法载入更多的 fielddata。
这个时候,我们被困在了死胡同。但我们仍然可以访问旧索引中的 fielddata,也无法加载任何新的值。相反,我们应该回收旧的数据,并为新值获得更多空间。
为了防止发生这样的事情,可以通过在 config/elasticsearch.yml 文件中增加配置为 fielddata 设置一个上限:
indices.fielddata.cache.size: 20%
可以设置堆大小的百分比,也可以是某个值,例如: 5gb 。
有了这个设置,最久未使用(LRU)的 fielddata 会被回收为新数据腾出空间。
在 Fielddata的大小 中,我们提过关于给 fielddata 的大小加一个限制,从而确保旧的无用 fielddata 被回收的方法。 indices.fielddata.cache.size 和 indices.breaker.fielddata.limit 之间的关系非常重要。
如果断路器的限制低于缓存大小,没有数据会被回收。为了能正常工作,断路器的限制 必须 要比缓存大小要高
监控fielddata内存使用
GET /_stats/fielddata?fields=* GET /_nodes/stats/indices/fielddata?fields=* GET /_nodes/stats/indices/fielddata?level=indices&fields=*
circuit breaker
如果一次query load的feilddata超过总内存,就会oom --> 内存溢出
circuit breaker会估算query要加载的fielddata大小,如果超出总内存,就短路,query直接失败
indices.breaker.fielddata.limit:fielddata的内存限制,默认60% indices.breaker.request.limit:执行聚合的内存限制,默认40% indices.breaker.total.limit:综合上面两个,限制在70%以内
fielddata filter的细粒度内存加载控制
POST /test_index/_mapping/my_type { "properties": { "my_field": { "type": "text", "fielddata": { "filter": { "frequency": { "min": 0.01, "min_segment_size": 500 } } } } } }
min:仅仅加载至少在1%的doc中出现过的term对应的fielddata
比如说某个值,hello,总共有1000个doc,hello必须在10个doc中出现,那么这个hello对应的fielddata才会加载到内存中来
min_segment_size:少于500 doc的segment不加载fielddata
加载fielddata的时候,也是按照segment去进行加载的,某个segment里面的doc数量少于500个,那么这个segment的fielddata就不加载
这个fielddata filter一般不会去设置它,了解就好。
fielddata预加载机制以及序号标记预加载
如果真的要对分词的field执行聚合,那么每次都在query-time现场生产fielddata并加载到内存中来,速度可能会比较慢,是不是可以预先生成加载fielddata到内存中来???
fielddata预加载 eager
POST /test_index/_mapping/test_type { "properties": { "test_field": { "type": "string", "fielddata": { "loading" : "eager" } } } }
query-time的fielddata生成和加载到内存,变为index-time,建立倒排索引的时候,会同步生成fielddata并且加载到内存中来,这样的话,对分词field的聚合性能当然会大幅度增强.
序号标记预加载 eager_global_ordinals
global ordinal原理解释
doc1: status1
doc2: status2
doc3: status2
doc4: status1
有很多重复值的情况,会进行global ordinal标记
status1 --> 0
status2 --> 1
doc1: 0
doc2: 1
doc3: 1
doc4: 0
建立的fielddata也会是这个样子的,这样的好处就是减少重复字符串的出现的次数,减少内存的消耗
POST /test_index/_mapping/test_type { "properties": { "test_field": { "type": "string", "fielddata": { "loading" : "eager_global_ordinals" } } } }