📣 极限科技诚招搜索运维工程师(Elasticsearch/Easysearch)- 全职/北京 👉 : 立即申请加入

适用版本: 6.8-7.7+

1. 错误异常的基本描述 #

another snapshot is currently running cannot delete 是 Elasticsearch 在删除快照时抛出的并发控制错误。当你尝试删除某个快照(DELETE /_snapshot/<repo>/<snapshot>),但同一仓库中还有其他快照任务正在执行时,就会触发此错误。这个错误的核心是:仓库此刻仍被其他快照任务占用,不是目标快照本身在运行,而是其他快照在运行。

常见现象 #

  • Elasticsearch 返回 HTTP 409 Conflict 状态码,响应体中包含 ConcurrentSnapshotExecutionException
  • 删除快照的请求(DELETE /_snapshot/<repo>/<snapshot>)失败。
  • 在 Elasticsearch 服务端日志中会记录详细的异常信息。
  • 如果是通过定时任务(SLM)或自动化脚本删除快照,会导致清理任务失败。
  • 可能导致旧快照积累,占用存储空间。

典型报错与异常栈 #

该异常的典型日志形态如下:

ConcurrentSnapshotExecutionException: [repo:snap-old] another snapshot is currently running cannot delete
    at org.elasticsearch.snapshots.SnapshotsService.deleteSnapshot(SnapshotsService.java:...)
    at org.elasticsearch.action.admin.cluster.SnapshotDeleteTransportAction.shardOperation(SnapshotDeleteTransportAction.java:...)

通过 API 请求的响应通常如下:

{
  "error": {
    "root_cause": [
      {
        "type": "snapshot_creation_exception",
        "reason": "[repo:snap-old] another snapshot is currently running cannot delete"
      }
    ],
    "type": "snapshot_creation_exception",
    "reason": "[repo:snap-old] another snapshot is currently running cannot delete",
    "status": 409
  }
}

另一种常见形态(查看当前快照):

GET /_snapshot/my_repo/_current

{
  "snapshots": [
    {
      "snapshot": "snap-new",
      "state": "STARTED",
      ...
    }
  ]
}

2. 为什么会发生这个错误 #

Elasticsearch 的快照服务(SnapshotsService)会维护一个 SnapshotsInProgress 状态机来管理当前仓库上正在执行的快照任务。源码中的逻辑是:

SnapshotsInProgress.Entry snapshotEntry = snapshots != null ? snapshots.snapshot(snapshot) : null;
if (snapshotEntry == null) {
    if (snapshots != null && !snapshots.entries().isEmpty()) {
        throw new ConcurrentSnapshotExecutionException(snapshot, "another snapshot is currently running cannot delete");
    }
}

这意味着:只要仓库中还有其他快照在运行(除了要删除的目标快照),删除操作就会被拒绝

常见原因包括:

  • SLM 定时快照还在运行:SLM 策略刚创建了一个新快照,还没结束,你又尝试删除旧快照。
  • 快照执行时间长:由于数据量大或存储慢,快照运行时间拉长,导致清理窗口和备份窗口重叠。
  • 自动化任务重叠:外部自动化同时做"创建新快照"和"清理旧快照",没有错开时间。
  • 快照卡住:某个快照异常卡住,长时间不结束,导致后续所有删除操作被拒绝。
  • 主节点切换:旧的快照条目可能由于主节点切换而未能正确清理,但仍然占用仓库。
  • 并发快照操作:同一仓库有多个快照操作同时进行(虽然 Elasticsearch 通常不允许,但某些边缘情况可能发生)。

3. 如何排查和解决这个异常和解决这个异常 #

排查步骤 #

建议按以下顺序进行排查:

第一步:确认当前仓库正在运行的快照 #

# 查看当前仓库正在执行的快照
curl -X GET "localhost:9200/_snapshot/my_repo/_current?pretty"

# 查看所有快照及其状态
curl -X GET "localhost:9200/_snapshot/my_repo/_all?pretty" | jq '.snapshots[] | .snapshot, .state'

# 查看快照状态详情
curl -X GET "localhost:9200/_snapshot/my_repo/_status?pretty"

第二步:检查快照是否卡住或异常 #

# 查看快照进度
curl -X GET "localhost:9200/_snapshot/my_repo/snap-new/_status?pretty" | jq '.snapshots[0].shard_stats'

# 检查是否有失败的分片
curl -X GET "localhost:9200/_snapshot/my_repo/snap-new/_status?pretty" | jq '.snapshots[0].failures'

# 查看任务执行时间
curl -X GET "localhost:9200/_tasks?actions=*snapshot*&pretty" | jq '.tasks[] | select(.start_time_in_millis > 0)'

第三步:检查 SLM 或自动化任务时间线 #

# 查看 SLM 策略配置
curl -X GET "localhost:9200/_slm/policy?pretty"

# 查看 SLM 执行历史
curl -X GET "localhost:9200/_slm/policy/my_policy/_stats?pretty"

# 检查自动化脚本是否重叠执行
grep -E "snapshot|delete" /path/to/cron.log | tail -20

第四步:在测试环境验证 #

# 在测试环境先等待所有快照完成后,再执行删除
curl -X GET "localhost:9200/_snapshot/test_repo/_current?pretty"
# 应该返回 {"snapshots":[]}

# 然后删除旧快照
curl -X DELETE "localhost:9200/_snapshot/test_repo/snap-old"

