为了让类似agg这种需要获取field value的操作是需要返回请求迅速的,这是为什么会把fielddata加载到内存中的原因,但是大量的fielddata会导致内存回收慢,甚至内存异常。
也许你惊奇的会发现es不仅把匹配你查询的文档的值放到了fielddata中,竟然把所有文档的值放在了内存中,甚至包括type。
逻辑是,你查询前面的可能会查询后面的,一次把所有数据加载进来并保存到内存中会很方便的和快速的,否则每个请求都去倒排索引中查一次。
JVM HEAP是有限制的,应该明智使用。有各种各样的机制来限制fielddata的使用,这些限制是非常重要的,因为滥用HEAP会导致系统不稳定甚至不可用。
选择一个HEAP SIZE
有两种规则来设置这个值,通过$ES_HEAP_SIZE变量
(1)不能超过系统内存的50%
(2)不能超过32GB,在32GB内,JVM会自动压缩,这样可以节约很多的内存,8bytes压缩到4 bytes,超过32,意味着你可用内存会越来越小,因为原来压缩的都变大了。
field data
indices.fielddata.cache.size 这个值控制了多少内存用于field data,当你进行查询的时候,需要新的field value,系统将会把数据加载dao内存而且尝试把数据加载到fielddata中,如果这些值超过了范围,其他的值将被踢出去来让出空间来。
默认es没有限制这个值,也就是说es从不会剔除数据去。
这个默认设置系统是有意这样做的,fielddata不是短暂数据,这些数据存在内存中能很快执行,而且建立这些数据是非常不容易的,如果每次请求再去加载这些数据表现会很差。
通过设置一个限定值将会根据值剔除数据,我们接下来会看到如何设置这个值。
首先,这个值是一个安全值,但不是内存不足的解决方案。
如果你没有足够的内存让这些数据常驻内存,es将会经常的从硬盘加载数据然后剔除旧的数据给新数据让出空间,evict过程会导致大量的IO操作,并且生成大量的内存垃圾需要去回收。
想象一下你正在index log数据,每天使用新的index,突然有天你想看前两天数据,如果是默认设置,旧数据酒会不断给内存中加入,直到circuit breaker。但这个时候系统也不能再加载新的数据了,因为都让fielddata占用了。
为了防止这件事情,需要在yml文件中进行设置:
indices.fielddata.cache.size: 40%
也可以设置一个具体的值:5gb
通过这个设置,上述那种情况中的新的fielddata会剔除出去,旧的会进来。
监控fielddata
对index监控
GET /_stats/fielddata?fields=*
对每个nodes监控
GET /_nodes/stats/indices/fielddata?fields=*
对每个index每个node监控
GET /_nodes/stats/indices/fielddata?level=indices&fields=*
circuit breaker(也许带入的fielddata 超过了整个的size)
细心的读者会发现一个问题:fielddata size是在数据加载进内存才核对的,如果一个query加入的fielddata比设置值大怎么办?结果是内存异常。
es有个circuit breaker就是用于处理这个问题的,这个模块会估计一个query进来带入的fielddata需要的内存,然后检察是否会超过fielddata的size
如果获取的值超过这个值,该query会在数据加载进来前被停止,也就是说不会看到内存异常.
默认这些值进行了设置:
indices.breaker.fielddata.limit 默认是HEAP的60%
indices.breaker.request.limit 默认是HEAP 40%
indices.breaker.total.limit 两者合起来不能超过HEAP 70%默认
可以动态的设置
PUT /_cluster/settings
{
"persistent" : {
"indices.breaker.fielddata.limit" : "40%"
}
}
circuit breaker limit 设置必须比indices.fielddata.cache.size大,如果小了,就不存在数据evict了,达到limit就阻止query了,cache第一道防线,limit最后一道防线。
最后这些值是保守值,用户还得好好思考啊