ElasticSearch fielddata的工作原理

本文涉及的产品
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
简介: 介绍fielddata的工作原理

1. 为什么使用fielddata

对于不分词的field,在进行POST/PUT的时候会创建doc->value正排索引,但是对于分词的field,在进行POST/PUT的时候,是不会自动生成doc->value正排索引的,因为这会消耗大量的空间。 对于分词的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,
    "failed_shards": [
      {
        "shard": 0,
        "index": "test_index",
        "node": "4onsTYVZTjGvIj9_spWz2w",
        "reason": {
          "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."
        }
      }
    ],
    "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 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执行聚合操作,而且会消耗很大的内存.

2.分词field+fielddata的工作原理

如果需要进行聚合操作的field不分词,那么在index-time就会自动生成doc->value正排索引,正对这些不分词的field执行聚合操作的时候,就会直接使用doc -> value来进行聚合操作。


对于分词的field,在index-time的时候是不会建立doc->value正排索引的,因为分词之后。占用的空间过大,所有默认不支持对分词的field进行聚合操作,如果直接对其进行聚合操作,会直接报错。


对于分词的field,要进行聚合操作,必须要打开和使用fielddata,即将field的fielddata设置为true。


在将fielddata设置为true之后,es就会在执行聚合操作的时候,现场将field对应的数据,建立一份fielddata正排索引,其结构和doc -> value正排索引类型,但是只会讲fielddata正排索引加载到内存中,然后基于内存中的fielddata正排索引执行分词field的聚合操作。


使用fielddata之后,生成的fielddata正排索引会加载到内存中,这会耗费内存空间;之所以将其放在内存之中,是因为基于磁盘和os cache,那么性能会很差。

3. fielddata核心原理

fielddata加载到内存中的过程是懒加载的,在对一个分词field执行聚合操作的时候,才会加载。 加载时field级别的加载,而不是匹配查询条件的文档,一个index的一个field,所有的doc都会被加载,而不是少数的doc被加载。 不是在index-time的时候创建,而是在quer-time的时候创建。

4.fielddata内存限制

indices.fielddata.cache.size 控制为fielddata分配的堆空间大小。 当你发起一个查询,分析字符串的聚合将会被加载到fielddata,如果这些字符串之前没有被加载过。


如果结果中fielddata大小超过了指定大小,其他的值将会被回收从而获得空间(使用LRU算法执行回收)。 默认无限制,限制内存使用,但是会导致频繁evict和reload,大量IO性能损耗,以及内存碎片和gc,这个参数是一个安全卫士,必须要设置: indices.fielddata.cache.size: 20%

5. 监控fielddata内存使用

Elasticsearch提供了监控监控fielddata内存使用的命令,我们在上面可以看到内存使用和替换的次数,过高的evictions值(回收替换次数)预示着内存不够用的问题和性能不佳的原因:

# 按照索引使用
GET /_stats/fielddata?fields=*
# 按照节点使用
GET /_nodes/stats/indices/fielddata?fields=*
# 按照节点索引使用
GET /_nodes/stats/indices/fielddata?level=indices&fields=*

fields=*表示所有的字段,也可以指定具体的字段名称。

6.circuit breaker

indices.fielddata.cache.size的作用范围是当前查询完成后,发现内存不够用了才执行回收过程,如果当前查询的数据比内存设置的fielddata 的总量还大,如果没有做控制,可能就直接OOM了。


熔断器的功能就是阻止OOM的现象发生,在执行查询时,会预算内存要求,如果超过限制,直接掐断请求,返回查询失败,这样保护Elasticsearch不出现OOM错误。

常用的配置如下:

  • indices.breaker.fielddata.limit:fielddata的内存限制,默认60%
  • indices.breaker.request.limit:执行聚合的内存限制,默认40%
  • indices.breaker.total.limit:综合上面两个,限制在70%以内


最好为熔断器设置一个相对保守点的值。fielddata需要与request断路器共享堆内存、索引缓冲内存和过滤器缓存,并且熔断器是根据总堆内存大小估算查询大小的,而不是实际堆内存的使用情况,如果堆内有太多等待回收的fielddata,也有可能会导致OOM发生。

7. fielddata filter的细粒度内存加载控制

滤的主要目的是去掉长尾数据,我们可以加一些限制条件,如下请求:

POST /test_index/_mapping/my_type
{
  "properties": {
    "my_field": {
      "type": "text",
      "fielddata": { 
        "filter": {
          "frequency": { 
            "min":              0.01, 
            "max":              0.1,
            "min_segment_size": 500  
          }
        }
      }
    }
  }
}

min: 仅仅加载至少在1%的doc中出现过得term对应的fielddata。 max: 仅仅加载最多在10%的doc中出现过得term对应的fielddata。 min_segment_size: 少于500个doc的segment不加载fielddata。


比如说某个值,hello,总共有1000个doc,hello必须在10个doc中出现,最多在100个doc中出现,那么这个field对应的fielddata才会加载到内存中去。


fidelddata是按段来加载的,某个segment里面的doc数量少于500个,那么这个segment的fielddata就不加载,所以出现频率是基于某个段计算得来的,如果一个段内只有少量文档,统计词频意义不大,等段合并到大的段当中,超过500个文档这个限制,就会纳入计算。

8. fielddata预加载

如果真的要对分词的field执行聚合,那么每次都在query-time现场生产fielddata并加载到内存中来,速度可能会比较慢。

POST /test_index/_mapping/test_type
{
  "properties": {
    "test_field": {
      "type": "string",
      "fielddata": {
        "loading" : "eager" 
      }
    }
  }
}

