2. ES 缓存体系深度剖析:Node Query Cache、Shard Request Cache、Field Data Cache、Indexing Buffer 的角色与生命周期
Elasticsearch 的缓存体系是保障查询性能与写入吞吐的核心机制。不同缓存服务于不同的数据访问模式,拥有各自的生命周期、失效策略与内存边界。理解这四类缓存(Node Query Cache、Shard Request Cache、Field Data Cache、Indexing Buffer)的角色差异,是构建可观测性与排查缓存一致性问题的前提。
核心认知:ES 的缓存并非全局统一,而是按节点、分片、字段、索引四个维度分层设计。缓存一致性故障通常源于对生命周期边界(如分片移动、段合并、refresh 间隔)的忽视。
2.1 四类缓存全景对比
| 缓存名称 | 作用域 | 存储内容 | 生命周期 | 默认大小 | 失效触发 |
|---|---|---|---|---|---|
| Node Query Cache | 节点级(所有分片共享) | 过滤上下文(filter)的位集结果 | 节点存活期间,LRU 淘汰 | 堆的 10%(最大) | 节点重启、LRU 逐出、索引设置变更 |
| Shard Request Cache | 分片级 | 聚合、命中数、字段值等完整响应 | 分片存活期间,LRU 淘汰 | 堆的 1%(默认) | 分片 refresh、索引写入、分片迁移 |
| Field Data Cache | 节点级(按字段加载) | 全局序数(Global Ordinals)、doc values 内存化 | 字段被查询期间,LRU 淘汰 | 无上限(受断路器保护) | 段合并、字段不再被查询、节点重启 |
| Indexing Buffer | 分片级(写入缓冲区) | 未 refresh 的文档、倒排索引增量 | 从写入到 refresh 或 flush | 堆的 10%(分片均分) | 自动 refresh(默认 1s)、buffer 满、flush |
可观测性要点:Node Query Cache 与 Shard Request Cache 是查询性能的“双刃剑”——命中率高时延迟极低,但分片移动或 refresh 后缓存失效会导致瞬间毛刺。Field Data Cache 的全局序数重建是聚合查询的常见瓶颈。
2.2 Node Query Cache:过滤结果的节点级复用
Node Query Cache 缓存的是 filter 上下文中每个段(segment)的位集(bitset)。当多个查询共享相同过滤条件时,ES 直接复用位集,避免重复遍历倒排索引。
- 存储粒度:按段缓存,每个段对应一个位集(0/1 标记文档是否匹配)。
- 生命周期:节点存活期间持续存在,采用 LRU 淘汰策略。当段被合并(merge)时,旧段的位集失效,新段需要重新构建。
- 关键参数:
indices.queries.cache.size(默认 10% 堆)。 - 一致性风险:段合并后,旧缓存不会立即清除,但查询会跳过失效条目。如果监控到 cache evictions 过高,说明 filter 复用率低或段合并频繁。
# 查看节点级 Query Cache 统计
GET _nodes/stats/indices/query_cache
# 响应关键字段
"query_cache": {
"memory_size_in_bytes": 52428800,
"total_count": 120000,
"hit_count": 95000,
"miss_count": 25000,
"evictions": 3000
}
2.3 Shard Request Cache:分片级响应缓存
Shard Request Cache 缓存的是 完整的分片级响应,包括 hits 总数、聚合结果、字段值等。适用于 size=0 的聚合查询或计数查询。
- 存储粒度:按分片缓存,key 由请求的 JSON 体、索引名、分片 ID 共同决定。
- 生命周期:分片存活期间有效。当分片发生 refresh(新数据可见)时,该分片的缓存自动失效。
- 关键参数:
indices.requests.cache.size(默认 1% 堆),可在索引级别通过index.requests.cache.enable关闭。 - 一致性风险:写入后未 refresh 时,缓存仍返回旧数据。这是最常见的“缓存与写入不一致”场景——查询结果滞后于写入。
避坑指南:不要对实时性要求高的查询启用 Shard Request Cache。例如日志写入后 1 秒内查询,可能因为 refresh 间隔(默认 1s)未到而命中旧缓存。建议在 Dashboard 或报表类查询中显式设置
request_cache=true,在实时搜索中关闭。
2.4 Field Data Cache:全局序数与内存化 doc values
Field Data Cache 主要用于 聚合(aggregation)和排序。当字段启用 fielddata=true(text 类型默认关闭)或使用 doc_values 时,ES 会将字段的全局序数(Global Ordinals)加载到内存。
- 存储内容:每个字段的全局序数映射(term → ordinal)以及 doc values 的堆内缓存。
- 生命周期:字段被查询时加载,LRU 淘汰。段合并后,全局序数需要重建——这是聚合性能毛刺的主要来源。
- 关键参数:
indices.fielddata.cache.size(默认无上限,受indices.breaker.fielddata.limit断路器保护)。 - 一致性风险:段合并导致全局序数重建期间,聚合查询可能返回不完整结果或触发断路器异常。
# 查看 Field Data Cache 使用情况
GET _nodes/stats/indices/fielddata
# 响应示例
"fielddata": {
"memory_size_in_bytes": 209715200,
"evictions": 150
}
2.5 Indexing Buffer:写入阶段的临时缓冲区
Indexing Buffer 不是传统意义上的“缓存”,而是 写入路径的临时存储区。文档先写入 Indexing Buffer,经过 refresh 后变为可搜索的段。
- 存储内容:未 refresh 的文档数据、倒排索引增量、translog 记录。
- 生命周期:从文档写入到下一次 refresh(默认 1s)或 buffer 满(触发强制 refresh)。flush 操作会将 buffer 中的段持久化到磁盘。
- 关键参数:
indices.memory.index_buffer_size(默认 10% 堆,按分片均分)。 - 一致性风险:节点宕机时,Indexing Buffer 中未 flush 的数据会丢失(但 translog 可恢复)。Buffer 满导致写入阻塞是写入压测中的常见问题。
2.6 缓存生命周期与一致性闭环
下图展示了四类缓存在写入→refresh→查询→段合并全流程中的生命周期交互:
2.7 缓存一致性故障模式速查
| 故障现象 | 根因缓存 | 典型场景 | 排查手段 |
|---|---|---|---|
| 写入后查询结果未更新 | Shard Request Cache | Dashboard 报表查询命中旧缓存 | 检查 _cache/clear 时间戳;查看 refresh 间隔 |
| 聚合查询延迟突然飙升 | Field Data Cache | 段合并后全局序数重建 | 监控 fielddata.evictions;查看 merge 线程活动 |
| filter 查询性能波动 | Node Query Cache | 段合并频繁导致位集重建 | 检查 query_cache.evictions;优化段合并策略 |
| 写入阻塞、拒绝请求 | Indexing Buffer | 写入速率超过 refresh 释放速度 | 查看 indexing_pressure 指标;增大 buffer 或缩短 refresh 间隔 |
| 节点重启后查询缓慢 | 全部缓存冷启动 | 滚动重启后流量突增 | 预热查询(Warmup API);逐步放量 |
可观测性最佳实践:在监控面板中同时展示
query_cache.hit_count、request_cache.hit_count、fielddata.evictions 和 indexing_buffer.used。当 evictions 或 miss 率突增时,自动关联段合并事件与 refresh 间隔,快速定位缓存一致性故障根因。