背景
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 提供了一些建议:
- global ordinals 是分片级别的,仅会对写入了新 segment 的的分片进行重建 global ordinals,所以对于存放时序数据的索引,可以把索引按照一定时间周期来创建,而不是全部数据放到一个索引里,这样可以有效减少重建的分片数和数据量。
- 把构建 global ordinals 的时机提前到写入后,而不是第一次查询时再构建,可以提高一定的查询效率,但是这种做法相应的会增加写入的负担,为了减少构建 global ordinals 的次数,可以配合增大索引的 refresh interval 来使用。
- term aggregation 不采用 global ordinals 的方式,而是直接使用 term 原值去聚合,这样就不需要构建 global ordinals 了,但是这种情况一定要谨慎使用,通常是在已知数据量较少或者过滤剩下较少数据时才使用。使用方式是聚合时把 execution_hint 设置为 map:
"aggregations": {
"emails": {
"terms": {
"field": "email",
"execution_hint": "map"
}
}
}
总结
- Elasticsearch 为了方便聚合和排序,使用 doc_values 列式存储结构来保存数据。
- 为了提高聚合效率和压缩率,Elasticsearch 使用 global ordinals 来代表 term 原值存储。
- global ordinals 是分片级别的,segment 修改后也需要相应的重建,默认是延时构建,即第一次查询时再构建。
- global ordinals 的机制对于基数大的字段有比较大的影响,查询时需要注意这一点。