How to Configure OpenRaft Storage: Implementing Log and State Machine Traits
To configure OpenRaft storage, implement the RaftLogStorage and RaftStateMachine traits, declare them in a TypeConfig using the declare_raft_types! macro, and pass instances to Raft::new.
OpenRaft is a Raft consensus implementation in Rust that separates the consensus engine from the persistence layer. To configure OpenRaft storage for your application, you must provide concrete implementations for log storage and state machine storage. This guide walks through the trait definitions, implementation steps, and testing procedures using the databendlabs/openraft repository.
Understanding OpenRaft Storage Architecture
OpenRaft uses a two-component storage model that separates log persistence from state machine persistence. This separation allows you to optimize each component independently based on your durability and performance requirements.
The Two-Storage Model
You must provide two distinct storage components when configuring OpenRaft:
- Log storage – Implements the
RaftLogStoragetrait. This handles append-only log entries, log truncation, and vote persistence. - State-machine storage – Implements the
RaftStateMachinetrait. This handles log entry application, snapshot creation, and snapshot installation.
According to the OpenRaft source code, this separation exists because Raft only requires ordering guarantees for the log, while the state machine can be rebuilt from snapshots and may use a different durability model.
Key Traits and Source Files
The core storage traits are defined in the openraft crate under the storage/v2 module:
RaftLogStorage– Defined inopenraft/src/storage/v2/raft_log_storage.rsRaftStateMachine– Defined inopenraft/src/storage/v2/raft_state_machine.rs
Implementing RaftLogStorage
The RaftLogStorage trait defines how OpenRaft persists and retrieves log entries and hard state (voted_for and term).
Required Methods
Your implementation must provide the following async methods as defined in raft_log_storage.rs:
append– Append log entries to the storetruncate_after– Delete log entries after a specific indexpurge– Permanently remove log entries up to a specific indexsave_vote– Persist the hard state (voted_for and current_term)get_log_state– Retrieve the current log state (last_log_id, etc.)get_log_reader– Return a reader for log entries
In-Memory Reference Implementation
For testing and development, OpenRaft provides an in-memory implementation in stores/memstore/src/lib.rs. This serves as a reference for implementing the trait:
use openraft::storage::RaftLogStorage;
use openraft::LogId;
use openraft::Vote;
pub struct LogStore {
// Implementation details...
}
impl RaftLogStorage<TypeConfig> for LogStore {
async fn append(
&mut self,
entries: &[Entry<TypeConfig>],
) -> Result<(), StorageError<TypeConfig>> {
// Append entries to internal storage
unimplemented!()
}
async fn save_vote(
&mut self,
vote: &Vote<NodeId>,
) -> Result<(), StorageError<TypeConfig>> {
// Persist vote
unimplemented!()
}
// ... other required methods
}
Implementing RaftStateMachine
The RaftStateMachine trait handles the application of committed log entries to your state machine and manages snapshotting.
Snapshot and Apply Operations
As defined in openraft/src/storage/v2/raft_state_machine.rs, your implementation must provide:
apply– Apply a batch of log entries to the state machine, returning the responsesapplied_state– Return the last applied log ID and membership statebegin_receiving_snapshot– Prepare to receive a snapshot stream from the leaderinstall_snapshot– Install a snapshot into the state machine, replacing current stateget_snapshot_builder– Return a builder for creating snapshots
RocksDB Example Reference
For production use, the RocksDB example in examples/rocksstore/src/state_machine.rs demonstrates a persistent implementation:
use openraft::storage::RaftStateMachine;
use openraft::Snapshot;
pub struct RocksStateMachine {
db: Arc<rocksdb::DB>,
// ... other fields
}
impl RaftStateMachine<TypeConfig> for RocksStateMachine {
async fn apply(
&mut self,
entries: Vec<Entry<TypeConfig>>,
) -> Result<Vec<Response>, StorageError<TypeConfig>> {
// Apply entries to RocksDB
unimplemented!()
}
async fn install_snapshot(
&mut self,
meta: &SnapshotMeta<TypeConfig>,
snapshot: Box<dyn SnapshotData>,
) -> Result<(), StorageError<TypeConfig>> {
// Install snapshot into RocksDB
unimplemented!()
}
// ... other required methods
}
Configuring the TypeConfig
Before instantiating the Raft node, you must declare your concrete types using the type configuration system.
Using declare_raft_types!
The declare_raft_types! macro binds your application-specific types to the Raft generic parameters. As shown in examples/rocksstore/src/lib.rs:
use openraft::declare_raft_types;
declare_raft_types!(
pub TypeConfig:
D = Request, // Your client request type
R = Response, // Your client response type
NodeId = u64, // Node identifier type
Node = BasicNode, // Node address type
Entry = Entry<TypeConfig>,
SnapshotData = Cursor<Vec<u8>>,
);
Wiring Storage into Raft::new
After implementing the traits and declaring the types, instantiate the Raft node by passing the storage implementations to Raft::new as documented in openraft/src/docs/getting_started/getting-started.md:
use openraft::Raft;
// Create storage instances
let log_store = MyLogStore::new().await?;
let state_machine = Arc::new(MyStateMachine::new().await?);
// Create Raft node
let raft = Raft::new(
node_id,
config,
network_impl,
log_store,
state_machine,
).await?;
Testing Your Storage Implementation
OpenRaft provides a comprehensive test harness to verify that your storage implementation satisfies the Raft safety requirements.
The StoreBuilder Trait
The StoreBuilder trait in openraft/src/testing/log/store_builder.rs defines how to construct fresh storage instances for testing:
use openraft::testing::log::StoreBuilder;
use openraft::StorageError;
#[async_trait::async_trait]
impl StoreBuilder<TypeConfig, MyLogStore, MyStateMachine, ()> for MyBuilder {
async fn build(&self) -> Result<((), MyLogStore, MyStateMachine), StorageError<TypeConfig>> {
let log_store = MyLogStore::new().await?;
let sm = MyStateMachine::new().await?;
Ok(((), log_store, sm))
}
}
Running Suite::test_all
Invoke the comprehensive test suite from openraft/src/testing/log/suite.rs to validate your implementation:
use openraft::testing::log::Suite;
#[tokio::test]
async fn test_my_storage() {
let builder = MyBuilder {};
Suite::test_all(builder).await.unwrap();
}
The RocksDB example demonstrates this pattern in examples/rocksstore/src/test.rs.
Summary
- OpenRaft requires two storage components: Implement
RaftLogStoragefor log persistence andRaftStateMachinefor state machine operations and snapshots. - Core trait definitions are located in
openraft/src/storage/v2/raft_log_storage.rsandopenraft/src/storage/v2/raft_state_machine.rs. - Type configuration uses the
declare_raft_types!macro to bind your concrete storage types to the Raft generic system. - Reference implementations include the in-memory
memstore(stores/memstore/src/lib.rs) for testing and the RocksDB example (examples/rocksstore/src/lib.rs) for production persistence. - Validation requires implementing the
StoreBuildertrait and runningSuite::test_allto ensure safety compliance before deployment.
Frequently Asked Questions
What is the difference between RaftLogStorage and RaftStateMachine?
RaftLogStorage persists the Raft log entries, hard state (voted_for and current_term), and provides log truncation and purging operations. It ensures durability of the consensus log. RaftStateMachine applies committed log entries to your application state, manages snapshot creation, and handles snapshot installation from the leader. The separation allows the log to use fast append-only storage while the state machine uses a database optimized for random access.
Can I use different storage backends for logs and state machine?
Yes. OpenRaft explicitly separates these concerns so you can use different backends. For example, you might store logs in a high-performance append-only file or dedicated log store while keeping the state machine in RocksDB or an in-memory data structure. The RocksDB example (examples/rocksstore) demonstrates using the same RocksDB instance for both, but you can implement the traits to use completely different backends.
How do I verify my storage implementation is correct?
You must implement the StoreBuilder trait from openraft/src/testing/log/store_builder.rs and run the comprehensive test suite using Suite::test_all from openraft/src/testing/log/suite.rs. This suite validates log consistency, snapshot behavior, and recovery scenarios. The RocksDB example in examples/rocksstore/src/test.rs demonstrates the standard pattern for integrating with this test harness.
Where can I find a complete production-ready storage example?
The RocksDB example in examples/rocksstore/ provides a full production-ready reference implementation. Key files include examples/rocksstore/src/lib.rs for the storage constructor and type configuration, examples/rocksstore/src/state_machine.rs for the state machine implementation, and examples/rocksstore/src/test.rs for the test harness integration. For a simpler in-memory reference suitable for testing, see stores/memstore/src/lib.rs.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →