帮助中心/最新通知

质量为本、客户为根、勇于拼搏、务实创新

< 返回文章列表

【开发相关】【最佳实践】深度剖析Elasticsearch快照导致的磁盘异常占用

发表时间:2025-01-16 01:32:56 小编:主机乐-Yutio

说明

本文描述问题及解决方法同样适用于 腾讯云 Elasticsearch Service(ES)

前言

在生产环境中,我们遇到了一个严重的磁盘占用问题:2025年11月25日17:10,集群磁盘异常达到洪水位,全量索引发生只读,直到重启后才恢复。

最初怀疑是快照备份导致的,但经过深入排查发现:

真正的根因是:2025-11-25 16:53:16 开始对500TB存量数据执行大规模forcemerge操作,与快照备份任务并行执行,导致forcemerge产生的大量临时segment文件无法释放,最终磁盘占用暴增50TB!

本文将通过深入分析Elasticsearch 8.16源码,揭示forcemerge与快照备份交互时的磁盘占用陷阱。

问题现象

完整时间线

时间

事件

磁盘占用

11-24 下午

快照备份任务开始

500TB(正常)

11-25 16:53:16

? 开始对存量数据执行forcemerge

500TB

11-25 16:53:16 之后

forcemerge持续进行,磁盘开始异常增长

500TB → 不断增长

11-26 17:10

磁盘达到洪水位,索引只读

550TB

重启后

自动释放临时segment文件

500TB(恢复正常)

关键发现:

  • 存量数据规模:500TB(TB级别索引)

  • 正常写入量:相对于500TB存量很少(可忽略)

  • forcemerge开始时间:2025-11-25 16:53:16

  • 快照任务:11-24下午开始,11-25仍在进行中

  • 磁盘异常增长:50TB(全部是forcemerge产生的临时segment)

  • 重启释放:50TB空间被释放

相关截图:

核心原理

ForceMerge + 快照 = 磁盘炸弹

什么是ForceMerge?

ForceMerge是Elasticsearch提供的强制合并segment的操作,用于:

  • 减少segment数量,提高查询性能

  • 删除已标记删除的文档,释放磁盘空间

  • 提高压缩率

关键特点

  • 会读取多个小segment,合并成大segment

  • 合并过程中,旧segment和新segment同时存在

  • 合并完成后,旧segment才能被删除

ForceMerge的磁盘占用模型

展开
代码语言:TXT
自动换行
自动换行
AI代码解释
合并前: ├─ segment_1.si (10GB) ├─ segment_2.si (10GB) ├─ segment_3.si (10GB) └─ segment_4.si (10GB) 总计:40GB 合并中: ├─ segment_1.si (10GB) ← 旧segment,等待删除 ├─ segment_2.si (10GB) ← 旧segment,等待删除 ├─ segment_3.si (10GB) ← 旧segment,等待删除 ├─ segment_4.si (10GB) ← 旧segment,等待删除 └─ segment_5.si (40GB) ← 新segment,合并结果 总计:80GB(翻倍) 合并后(正常情况): └─ segment_5.si (40GB) ← 旧segment被删除 总计:40GB(恢复正常)
展开更多

快照是如何阻止ForceMerge清理旧Segment的?

当快照备份与forcemerge并行执行时:

1.  快照获取IndexCommit引用

  • 快照开始时,通过acquireIndexCommit()获取当前IndexCommit引用

  • 该IndexCommit包含forcemerge开始前的所有segment文件

2.  ForceMerge生成新Segment

  • forcemerge读取旧segment(segment_1, segment_2, ...)

  • 生成新的merged segment(segment_merged)

  • 创建新的IndexCommit

3.  旧Segment无法删除

  • 虽然forcemerge完成,但旧segment仍被快照引用

  • CombinedDeletionPolicy.onCommit()检查到引用计数>0

  • 跳过删除,旧segment继续占用磁盘

4.  磁盘占用翻倍

  • 新segment:40GB(forcemerge结果)

  • 旧segment:40GB(被快照引用,无法删除)

  • 总计:80GB

对于500TB存量数据,如果全部执行forcemerge,磁盘占用可能达到1000TB

ForceMerge与快照交互的核心逻辑

关键代码片段分析

1. ForceMerge入口

InternalEngine.forceMerge()

展开
代码语言:Java
自动换行
自动换行
AI代码解释
// InternalEngine.java:1950 @Override public void forceMerge(boolean flush, int maxNumSegments, boolean onlyExpungeDeletes, boolean upgrade, boolean upgradeOnlyAncientSegments) throws EngineException { ensureOpen(); // 关键:flush确保所有数据持久化 if (flush) { flush(false, true); } try (ReleasableLock lock = readLock.acquire()) { ensureOpen(); // 调用Lucene的forceMerge // 这会生成新的merged segment文件 indexWriter.forceMerge(maxNumSegments, true); // 关键:forcemerge完成后,创建新的IndexCommit indexWriter.commit(); // 这里会触发CombinedDeletionPolicy.onCommit() // 但如果有快照引用旧commit,旧segment不会被删除 } catch (Exception e) { throw new ForceMergeFailedEngineException(shardId, e); } }
展开更多
  • indexWriter.forceMerge()会读取所有旧segment,生成新的merged segment

  • 在合并过程中,旧segment和新segment同时存在,磁盘占用翻倍

  • indexWriter.commit()创建新commit后,会触发onCommit()检查是否可以删除旧commit

  • 如果快照正在进行,旧commit的引用计数>0,删除被跳过

