3. 缓存失效机制与触发条件:Refresh、Flush、Merge 操作如何导致缓存失效,以及 Translog 的作用

在 Elasticsearch 中,缓存一致性并非自动保证,而是由一系列底层操作(Refresh、Flush、Merge)的触发条件与副作用共同决定。理解这些操作如何使缓存失效,以及 Translog 如何作为“安全网”保障数据不丢失,是构建可观测性与故障排查能力的基础。

3.1 核心操作概述与缓存失效全景

下表总结了三个关键操作对缓存的影响,以及 Translog 的补偿角色:

操作 触发条件 缓存失效范围 数据可见性 磁盘写入
Refresh 默认 1s 间隔 / 手动 POST /_refresh 文件系统缓存(OS Cache)中的段变为可搜索,但不清空 Filter Cache 或 Field Data Cache 新数据变为 searchable(近实时) 仅写入文件系统缓存,未 fsync 到磁盘
Flush 默认 30min 或 Translog 达到 512MB / 手动 POST /_flush 清空 Translog,生成新的提交点(commit point),可能触发段合并后的缓存失效 数据持久化到磁盘,保证 crash-safe 执行 fsync,数据落盘
Merge 后台自动触发(段数量/大小阈值) 直接导致被合并段的 Filter Cache、Field Data Cache、Request Cache 全部失效 合并后段替换旧段,查询路由更新 重写段文件,删除旧段
可观测性要点: 在监控中,refresh.time、flush.time 和 merge.time 的突增往往伴随着缓存命中率下降。建议在 Grafana 面板中同时叠加这三条曲线与 fielddata.evictions 指标。

3.2 Refresh 操作:近实时搜索的代价

Refresh 操作将内存缓冲区(Buffer)中的数据写入一个新的段(Segment),并打开该段使其可被搜索。但此过程不保证数据已落盘,仅写入文件系统缓存(Page Cache)。

缓存失效机制

  • Filter Cache 不受影响: Refresh 不改变已有段的过滤结果,因此 Filter Cache 中的条目仍然有效。
  • Field Data Cache 不受影响: 新段加入后,Field Data 仅在首次对该段执行聚合或排序时才会加载,不会主动驱逐旧缓存。
  • Request Cache 部分失效: 如果 Refresh 改变了索引的元数据(如新增字段映射),可能导致部分查询缓存失效,但通常影响较小。
常见误区: 很多运维人员认为 Refresh 会清空缓存,实际上它只负责“让新数据可见”。缓存失效主要发生在 Merge 阶段。频繁 Refresh(如设置 refresh_interval: 100ms)会导致段数量激增,间接加速 Merge 触发,从而间接导致缓存失效。

3.3 Flush 操作:持久化与 Translog 的截断

Flush 操作执行一次完整的提交(commit):将文件系统缓存中的数据 fsync 到磁盘,生成一个新的提交点,并清空 Translog。Flush 本身不直接导致 Filter Cache 或 Field Data Cache 失效,但它会触发以下连锁反应:

  1. Translog 截断: 清空已提交的 Translog 文件,释放磁盘空间。
  2. 段文件落盘: 所有未 fsync 的段数据写入磁盘,此时段变为“安全”状态。
  3. 可能触发 Merge: Flush 后如果段数量超过阈值,Merge 进程会被唤醒,进而导致缓存失效。

Translog 的作用

Translog(事务日志)是 Elasticsearch 保证数据不丢失的核心机制。它记录所有尚未落盘的索引/删除操作,类似于数据库的 WAL(Write-Ahead Log)。

特性 说明
故障恢复 节点宕机后重启,从最后一个提交点恢复段文件,然后重放 Translog 中未提交的操作,确保数据完整。
缓存一致性 Translog 不直接参与缓存管理,但它保证了即使缓存因 Merge 失效,数据也不会丢失。
性能影响 Translog 默认每 5s 或每次写操作后 fsync(取决于 index.translog.durability 设置),是写入延迟的主要来源之一。
故障排查线索: 如果发现节点重启后缓存预热时间异常长,应检查 Translog 重放耗时。在日志中搜索 recovery completed 和 translog recovery 关键字,结合 indices.recovery API 获取详细进度。

