# OpenRaft Storage Interface: Implementation Guide for Rust Raft Applications

> Implement the OpenRaft storage interface in Rust for log persistence, state machine, and snapshot management. Connect any storage backend with this abstract interface.

- Repository: [Databend Labs/openraft](https://github.com/databendlabs/openraft)
- Tags: implementation-guide
- Published: 2026-02-28

---

**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`](https://github.com/databendlabs/openraft/blob/main/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`](https://github.com/databendlabs/openraft/blob/main/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`](https://github.com/databendlabs/openraft/blob/main/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`](https://github.com/databendlabs/openraft/blob/main/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`](https://github.com/databendlabs/openraft/blob/main/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.

```rust
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.

```rust
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`](https://github.com/databendlabs/openraft/blob/main/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`](https://github.com/databendlabs/openraft/blob/main/openraft/src/storage/mod.rs) | Public re-exports and module documentation |
| [`openraft/src/storage/v2/raft_log_storage.rs`](https://github.com/databendlabs/openraft/blob/main/openraft/src/storage/v2/raft_log_storage.rs) | `RaftLogStorage` trait definition |
| [`openraft/src/storage/v2/raft_log_reader.rs`](https://github.com/databendlabs/openraft/blob/main/openraft/src/storage/v2/raft_log_reader.rs) | `RaftLogReader` trait definition |
| [`openraft/src/storage/v2/raft_state_machine.rs`](https://github.com/databendlabs/openraft/blob/main/openraft/src/storage/v2/raft_state_machine.rs) | `RaftStateMachine` trait definition |
| [`openraft/src/storage/v2/raft_snapshot_builder.rs`](https://github.com/databendlabs/openraft/blob/main/openraft/src/storage/v2/raft_snapshot_builder.rs) | `RaftSnapshotBuilder` trait definition |
| [`openraft/src/storage/v2/raft_log_storage_ext.rs`](https://github.com/databendlabs/openraft/blob/main/openraft/src/storage/v2/raft_log_storage_ext.rs) | Extension methods for storage operations |
| [`openraft/src/storage/helper.rs`](https://github.com/databendlabs/openraft/blob/main/openraft/src/storage/helper.rs) | `StorageHelper` utility struct |
| [`stores/memstore/src/lib.rs`](https://github.com/databendlabs/openraft/blob/main/stores/memstore/src/lib.rs) | Reference in-memory implementation |
| [`tests/tests/public_api_test.rs`](https://github.com/databendlabs/openraft/blob/main/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`](https://github.com/databendlabs/openraft/blob/main/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`](https://github.com/databendlabs/openraft/blob/main/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.