2. 删除检查逻辑

CombinedDeletionPolicy.onCommit()

展开
代码语言:Java
自动换行
自动换行
AI代码解释
// CombinedDeletionPolicy.java:107 @Override public synchronized void onCommit(List<? extends IndexCommit> commits) throws IOException { // 更新safeCommit和lastCommit引用 updateCommits(commits); // 关键:遍历所有commit,决定哪些可以删除 for (IndexCommit commit : commits) { // ForceMerge场景:如果commit被快照引用,跳过删除 if (acquiredIndexCommits.containsKey(commit)) { // 这里就是问题所在! // forcemerge前的旧commit被快照引用 // 即使forcemerge完成,旧segment也无法删除 continue; // 跳过删除 } // 如果是safeCommit或lastCommit,保留 if (commit.equals(safeCommit) || commit.equals(lastCommit)) { continue; } // 其他commit可以删除 deleteCommit(commit); } }
展开更多
  • forcemerge完成后,会有两个commit:

    • commit_old:forcemerge前的commit,包含旧segment

    • commit_new:forcemerge后的commit,包含新merged segment

  • 如果快照引用了commit_old,acquiredIndexCommits.containsKey(commit_old) 返回 true

  • 删除被跳过,旧segment继续占用磁盘

  • 这就是为什么forcemerge后磁盘占用不降反升的原因

3. 重启时的清理逻辑

CombinedDeletionPolicy.onInit()

展开
代码语言:Java
自动换行
自动换行
AI代码解释
// CombinedDeletionPolicy.java:89 @Override public synchronized void onInit(List<? extends IndexCommit> commits) throws IOException { // 更新safeCommit和lastCommit updateCommits(commits); // 关键:删除所有旧commit for (IndexCommit commit : commits) { // 重启后acquiredIndexCommits为空 // 所以这个检查永远不会命中 if (acquiredIndexCommits.containsKey(commit)) { continue; // 不会执行 } // 只保留safeCommit和lastCommit if (commit.equals(safeCommit) || commit.equals(lastCommit)) { continue; } // 删除所有旧commit // 包括forcemerge前的commit_old deleteCommit(commit); } // 触发Lucene删除未使用的segment文件 indexWriter.deleteUnusedFiles(); }
展开更多
  • 重启后,acquiredIndexCommits是空的(新创建的Map)

  • onInit()会删除所有旧commit,只保留最新的commit

  • 对于forcemerge场景:

    • commit_old(forcemerge前)被删除

    • commit_new(forcemerge后)被保留

  • indexWriter.deleteUnusedFiles()删除commit_old的所有segment文件

  • 这就是为什么重启能释放50TB空间的原因

解决方案与最佳实践

  1. 不要在快照期间执行ForceMerge(重要原则)

  2. 在ForceMerge期间避免大规模全量备份

  3. 建立ForceMerge执行检查机制(规避场景有限)

展开
代码语言:Bash
自动换行
自动换行
AI代码解释
#!/bin/bash # forcemerge_safe_check.sh ES_HOST="localhost:9200" echo "=== 检查是否有进行中的快照 ===" RUNNING_SNAPSHOTS=$(curl -s "$ES_HOST/_snapshot/_status" | jq '.snapshots | length') if [ "$RUNNING_SNAPSHOTS" -gt 0 ]; then echo "❌ 发现 $RUNNING_SNAPSHOTS 个进行中的快照,禁止执行forcemerge!" echo "进行中的快照:" curl -s "$ES_HOST/_snapshot/_status" | jq '.snapshots[] | {repository, snapshot, state}' exit 1 fi echo "✅ 没有进行中的快照,可以安全执行forcemerge" # 执行forcemerge echo "=== 开始执行forcemerge ===" curl -X POST "$ES_HOST/_forcemerge?max_num_segments=1"
展开更多

4.ForceMerge前预留足够磁盘空间

展开
代码语言:Bash
自动换行
自动换行
AI代码解释
所需磁盘空间 = 当前索引大小 × 2 + 安全余量 示例: - 索引大小:1TB - 所需空间:1TB × 2 + 10TB = 12TB - 当前可用:10TB - 结论:空间不足,不能执行forcemerge
展开更多

总结

不是快照备份本身导致的磁盘占用,而是大规模ForceMerge与快照并行执行导致的

1. ForceMerge机制

  • 读取旧segment,生成新merged segment

  • 合并过程中,旧segment和新segment同时存在

  • 正常情况下,合并完成后旧segment会被删除

2. 快照引用机制

  • 快照通过acquireIndexCommit()获取IndexCommit引用

  • 被引用的commit包含的segment文件无法删除

  • 引用计数>0时,CombinedDeletionPolicy跳过删除

3. 交互问题

  • 快照引用了forcemerge前的commit(包含旧segment)

  • forcemerge生成新segment后,旧segment因被引用无法删除

  • 磁盘占用 = 旧segment + 新segment ≈ 2倍索引大小

4. 规模放大

  • 500TB存量数据执行forcemerge

  • 即使只有10%索引执行forcemerge,也会产生50TB临时占用

  • 多个索引并行forcemerge,问题进一步放大

参考资料


联系我们
返回顶部