大数据-184 Elasticsearch Doc Values 机制详解:列式存储如何支撑排序/聚合/脚本

TL;DR

  • 场景:ES 排序/聚合/脚本访问字段值时,倒排索引不擅长按"列"读数据
  • 结论:Doc Values 是磁盘列式结构(除 text 外多数类型默认开启),用来高效读字段值
  • 产出:给出可落地的字段选型、mapping 约束、常见报错定位与修复速查

版本矩阵

说明 验证状态 来源
ES doc_values:磁盘列式结构,主要用于排序/聚合/脚本访问字段值 官方文档
keyword 有 doc values;text 默认没有 doc values,聚合/排序需 fielddata 或改用 keyword 社区一致结论
既有索引的 mapping 不能"替换/删除",需要新建索引 + reindex 才能彻底改字段类型/结构 部分 社区/实践共识
"analyzed strings 不能用 doc values"在旧版本表述为 string/not_analyzed,ES 5+ 已拆分为 text/keyword,不要混用旧术语 历史/版本差异
OpenSearch:doc values 默认对绝大多数字段开启,text 例外(与 ES 认知一致) OpenSearch 参考

DocValues 机制

Doc Values 是一种以列式存储的索引机制,用于在检索时优化磁盘读取操作。与传统的行式存储不同,Doc Values 将每个字段的所有值按列组织存储,这种结构特别适合分析型查询场景。以下是其核心特点:

  1. 存储结构对比:
  • 反向索引:基于倒排表的结构,主要用于全文检索场景,可以快速查找文档中包含某个词的情况
  • Doc Values:采用紧凑的列式存储格式,每个字段的值被连续存储在磁盘上,支持高效的顺序读取
  1. 性能优势:
  • 内存效率:仅需加载查询涉及的列,而非整个文档
  • CPU缓存友好:连续存储的列数据能更好利用CPU缓存行
  • 压缩率高:同列数据通常具有相似性,可采用更高效的压缩算法
  1. 典型应用场景:

排序场景:

  • 示例:对商品按价格排序时,只需顺序读取price列的Doc Values
  • 优势:相比加载完整文档,可减少90%以上的IO操作

聚合场景:

  • 支持操作:terms、sum、average、percentiles等
  • 实际案例:计算某时间段内订单金额的分布情况
  • 性能对比:比使用脚本计算快5-10倍

过滤场景:

  • 适用操作:范围过滤(price>100)、精确匹配(status="active")等
  • 实现原理:通过bitmap快速筛选符合条件的文档ID
  • 特殊优化:对数值类型支持位图索引加速
  1. 实现细节:
  • 默认开启:在Elasticsearch等系统中通常默认启用
  • 存储位置:既支持内存映射也支持磁盘持久化
  • 更新机制:采用追加写方式,定期合并优化
  1. 使用建议:
  • 适合字段:高基数字段、需要频繁聚合/排序的字段
  • 不适合场景:大文本字段(超过几KB)、很少被查询的字段
  • 配置参数:可通过"doc_values: false"显式关闭

通过合理使用Doc Values,可以显著提升分析型查询的性能,特别是在处理大数据集时效果更为明显。

为什么要有Doc Values

Elasticsearch之所以搜索这么迅速,归功于它的倒排索引设计,然后它也不是万能的,倒排索引的检索性能是非常快的,但是在字段排序时却不是理想的结构:

shell 复制代码
Term Doc_1 Doc_2
-------------------------
quick  |   | X
the    | X |
brown  | X | X
dog    | X |
dogs   |   | X
fox    | X |
foxes  |   | X
in     |   | X
jumped | X |
lazy   | X | X
leap   |   | X
over   | X | X
summer |   | X
the    | X |
------------------------

如上面的内容中可以看出,它只有词对应doc,但是并不知道每一个doc中的内容,那么如果想排序的话每一个doc都去获取一次文档内容岂不是非常耗时?DocValues的出现使得这个问题迎刃而解。 字段的 doc_values 属性有两个值,true、false,默认是true,即开启。 当 doc_values 为 false 时,无法基于该字段排序、聚合、在脚本中访问字段值。 当 doc_values 为 true 时,ES会增加一个相应的正排索引,这增加的磁盘占用,也会导致索引数据速度慢一些。

Doc Values 是列式存储的,这意味着每个字段值都以列的形式存储在磁盘中,而不是像原始文档那样存储在行中。这种方式有助于优化数据的读取,因为在执行排序或聚合时,Elasticsearch 只需访问与操作相关的字段,而不需要加载整个文档。

每个文档的字段值在索引时被预处理,并以压缩的形式存储为 Doc Values,这些值会以内存映射文件(memory-mapped file)的方式加载到内存中,以便进行快速读取。

Doc Values举例

创建一个索引:

json 复制代码
PUT /person
{
  "mappings" : {
    "properties" : {
      "name" : {
        "type" : "keyword",
        "doc_values": true
      },
      "age" : {
        "type" : "integer",
        "doc_values": false
      }
    }
  }
}

写入相对应的数据:

