Elasticsearch 列式存储详解:Doc Values 的原理与实践
本文来自于我关于 Elasticsearch 的系列文章。欢迎阅读、点评与交流~
1、Elasticsearch 深度解析:从核心原理到开发者实战
2、深入理解倒排索引(Inverted Index):搜索引擎的核心数据结构
3、Elasticsearch 中的聚合(Aggregations)技术详解
4、Elasticsearch 列式存储详解:Doc Values 的原理与实践

文章目录
- [Elasticsearch 列式存储详解:Doc Values 的原理与实践](#Elasticsearch 列式存储详解:Doc Values 的原理与实践)
-
- 一、从倒排索引的短板说起
- [二、什么是 Doc Values?](#二、什么是 Doc Values?)
- 三、工作原理
-
- [3.1 索引时创建](#3.1 索引时创建)
- [3.2 磁盘存储](#3.2 磁盘存储)
- [3.3 查询时加载](#3.3 查询时加载)
- 四、核心优势
-
- [4.1 存储效率高](#4.1 存储效率高)
- [4.2 查询性能好](#4.2 查询性能好)
- [4.3 按需加载,内存可控](#4.3 按需加载,内存可控)
- 五、典型应用场景
- 六、使用与管理
-
- [6.1 默认开启](#6.1 默认开启)
- [6.2 对 text 类型的处理](#6.2 对 text 类型的处理)
- [6.3 手动禁用](#6.3 手动禁用)
- [6.4 检查 Doc Values 使用情况](#6.4 检查 Doc Values 使用情况)
- 七、与倒排索引的分工协作
- 八、总结
在 Elasticsearch 中,提到"列式存储",实际上指的就是 Doc Values 。它与大家熟知的 _source(行式存储)互为补充,共同支撑起 Elasticsearch 强大的混合负载能力。本文将深入介绍 Doc Values 的设计动机、工作原理、核心优势以及如何在实际开发中使用和优化。
一、从倒排索引的短板说起
Elasticsearch 的核心搜索引擎是 倒排索引(Inverted Index):它记录了每个词项(Term)出现在哪些文档中,因此能在毫秒级完成全文检索。然而,倒排索引并非万能。
假设我们需要计算所有订单的 price 字段的平均值,倒排索引会怎么做?
- 遍历
price字段的所有词项(如 10、20、30...)。 - 对于每个词项,获取其对应的文档 ID 列表。
- 再根据文档 ID 去查找原始文档的值,进行累加。
这个过程需要大量的随机 I/O 和多次跳转,效率极低。更糟糕的是,倒排索引本身并不适合处理 排序(Sort) 、聚合(Aggregation) 和 脚本(Scripting) 这类需要"从文档到值"的操作。
为了解决这个问题,Elasticsearch 引入了 Doc Values------一种在索引时并行构建的、基于磁盘的列式存储结构。
二、什么是 Doc Values?
Doc Values 是 Elasticsearch 为每个字段(text 和 annotated_text 类型除外)默认开启的数据结构。它采用 列式存储 的方式,将同一字段的所有值按文档顺序组织成一列,从而让分析型操作可以像扫描数组一样高效。
一句话总结:倒排索引回答"哪些文档包含这个词",Doc Values 回答"这个文档的字段值是什么"。
三、工作原理
Doc Values 的构建和使用分为三个阶段:
3.1 索引时创建
当文档被写入索引时,Elasticsearch 在构建倒排索引的同时,也会为每个启用了 doc_values 的字段构建 Doc Values。这一过程是并行的,因此不会显著增加索引延迟。
3.2 磁盘存储
Doc Values 最终以列式格式写入磁盘上的两个文件:
.dvd:存储实际的列数据。.dvm:存储元数据(如值的偏移量、编码信息等)。
由于同一列的数据类型一致、取值模式相近,Elasticsearch 可以使用多种压缩算法(如增量编码、位压缩)大幅减少磁盘占用。
3.3 查询时加载
当查询包含排序、聚合等操作时,Elasticsearch 会按需将对应字段的 Doc Values 加载到操作系统的页面缓存(Page Cache)中。这个过程是按字段 且按需的,不会将整个索引的所有列都读入内存。
加载完成后,执行引擎可以顺序扫描列数据,极快地完成求和、平均值、分组等计算。
四、核心优势
4.1 存储效率高
- 列式存储天然适合压缩,相同类型的数据压缩比远高于行式 JSON。
- 实际生产环境中,启用 Doc Values 的字段比存储
_source可以节省 50%~90% 的磁盘空间。
4.2 查询性能好
- 顺序 I/O:扫描一列数据时,磁盘和 CPU 缓存友好。
- 避免 JSON 解析 :直接读取二进制列值,无需从
_source中解析复杂的 JSON 结构。 - 官方测试显示:对千万级文档进行聚合,使用 Doc Values 比从
_source解析快 10~100 倍。
4.3 按需加载,内存可控
- 只有查询中实际用到的字段才会加载到 Page Cache。
- 不会因为文档数量增加而导致内存爆炸,适合大规模数据分析。
五、典型应用场景
| 场景 | 说明 |
|---|---|
| 排序 | 例如按 timestamp 排序返回最新日志。 |
| 聚合 | terms、avg、sum、histogram 等聚合都依赖 Doc Values。 |
| 脚本计算 | 在 Painless 脚本中通过 doc['field'].value 访问值,性能远优于 params._source.field。 |
| 过滤后聚合 | 即使有查询条件,Elasticsearch 也会利用 Doc Values 对筛选后的文档集合进行高效聚合。 |
六、使用与管理
6.1 默认开启
对于所有 非 text 类型(如 keyword、long、date、boolean、ip 等),doc_values 默认为 true。
6.2 对 text 类型的处理
text 类型字段本身不支持 Doc Values。如果确实需要对一个全文检索字段进行排序或聚合,通常的做法是:
json
PUT my_index
{
"mappings": {
"properties": {
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
}
}
}
}
然后使用 title.keyword 字段进行排序/聚合,该 keyword 子字段默认开启 Doc Values。
6.3 手动禁用
如果某个字段 永远不会 用于排序、聚合或脚本,可以显式禁用以节省磁盘空间:
json
PUT my_index
{
"mappings": {
"properties": {
"session_id": {
"type": "keyword",
"doc_values": false
}
}
}
}
注意 :doc_values 设置只能在创建索引时指定,不可动态修改。如需更改,必须重建索引。
6.4 检查 Doc Values 使用情况
使用 Field data stats API 或 _field_usage API(需要 Elasticsearch 7.x+)可以查看各字段的 Doc Values 内存占用和访问频率。
七、与倒排索引的分工协作
| 倒排索引 | Doc Values(列式存储) | |
|---|---|---|
| 组织方式 | 词项 → 文档 ID 列表 | 文档 ID → 字段值 |
| 擅长操作 | 全文检索、精确匹配、范围查询 | 排序、聚合、脚本、TopN |
| 存储形式 | 词项字典 + 跳表 + 压缩的文档列表 | 压缩的列式数据(连续数组) |
| 内存占用 | 常驻内存(FST) + 磁盘 | 按需加载至 Page Cache,可置换 |
Elasticsearch 之所以强大,正是因为它同时拥有这两种数据结构,并根据查询类型自动选择最优的执行路径。
八、总结
- Doc Values 是 Elasticsearch 中的列式存储实现,专为排序、聚合等分析型操作优化。
- 默认开启,开箱即用,无需额外配置即可享受列式存储的性能红利。
- 存储与查询双高效:通过压缩减少磁盘占用,通过顺序 I/O 提升聚合速度。
- 与倒排索引形成互补:一个管搜索,一个管分析,共同支撑起 Elasticsearch 的混合负载能力。
在实际使用中,建议保持绝大多数字段的 doc_values 为开启状态,仅对明确不需要分析的超高基数或临时字段进行关闭。同时,对于 text 字段的分析需求,请使用 keyword 子字段作为替代。
通过理解 Doc Values 的设计哲学,我们可以写出更高效的映射(Mapping),也能在遇到聚合性能瓶颈时,快速定位问题------答案往往就藏在列式存储的每一列中。