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→查询→段合并全流程中的生命周期交互:

ES 缓存生命周期与一致性闭环 ES 缓存生命周期与一致性闭环 写入请求 Indexing Buffer Refresh(生成段) 查询请求 Node Query Cache Shard Request Cache 段合并(Merge) Field Data Cache 失效 重建 一致性闭环关键点: 1. 写入 → Indexing Buffer → Refresh → 段可见 → Shard Request Cache 失效 2. 段合并 → Node Query Cache 位集失效 + Field Data Cache 全局序数重建 3. 节点重启 → 所有缓存清空;分片迁移 → 目标节点缓存冷启动

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 间隔,快速定位缓存一致性故障根因。