perf(persistence): park-free AOF writer poll under everysec/no#241
Conversation
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds a park-free polling receive path for monoio AOF writer loops under EverySec/No fsync policies, while keeping parked receive behavior for FsyncPolicy::Always. Also updates the changelog and adds unit tests for the polling receive helper. ChangesAOF Writer Park-Free Polling
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant WriterLoop
participant recv_next
participant poll_recv
participant Channel
WriterLoop->>recv_next: recv_next(rx, wait, park)
alt park is true (FsyncPolicy::Always)
recv_next->>Channel: rx.recv_timeout(wait)
Channel-->>recv_next: message or Timeout
else park is false (EverySec/No)
recv_next->>poll_recv: poll_recv(rx, wait)
loop until deadline
poll_recv->>Channel: rx.try_recv()
Channel-->>poll_recv: Empty or message
poll_recv->>poll_recv: sleep(step interval)
end
poll_recv-->>recv_next: message or Timeout/Disconnected
end
recv_next-->>WriterLoop: RecvTimeoutResult
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Under appendfsync everysec, every SET paid a futex WAKE on the SHARD thread: the AOF writer parks in flume recv_timeout, so each producer try_send must wake it. strace -c on the shard thread during an 8s non-pipelined SET run (Linux VM, monoio, shards=1): nodur: io_uring_enter 47,017 | futex 2 esec: io_uring_enter 45,639 | futex 149,718 (63% of syscall time) This is the mechanism behind the esec p1 SET deficit vs Redis (0.80x on GCE): Redis's AOF append is a plain memcpy into aof_buf; Moon's was a channel send + cross-thread futex wake roughly every other command. Fix: under EverySec/No the two std-thread writer loops (monoio TopLevel + PerShard) receive via poll_recv() -- try_recv + adaptive sleep (wait/16, clamped 500us..50ms) -- which never registers the writer as a flume waiter, so producer try_sends stay pure userspace atomics. Post-fix the same strace shows 96 futex calls (>1500x reduction). Trade-offs (documented on poll_recv): - Always keeps the parked recv: its callers await the per-batch fsync ack, so receive latency there is client-visible RTT. - EverySec bound unchanged: at the 50ms IdleWait fast floor the deadline is re-checked within ~3ms slack (50ms/16 steps). - Idle cost: <=20 writer wakes/s at the escalated 1s wait (vs 1/s parked) -- still far below the pre-wave-5 fixed 50ms cadence, and only on the writer thread. - Under load try_recv returns immediately; no sleep is taken and batching per drain improves slightly. Validation: - 4 new poll_recv unit tests (immediate message, mid-wait pickup, timeout, disconnect). - crash_matrix_per_shard_aof --ignored 3/3 green (SIGKILL durability contract intact). - fmt, clippy x2, cargo test --lib x2 (monoio 3868 / tokio 3143) green. - GCE esec p1 A/B vs Redis to be posted on the PR before merge. author: Tin Dang
6d3138d to
ad68b73
Compare
Summary
Closes the last per-cell Moon-vs-Redis durability deficit: everysec P1 SET was 0.80–0.86× Redis. Root cause found via
strace -con the shard thread during an 8s non-pipelined SET run (Linux, monoio, shards=1):The AOF writer parks in flume
recv_timeout, so every producertry_sendpays a futex WAKE on the shard thread (~1 per 2 SETs). Redis's AOF append is a plain memcpy intoaof_buf— hence the gap.Change
Under
EverySec/No, the two std-thread writer loops (monoio TopLevel + PerShard) receive via a newpoll_recv()—try_recv+ adaptive sleep (wait/16, clamped 500µs–50ms) — which never registers the writer as a flume waiter, so producer sends stay pure userspace atomics.Alwayskeeps the parked recv (its callers await the per-batch fsync ack; receive latency there is client-visible RTT).try_recvhits immediately — no sleep, slightly better batching per drain.GCE A/B (c3-standard-8, Redis 7.0.15, 3 alternated reps,
-c 8 -n 300000 -r 100000 -d 64, Moon--shards 2)4f01e68b)Test plan
poll_recvunit tests (immediate message, mid-wait pickup, timeout, disconnect)crash_matrix_per_shard_aof --ignored3/3 green (SIGKILL durability contract intact)cargo test --lib×2 (monoio 3868 / tokio 3143)Summary by CodeRabbit