作者:来自 Elastic Alan Woodward

DocValuesSkippers 为 Lucene DocValues 字段增加了块级跳过能力,使得在已排序或按插入顺序的索引上进行范围查询时加速,同时存储开销小于 0.1%。
想获得 Elastic 认证吗?了解下一期 Elasticsearch 工程师培训 什么时候开课!你可以开始一个免费云试用,或者立即在你的本地机器上尝试 Elastic。
DocValuesSkippers 为 Lucene DocValues 添加了块级跳过能力,使范围查询在与文档插入顺序或索引排序相关的字段上显著更快,而无需将数据复制到块 k 维树(BKD tree)中。skipper 索引的大小不到基础 DocValues 字段的 0.1%,索引时开销极小,并且在查询时如果可用,Lucene 会自动应用它。如果你的索引定义了排序,或者你的时间戳、计数器或数值字段大致按顺序写入,那么启用 skippers 可以减少范围查询需要访问的文档数量。
问题:DocValues 很适合读取,但不太适合搜索
Lucene 可以用两种方式存储数值:DocValues 是按文档的列式存储,可以快速查找单个文档的值;BKD tree 是一种索引结构,可以高效执行范围搜索。DocValues 用于排序、分组(faceting)和聚合,但不太适合搜索,因为大多数查询最终需要检查索引中的每个文档。全表扫描很慢!因此,为了快速查询和快速聚合,你需要这两种数据结构,本质上是在存储同一份数据两次。
不过在很多情况下,索引本身具有内在结构,这意味着 DocValues 字段中的数值与内部 docid[0] 大致相关;例如,带时间戳的日志条目通常会按近似顺序写入索引,因此 docid 较低的文档也会有较小的时间戳。如果索引定义了排序,那么对于属于排序字段的字段值与 docid 之间会存在精确对应关系。在这种情况下,完整的 BKD 索引是过度设计;我们真正需要的是一种数据结构,将列式存储按块划分,并允许查询高效识别哪些数据块可以被排除在搜索之外。这就是 DocValuesSkipper 的作用。
什么是 DocValuesSkipper?
DocValuesSkipper 是一种可选的、分层的跳过索引,用于覆盖 DocValues 字段,由 Elastic 的 Ignacio Vera 在 Lucene 10.0 中引入。写入时,你可以通过在 FieldType 上设置 DocValuesSkipIndexType 来启用 skipper,或者使用 XXDocValuesField 类中的 indexedField 语法糖方法之一来启用。
每个索引段[1]中的基础 DocValues 字段会被划分为每 4096 个文档一个块,每个块都会记录其 min/max docid、该块中出现的最小值和最大值,以及它覆盖的文档数量(如果部分文档缺失值,这个数量可能不同于 maxDoc - minDoc)。随后,这些块会通过合并连续区间来构建层级结构:底层的 8 个区间会在第 1 层合并为一个更大的区间,如此递进,最多可达 4 层深度。最粗的一层每个块大约覆盖 200 万个文档。

DocValuesSkippers 可以用于 numeric、sorted_numeric、sorted 和 sorted_set 类型的 DocValues。对于后两种类型,跳过索引中的值会使用 ordinals(序号值)[2]。
DocValuesSkippers 如何加速范围查询?
Lucene 可以在查询时利用 skippers 加速多种操作:
-
范围查询(range queries) 可以使用基于 skipper 的 block iterator,快速跳过那些完全不在目标范围内的文档块。当范围查询与更复杂的查询(例如短语匹配)组合时,还可以利用 skipper 构建 two-phase iterator,从而提升性能。
-
排序剪枝(sort pruning) 可以使用 skippers 在 top-k 查询中排除部分数据块;当已经找到足够多的匹配文档来填充结果列表后,任何排序值低于当前结果列表底部的文档都可以被跳过。
-
MultiTermQueries(例如正则或通配符查询)可以被重写为 ordinal range queries,然后像数值范围查询一样使用 skipper 加速。
何时使用 DocValuesSkippers:相关字段 vs 随机数据
如果 skipper 字段是索引排序的一部分(或与其相关),字段值会按 docid 聚集,从而使 skip block 的 min/max 边界非常紧凑,大量文档区域可以被完全排除在搜索之外。
相反,如果值在文档中是随机分布的,那么每个 block 几乎都会覆盖完整的值范围,导致无法跳过任何数据块,这时 skipper 索引就只会带来额外开销。
| 字段特性 Skipper 效果 | |
|---|---|
| 与索引排序相关 | 高:min/max 边界紧凑,可跳过大量区域 |
| 与插入顺序相关(例如时间戳) | 高:近似相关仍然有效 |
| 随机分布的值 | 低:block 覆盖完整范围,无法跳过 |
| 属于定义好的索引排序字段 | 最高:精确 value-docID 关联 |
Skipper 索引占用磁盘空间非常少(通常小于基础 DocValues 字段的 0.1%),但在索引和 merge 阶段会带来一些额外开销,因此最适合用于那些与索引顺序具有一定相关性的字段。
DocValuesSkippers 在 Lucene 的未来方向
DocValuesSkippers 未来可能会有多项改进方向,包括索引阶段(提升构建速度、利用 skipper 元数据优化 merge 过程),以及查询与聚合阶段(更好的基数估计、增加更多元数据以支持快速 sum 和 average 计算等)。值得持续关注这一方向的发展。
参考说明
[0] docid: Lucene 在内部使用的一个整数值,用于标识索引中的单个文档。
[1] segment(段): Lucene 索引被划分为多个不可变的索引段(segment)。搜索会在每个段上分别执行(可以顺序或并行),最后将结果合并,形成整个索引的查询结果视图。
[2] ordinal(序号): 基于 term 的 DocValues 使用一种双结构存储:一个 terms dictionary(词典)保存字段的所有 term 值,并为每个 term 分配一个 ordinal(序号)。随后,每个文档的列式 DocValues 存储该 ordinal。这样可以用整数 ordinal 进行高效比较,需要时再通过 ordinal 去词典中反查原始 term。
原文:Faster range queries in Lucene: how DocValuesSkippers work - Elasticsearch Labs
2145

被折叠的 条评论
为什么被折叠?