3.4 Merge 操作:缓存失效的“主犯”

Merge 操作将多个小段合并为一个大段,以减少段数量、提升查询性能。但合并过程直接且彻底地导致缓存失效,是缓存一致性问题的最大来源。

缓存失效的详细过程

  1. 段被标记为删除: 被合并的旧段不再用于查询,但仍在内存中。
  2. 新段创建并打开: 新段替换旧段,查询路由切换到新段。
  3. 缓存条目失效:
    • Filter Cache: 所有基于旧段的位集(bitset)被逐出。
    • Field Data Cache: 所有基于旧段的字段值被逐出,新段需要重新加载。
    • Request Cache: 如果查询涉及被合并的段,缓存结果被标记为无效。
  4. 缓存预热: 后续查询触发缓存重新加载,导致查询延迟突增。
可观测性指标: 使用 GET /_nodes/stats/indices/merge 查看当前合并线程数、合并文档数、合并时间。当 merge.current 持续大于 0 且 fielddata.evictions 同步上升时,说明缓存正在被 Merge 大量驱逐。

3.5 缓存失效链路与 Translog 的补偿角色(SVG 图)

下图展示了从数据写入到缓存失效的完整链路,以及 Translog 如何作为安全网保障数据不丢失:

缓存失效链路与 Translog 补偿角色 缓存失效链路与 Translog 补偿角色 写入请求 Translog 记录 内存 Buffer Refresh → 新段可搜索 Flush → 落盘 + 截断 Translog Merge → 缓存失效 Filter/FieldData/Request Cache 失效 Translog 补偿角色 • 节点宕机后重放未提交操作 • 保证缓存失效时数据不丢失 • 与 Flush 配合截断日志

3.6 缓存失效的触发条件总结

以下列表归纳了各操作触发缓存失效的具体条件,便于在故障排查时快速定位:

  • Refresh 触发缓存失效的条件:
    • 几乎不直接触发缓存失效,但频繁 Refresh 会导致段数量增长,间接加速 Merge。
    • 如果 Refresh 伴随映射更新,可能导致 Request Cache 部分失效。
  • Flush 触发缓存失效的条件:
    • 不直接失效缓存,但 Flush 后如果段数量超过 index.merge.policy.segments_per_tier 阈值,会触发 Merge。
    • Flush 清空 Translog,释放内存,但不会影响 Filter/Field Data Cache。
  • Merge 触发缓存失效的条件:
    • 任何 Merge 操作都会导致被合并段的缓存失效。
    • 大段合并(如 max_merged_segment 达到 5GB)会导致大量缓存被逐出。
    • 强制合并(Force Merge)会一次性失效所有相关段的缓存,影响最大。
生产环境建议: 在写入高峰期,应避免执行 Force Merge 或手动 Flush,以免引发大规模缓存失效导致查询延迟飙升。建议将 index.merge.scheduler.max_thread_count 限制为 Math.min(4, Runtime.getRuntime().availableProcessors() / 2),降低 Merge 对缓存的冲击。

3.7 可观测性视角:如何监控缓存失效

在链路追踪与故障排查中,以下指标是判断缓存失效是否由 Refresh/Flush/Merge 引起的直接依据:

指标 API 路径 关联操作 异常阈值参考
fielddata.evictions GET /_nodes/stats/indices/fielddata Merge 持续 > 10/s 需关注
filter_cache.evictions GET /_nodes/stats/indices/filter_cache Merge 持续 > 50/s 需告警
merge.total_time_in_millis GET /_nodes/stats/indices/merge Merge 单次 Merge > 10s 需分析
refresh.total_time_in_millis GET /_nodes/stats/indices/refresh Refresh 单次 Refresh > 1s 需优化
translog.size_in_bytes GET /_nodes/stats/indices/translog Flush 超过 1GB 需检查 Flush 频率
链路追踪实践: 在 APM 工具中,将 search_query_latency 与 merge.current 绘制在同一时间轴。如果查询延迟峰值与 Merge 活动窗口高度重合,即可确认缓存失效是延迟突增的根因。此时应优先调整 Merge 策略(如增大 index.merge.policy.max_merged_segment)而非盲目扩容。