shell 复制代码
POST _bulk
{ "index" : { "_index" : "person", "_id" : "1" } }
{ "name" : "明明", "age": 22 }
{ "index" : { "_index" : "person", "_id" : "2" } }
{ "name" : "丽丽", "age": 18 }
{ "index" : { "_index" : "person", "_id" : "3" } }
{ "name" : "媛媛", "age": 19 }

执行结果如下图所示: 进行全量查询,确认一下数据的情况:

json 复制代码
POST /person/_search
{
  "query": {
    "match_all": {}
  },
  "sort" : [
    {
      "name": {
        "order": "desc"
      }
    }
  ]
}

执行结果如下图所示:

什么是Doc Values

Doc Values 通过转置倒排索引和正排索引两者间的关系来解决这个问题,倒排索引将词项映射到包含它的文档:

shell 复制代码
Doc Terms
-----------------------------------------------------------------
Doc_1 | brown, dog, fox, jumped, lazy, over, quick, the
Doc_2 | brown, dogs, foxes, in, lazy, leap, over, quick, summer
Doc_3 | dog, dogs, fox, jumped, over, quick, the
-----------------------------------------------------------------

当数据被转置后,想要收集到每个文档行,获取所有的词项就非常简单了。

深入理解ES Doc Values

DocValues是Lucene索引机制中的重要组成部分,它与倒排索引同时生成,具有以下特性:

  1. 生成机制:
  • 在索引创建阶段,DocValues会与倒排索引并行构建
  • 基于Segment级别生成,每个Segment都包含自己独立的DocValues数据
  • 生成后是不可变的,与倒排索引保持一致性
  1. 存储特性:
  • 采用高效的序列化方式将数据结构持久化到磁盘
  • 存储格式经过优化,支持快速随机访问和顺序扫描
  • 与倒排索引共享相同的Segment合并策略
  1. 内存管理优势:
  • 利用操作系统文件缓存机制,而非JVM堆内存
  • 当数据量小于系统可用内存时(workingset < RAM):
    • 操作系统会自动将频繁访问的DocValues保留在内存中
    • 实现接近内存数据库的访问速度
    • 典型场景:过滤、排序、聚合等操作能获得极快响应
  1. 大容量处理能力:
  • 当数据量超过内存容量时(workingset >> RAM):
    • 操作系统会自动将不活跃数据换出到磁盘
    • 虽然访问速度有所下降,但避免了OOM风险
    • 典型场景:处理数十亿文档时仍能保持稳定性能
  1. 应用场景示例:
  • 电商平台中百万级商品的价格排序
  • 日志分析系统中的时间范围过滤
  • 大数据分析中的分组聚合计算

这种设计使DocValues在保持高性能的同时,也能处理远超物理内存容量的数据集,为大规模数据应用提供了可靠的底层支持。

DocValues 压缩

从广义来说,DocValues本质上是一个序列化的列式存储,这个结构非常适用于聚合、排序、脚本等操作。而且,这种存储方式非常的便于压缩,特别是数字类型,这样可以减少磁盘空间并且提高访问速度。 下面我们看一组数字类型的DocValues:

shell 复制代码
Doc Terms
-----------------------------------------------------------------
Doc_1 | 100
Doc_2 | 1000
Doc_3 | 1500
Doc_4 | 1200
Doc_5 | 300
Doc_6 | 1900
Doc_7 | 4200
-----------------------------------------------------------------

你会注意到这里每个数字都是100的倍数,DocValues会检测一个段里面的所有数值,并使用一个最大公约数,方便做进一步的数据压缩,我们可以对每个数字都除以100,然后得到:[1,10,15,12,3,19,42]。现在这些数字变小了,只需要很少的位就可以存储下,也减少了磁盘存放的大小。

DocValues在压缩过程中使用如下技巧,它会依次检测以下压缩模式:

  • 如果所有的数值各不相同(或缺失),设置一个标记并记录这些值
  • 如果这些值小于256,将使用一个简单的编码表
  • 如果这些值大于256,检测是否存在一个最大公约数
  • 如果没有存在最大公约数,从最小的数值开始,统一计算偏移量进行编码 当然如果存储String类型,其一样可以通过顺序表对String类型进行数字编码,然后再把数字类型构建DocValues。

禁用 Doc Values

DocValues 默认对所有字段启动,除了 analyzed strings。也就是说所有的数字、地理坐标、日期、IP和不分析(not_analyzed)字符类型都会默认开启。 analyzed strings暂时还不能使用 DocValues,是因为经过分析以后得文本会生成大量的Token,这样非常影响性能。 虽然DocValues非常好用,但是如果你存储的数据确实不需要这个特性,就不如禁用它,这样不仅节省磁盘空间,也许会提升索引的速度。 要禁用DocValues,在字段的映射mapping设置doc_values:false即可。例如,这里我们创建了一个新的索引,字段 session_id禁用了DocValues:

json 复制代码
DELETE /my_index
{
  "mappings": {
    "properties": {
      "session_id": {
        "type": "keyword",
        "doc_values": false
      }
    }
  }
}