排查时需要注意的问题_ #

  • 区分"目标快照"和"其他快照":错误说的是其他快照在运行,不是你要删除的那个。
  • 检查所有相关快照:可能是任何一个快照在运行,都会阻止删除操作。
  • 注意 SLM 和手动操作:可能是 SLM 定时任务创建的快照在运行。
  • 查看时间线:确认错误发生时是否有快照创建、恢复等操作重叠。

4. 如何解决这个错误 #

常用修复思路_ #

方案一:等待当前快照完成后再删除(推荐) #

# 监控当前快照状态,等待完成
while true; do
  STATUS=$(curl -s "localhost:9200/_snapshot/my_repo/_current?pretty" | jq -r '.snapshots[0].state // "NONE"')
  echo "Current snapshot state: $STATUS"
  if [ "$STATUS" = "NONE" ]; then
    echo "No active snapshot, proceeding with delete..."
    break
  fi
  sleep 30
done

# 删除目标快照
curl -X DELETE "localhost:9200/_snapshot/my_repo/snap-old"

方案二:错开快照创建和删除时间窗口_ #

# 修改 SLM 策略,避免时间重叠
curl -X PUT "localhost:9200/_slm/policy/my_policy" -H 'Content-Type: application/json' -d '
{
  "schedule": "0 1 * * *",  # 调整到凌晨 1 点,避开清理时间
  "name": "<my_index-{now/d}>",
  "repository": "my_repo",
  "config": {
    "indices": ["my_index*"]
  }
}'

# 清理脚本也调整到不同时间
# 例如:快照创建在凌晨 1 点,清理在凌晨 3 点

方案三:解决卡住的快照问题_ #

# 如果快照卡住,先查看详细信息
curl -X GET "localhost:9200/_snapshot/my_repo/snap-stuck?pretty"

# 如果确实卡住,考虑删除仓库重建(⚠️ 危险操作)
# 1. 先查看卡住的快照
# 2. 如果确定要删除仓库(会删除所有快照!)
curl -X DELETE "localhost:9200/_snapshot/my_repo"

# 3. 重新创建仓库
curl -X PUT "localhost:9200/_snapshot/my_repo" -H 'Content-Type: application/json' -d '
{
  "type": "fs",
  "settings": {
    "location": "/mnt/snapshot/repo",
    "compress": true
  }
}'

方案四:在脚本中添加时间窗口检查_ #

#!/bin/bash
# 检查当前是否有快照在运行
ACTIVE_SNAPSHOT=$(curl -s "localhost:9200/_snapshot/my_repo/_current?pretty" | jq -r '.snapshots[0].snapshot // null')

if [ "$ACTIVE_SNAPSHOT" != "null" ] && [ "$ACTIVE_SNAPSHOT" != "" ]; then
  echo "Warning: Snapshot [$ACTIVE_SNAPSHOT] is currently running, skipping delete..."
  exit 0  # 不报错,只是跳过
fi

# 执行删除
curl -X DELETE "localhost:9200/_snapshot/my_repo/snap-old"

后续注意事项与推荐建议_ #

  • 建立快照时间窗口规范:为 SLM 快照创建和旧快照清理分配不同的时间窗口,避免重叠。
  • 监控快照执行状态:通过 INFINI Console 或 Elasticsearch 监控功能,及时发现长时间运行的快照。
  • 在脚本中添加检查:对于自动化清理任务,先检查 _current 状态再决定是否执行删除。
  • 设置合理的超时:如果快照确实卡住,需要有超时机制来处理异常状态。
  • 定期清理旧快照:建立自动化的快照生命周期管理,避免手动删除时的并发冲突。

借助 INFINI 产品提升排障效率_ #

  • INFINI Console 提供快照状态的可视化监控界面,可以直观地查看、管理和调试快照任务。通过 Console 的快照管理功能,可以快速发现正在运行的快照、查看执行进度,并直接删除旧快照。

  • INFINI Gateway 可以作为 Elasticsearch API 的代理层,在删除请求到达 Elasticsearch 之前进行拦截和检查。Gateway 可以自动检测仓库状态,并根据预定义的策略(如排队等待、返回友好错误、转发到备用仓库等)进行处理。

  • 对于依赖快照进行数据备份的团队,建议结合 INFINI Console 的快照监控功能和 INFINI Gateway 的请求治理能力,建立从快照创建、清理、到监控的完整自动化流程,减少因并发冲突导致的删除失败。

5. 小结_ #

another snapshot is currently running cannot delete 是一个典型的并发控制错误,根源在于仓库中还有其他快照任务正在执行。虽然报错信息直接指向并发冲突,但解决思路需要根据具体情况来决定:是等待当前快照完成、错开时间窗口,还是解决卡住的快照。

在实际工作中,为避免此类问题,建议建立快照时间窗口规范、错开并发操作时间、并在自动化脚本中添加状态检查。更重要的是,考虑使用 INFINI Console 来实现可视化的快照管理,使用 INFINI Gateway 来拦截和预处理快照删除请求,从源头减少并发冲突的发生。

相关错误_ #

参考文档_ #

附:日志上下文_ #

下面保留当前页面中的源码或日志片段,便于继续结合异常调用栈定位问题:

SnapshotsInProgress.Entry snapshotEntry = snapshots != null ? snapshots.snapshot(snapshot) : null;
if (snapshotEntry == null) {
    if (snapshots != null && !snapshots.entries().isEmpty()) {
        throw new ConcurrentSnapshotExecutionException(snapshot, "another snapshot is currently running cannot delete");
    }
}