RaftEx

⚠️ EXPERIMENTAL / UNDER ACTIVE DEVELOPMENT ⚠️

This library is an experimental Elixir port of the RabbitMQ RA Raft consensus algorithm. It is currently in early development and NOT READY FOR PRODUCTION USE.

Use at your own risk for learning, testing, and evaluation purposes only.


Raft consensus algorithm implementation in Elixir, ported from the RabbitMQ RA library (original Erlang repo).

Overview

RaftEx implements the Raft consensus algorithm, providing:

Current Status

✅ Implemented

Component Status Description
Types & RPCs ✅ Complete All Raft protocol message structs
WAL ✅ Complete Batched writes, fsync, recovery, truncation
Log ✅ Complete Append, read, fold, snapshot, config persistence
Metadata Store ✅ Complete DETS + ETS backed term/voted_for/last_applied
Election Logic ✅ Complete Pre-vote, candidate, vote handling, quorum eval
RPC Handlers ✅ Complete AppendEntries, InstallSnapshot, Heartbeat
Cluster Mgmt ✅ Complete Membership, voter status, match indexes
Effects System ✅ Complete Reply routing, notifications, machine effects
Tests ✅ 85 passing Unit tests for all major components

🚧 In Progress

Component Status Description
Server Process 🚧 Partial Leader/follower states work, candidate/pre_vote need completion
Segment Files 🚧 Stub Long-term log storage (WAL-only currently)
Snapshot Transfer 🚧 Partial Chunked sending logic needs full implementation
Network Layer 🚧 Stub Distributed RPC between nodes not yet wired
Integration Tests 🚧 Partial Single-node tests pass, multi-node tests needed

❌ Not Started

Component Status Description
Performance ❌ TODO gen_batch_server, optimizations
Metrics ❌ TODO Seshat integration incomplete
Documentation ❌ TODO API docs, guides, examples

Installation

⚠️ This package is not yet published to Hex.pm due to its experimental status.

If available, add to your mix.exs:

def deps do
  [
    {:raft_ex, "~> 0.0.1"}
  ]
end

Or use directly from Git:

def deps do
  [
    {:raft_ex, git: "https://github.com/your-org/raft_ex", branch: "main"}
  ]
end

Quick Start

# Start the RaftEx system
RaftEx.start_in("/tmp/raft_data")

# Define a simple state machine
defmodule CounterMachine do
  @behaviour RaftEx.Machine

  @impl RaftEx.Machine
  def init(_conf), do: 0

  @impl RaftEx.Machine
  def apply(_meta, {:increment}, state), do: {state + 1, state + 1}
  def apply(_meta, {:get}, state), do: {state, state}
end

# Start a cluster (single node for testing)
server_id = {:server1, node()}
machine = {:machine, CounterMachine, %{}}

{:ok, _} = RaftEx.start_cluster(
  :default,
  :my_cluster,
  machine,
  [server_id]
)

# Send a command
{:ok, reply, _leader} = RaftEx.process_command(server_id, {:increment})
IO.inspect(reply) # => 1

Architecture

┌─────────────────────────────────────────────────────────────┐
│                        RaftEx.Server                        │
│  ┌─────────────┐  ┌──────────────┐  ┌────────────────────┐ │
│  │   Leader    │  │  Follower    │  │    Candidate       │ │
│  │   State     │  │  State       │  │    State           │ │
│  └──────┬──────┘  └──────┬───────┘  └────────┬───────────┘ │
│         │                │                    │             │
│  ┌──────▼────────────────▼────────────────────▼───────────┐ │
│  │              Server Process (gen_statem)               │ │
│  └──────────────────────┬───────────────────────────────┘ │
│                         │                                 │
│  ┌──────────────────────▼───────────────────────────────┐ │
│  │                 RaftEx.Log                           │ │
│  │  ┌────────────┐  ┌────────────┐  ┌───────────────┐  │ │
│  │  │   WAL      │  │  Mem Table │  │   Snapshots   │  │ │
│  │  │ (GenServer)│  │   (ETS)    │  │   (Files)     │  │ │
│  │  └────────────┘  └────────────┘  └───────────────┘  │ │
│  └──────────────────────────────────────────────────────┘ │
│                         │                                 │
│  ┌──────────────────────▼───────────────────────────────┐ │
│  │              State Machine (Pluggable)                │ │
│  └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

Key Components

Configuration

# System configuration
%{
  name: :default,
  data_dir: "/var/lib/raft_ex",
  wal_max_size_bytes: 256_000_000,
  wal_max_batch_size: 8192,
  wal_sync_method: :datasync, # :fsync | :datasync | :none
  wal_compute_checksums: true,
  segment_max_entries: 4096,
  segment_max_size_bytes: 64_000_000
}

# Server configuration
%{
  id: {:server1, node()},
  uid: "unique_id",
  cluster_name: :my_cluster,
  initial_members: [{:server1, node()}, {:server2, node()}],
  machine: {:machine, MyMachine, %{}}
}

Development

# Install dependencies
mix deps.get

# Run tests
mix test

# Run tests with coverage
mix test --cover

# Check formatting
mix format --check-formatted

# Generate documentation
mix docs

Contributing

This project is in early development. Contributions are welcome but please note:

  1. This is experimental software - expect breaking changes
  2. All contributions must include tests
  3. Follow the existing code style (mix format)
  4. Document public APIs with @doc and @moduledoc

License

Dual-licensed under:

See LICENSE, LICENSE-APACHE2, and LICENSE-MPL-RabbitMQ for details.

Acknowledgments