通过设置 doc_values:false,这个字段将不能被用于聚合、排序以及脚本操作

带来的优势

  • 减少内存使用:由于 Doc Values 将字段值存储在磁盘上并在需要时读取,因此相比内存中保持字段值的方式(例如 fielddata),它极大地减少了内存的使用。
  • 高效的磁盘读取:Doc Values 的列式存储意味着在执行排序或聚合操作时,Elasticsearch 可以只加载所需的字段值,而不必加载整个文档。
  • 提高排序和聚合的性能:对于经常需要排序或聚合的字段,Doc Values 可以显著提高性能,因为它优化了读取路径。

使用场景

  • 排序:例如,用户需要根据时间戳排序查询结果,Doc Values 会提供优化的列式存储,直接从磁盘读取时间戳的值进行排序,而不需要加载整个文档。
  • 聚合:在执行例如统计某个字段的平均值、最大值或分布情况时,Doc Values 可以极大地提高查询的响应速度,因为只需读取相关字段即可。
  • 范围查询:例如查找价格在一定范围内的文档时,Doc Values 允许快速扫描价格字段而不涉及文档的其他内容。

错误速查

症状 根因 定位 修复
sort/terms agg 报:Fielddata is disabled on text fields text 字段上做聚合/排序;text 默认无 doc values GET index/_mapping 看字段类型是否为 text,是否存在 xxx.keyword 查询改用 xxx.keyword;或显式 fielddata=true(评估堆内存与热度);长期方案:建新索引用 text + keyword 多字段
按字段排序/聚合失败,提示该字段不支持 doc values 字段类型不支持或被设置 doc_values:false mapping 中查 doc_values 与字段类型 对需要排序/聚合/脚本的字段保持 doc_values:true(可省略默认);对不需要分析能力的字符串用 keyword
PUT mapping 后发现"没生效/改不掉/删不掉字段" ES mapping 不能替换或移除既有字段定义 PUT _mapping 返回与实际 mapping 对比;检查是否试图修改已有字段参数 走"新建索引 + 正确 mapping + reindex + 切别名"的迁移路径;不要期待在线覆盖式改 mapping
照抄示例用 DELETE /index 携带 body 设置 mapping,执行报错 DELETE 用于删索引,不是建 mapping;示例请求方法不匹配 回看请求方法与 API:是否把建索引/建 mapping 写成 DELETE 创建索引用 PUT /index {mappings...};更新 mappingPUT /index/_mapping {properties...}(但不保证可改已有字段)
doc_values 关闭后,磁盘省了但查询/脚本功能缺失 关闭 doc values 会直接丢失排序/聚合/脚本取值能力 mapping 检查 doc_values:false;复现:sort/agg/script 立刻失败 对"只写入不分析"的字段才关;若线上已关且必须恢复,通常需要新索引重建(依赖具体字段与版本限制)
聚合/排序延迟抖动,CPU/IO 异常 working set 大、冷热不均;把 textfielddata 导致堆压力 看 JVM 堆、GC、热字段;确认是否启用 fielddata 优先用 doc valueskeyword/numeric/date);避免在 text 上启 fielddata;必要时做字段降维/分桶/预聚合

其他系列

🚀 AI篇持续更新中(长期更新)

AI炼丹日志-29 - 字节跳动 DeerFlow 深度研究框斜体样式架 私有部署 测试上手 架构研究 ,持续打造实用AI工具指南! AI研究-132 Java 生态前沿 2025:Spring、Quarkus、GraalVM、CRaC 与云原生落地

💻 Java篇持续更新中(长期更新)

Java-196 消息队列选型:RabbitMQ vs RocketMQ vs Kafka MyBatis 已完结,Spring 已完结,Nginx已完结,Tomcat已完结,分布式服务已完结,Dubbo已完结,MySQL已完结,MongoDB已完结,Neo4j已完结,FastDFS 已完结,OSS已完结,GuavaCache已完结,EVCache已完结,RabbitMQ正在更新... 深入浅出助你打牢基础!

📊 大数据板块已完成多项干货更新(300篇):

包括 Hadoop、Hive、Kafka、Flink、ClickHouse、Elasticsearch 等二十余项核心组件,覆盖离线+实时数仓全栈! 大数据-278 Spark MLib - 基础介绍 机器学习算法 梯度提升树 GBDT案例 详解

相关推荐
四月__2 小时前
http八股
后端
沐森2 小时前
rust并发
后端
喵个咪2 小时前
开箱即用的 GoWind Admin|风行,企业级前后端一体中后台框架:Casbin集成指南
后端·go
墨守城规2 小时前
FutureTask源码分析
后端
梨子同志2 小时前
Java 基础语法详解
后端
bcbnb2 小时前
详细教程:iOS应用中Swift代码混淆步骤与工具推荐
后端
expect7g2 小时前
Paimon源码解读 -- Compaction-8.专用压缩任务
大数据·后端·flink
开心就好20252 小时前
H5 混合应用加密 Web 资源暴露到 IPA 层防护的完整技术方案
后端
bcbnb2 小时前
最新版本iOS系统设备管理功能全面指南
后端