在上一篇DocValues中介绍过,它主要是针对not analyzed String字段存储,那要针对需要分词的字段该如何sort,agg,group,facet呢?一般默认情况下,它会报错。
GET /test_index/test_type/_search
{
"aggs": {
"group_by_test_field": {
"terms": {
"field": "test_field"
}
}
}
}
{
"error": {
"root_cause": [
{
"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."
}
],
"type": "search_phase_execution_exception",
"reason": "all shards failed",
"phase": "query",
"grouped": true,
"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."
}
},
"status": 400
}
是的,错误信息中已经找到了答案!FieldData主要是针对analyzed
String,它是一种查询时(query-time
)的数据结构。
FieldData
缓存主要应用场景是在对某一个field排序或者计算类的聚合运算时,它会把这个field列的所有值加载到内存,这样做的目的是提供对这些值的快速文档访问。为field构建FieldData
缓存可能会很昂贵,因此建议有足够的内存来分配它,并保持其处于已加载状态。
FieldData
是在第一次将该filed用于聚合,排序或在脚本中访问时按需构建。FieldData
是通过从磁盘读取每个段来读取整个反向索引,然后逆置term
↔︎doc
的关系,并将结果存储在JVM堆中构建的。
所以,加载FieldData
是开销很大的操作,一旦它被加载后,就会在整个段的生命周期中保留在内存中。这了可以注意下FieldData
和Doc Values
的区别。较早的版本中,其他数据类型也是用的FieldData
,但是目前已经用随文档索引时创建的Doc Values
所替代。
FieldData默认是禁用的,更多设置
PUT my_index/_mapping/_doc
{
"properties": {
"my_field": {
"type": "text",
"fielddata": true
}
}
}
fielddata加载到内存又几种策略,默认是懒加载。即对一个分词的字段执行聚合或者排序的时候,加载到内存。所以他不是在索引创建期间创建的,而是查询在期间创建的。
参数值 | 含义 |
lazy | (默认)FieldData 只会在需要用到时加载到内存 |
eager | 创建新段(通过refresh,flush或段合并)时,启用了eager loding的field将在段对搜索可见之前预先加载其每段的fielddata。如果用户的搜索请求必须触发对一个大型段的延迟加载时,这个选项可以减少延迟。其实说白了,就是把加载FieldData的时间成本从搜索时转移到了处理段可见时。 |
eager_global_ordinals | 将FieldData 和Global Ordinals 加载提前到一个新段对搜索可见之前。 |
GET /_stats/fielddata?fields=*
GET /_nodes/stats/indices/fielddata?fields=*
GET/_nodes/stats/indices/fielddata?level=indices&fields=*
那么基于内存,那又会带来一些问题。如果我们不对内存加以控制,如果数据量太大,很容易造成OOM。那我们应该如何控制fielddata呢?
缓存限制
我们可以配置fielddata内存限制,超出这个限制就清除内存中已有的fielddata数据。默认无限制,限制内存使用,但是会导致频繁evict和reload,大量IO性能损耗,以及内存碎片和gc。
indices.fielddata.cache.size: 20%
circuitbreaker
如果一次query load的fielddata超过总内存,就会发生内存溢出,circuit breaker会估算query要加载的fielddata大小,如果超出总内存,就短路,query直接失败。
indices.breaker.fielddata.limit:fielddata的内存限制,默认60%
indices.breaker.request.limit:执行聚合的内存限制,默认40%
indices.breaker.total.limit:综合上面两个,限制在70%以内
frequency
min: 0.01 只是加载至少1%的doc文档中出现过的term对应的文档。比如说tiger,总共有10000个文档,tiger必须在100个文档中出现。min_segment_size: 500 少于500 文档数的segment不加载fielddata,加载fielddata的时候,也是按照segment去进行加载的,某个segment里面的doc数量少于500个,那么这个segment的fielddata就不加载。
PUT /animals/_mapping/crawler
{
"properties":{
"desc":{
"type":"text",
"fielddata": {
"filter": {
"frequency": {
"min":0.01,
"min_segment_size": 500
}
}
}
}
}
}
相同点
不同点
我们知道fielddata堆内存要求很高,如果数据量太大,对于JVM来及回收来说存在一定的挑战。所以doc_value的出现我们可以使用磁盘存储,他同样是和fielddata一样的数据结构,在倒排索引基础上反向出来的正排索引,并且是预先构建,即在建倒排索引的时候,就会创建doc values。,这会消耗额外的存储空间,但是对于JVM的内存需求就会减少。总体来看,DocValues只是比fielddata慢一点,大概10-25%,则带来了更多的稳定性。