# How Dragonboat's Internal Pipeline and Batching Mechanisms Maximize Throughput

> Discover how Dragonboat's internal pipeline and batching mechanisms maximize throughput by decoupling replica processing with staged pipelines and aggressively batching messages and log entries.

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

---

**Dragonboat achieves high-throughput Raft consensus by decoupling replica processing into staged pipelines and aggressively batching both network messages and log entries before persistence or transmission.**

Dragonboat is a high-performance Raft consensus library written in Go that powers distributed systems requiring strong consistency. By implementing an internal pipeline architecture and sophisticated batching mechanisms, the library minimizes lock contention and network overhead. This article examines the specific implementation details in the `lni/dragonboat` codebase that enable these throughput optimizations.

## Understanding Dragonboat's Internal Pipeline Architecture

The internal pipeline breaks Raft replica processing into distinct stages, allowing concurrent execution across CPU cores while maintaining state machine safety.

### The Four Core Pipeline Stages

Each Raft replica (shard) progresses through specialized stages managed via the pipeline interface defined in [`node.go`](https://github.com/lni/dragonboat/blob/main/node.go):

- **Step**: Processes incoming Raft messages via `node.StepReady()`, triggered through `pipeline.setStepReady()`
- **Commit**: Marks log entries as committed after quorum verification via `node.commitReady()` and `pipeline.setCommitReady()`
- **Apply**: Delivers committed entries to user state machines through `node.applyReady()` and `pipeline.setApplyReady()`
- **Stream/Save/Recover**: Handles snapshot streaming, log persistence, and recovery via `setStreamReady()`, `setSaveReady()`, and `setRecoverReady()`

### Pipeline Interface and Engine Integration

The `pipeline` interface injected into each node during `newNode` decouples stage signaling from execution:

```go
type pipeline interface {
    setCloseReady(*node)
    setStepReady(shardID uint64)
    setCommitReady(shardID uint64)
    setApplyReady(shardID uint64)
    setStreamReady(shardID uint64)
    setSaveReady(shardID uint64)
    setRecoverReady(shardID uint64)
}

```

The concrete implementation resides in [`engine.go`](https://github.com/lni/dragonboat/blob/main/engine.go). When a stage completes, the engine notifies the work-ready subsystem without blocking the Raft state machine.

### Work-Ready Partitioning for Parallel Scalability

The [`engine.go`](https://github.com/lni/dragonboat/blob/main/engine.go) file implements a partitioned work-ready system that eliminates global lock contention. The `workReady` structure partitions shards across fixed channels using `server.NewFixedPartitioner`:

```go
func (e *engine) setStepReady(shardID uint64) {
    e.stepWorkReady.shardReady(shardID)
}

```

Instead of a single ready flag, the `readyShard` map (defined in [`queue.go`](https://github.com/lni/dragonboat/blob/main/queue.go)) aggregates ready shards per partition. This design allows multiple CPU cores to drive Raft processing concurrently, with each worker processing batches of ready shards before yielding.

## Batching Mechanisms in Dragonboat

Dragonboat groups multiple Raft operations into atomic batches to reduce network syscalls and disk I/O overhead.

### MessageBatch and EntryBatch Types

The library defines two primary batch containers in the `raftpb` package:

- **`MessageBatch`**: Aggregates multiple `pb.Message` RPCs into single network packets (defined in [`raftpb/messagebatch.go`](https://github.com/lni/dragonboat/blob/main/raftpb/messagebatch.go))
- **`EntryBatch`**: Groups log entries for append-entries RPCs and log database persistence (defined in [`raftpb/entrybatch.go`](https://github.com/lni/dragonboat/blob/main/raftpb/entrybatch.go))

Both respect the `MaxMessageBatchSize` limit (approximately 128KB, defined in [`settings/hard.go`](https://github.com/lni/dragonboat/blob/main/settings/hard.go) as `LargeEntitySize`).

### Transport Layer Batching

The transport implementation in [`internal/transport/transport.go`](https://github.com/lni/dragonboat/blob/main/internal/transport/transport.go) handles batch reception and transmission efficiently.

When receiving, `HandleMessageBatch` processes entire batches in single invocations:

```go
func (h *messageHandler) HandleMessageBatch(msg pb.MessageBatch) (uint64, uint64) {
    // Process each request in the batch
    nh.engine.setStepReadyByMessageBatch(msg)   // Single notification for entire batch
}

```

For transmission, `Transport.sendMessageBatch` writes aggregated messages via `TCPConnection.SendMessageBatch`, collapsing multiple small RPCs into one socket write operation.

### Automatic Size Management

When batches exceed `MaxMessageBatchSize`, the transport layer automatically splits them while maintaining ordering guarantees. This ensures optimal packet sizes without manual intervention:

```go
max := settings.Soft.MaxMessageBatchSize // Default ~128KB

```

## How Pipeline and Batching Synergize for Maximum Throughput

The interaction between pipeline stages and batching creates a multiplicative performance benefit:

1. **Batch Reception**: `HandleMessageBatch` receives multiple messages in one network packet
2. **Aggregated Signaling**: `setStepReadyByMessageBatch` marks all affected shards as ready in a single pass
3. **Partitioned Execution**: `workReady.shardReadyByMessageBatch` notifies worker channels once per partition rather than per message
4. **Staged Processing**: Workers execute step, commit, and apply stages across batches of entries, improving CPU cache locality

This architecture reduces context switches, minimizes TCP/IP header overhead, and sustains high proposal rates by ensuring workers process many shards before yielding.

## Code Examples

### Configuring Automatic Proposal Batching

Dragonboat automatically batches client proposals without explicit configuration:

```go
cfg := config.NodeHostConfig{
    RaftAddress:      "localhost:63001",
    DeploymentID:     1,
    MaxSendQueueSize: 1000,
}
nh, _ := dragonboat.NewNodeHost(cfg)

// Proposals are automatically queued and batched
for i := 0; i < 1000; i++ {
    session, _ := nh.GetClientSession(1)
    cmd := []byte(fmt.Sprintf("cmd-%d", i))
    _ = nh.SyncPropose(context.Background(), session, cmd)
}

```

The transport layer coalesces these 1000 proposals into fewer `MessageBatch` transmissions.

### Inspecting Batch Size Limits

Verify the batch size constraints programmatically:

```go
import "github.com/lni/dragonboat/v3/settings"

maxSize := settings.Soft.MaxMessageBatchSize
fmt.Printf("Maximum batched payload: %d bytes (~%d KB)\n", maxSize, maxSize/1024)

```

## Summary

- **Dragonboat's internal pipeline** decouples Raft processing into step, commit, and apply stages, enabling parallel execution across CPU cores via the `pipeline` interface in [`node.go`](https://github.com/lni/dragonboat/blob/main/node.go)
- **Work-ready partitioning** in [`engine.go`](https://github.com/lni/dragonboat/blob/main/engine.go) eliminates global locks by distributing shard notifications across fixed partitions using `readyShard` structures
- **MessageBatch and EntryBatch** types aggregate RPCs and log entries, reducing network syscalls and improving MTU utilization
- **Transport layer integration** handles batch reception via `HandleMessageBatch` and signals the engine through `setStepReadyByMessageBatch` for aggregated processing
- **Automatic size management** ensures batches stay within `MaxMessageBatchSize` limits while maintaining throughput

## Frequently Asked Questions

### What is the maximum batch size in Dragonboat?

Dragonboat limits batches to approximately 128KB via `settings.Soft.MaxMessageBatchSize`, defined as `LargeEntitySize` in [`settings/hard.go`](https://github.com/lni/dragonboat/blob/main/settings/hard.go). When proposals exceed this threshold, the transport layer automatically splits them into multiple batches while preserving ordering guarantees.

### How does Dragonboat prevent head-of-line blocking in the pipeline?

The pipeline architecture separates step, commit, and apply stages into distinct work-ready queues. By partitioning shards across multiple channels using `server.NewFixedPartitioner`, the system ensures that slow operations in one shard do not block progress on others, as each partition processes independently.

### Can developers tune the batching behavior?

While Dragonboat handles batching automatically, developers can influence throughput via `NodeHostConfig` parameters like `MaxSendQueueSize` and `MaxReceiveQueueSize`. The `MaxMessageBatchSize` constant is compile-time configurable in the settings package for specialized deployments requiring different payload limits.

### Where is the pipeline interface implemented?

The `pipeline` interface is defined in [`node.go`](https://github.com/lni/dragonboat/blob/main/node.go) and implemented by the `engine` struct in [`engine.go`](https://github.com/lni/dragonboat/blob/main/engine.go). The engine methods (`setStepReady`, `setCommitReady`, etc.) forward notifications to the `workReady` subsystem, which manages the actual worker thread coordination through [`queue.go`](https://github.com/lni/dragonboat/blob/main/queue.go).