深入理解 Elasticsearch doc_values 和 global_ordinals

2,257 阅读4分钟

背景

Elasticsearch 利用倒排索引可以快速的进行搜索,但是倒排索引这种数据结构并不适合用作聚合,排序,在脚本里访问字段等等这些场景,于是 doc_values 数据结构应运而生。

doc_values

doc_values 存储在磁盘上,在文档构建索引的时候建立。doc_values 拥有以下特点:

  • 以 segment 为单位保存
  • 按字段列式存储,这样更有利于排序与聚合
  • 受mapping影响,比如说 mapping 的类型是 integer ,但是插入的值是浮点型,例如是1.7,实际保存的会是 1
  • 支持所有类型,除了 text 和 annotated_text 类型
  • 对支持的类型默认开启

ordinals

对于 Term-based 类型(例如 keyword)字段,使用自增数字而不是原值来存储 doc_values,并且维护这个自增数字与实际值的映射关系,这个映射关系是 segment 级别的。映射关系大致如下:

----------------------
term      |  ordinal
----------------------
中国       |  1
日本       |  2
美国       |  3
英国       |  4
----------------------

这样做有很多的好处:

  • 更极致的压缩比
  • 更高的性能

当进行聚合时,例如 terms 聚合,对于每个分片,对每个 segment 的 ordinal 进行分组搜集,合并结果时根据映射关系转化为原值。

global ordinals

ordinals 是 segment 级别的,但是我们做聚合操作时往往需要结合多个 segment 的结果,而每个 segment 的 ordinals 映射关系是不一致的,所以为了能够进行聚合等操作, Elasticsearch 会在每个分片上创建 global ordinals 结构 —— 一个全局统一的映射,它维护了全局的 ordinal 与每个 segment 的 ordinal 的映射关系。

构建时机

默认情况下,global ordinals 是在第一次搜索需要用到 global ordinals 时才构建的,这种方式有利于索引写入效率。 也可以配置使 global ordinals 提前构建,以此来提高搜索效率。

PUT my-index-000001/_mapping
{
  "properties": {
    "tags": {
      "type": "keyword",
      "eager_global_ordinals": true
    }
  }
}

加入上面的配置以后,每次执行 refresh 操作都会构建 global ordinals,相当于把搜索时候花费的构建成本转移到写入时,所以会对写入效率有一定的影响。

对于已经冻结的索引来说,每次搜索都会构建 global ordinals,搜索完成后丢弃掉,所以建议已冻结的索引不要开启 eager_global_ordinals

global ordinals 对基数大的字段的影响

通过上面的介绍可以了解到,global ordinals 默认是延时构建,在第一次查询例如 term aggregation 使用到时才会构建。 在需要频繁写入和查询的时候,会不断的触发 global ordinals 的重建,对于基数大的字段,构建成本较大,从而造成比较明显的性能问题。 针对上面的情况,Elasticsearch 提供了一些建议:

  1. global ordinals 是分片级别的,仅会对写入了新 segment 的的分片进行重建 global ordinals,所以对于存放时序数据的索引,可以把索引按照一定时间周期来创建,而不是全部数据放到一个索引里,这样可以有效减少重建的分片数和数据量。
  2. 把构建 global ordinals 的时机提前到写入后,而不是第一次查询时再构建,可以提高一定的查询效率,但是这种做法相应的会增加写入的负担,为了减少构建 global ordinals 的次数,可以配合增大索引的 refresh interval 来使用。
  3. term aggregation 不采用 global ordinals 的方式,而是直接使用 term 原值去聚合,这样就不需要构建 global ordinals 了,但是这种情况一定要谨慎使用,通常是在已知数据量较少或者过滤剩下较少数据时才使用。使用方式是聚合时把 execution_hint 设置为 map:
"aggregations": {
  "emails": {
    "terms": {
      "field": "email",
      "execution_hint": "map"
    }
  }
}

总结

  1. Elasticsearch 为了方便聚合和排序,使用 doc_values 列式存储结构来保存数据。
  2. 为了提高聚合效率和压缩率,Elasticsearch 使用 global ordinals 来代表 term 原值存储。
  3. global ordinals 是分片级别的,segment 修改后也需要相应的重建,默认是延时构建,即第一次查询时再构建。
  4. global ordinals 的机制对于基数大的字段有比较大的影响,查询时需要注意这一点。

参考

doc_values

eager_global_ordinals

Elasticsearch Global Ordinals and High Cardinality Fields