Snapshot logic has flaws in scenarios where the disk is full #461
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
问题背景
我们的线上服务使用了braft框架, 在运行期间服务所在的机器磁盘使用量达到上限, 查看日志, 在磁盘达到上限的期间有部分关于snapshot的异常日志, 随后我们扩容了磁盘, 并且重启了服务, 但是故障恢复期间在log_manager.cpp内部一直校验失败, 服务无法正常启动.
原因分析
通过查看本地目录结构, 发现最近一次成功生成的快照目录名称为
snapshot_00000000000000009354
, 快照的生成时间是20240701 11:11:20.936393
, 说明重启之后加载该快照后应该从点位9355
处的日志开始进行回放, 但是log中的第一条有效日志的编号却是10709, 说明日志有缺失(缺失的区间[9355, 10709)
), 校验确实应该失败, 不允许启动成功.然后我们去看了最后一次成功生成快照的时间点之后的日志, 发现在
20240701 13:11:54.375097
和20240701 13:35:03.102234
这个两个时间点都触发了快照, 并且当时由于磁盘已经满了, 导致产生了部分错误日志(两次快照的日志差不多, 这里截取20240701 13:11:54.375097
的日志进行分析)从这里可以看出来在快照期间
__raft_snapshot_meta
存盘操作失败了, 所以后面删除了新生成的temp快照目录(此时最近一次成功快照实际上还是snapshot_00000000000000009354
), 但是快照流程还是在继续, 后续清理了区间[9352, 10083]
之间的日志并且更新了log_meta
文件中的点位信息到10090
, 此时快照数据就已经和日志对应不上了(实际上由于日志的清理, 这里已经存在丢失数据的问题了).函数调用链
在业务层快照逻辑完成之后框架会回调
on_snapshot_save_done()
函数:其中有一段逻辑是
_snapshot_storage
调用close进行快照元信息的存盘以及快照目录的rename操作:writer->sync()
的内部实现实际上就是将快照的元信息进行存盘操作, 在磁盘满导致内部存盘失败的场景下, 这个函数的返回值是-1
, 然后跳到函数末尾判断return ret != EIO ? 0 : -1;
, 这里实现实际上是有严重问题的, 只有ret的值等于EIO(5)
的时候才认为出错了, 如果ret为-1
, 实际上close()
函数返回了0, 静默了内部错误, 导致上层认为close()
函数正常执行, 并且进行了raft 日志的清理工作.最终导致了实际上快照目录没有更新(一直是最后一次成功快照生成的目录, 也就是
20240701 11:11:20.936393
时刻生成的snapshot_00000000000000009354
), 但是raft日志缺被清理掉了, 并且log_meta
的文件中记录的点位一直后移, 造成快照目录数据和raft日志接不上的问题.修复办法
问题分析清楚之后实际上修复办法还是比较简单的, 当
writer->sync()
失败之后, 将ret设置成EIO
, 将错误向上层抛, 而不是静默错误让上层继续清理braft日志.