query-time的fielddata生成和加载到内存,变为index-time,建立倒排索引的时候,会同步生成fielddata并且加载到内存中来,这样的话,对分词field的聚合性能当然会大幅度增强。

9. 序号标记预加载

假设我们的文档用来标记状态有几种字符串:

  • SUCCESS
  • FAILED
  • PENDING
  • WAIT_PAY

状态这类的字段,系统设计时肯定是可以穷举的,如果我们存储到Elasticsearch中也用的是字符串类型,需要的存储空间就会多一些,如果我们换成1,2,3,4这种Byte类型的,就可以节省很多空间。


"序号标记"做的就是这种优化,如果文档特别多(PB级别),那节省的空间就非常可观,我们可以对这类可以穷举的字段设置序号标记,如下请求:

POST /test_index/_mapping/test_type
{
  "properties": {
    "test_field": {
      "type": "string",
      "fielddata": {
        "loading" : "eager_global_ordinals" 
      }
    }
  }
}

10. 深度优先VS广度优先

Elasticsearch的聚合查询时,如果数据量较多且涉及多个条件聚合,会产生大量的bucket,并且需要从这些bucket中挑出符合条件的,那该怎么对这些bucket进行挑选是一个值得考虑的问题,挑选方式好,事半功倍,效率非常高,挑选方式不好,可能OOM,我们拿深度优先和广度优先这两个方式来讲解。


我们举个电影与演员的例子,一部电影由多名演员参与,我们搜索的需求:出演电影最多的10名演员以及他们合作最多的5名演员。


如果是深度优先,这种查询方式需要构建完整的数据,会消耗大量的内存。假设我们每部电影有10位演员(1主9配),有10万部电影,那么第一层的数据就有10万条,第二层为9*10万=90万条,共100万条数据。


我们对这100万条数据进行排序后,取主角出演次数最多的10个,即10条数据,裁掉99加上与主角合作最多的5名演员,共50条数据。


构建了100万条数据,最终只取50条,内存是不是有点浪费?

如果是广度优先,这种查询方式先查询电影主角,取前面10条,第一层就只有10条数据,裁掉其他不要的,然后找出跟主角有关联的配角人员,与合作最多的5名,共50条数据。


聚合查询默认是深度优先,设置广度优先只需要设置collect_mode参数为breadth_first,示例:

GET /music/children/_search
{
  "size": 0,
  "aggs": {
    "lang": {
      "terms": {
        "field": "language",
        "collect_mode" : "breadth_first" 
      },
      "aggs": {
        "length_avg": {
          "avg": {
            "field": "length"
          }
        }
      }
    }
  }
}
注意

使用深度优先还是广度优先,要考虑实际的情况,广度优先仅适用于每个组的聚合数量远远小于当前总组数的情况,比如上面的例子,我只取10位主角,但每部电影都有一位主角,聚合的10位主角组数远远小于总组数,所以是适用的。


另外一组按月统计的柱状图数据,总组数固定只有12个月,但每个月下的数据量特别大,广度优先就不适合了。 所以说,使用哪种方式要看具体的需求。

相关实践学习
使用阿里云Elasticsearch体验信息检索加速
通过创建登录阿里云Elasticsearch集群,使用DataWorks将MySQL数据同步至Elasticsearch,体验多条件检索效果,简单展示数据同步和信息检索加速的过程和操作。
ElasticSearch 入门精讲
ElasticSearch是一个开源的、基于Lucene的、分布式、高扩展、高实时的搜索与数据分析引擎。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr(也是基于Lucene)。 ElasticSearch的实现原理主要分为以下几个步骤: 用户将数据提交到Elastic Search 数据库中 通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据 当用户搜索数据时候,再根据权重将结果排名、打分 将返回结果呈现给用户 Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。
相关文章
|
5月前
|
存储 数据采集 数据处理
数据处理神器Elasticsearch_Pipeline:原理、配置与实战指南
数据处理神器Elasticsearch_Pipeline:原理、配置与实战指南
180 12
|
6月前
|
存储 缓存 负载均衡
elasticsearch写入流程和请求检索流程原理全方位解析
elasticsearch写入流程和请求检索流程原理全方位解析
|
6月前
|
存储 监控 固态存储
elasticsearch索引生命周期管理(ILM):原理和实践
elasticsearch索引生命周期管理(ILM):原理和实践
|
6月前
|
数据采集 API 定位技术
elasticsearch pipelineI详解:原理与使用
elasticsearch pipelineI详解:原理与使用
|
6月前
|
缓存 自然语言处理 监控
elasticsearch过滤器filter:原理及使用
elasticsearch过滤器filter:原理及使用
|
6月前
|
存储 数据库 开发者
Elasticsearch中的三种分页策略深度解析:原理、使用及对比
Elasticsearch中的三种分页策略深度解析:原理、使用及对比
|
6月前
|
缓存 监控 安全
深入解析Elasticsearch中脚本原理
深入解析Elasticsearch中脚本原理
|
6月前
|
缓存 监控 Java
深入Elasticsearch:线程池的原理与应用
深入Elasticsearch:线程池的原理与应用
|
7月前
|
存储 缓存 搜索推荐
深入理解Elasticsearch倒排索引原理与优化策略
总之,Elasticsearch的倒排索引是其高效全文搜索的核心。为了提高性能和可伸缩性,Elasticsearch采用了多种优化策略,包括压缩、分片、合并、位集合和近实时搜索等。这些策略使Elasticsearch成为处理大规模文本数据的强大工具。
635 0
|
7月前
|
搜索推荐 算法 数据挖掘
探索 Elasticsearch 8.X Terms Set 检索的应用与原理
探索 Elasticsearch 8.X Terms Set 检索的应用与原理
107 0