Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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);