来自 #117 的架构级评审建议。不阻塞合入,仅供参考是否有更好的架构解法。
💡 [建议 · 性能] 批写入错误时可能已写入部分 entry Raft/raft_wal.go:164
问题根因:AppendLogs 在循环中若中途某条 entry 写入失败直接 return err,但之前已写入的 entry 已进内核缓冲区却未 fsync,属于部分写入的不一致状态——已写入的 entry 在崩溃恢复时处于悬空状态(WAL 文件尾部有不完整的记录)。虽然现有代码在调用方(AppendEntries)的 needRebuild 或 persistStateLocked 路径上会修复,但从 WAL 本身的数据完整性视角看,AppendLogs 对外呈现了"要么全有要么全无"的接口假象,实际上可能留下部分写入。
为什么低级解法不够:加一行注释"调用方需处理部分写入"只是推卸责任;若在 writeEntry 失败后尝试 truncate 恢复文件指针,又引入了另一个 fsync 且可能掩盖真实错误。核心问题是:批写入 API 缺少帮助调用方做原子性判断的能力。
架构级方案:在 WAL 接口层面明确承诺行级不变量:AppendLogs 写入中途失败时,文件状态保证不会读入畸形数据。有两种方式:(a) 在 entry 之间写入可校验的边界标记(如 entry 长度 checksum),加载时跳过不完整尾部;(b) 在文件头维护最后一次有效写入位置的偏移(类似 checkpoint),恢复时截断到该位置。方案 (b) 更健壮但在每次 fsync 后需额外写一个 8 字节偏移,代价略高。对本 PR 而言,当前调用方均能通过全量重建恢复,可列为后续改进。
代价/收益:方案 (a) 增加少量磁盘格式开销(每条 entry 多 4-8 字节校验)和加载时的校验逻辑;方案 (b) 每次批 fsync 后多写 8 字节偏移并 fsync 一次(但 offset 的 fsync 可与批 fsync 合并)。当前不做不影响正确性。建议作为后续 issue 跟进。
💡 [建议 · 性能] 批写入错误时可能已写入部分 entry
Raft/raft_wal.go:164问题根因:
AppendLogs在循环中若中途某条 entry 写入失败直接 return err,但之前已写入的 entry 已进内核缓冲区却未 fsync,属于部分写入的不一致状态——已写入的 entry 在崩溃恢复时处于悬空状态(WAL 文件尾部有不完整的记录)。虽然现有代码在调用方(AppendEntries)的needRebuild或persistStateLocked路径上会修复,但从 WAL 本身的数据完整性视角看,AppendLogs对外呈现了"要么全有要么全无"的接口假象,实际上可能留下部分写入。为什么低级解法不够:加一行注释"调用方需处理部分写入"只是推卸责任;若在 writeEntry 失败后尝试 truncate 恢复文件指针,又引入了另一个 fsync 且可能掩盖真实错误。核心问题是:批写入 API 缺少帮助调用方做原子性判断的能力。
架构级方案:在 WAL 接口层面明确承诺行级不变量:
AppendLogs写入中途失败时,文件状态保证不会读入畸形数据。有两种方式:(a) 在 entry 之间写入可校验的边界标记(如 entry 长度 checksum),加载时跳过不完整尾部;(b) 在文件头维护最后一次有效写入位置的偏移(类似 checkpoint),恢复时截断到该位置。方案 (b) 更健壮但在每次 fsync 后需额外写一个 8 字节偏移,代价略高。对本 PR 而言,当前调用方均能通过全量重建恢复,可列为后续改进。代价/收益:方案 (a) 增加少量磁盘格式开销(每条 entry 多 4-8 字节校验)和加载时的校验逻辑;方案 (b) 每次批 fsync 后多写 8 字节偏移并 fsync 一次(但 offset 的 fsync 可与批 fsync 合并)。当前不做不影响正确性。建议作为后续 issue 跟进。