# How to Implement a Custom Raft Log Storage Solution Using Dragonboat's LogDB Interface

> Implement a custom Raft log storage solution in Dragonboat by making your own ILogDB and factory. Inject it into NodeHostConfig for flexibility.

- Repository: [lni/dragonboat](https://github.com/lni/dragonboat)
- Tags: how-to-guide
- Published: 2026-03-06

---

**You can implement a custom Raft log storage solution in Dragonboat by implementing the `ILogDB` interface defined in the `raftio` package, creating a factory that satisfies the `LogDBFactory` interface, and injecting it into `NodeHostConfig.Expert.LogDBFactory` before starting your NodeHost.**

Dragonboat is a high-performance Go implementation of the Raft consensus protocol. While it ships with a built-in Pebble-based storage backend, production environments often require specialized storage solutions—whether for compliance, performance optimization, or integration with existing infrastructure. By leveraging the **LogDB** abstraction, you can replace the default storage engine with any custom backend, from in-memory caches to distributed databases or cloud object storage.

## Understanding the LogDB Architecture

### The ILogDB Interface Contract

The core contract for custom storage implementations is the `ILogDB` interface, defined in [`raftio/logdb.go`](https://github.com/lni/dragonboat/blob/main/raftio/logdb.go) (lines 59-110). This interface mandates methods for persisting Raft state, managing log entries, and handling snapshots. Key methods include:

- `SaveRaftState(updates []pb.Update, shardID uint64) error` – Persists Raft state and log entries
- `IterateEntries(ents []pb.Entry, size uint64, shardID, replicaID, low, high, maxSize uint64)` – Retrieves log entries within a specific range
- `ReadRaftState(shardID, replicaID, lastIdx uint64)` – Reads the current persistent state
- `CompactEntriesTo(shardID, replicaID, idx uint64)` – Removes entries up to a specific index
- `SaveSnapshots` and `GetSnapshot` – Handle snapshot persistence

### The LogDBFactory Extension Point

Dragonboat instantiates storage through a factory pattern defined in [`config/config.go`](https://github.com/lni/dragonboat/blob/main/config/config.go) (lines 487-495). The `LogDBFactory` interface requires:

- `Create(cfg NodeHostConfig, cb LogDBCallback, dirs []string, wals []string) (raftio.ILogDB, error)`
- `Name() string`

If you do not provide a custom factory, Dragonboat defaults to the built-in Pebble implementation (previously RocksDB) via `defaultLogDB` in the `Prepare` method (lines 83-86 of [`config/config.go`](https://github.com/lni/dragonboat/blob/main/config/config.go)).

## Step-by-Step Implementation Guide

### Step 1: Implement the ILogDB Interface

Create a struct that satisfies all methods of `raftio.ILogDB`. The implementation must be **thread-safe**, as Dragonboat may invoke these methods concurrently. Here is a minimal in-memory skeleton:

```go
type customLogDB struct {
    mu    sync.RWMutex
    state map[raftio.NodeInfo]raftio.RaftState
    logs  map[raftio.NodeInfo][]pb.Entry
    snaps map[raftio.NodeInfo]pb.Snapshot
}

func (c *customLogDB) Name() string { return "custom" }
func (c *customLogDB) Close() error { return nil }
func (c *customLogDB) BinaryFormat() uint32 { return raftio.PlainLogDBBinVersion }

// Implement remaining methods: SaveRaftState, IterateEntries, ReadRaftState, etc.

```

### Step 2: Create the LogDBFactory

Implement the factory that Dragonboat will call during `NodeHost` initialization:

```go
type customFactory struct{}

func (f customFactory) Create(cfg config.NodeHostConfig, cb config.LogDBCallback, dirs []string, wals []string) (raftio.ILogDB, error) {
    return &customLogDB{
        state: make(map[raftio.NodeInfo]raftio.RaftState),
        logs:  make(map[raftio.NodeInfo][]pb.Entry),
        snaps: make(map[raftio.NodeInfo]pb.Snapshot),
    }, nil
}

func (f customFactory) Name() string { return "custom" }

```

### Step 3: Configure NodeHost to Use Your Factory

Wire the factory into your `NodeHost` configuration before starting:

```go
cfg := config.NodeHostConfig{
    DeploymentID: 1,
    WALDir:       "./wal",
    NodeHostDir:  "./data",
}

// Attach the custom LogDB factory
cfg.Expert.LogDBFactory = customFactory{}

if err := cfg.Prepare(); err != nil {
    log.Fatalf("config error: %v", err)
}

nh, err := dragonboat.NewNodeHost(cfg, config.DefaultRAFTAddress)
if err != nil {
    log.Fatalf("failed to start NodeHost: %v", err)
}

```

Note: While the deprecated `cfg.LogDBFactory` field still works, the modern approach uses `cfg.Expert.LogDBFactory` as implemented in [`config/config.go`](https://github.com/lni/dragonboat/blob/main/config/config.go).

## Complete Working Example: In-Memory LogDB

Below is a complete, runnable in-memory implementation demonstrating all required methods. This example is suitable for testing or ephemeral clusters where durability is not required.

```go
package main

import (
    "sync"
    
    "github.com/lni/dragonboat/v4/config"
    "github.com/lni/dragonboat/v4/raftio"
    "github.com/lni/dragonboat/v4/raftpb"
)

type memLogDB struct {
    mu    sync.RWMutex
    state map[raftio.NodeInfo]raftio.RaftState
    logs  map[raftio.NodeInfo][]raftpb.Entry
    snaps map[raftio.NodeInfo]raftpb.Snapshot
}

func (m *memLogDB) Name() string                         { return "memlogdb" }
func (m *memLogDB) Close() error                         { return nil }
func (m *memLogDB) BinaryFormat() uint32                 { return raftio.PlainLogDBBinVersion }
func (m *memLogDB) ListNodeInfo() ([]raftio.NodeInfo, error) {
    m.mu.RLock()
    defer m.mu.RUnlock()
    var res []raftio.NodeInfo
    for ni := range m.state {
        res = append(res, ni)
    }
    return res, nil
}
func (m *memLogDB) SaveBootstrapInfo(shardID, replicaID uint64, b raftpb.Bootstrap) error {
    return nil
}
func (m *memLogDB) GetBootstrapInfo(shardID, replicaID uint64) (raftpb.Bootstrap, error) {
    return raftpb.Bootstrap{}, raftio.ErrNoBootstrapInfo
}
func (m *memLogDB) SaveRaftState(updates []raftpb.Update, shardID uint64) error {
    m.mu.Lock()
    defer m.mu.Unlock()
    for _, ud := range updates {
        ni := raftio.GetNodeInfo(ud.ShardID, ud.ReplicaID)
        m.logs[ni] = append(m.logs[ni], ud.Entries...)
        if ud.State != nil {
            m.state[ni] = raftio.RaftState{
                State:      *ud.State,
                FirstIndex: ud.FirstIndex,
                EntryCount: uint64(len(m.logs[ni])),
            }
        }
    }
    return nil
}
func (m *memLogDB) IterateEntries(ents []raftpb.Entry, size uint64,
    shardID, replicaID, low, high, maxSize uint64) ([]raftpb.Entry, uint64, error) {
    m.mu.RLock()
    defer m.mu.RUnlock()
    ni := raftio.GetNodeInfo(shardID, replicaID)
    log := m.logs[ni]
    if low >= uint64(len(log)) {
        return nil, 0, nil
    }
    var total uint64
    var out []raftpb.Entry
    for i := low; i < high && i < uint64(len(log)); i++ {
        e := log[i]
        if total+uint64(e.Size()) > maxSize {
            break
        }
        out = append(out, e)
        total += uint64(e.Size())
    }
    return out, total, nil
}
func (m *memLogDB) ReadRaftState(shardID, replicaID, lastIdx uint64) (raftio.RaftState, error) {
    m.mu.RLock()
    defer m.mu.RUnlock()
    return m.state[raftio.GetNodeInfo(shardID, replicaID)], nil
}
func (m *memLogDB) RemoveEntriesTo(shardID, replicaID, idx uint64) error { return nil }
func (m *memLogDB) CompactEntriesTo(shardID, replicaID, idx uint64) (<-chan struct{}, error) {
    ch := make(chan struct{}, 1)
    ch <- struct{}{}
    return ch, nil
}
func (m *memLogDB) SaveSnapshots(updates []raftpb.Update) error { return nil }
func (m *memLogDB) GetSnapshot(shardID, replicaID uint64) (raftpb.Snapshot, error) {
    m.mu.RLock()
    defer m.mu.RUnlock()
    return m.snaps[raftio.GetNodeInfo(shardID, replicaID)], nil
}
func (m *memLogDB) RemoveNodeData(shardID, replicaID uint64) error { return nil }
func (m *memLogDB) ImportSnapshot(snap raftpb.Snapshot, replicaID uint64) error { return nil }

```

## Key Source Files and Reference Implementations

When building a production-grade custom storage backend, study these files in the `lni/dragonboat` repository:

- **[`raftio/logdb.go`](https://github.com/lni/dragonboat/blob/main/raftio/logdb.go)** – Defines the `ILogDB` interface and related types (`NodeInfo`, `RaftState`, `ErrNoBootstrapInfo`). This is the primary contract your implementation must satisfy.
- **[`config/config.go`](https://github.com/lni/dragonboat/blob/main/config/config.go)** – Contains the `LogDBFactory` interface definition (lines 487-495) and the `NodeHostConfig.Expert.LogDBFactory` field used to inject your factory.
- **[`internal/logdb/logdb.go`](https://github.com/lni/dragonboat/blob/main/internal/logdb/logdb.go)** – Reference implementation of the built-in Pebble-backed LogDB. Study this for patterns on batching, compaction, and error handling.
- **[`plugin/tee/tee.go`](https://github.com/lni/dragonboat/blob/main/plugin/tee/tee.go)** – Example of a factory wrapper (`TanPebbleLogDBFactory`) that demonstrates how to compose multiple storage backends.
- **[`docs/storage.md`](https://github.com/lni/dragonboat/blob/main/docs/storage.md)** – High-level documentation summarizing the custom storage integration process.

## Summary

Implementing a **custom Raft log storage solution using Dragonboat's LogDB interface** requires three main steps:

- **Implement `ILogDB`** – Create a thread-safe struct satisfying all methods in [`raftio/logdb.go`](https://github.com/lni/dragonboat/blob/main/raftio/logdb.go), including `SaveRaftState`, `IterateEntries`, and `ReadRaftState`.
- **Create a Factory** – Build a `LogDBFactory` implementation that instantiates your custom LogDB, providing the `Create` and `Name` methods as defined in [`config/config.go`](https://github.com/lni/dragonboat/blob/main/config/config.go).
- **Configure NodeHost** – Assign your factory to `NodeHostConfig.Expert.LogDBFactory` (or the deprecated `LogDBFactory` field) before calling `NewNodeHost`.

By following this pattern, you can integrate any storage backend—from in-memory caches to distributed databases—while Dragonboat handles the Raft consensus logic.

## Frequently Asked Questions

### What methods are mandatory when implementing the ILogDB interface?

You must implement all methods defined in [`raftio/logdb.go`](https://github.com/lni/dragonboat/blob/main/raftio/logdb.go), including `SaveRaftState` for persisting Raft updates, `IterateEntries` for log retrieval, `ReadRaftState` for reading persistent state, `CompactEntriesTo` for log compaction, and `SaveSnapshots`/`GetSnapshot` for snapshot management. The interface also requires `ListNodeInfo`, `SaveBootstrapInfo`, and lifecycle methods like `Close` and `Name`.

### Can I use multiple different storage backends simultaneously in one NodeHost?

Yes, you can implement a composite factory that routes different shards to different backends, or use a wrapper pattern like the `TanPebbleLogDBFactory` shown in [`plugin/tee/tee.go`](https://github.com/lni/dragonboat/blob/main/plugin/tee/tee.go). However, each individual shard must use a consistent storage backend throughout its lifetime to maintain Raft safety guarantees.

### Is the in-memory implementation shown in the examples suitable for production use?

No, the in-memory `memLogDB` example is intended for testing and demonstration only. Production implementations must provide durable persistence, proper crash recovery, and efficient compaction. Study the reference implementation in [`internal/logdb/logdb.go`](https://github.com/lni/dragonboat/blob/main/internal/logdb/logdb.go) for production-grade patterns using Pebble.

### What is the difference between LogDBFactory and Expert.LogDBFactory?

`LogDBFactory` is the deprecated field in `NodeHostConfig` for specifying custom storage factories. The modern approach uses `Expert.LogDBFactory` (defined in [`config/config.go`](https://github.com/lni/dragonboat/blob/main/config/config.go)), which provides better encapsulation of advanced configuration options. Both currently work, but `Expert.LogDBFactory` is the recommended path for new implementations.