Commands & Queries
Once you have a Group<M> handle, you interact with the replicated state machine through commands (writes) and queries (reads).
Commands
Commands mutate state and are replicated to all group members through the Raft log. They are guaranteed to be applied in the same order on every node.
execute — Send and Wait
Sends a command and waits for it to be committed (replicated to a quorum):
let index = group.execute(CounterCmd::Increment(5)).await?;
println!("Command committed at log index {index}");
If the local node is:
- Leader: replicates to followers, resolves when quorum acknowledges
- Follower: forwards to the leader, resolves when the leader commits
execute_many — Batch Send and Wait
Send multiple commands atomically:
let range = group.execute_many([
CounterCmd::Increment(1),
CounterCmd::Increment(2),
CounterCmd::Increment(3),
]).await?;
println!("Commands committed at indices {range:?}");
Returns an IndexRange (RangeInclusive<Index>) covering all committed entries.
feed — Fire and Forget
Sends a command without waiting for commitment. Resolves once the leader acknowledges receipt and assigns a log index:
let index = group.feed(CounterCmd::Increment(10)).await?;
println!("Command assigned index {index}, not yet committed");
feed_many — Batch Fire and Forget
let range = group.feed_many([
CounterCmd::Reset,
CounterCmd::Increment(100),
]).await?;
Waiting for Commitment After Feed
Combine feed with the cursor watcher:
let range = group.feed_many(commands).await?;
// Wait until all commands are committed
group.when().committed().reaches(range.clone()).await;
Command Errors
pub enum CommandError<M: StateMachine> {
Offline(Vec<M::Command>), // Node is offline; commands returned
NoCommands, // Empty command list
GroupTerminated, // Group is shut down
}
The Offline variant returns the unsent commands so they can be retried:
match group.execute(cmd).await {
Ok(index) => println!("Committed at {index}"),
Err(CommandError::Offline(cmds)) => {
// Save for retry
group.when().online().await;
group.execute(cmds.into_iter().next().unwrap()).await?;
}
Err(CommandError::GroupTerminated) => {
panic!("Group is gone");
}
Err(CommandError::NoCommands) => unreachable!(),
}
Queries
Queries are read-only operations against the state machine. They are not replicated in the log.
Weak Consistency
Reads from the local node’s state machine. Fast but may return stale data:
let result = group.query(CounterQuery::Value, Consistency::Weak).await?;
println!("Local counter value: {} (at index {})", result.result, result.at_position);
Strong Consistency
Forwards the query to the current leader, guaranteeing linearizable reads:
let result = group.query(CounterQuery::Value, Consistency::Strong).await?;
println!("Leader counter value: {} (at index {})", *result, result.state_position());
CommittedQueryResult
Query results are wrapped in CommittedQueryResult<M>:
pub struct CommittedQueryResult<M: StateMachine> {
pub result: M::QueryResult, // The actual result
pub at_position: Index, // Log index at query time
}
It implements Deref to M::QueryResult, so you can use it directly:
let result = group.query(CounterQuery::Value, Consistency::Weak).await?;
// Deref to the inner result
let value: i64 = *result;
// Or access explicitly
let position = result.state_position();
let inner = result.into(); // Consume and get M::QueryResult
Query Errors
pub enum QueryError<M: StateMachine> {
Offline(M::Query), // Node offline; query returned
GroupTerminated, // Group shut down
}
Ordering Guarantee
Consecutive calls to execute, execute_many, feed, or feed_many on the same Group handle are guaranteed to be processed in the order they were issued.
Common Patterns
Command-Query Separation
// Write path: fire and forget for throughput
group.feed(OrderCmd::Place(order)).await?;
// Read path: weak consistency for speed
let book = group.query(OrderQuery::Snapshot, Consistency::Weak).await?;
// Read path: strong consistency for accuracy
let book = group.query(OrderQuery::Snapshot, Consistency::Strong).await?;
Execute and Verify
let index = group.execute(CounterCmd::Increment(1)).await?;
let result = group.query(CounterQuery::Value, Consistency::Weak).await?;
assert!(result.at_position >= index);