ElasticSearch索引原理浅析(DocValues 和 Fielddata)

ElasticSearch使用的是倒排索引,既然是倒排索引,对应的肯定有正向索引,我们先来把这两个概念弄清楚

正向索引

正排索引表是以文档的ID为关键字,表中记录文档中每个字段的值信息,主要场景是通过查询id来把整条文档拿出来,一般mysql关系型数据库是这种方式来查询的

正排表结构如下图所示


image.png

这种组织方法在建立索引的时候结构比较简单,建立比较方便且易于维护,当对ID查询的时候检索效率会很高。

倒排索引

倒排索引表以字或词为关键字进行索引,表中关键字所对应的记录项记录了出现这个字或词的所有文档,每个字段记录该文档的ID和关键字在该文档中出现的位置情况。

倒排表的结构图如图2:


image.png

由于每个字或词对应的文档数量在动态变化,所以倒排表的建立和维护都较为复杂,但是一旦完成创建,在查询的时候由于可以一次得到查询关键字所对应的所有文档

ElasticSearch索引

在ElasticSearch中每个文件都对应一个文件ID,文件内容被表示为一系列关键词的集合。例如“文档1”经过分词,提取了20个关键词,每个关键词都会记录它在文档中的出现次数和出现位置

得到正向索引的结构如下:

image.png

当用户在主页上搜索关键词“china”时,在正向索引下,就需要扫描所有文档,找出所有包含关键词“china”的文档,由于一般在搜索引擎中的文档的数目是个天文数字,这样的索引结构根本无法满足实时返回结果的要求。

所以,搜索引擎会将正向索引重新构建为倒排索引,即把文件ID对应到关键词的映射转换为关键词到文件ID的映射,每个关键词都对应着一系列的文件,这些文件中都出现这个关键词。
得到倒排索引的结构如下:

image.png

从词的关键字,去找文档,这种情况下,搜索关键字的效率会很高,满足搜索引擎的业务场景。

虽然每个字或词对应的文档数量在动态变化,所以倒排表的建立和维护都较为复杂,但是在查询的时候由于可以一次得到查询关键字所对应的所有文档,所以效率高于正排表。在全文检索中,检索的快速响应是一个最为关键的性能,而索引建立由于在后台进行,尽管效率相对低一些,但不会影响整个搜索引擎的效率。

DocValues

上面的倒排索引满足了关键字搜索的效率,但是对于从另外一个方向的相反操作并不高效,比如聚合(aggregations)、排序(Sorting)和字段的全值查询等时候需要其它的访问模式。

我们首先想到的是遍历正向索引来进行统计。但是这很慢而且难以扩展:

随着词项和文档的数量增加,执行时间也会增加。

为了能够解决上述问题,我们使用了Doc values通过转置两者间的关系来解决这个问题。

举例:

Doc1含有关键字:China,India
Doc2含有关键字:Love,You
Doc3含有关键字:Hello

doc_values表如下:

QQ图片20180913104700.png

DocValues是在索引时与倒排索引同时生成的,并且是不可变的。与倒排一样,保存在lucene文件中(序列化到磁盘),此值默认是启动状态,如果没有必要使用可以设置 doc_values: false来禁用。

Doc values 是不支持 analyzed 字符串字段的,想象一下,如果一个字段是analyzed,如the first,则在分析阶段则会docvalues则会存储为两条docvalue(the和first),计算时候则会得到

QQ图片20180913105223.png

而非
QQ图片20180913105254.png

此时需要Fielddata来解决。

Fielddata

Doc values 是不支持 analyzed 字符串字段的,然而,这些字段仍然可以使用聚合,是因为使用了fielddata 的数据结构。与 doc values 不同,fielddata 构建和管理 100% 在内存中,常驻于 JVM 内存堆。

Fielddata默认是不启用的,因为text字段比较长,一般只做关键字分词和搜索,很少拿它来进行全文匹配和聚合还有排序,因为大多数这种情况是无意义的,一旦启用将会把text都加载到内存中,那将带来很大的内存压力。

Fielddata一些特性:

  1. Fielddata 是延迟加载的。如果你从来没有聚合一个分析字符串,就不会加载 fielddata 到内存中,是在查询时候构建的。
  2. fielddata 是基于字段加载的, 只有很活跃地使用字段才会增加fielddata 的负担。
  3. fielddata 会加载索引中(针对该特定字段的) 所有的文档,而不管查询是否命中。逻辑是这样:如果查询会访问文档 X、Y 和 Z,那很有可能会在下一个查询中访问其他文档。
  4. 如果空间不足,使用最久未使用(LRU)算法移除fielddata。

所以,fielddata应该在JVM中合理利用,否则会影响es性能。

如果一次性加载字段直接超过内存值会发生什么?挂掉?所以es为了防止这种情况,采用了circuit breaker(熔断机制)。

它通过内部检查(字段的类型、基数、大小等等)来估算一个查询需要的内存。它然后检查要求加载的 fielddata 是否会导致 fielddata 的总量超过堆的配置比例。如果估算查询大小超出限制,就会触发熔断,查询会被中止并返回异常。

indices.breaker.fielddata.limit fielddata级别限制,默认为堆的60% 
indices.breaker.request.limit request级别请求限制,默认为堆的40% 
indices.breaker.total.limit 保证上面两者组合起来的限制,默认堆的70%

最后

1.ElasticSearch原理是倒排索引和正排索引的转化版
2.DocValues满足非analyed字段的正排索引转化版,Fielddata对应analyed
3.DocValues存在于磁盘,消耗Lucene内存来提升效率,Fielddata存在于ElasticSearch内存(jvm)

推荐阅读更多精彩内容