Repository navigation
Feature: leader replicates without waiting for its own log flush (0.9) - #2163
Open
glennswest wants to merge 2 commits into
Open
glennswest wants to merge 2 commits into
glennswest wants to merge 2 commits into
Conversation
In 0.9, RaftCore::run_command(AppendInputEntries) awaits the leader's LogFlushed before the Replicate commands queued after it run, so on a leader the local fsync and a follower's fsync happen in series: a lone write on a 3-node cluster costs two fsyncs. An AppendInputEntries carrying this node's own leader vote now submits the append (RaftLogStorage::append must already leave the entries readable when it returns) and goes on. A forwarder task waits for the flushes in submission order and sends Notify::LocalLogFlushed; only then is the leader's own progress updated (if its vote is unchanged), so a commit still needs a quorum of durable copies. A flush error stops RaftCore as before. Followers are unchanged. Bounds: at most 16 unflushed leader appends; and if the oldest has been pending for a heartbeat interval, RaftCore waits for every submitted flush before sending anything to a follower (heartbeats included), so a leader with a stalled disk still goes silent and is replaced.
glennswest
added a commit
to glennswest/fastetcd
that referenced
this pull request
Oct 13, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
What
In 0.9,
RaftCore::run_command(Command::AppendInputEntries)awaits the log store'sLogFlushed(append_to_log) before theReplicatecommands queued after it run. So on a leader, the local fsync and a follower's fsync happen one after the other, and a lone write on a 3-node cluster costs two fsyncs.This PR changes an
AppendInputEntriesthat carries this node's own leader vote (engine.leader.vote == vote):RaftLogStorage::appendand goes on, soReplicategoes out at once. The trait already requires the entries to be readable onceappendreturns.Notify::LocalLogFlushed.update_local_progress), and only if its vote is unchanged. A commit therefore still needs a quorum of durable copies; the leader's copy just no longer has to be first.append_to_logdid.Followers are unchanged: they still answer AppendEntries only after their flush.
Bounds, so a slow or stuck disk behaves as before:
One consequence for applications: the state machine can be asked to apply an entry, committed on followers, before the leader's own flush of it returns. An application that must never apply ahead of its local log has to wait for that flush itself. openraft's startup already handles
last_applied > last_log_idby purging the hole, so this is safe either way.Why
This came from fastetcd, an etcd v3 implementation on openraft. There, a 3-member cluster paid two fsyncs per lone write where etcd pays one (etcd's leader writes its WAL while the entries are on their way to the followers).
Measured on 3 in-process members over gRPC, with every member's fsync delayed by 200 ms, nine lone writes, three rounds:
fastetcd has run this change, vendored on 0.9.24, through its full test suite, crash (SIGKILL) tests, leader-stall election tests, and rolling upgrade and downgrade against unpatched members.
Tests on this branch
cargo test -p openraft --lib: 188 passed.cargo test -p tests --no-fail-fast: everything passed, twice. The base (release-0.9, d60f28b) gave the same result.cargo clippy -p openraft --all-targets -- -D warningsandcargo fmt --checkare clean.membership::t30_commit_joint_config::commit_joint_config_during_0_to_012, run alone (cargo test -p tests --test membership t30_commit_joint_config), fails 20 of 20 on this branch and 20 of 20 on the base, with the samethe cluster is already undergoing a configuration changeerror from its two concurrentadd_learnercalls. It passes inside the full run. That is unrelated to this change.0.10's IO pipeline separates submitting from flushing, so this is meant for 0.9 users until then. If you'd rather have it behind a
Configflag, I'm happy to add one.This change is