Elasticsearch 的 doc_values 和 fielddata 有什么区别?
简化版
doc_values 是面向排序、聚合、脚本访问的列式磁盘结构,keyword、数字、日期等字段默认通常启用;fielddata 是 text 字段为了排序/聚合临时在堆内存中构建的结构,容易占用大量 JVM heap。生产中不要随便对 text 开 fielddata,通常用 .keyword 做聚合和排序。
详细版
倒排索引适合从 term 找文档,但排序和聚合经常要从文档找字段值。doc_values 就是为这种访问模式准备的列式结构,更多依赖磁盘和文件系统缓存。
text 字段默认没有 doc_values,因为它会分词;如果硬要对 text 排序或聚合,可能需要开启 fielddata,把 token 到文档的关系加载到 JVM heap,内存风险很高。面试回答抓住:查询检索看倒排,排序聚合看 doc_values;text 聚合优先用 keyword 子字段。
完整版教学
一、倒排索引不擅长排序和聚合
倒排索引回答的问题是:某个 term 出现在哪些文档里。
term "paid" -> doc1, doc7, doc9
term "new" -> doc2, doc5
但排序和聚合经常反过来问:每个文档的字段值是什么。例如按价格排序、按状态分桶、计算每天订单数。
doc1 -> price = 99
doc2 -> price = 35
doc3 -> price = 120
这种“按文档读列值”的访问模式,正是 doc_values 的用武之地。
记忆钩子:倒排负责“词到文档”,doc_values 负责“文档到值”。
二、doc_values 是列式结构
doc_values 是一种列式存储结构,适合排序、聚合和脚本访问字段值。keyword、numeric、date、boolean、ip 等字段通常默认启用。
| 操作 | 需要什么 | 常用结构 |
|---|---|---|
| 全文检索 | term -> docs | 倒排索引 |
| 精确过滤 | term -> docs | 倒排索引 |
| 排序 | doc -> value | doc_values |
| 聚合 | doc -> value | doc_values |
| 脚本字段访问 | doc -> value | doc_values |
由于 doc_values 更多利用磁盘和操作系统页缓存,它比把大量数据塞进 JVM heap 更稳。
三、fielddata 为什么危险
text 字段被分词后,天然适合全文检索,不适合直接排序和聚合。为了让 text 字段也能按 token 做聚合,Elasticsearch 可以构建 fielddata。
问题在于 fielddata 通常加载到 JVM heap,数据量一大就可能触发频繁 GC,甚至 OOM。
1000 万文档
每个 title 平均 8 个 token
可能产生 8000 万级 token-doc 关系
加载到 heap 风险极高
所以线上看到错误提示让你开启 fielddata 时,不要机械照做,先检查是不是应该用 .keyword 字段。
四、为什么 text.keyword 是常见设计
多字段 mapping 可以让一个字段同时支持全文搜索和精确聚合。
{
"mappings": {
"properties": {
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
title 用于 match 全文搜索,title.keyword 用于 term 精确匹配、排序和聚合。
{ "terms": { "field": "title.keyword" } }
这是很多 ES 面试和生产排障的标准答案。
五、什么时候关闭 doc_values
doc_values 也不是完全没有成本。它会占用磁盘空间。如果某个字段只用于全文检索,不会排序、聚合、脚本访问,可以考虑关闭 doc_values。
但大多数场景不建议过早关闭,尤其是 keyword、日期、状态、类型字段,后续很可能会用于聚合或排序。
{
"type": "keyword",
"doc_values": false
}
这个配置一旦用于生产,要确认不会影响已有查询。否则后面临时要做聚合,会发现字段能力不够。
六、排查聚合内存问题的思路
如果 ES 聚合导致内存高,可以按下面检查:
1. 聚合字段是不是 text
2. 有没有误开 fielddata
3. 是否应该改用 keyword 子字段
4. terms 聚合桶数量是否过大
5. 是否需要 composite aggregation 分页
6. JVM heap 和 circuit breaker 是否报警
不要只盯着机器内存。ES 的 JVM heap、文件系统缓存和磁盘结构分工不同,fielddata 问题尤其容易把 heap 打爆。
七、常见误区与追问
- 误区:倒排索引可以高效完成所有查询。 倒排适合检索,排序和聚合更依赖 doc_values。
- 误区:text 字段聚合报错就直接开启 fielddata。 fielddata 可能大量占用 heap,通常应使用 keyword 子字段。
- 误区:doc_values 在内存里,所以很危险。 doc_values 是列式磁盘结构,更多依赖文件系统缓存。
- 追问:为什么 keyword 能聚合,text 不适合? keyword 保留整体值并有 doc_values,text 分词后语义不同。
- 追问:fielddata 什么时候能用? 少量字段、明确 token 聚合需求、评估内存后才考虑。
- 追问:
ignore_above会影响什么? 超过长度的 keyword 值可能不进入 keyword 子字段,聚合和精确查询会受影响。
八、加强记忆
doc_values 和 fielddata 记成“列式稳,堆内险”。检索靠倒排,排序聚合靠 doc_values;keyword、数字、日期天然适合,text 字段如果硬聚合会牵出 fielddata,容易吃 JVM heap。生产里优先用 .keyword 子字段做精确过滤、排序和聚合,只有非常明确的 token 聚合需求才考虑 fielddata。