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 RaftLogStorage trait. This handles append-only log entries, log truncation, and vote persistence.
  • State-machine storage – Implements the RaftStateMachine trait. 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:

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 store
  • truncate_after – Delete log entries after a specific index
  • purge – Permanently remove log entries up to a specific index
  • save_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 responses
  • applied_state – Return the last applied log ID and membership state
  • begin_receiving_snapshot – Prepare to receive a snapshot stream from the leader
  • install_snapshot – Install a snapshot into the state machine, replacing current state
  • get_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

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →