Skip to content

Repository files navigation

rafter

A deterministic Raft stack for systems that own their runtime.

Rafter gives you a sans-IO protocol core, durable storage/runtime layers, simulation tools, and small embedding crates without taking over your transport, task model, or application state machine.

Model · API Layers · Example · Crates · Reference Consumers · Testing · Benchmarks


Model

The kernel turns ticks, peer messages, and client requests into explicit outputs. It performs no IO. A durable embedding handles each step in this order:

input -> Raft step -> persist required changes -> send messages / apply entries

rafter-runtime enforces this persist-before-output boundary. You supply the transport, application state machine, and scheduling policy. See the architecture guide for the runtime contract and recovery.

API Layers

Choose the layer that matches how much of the integration you want Rafter to provide.

Layer Reach for it when
rafter You want the deterministic protocol kernel and explicit outputs.
rafter-runtime You want a durable persist-before-output node.
rafter-app You want an embedded replicated state machine.
rafter-service You want async handles and transport traits.
rafter-multiraft You want many caller-defined Raft groups in one host.

Example

use rafter::{Input, Node, NodeConfig, NodeId, Output};

let config = NodeConfig::new(NodeId(1), vec![NodeId(2), NodeId(3)], 10).expect("valid raft config");
let mut node = Node::new(config);

for output in node.step(Input::Tick) {
    match output {
        Output::Send { to, message } => {
            // route through your transport
            let _ = (to, message);
        }
        Output::Apply { index, payload, .. } => {
            // apply to your state machine
            let _ = (index, payload);
        }
        _ => {}
    }
}

Crates

Need Crate
Protocol kernel rafter
Durable node rafter-runtime-api, rafter-storage, rafter-runtime
Embedded state machine rafter-app
Async managed handle rafter-service
Many Raft groups rafter-multiraft
Wire format and integrity rafter-codec, rafter-crc32
Peer transport rafter-transport-tls, rafter-transport-tcp-insecure (development only)
Simulation and workload testing rafter-sim, rafter-maelstrom
Repository verification rafter-invariants, rafter-invariant-test, rafter-invariant-test-macrosverification guide

Reference Consumers

Three independent applications exercise Rafter through its public APIs:

  • Replicated ledger: application durability and recovery.
  • Fenced lock service: linearizable authority and authenticated composition.
  • Sharded counter: managed scheduling across 64, 1,024, and 4,096 groups.

Each runs against both checkout sources and exact package archives, with durable process coverage. See reference consumers for the contracts and work completion for the proof map.

Testing

rustup toolchain install 1.97.1 --profile minimal
cargo +1.97.1 install zcheck --version 0.0.1 --locked
cargo +1.97.1 install zrail --version 0.0.3-rc.8 --locked
zcheck

The install toolchain matches CI. Rafter itself is qualified on Rust 1.88.

zcheck runs formatting, lints, documentation, architecture checks, and workspace tests, with logs and a receipt for each run. Use zcheck plan check to inspect the tasks or zcheck run full for deeper lanes.

zrail checks the reviewed architecture in zrail.toml: crate layers, sans-IO boundaries, capability and mutation owners, macro allowances, module docs, sibling tests, and file-size ratchets. CI checks the same locked contract on every pull request.

Simulation, TLA+, Maelstrom, and the reference applications provide the runtime evidence. See development and checks for commands, platform requirements, and the Rafter-specific guards that remain alongside zrail.

Benchmarks

The current qualified service comparison measures client requests over TCP, three-node replication, durable consensus storage, durable application updates, and responses sent only after application durability.

Added loopback egress Rafter pipeline + completion priority OpenRaft Ratio Rafter p99 at 1,000/s OpenRaft p99 at 1,000/s
0 ms 10,263 writes/s 2,590 writes/s 3.96x 4.063 ms 9.568 ms
2 ms 5,042 writes/s 1,827 writes/s 2.76x 12.583 ms 24.117 ms

Conditions: 3 nodes on one host, 64 clients, 512-byte writes, and medians of 3 repetitions. Added delay affects both client and peer egress. The OpenRaft arm is the tested asynchronous-flusher integration. The selected Rafter arm had zero errors, unknown outcomes, or unsent requests and beat the synchronous and asynchronous OpenRaft controls in all 24 displayed p99 and p99.9 comparisons. Against same-code FIFO Rafter, completion priority improved saturated throughput 29.3% at 0 ms and 5.9% at 2 ms; its small fixed-load tail tradeoffs remain visible in the report.

The immutable report and source cases contain the methodology, individual runs, accounting, and known limitations. These are implementation and integration results, not a claim about a faster Raft algorithm or every workload. The report also retains a completion-aligned seven-repeat in-memory comparison and the separately named asynchronous OpenRaft storage control.

Reproduce the in-memory comparison or measure Rafter's durable runtime with:

scripts/bench-compare.sh
cargo run --release -p rafter-runtime --bin rafter-bench-cluster

The comparison harness measures the in-memory implementation path. The rafter-bench-cluster binary measures Rafter's durable runtime path, including file-backed storage and group commit. The optional pipelined runtime overlaps eligible leader replication with local persistence while retaining the durable output boundary. ThreadedPipelinedRaftNode provides the tested one-credit node/worker composition; application durability and applied-index recovery remain the embedding's responsibility.

The opt-in shared WAL uses checkpoint-selected generation segments to reclaim compacted physical history and bound Raft replay work. Its default reclaims on every compaction; an explicit byte threshold can defer physical generation replacement while keeping each logical compaction durable. The threshold is checked only at compaction opportunities, and retained live entries are additional. That bound does not cover application-state or snapshot retention, which remain separate policies. A file-backed runtime can apply an explicit SnapshotRetention policy through DurableRaftNode::prune_snapshot_files; the reference durable service keeps only its manifest-selected snapshot after successful compaction and uses cleanup_abandoned_snapshot_temporary_files after recovery to remove only recognized interrupted-publication residue. After durable compaction, the opt-in bounded LogRetirementWorker can release the already-retired in-memory log prefix away from the consensus owner; full or stopped workers return ownership for immediate inline release. The runtime's opt-in application::ApplicationWorker makes the measured ordered application pattern reusable: bounded entry and byte credits include queued, executing, and unconsumed results, and durable completions are verified against the application's own applied floor so the embedding can answer clients only after consuming that completion.

Boundaries

Rafter is not a database, a transport security layer, or a server framework. Production embeddings still own:

application state durability
applied-index recovery
peer identity and authorization
removed-peer fencing
transport encryption
snapshot validation

Status

Rafter is pre-1.0. APIs and durable formats remain alpha. See the changelog for recent work and release guide for published versions and compatibility boundaries.

License

Licensed under Apache-2.0. See LICENSE and NOTICE.