Elasticsearch系列:番外篇-Fielddata

在上一篇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)的数据结构。

1. 原理

FieldData缓存主要应用场景是在对某一个field排序或者计算类的聚合运算时,它会把这个field列的所有值加载到内存,这样做的目的是提供对这些值的快速文档访问。为field构建FieldData缓存可能会很昂贵,因此建议有足够的内存来分配它,并保持其处于已加载状态。

FieldData是在第一次将该filed用于聚合,排序或在脚本中访问时按需构建。FieldData是通过从磁盘读取每个段来读取整个反向索引,然后逆置term↔︎doc的关系,并将结果存储在JVM堆中构建的

所以,加载FieldData是开销很大的操作,一旦它被加载后,就会在整个段的生命周期中保留在内存中。这了可以注意下FieldDataDoc Values的区别。较早的版本中,其他数据类型也是用的FieldData,但是目前已经用随文档索引时创建的Doc Values所替代。

2. 配置

FieldData默认是禁用的,更多设置

PUT my_index/_mapping/_doc
{
  "properties": {
    "my_field": { 
      "type":     "text",
      "fielddata": true
    }
  }
}

3. 加载

fielddata加载到内存又几种策略,默认是懒加载。即对一个分词的字段执行聚合或者排序的时候,加载到内存。所以他不是在索引创建期间创建的,而是查询在期间创建的。

参数值 含义
lazy (默认)FieldData只会在需要用到时加载到内存
eager 创建新段(通过refresh,flush或段合并)时,启用了eager loding的field将在段对搜索可见之前预先加载其每段的fielddata。如果用户的搜索请求必须触发对一个大型段的延迟加载时,这个选项可以减少延迟。其实说白了,就是把加载FieldData的时间成本从搜索时转移到了处理段可见时。
eager_global_ordinals FieldDataGlobal Ordinals加载提前到一个新段对搜索可见之前。

4. 监控

GET /_stats/fielddata?fields=*
GET /_nodes/stats/indices/fielddata?fields=*
GET/_nodes/stats/indices/fielddata?level=indices&fields=*

5. 内存管理

那么基于内存,那又会带来一些问题。如果我们不对内存加以控制,如果数据量太大,很容易造成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 
                   }
               }
            }
        }
    }
}

6. 和doc_value对比

相同点

  • 都要创建正排索引,数据结构类似于列式存储
  • 都是为了可以聚合,排序之类的操作

不同点

  • 存储索引数据的方式不一样:fielddata: 内存存储;doc_values: OS Cache+磁盘存储
  • 对应的字段类型不一样:fielddata: 对应的字段类型是text; doc_values:对应的字段类型是keyword
  • 针对的类型,也不一样:field_data主要针对的是分词字段;doc_values针对大是不分词字段
  • 是否开启:fielddata默认不开启;doc_values默认是开启

7. 总结

我们知道fielddata堆内存要求很高,如果数据量太大,对于JVM来及回收来说存在一定的挑战。所以doc_value的出现我们可以使用磁盘存储,他同样是和fielddata一样的数据结构,在倒排索引基础上反向出来的正排索引,并且是预先构建,即在建倒排索引的时候,就会创建doc values。,这会消耗额外的存储空间,但是对于JVM的内存需求就会减少。总体来看,DocValues只是比fielddata慢一点,大概10-25%,则带来了更多的稳定性。

你可能感兴趣的:(最新,elasticsearch,搜索技术,Elasticsearch)