Skip to content

perf(raft): WAL 批量 fsync (group commit)#117

Merged
NeverENG merged 1 commit into
mainfrom
perf/raft-wal-group-commit
Jun 1, 2026
Merged

perf(raft): WAL 批量 fsync (group commit)#117
NeverENG merged 1 commit into
mainfrom
perf/raft-wal-group-commit

Conversation

@NeverENG
Copy link
Copy Markdown
Owner

@NeverENG NeverENG commented Jun 1, 2026

@

背景 (路线图 #111 ①)

排查写密集瓶颈发现: RaftWAL.AppendLog 每条 entry 一次 file.Sync()

  • follower 端 rpc.go AppendEntries: 一整批 N 条 entry 循环 AppendLogN 次 fsync (主战场);
  • TruncateLogs / RebuildLogFile / SavePersist: 同样逐条 fsync (冷路径)。

注: storage 层的 storage/zstorage/WAL.go 并不在写路径上, 已于 #112 删除; 真正每条写落盘的是 Raft WAL。

改动 (方案 A: 批量 fsync API, 外科手术式)

  • raft_wal.go: 拆出 writeEntry(只写不 sync); AppendLog = writeEntry+Sync (行为不变); 新增 AppendLogs(entries) — 全部写完只 fsync 一次, 空批次 no-op。
  • rpc.go: follower 批量循环 → AppendLogs(newEntries)
  • raft_wal.go: TruncateLogs/RebuildLogFile/SavePersist 的逐条 fsync 循环 → AppendLogs
  • leader 单条路径 (raft.go AppendEntry) 不变 (每次客户端写 1 次 fsync 是固有的)。

durability

整批写入仍在 persistStateLocked / 回复成功前完成 fsync, 持久性契约不变, 仅把同一批的 N 次 fsync 摊销为 1 次。

验证

顺带发现 (另开 issue, 不在本 PR 处理)

  • rpc.go:161 AppendEntries: entry.Index=0 && LastIncludedIndex=0relativeIndex=-1r.raft.log[-1] 越界 panic (hermetic 下暴露)。

🤖 Generated with Claude Code
@

RaftWAL.AppendLog 此前每条 entry 一次 file.Sync(), follower 端
AppendEntries 处理一整批 N 条时循环调用 → N 次 fsync; rebuild/
SavePersist 同样逐条 fsync。

拆出 writeEntry(只写不 sync), AppendLog = writeEntry + Sync 不变;
新增 AppendLogs(entries): 全部写完只 fsync 一次。rpc.go follower
批量循环及 TruncateLogs/RebuildLogFile/SavePersist 改用 AppendLogs。

durability 契约不变: 整批写入仍在 persistStateLocked/回复成功前落盘。
空批次为 no-op。leader 单条路径 (raft.go AppendEntry) 不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

Warning

Review limit reached

@NeverENG, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 34 minutes and 24 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1c5ac403-ccee-4e2c-b603-41be74940b49

📥 Commits

Reviewing files that changed from the base of the PR and between a20c31d and b273390.

📒 Files selected for processing (2)
  • Raft/raft_wal.go
  • Raft/rpc.go
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/raft-wal-group-commit

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

🐯 BanGD 数据库内核评审

整体风险:🟢 低

变更总结:## PR 变更总结

本 PR 在 Raft WAL 的写入路径上实现了 组提交(group commit):将原本每条 entry 一次 fsync 的逐条写入模式,改为批量写入后统一 fsync 一次。核心动作是:

  1. 拆分写入与持久化:从 AppendLog 中提取出 writeEntry(仅 WriteSync),使 fsync 可被批量调用方延迟执行。
  2. 新增 AppendLogs(entries):对一批 entry 循环调用 writeEntry,最后只调用一次 file.Sync(),空批次直接 no-op 返回。
  3. 调用方替换:follower 端 AppendEntries(热路径)由逐条 AppendLog 改为 AppendLogsTruncateLogsRebuildLogFileSavePersist(冷路径)也同样改为 AppendLogs。leader 单条写入路径 AppendEntry 保持不变。

此改动不改变持久化契约(分批写入仍在该 RPC 返回成功前完成 fsync),也不改变磁盘格式或 WAL 记录格式。

本评审不阻塞合入;架构级建议以 Issue 形式跟踪,普通问题在下方内联列出。

架构问题(共 2 项)

普通问题(共 1 项)

⚠️ [重要 · 逻辑错误] Raft/rpc.go:178 AppendLogs 失败后继续执行可能导致持久化不一致

  • 第 183 行调用 r.raft.wal.AppendLogs(newEntries) 失败后仅 slog.Error,然后继续执行第 185 行的 r.raft.persistStateLocked()、更新 commitIndexapplyCommittedLogs()。WAL 写入失败意味着日志未持久化,但随后 persistStateLocked 会持久化 term/votedFor(这是正确的),然而 commitIndex 的更新和日志的应用会导致状态机执行了未被持久化的日志——崩溃恢复后回滚。
  • 建议:在 AppendLogs 失败后 return err(或至少 return 一个适当的 error),不让后续流程继续执行。

本次评审消耗 token:共 85121 tokens(输入 58032,输出 6737,缓存命中 20352,缓存写入 0)|维度 [memory, lock, storage, performance, resource]|补充阅读周边文件 [Raft/raft.go]|对抗式复核 3 票/条,过滤疑似误报 2 条

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant