OpenRaft Storage Interface: Implementation Guide for Rust Raft Applications

The OpenRaft storage interface is a collection of Rust traits in the openraft::storage module that abstracts log persistence, state machine application, and snapshot management, enabling you to plug in any storage backend from in-memory maps to distributed databases.

The OpenRaft storage interface defines the strict contract between the Raft consensus engine and your persistence layer. Located in the databendlabs/openraft repository, these traits require you to implement log storage via RaftLogStorage, state machine updates via RaftStateMachine, and optional snapshot streaming via RaftSnapshotBuilder to integrate OpenRaft with your specific infrastructure.

Core Storage Traits in OpenRaft

OpenRaft isolates all persistence concerns behind a small set of traits defined in openraft/src/storage/v2/. Your application must implement these traits to supply durable storage for logs, votes, and state machine state.

RaftLogStorage: Persistent Log and Vote Handling

RaftLogStorage is the central trait for durable log storage. Defined in openraft/src/storage/v2/raft_log_storage.rs, it manages the Raft log entries, hard state (votes), and committed index tracking.

Key methods you must implement:

  • get_log_state – Returns LogState containing last_purged_log_id and last_log_id
  • get_log_reader – Returns a RaftLogReader instance for streaming entries
  • save_vote – Persists the hard state (voted_for and term)
  • append – Atomically appends entries and triggers the IOFlushed callback
  • truncate_after – Removes entries after a specific log ID (for log conflict resolution)
  • purge – Permanently removes entries up to a specific log ID (after snapshotting)

The trait also provides default implementations for save_committed and read_committed to track the committed log ID, though you may override these for persistent commit tracking.

RaftLogReader: Read-Only Log Access

RaftLogReader provides read-only access to stored log entries for replication tasks. This trait is defined in openraft/src/storage/v2/raft_log_reader.rs and is typically implemented by the same type as RaftLogStorage or a dedicated reader handle.

Required methods:

  • try_get_log_entry – Retrieves a single entry by index
  • read_log_entries – Reads a range of entries between start and end log IDs
  • get_log_state – Returns current log bounds (mirrors RaftLogStorage)

The Raft core uses this trait to stream entries to followers without blocking the main storage writer.

RaftStateMachine: Applying Committed Entries

RaftStateMachine is where your application logic lives. Defined in openraft/src/storage/v2/raft_state_machine.rs, this trait receives committed log entries and applies them to your state machine.

Core methods:

  • apply – Receives a slice of committed entries and must apply them to the state machine
  • snapshot – Returns a snapshot of the current state machine state
  • restore_snapshot – Restores the state machine from a previously created snapshot

The apply method is called sequentially with committed entries, ensuring that your state machine transitions are deterministic and consistent across the cluster.

RaftSnapshotBuilder: Incremental Snapshot Construction

For large state machines, RaftSnapshotBuilder (defined in openraft/src/storage/v2/raft_snapshot_builder.rs) supports streaming snapshot construction to avoid memory spikes.

Methods:

  • begin_snapshot – Initiates snapshot creation and returns metadata
  • build_snapshot – Writes snapshot data to the provided async writer

This trait is optional but recommended for production implementations where state machine snapshots may exceed available RAM.

StorageHelper: Utility Wrapper

StorageHelper (located in openraft/src/storage/helper.rs) is a convenience wrapper that aggregates the storage traits and provides common helpers such as last_membership_in_log and get_initial_state. While not required for basic implementations, it simplifies common storage operations used throughout the OpenRaft test suite and examples.

Implementing the OpenRaft Storage Interface

To integrate OpenRaft with your application, you must provide concrete implementations of the storage traits. Below are practical patterns for implementing these interfaces.

Minimal In-Memory Log Store Example

The following example demonstrates a minimal RaftLogStorage and RaftLogReader implementation using an in-memory BTreeMap. This pattern is useful for testing and understanding the interface contract.

use openraft::storage::{RaftLogStorage, RaftLogReader, IOFlushed, LogState};
use openraft::{LogId, LogIdOf, RaftTypeConfig, VoteOf};
use std::collections::BTreeMap;
use std::io;

pub struct SimpleLogStore<C>
where
    C: RaftTypeConfig,
{
    log: BTreeMap<u64, C::Entry>,
    last_purged: Option<LogId<C>>,
}

#[async_trait::async_trait]
impl<C> RaftLogStorage<C> for SimpleLogStore<C>
where
    C: RaftTypeConfig,
{
    type LogReader = Self;

    async fn get_log_state(&mut self) -> Result<LogState<C>, io::Error> {
        let last = self.log.keys().next_back().cloned()
            .map(|i| LogId::new(0, i));
        Ok(LogState {
            last_purged_log_id: self.last_purged,
            last_log_id: last,
        })
    }

    async fn get_log_reader(&mut self) -> Self::LogReader {
        self.clone()
    }

    async fn save_vote(&mut self, _vote: &VoteOf<C>) -> Result<(), io::Error> {
        Ok(())
    }

    async fn append<I>(&mut self, entries: I, callback: IOFlushed<C>) -> Result<(), io::Error>
    where
        I: IntoIterator<Item = C::Entry> + openraft::OptionalSend,
    {
        for entry in entries {
            let idx = entry.log_id().index();
            self.log.insert(idx, entry);
        }
        callback.io_completed(Ok(()));
        Ok(())
    }

    async fn truncate_after(&mut self, last_log_id: Option<LogIdOf<C>>) -> Result<(), io::Error> {
        let keep = last_log_id.map(|id| id.index() + 1).unwrap_or(0);
        self.log = self.log.split_off(&keep);
        Ok(())
    }

    async fn purge(&mut self, log_id: LogIdOf<C>) -> Result<(), io::Error> {
        self.log = self.log.split_off(&(log_id.index() + 1));
        self.last_purged = Some(log_id);
        Ok(())
    }
}

#[async_trait::async_trait]
impl<C> RaftLogReader<C> for SimpleLogStore<C>
where
    C: RaftTypeConfig,
{
    async fn get_log_state(&mut self) -> Result<LogState<C>, io::Error> {
        <Self as RaftLogStorage<C>>::get_log_state(self).await
    }

    async fn try_get_log_entry(&mut self, index: u64) -> Result<Option<C::Entry>, io::Error> {
        Ok(self.log.get(&index).cloned())
    }

    async fn read_log_entries(
        &mut self,
        start: LogIdOf<C>,
        end: LogIdOf<C>,
    ) -> Result<Vec<C::Entry>, io::Error> {
        let range = self
            .log
            .range(start.index()..=end.index())
            .map(|(_, v)| v.clone())
            .collect();
        Ok(range)
    }
}

Critical implementation details demonstrated above include calling the IOFlushed callback in append to signal durability, maintaining last_purged state for purge operations, and implementing both RaftLogStorage and RaftLogReader on the same type for simplicity.

Using the Built-In MemStore for Testing

OpenRaft provides a reference in-memory implementation in the memstore crate. This implementation demonstrates production-quality patterns for the storage interface and is suitable for testing and development.

use openraft::{Raft, Config};
use openraft::memstore::{MemLogStore, MemStateMachine, TypeConfig};
use std::sync::Arc;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize the built-in memory storage implementations
    let log_store = Arc::new(MemLogStore::default());
    let state_machine = Arc::new(MemStateMachine::default());

    // Instantiate the Raft node with the storage implementations
    let raft = Raft::new(
        0,
        Config::default(),
        log_store.clone(),
        state_machine.clone()
    ).await?;

    // The Raft instance will invoke log_store.append(), 
    // state_machine.apply(), and other trait methods automatically
    // during log replication and commitment.
    Ok(())
}

The MemLogStore implementation is located in stores/memstore/src/lib.rs and provides a complete reference for handling concurrent access, log truncation, and snapshot coordination.

Key Source Files and Module Structure

Understanding the layout of the storage module helps navigate the implementation requirements:

File Path Purpose
openraft/src/storage/mod.rs Public re-exports and module documentation
openraft/src/storage/v2/raft_log_storage.rs RaftLogStorage trait definition
openraft/src/storage/v2/raft_log_reader.rs RaftLogReader trait definition
openraft/src/storage/v2/raft_state_machine.rs RaftStateMachine trait definition
openraft/src/storage/v2/raft_snapshot_builder.rs RaftSnapshotBuilder trait definition
openraft/src/storage/v2/raft_log_storage_ext.rs Extension methods for storage operations
openraft/src/storage/helper.rs StorageHelper utility struct
stores/memstore/src/lib.rs Reference in-memory implementation
tests/tests/public_api_test.rs Integration tests demonstrating storage usage

These files collectively define the storage contract that decouples OpenRaft's consensus logic from your specific persistence mechanism.

Summary

  • OpenRaft storage interface consists of five primary traits: RaftLogStorage, RaftLogReader, RaftStateMachine, RaftSnapshotBuilder, and the utility StorageHelper.
  • RaftLogStorage handles durable log storage, vote persistence, and log compaction via append, truncate_after, and purge methods.
  • RaftStateMachine receives committed entries through the apply method and must support snapshot creation and restoration.
  • Reference implementations in stores/memstore/src/lib.rs demonstrate production-quality patterns for in-memory storage.
  • All traits are re-exported from openraft::storage and require Send and Sync bounds for async operation safety.

Frequently Asked Questions

What is the difference between RaftLogStorage and RaftStateMachine?

RaftLogStorage persists the Raft log entries and hard state (votes) durably, ensuring that committed entries survive crashes. RaftStateMachine applies those committed entries to your application state. The log storage maintains the history of commands, while the state machine maintains the current computed result of applying those commands. OpenRaft calls RaftLogStorage::append during replication and RaftStateMachine::apply only after entries are committed.

How do I handle snapshotting in OpenRaft?

You implement the RaftStateMachine::snapshot method to return a snapshot of your current state, and restore_snapshot to apply a received snapshot. For large state machines that cannot fit in memory, implement RaftSnapshotBuilder to stream snapshot data incrementally using begin_snapshot and build_snapshot. When the Raft core decides to compact the log, it calls these methods and then invokes RaftLogStorage::purge to remove obsolete log entries.

Can I use a custom storage backend like RocksDB or Sled with OpenRaft?

Yes, the OpenRaft storage interface is backend-agnostic. You implement the storage traits (RaftLogStorage, RaftLogReader, RaftStateMachine) for your chosen backend. For example, you would implement append to write to RocksDB batches, get_log_reader to return a reader handle that queries the database, and apply to update your application state stored in the database. The stores/memstore reference implementation demonstrates the required concurrency patterns and error handling.

What is the StorageHelper and when should I use it?

StorageHelper is a utility wrapper defined in openraft/src/storage/helper.rs that aggregates a RaftLogStorage and RaftStateMachine implementation. It provides convenience methods like last_membership_in_log to find configuration changes, get_initial_state to recover node state on startup, and other common operations that require coordinating between the log and state machine. While not required for basic operation, using StorageHelper reduces boilerplate when implementing advanced features like membership changes or automated snapshotting.

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 →