README.md

# 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**.
>
> - APIs are unstable and may change without notice
> - Data formats are not finalized
> - Critical edge cases may not yet be handled
> - Distributed multi-node testing is incomplete
>
> 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](https://github.com/rabbitmq/ra)).

## Overview

RaftEx implements the Raft consensus algorithm, providing:

- **Leader Election**: Pre-vote and candidate phases with quorum-based voting
- **Log Replication**: Write-ahead log (WAL) with batched writes and fsync support
- **Snapshotting**: Point-in-time state snapshots with chunked transfer
- **Cluster Membership**: Dynamic membership changes (join/leave)
- **State Machine**: Pluggable state machine interface for application logic
- **Consistent Queries**: Leader-verified reads with majority acknowledgement

## 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`:

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

Or use directly from Git:

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

## Quick Start

```elixir
# 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

- **`RaftEx`**: Main public API for cluster and server management
- **`RaftEx.ServerProc`**: `gen_statem` process managing server lifecycle
- **`RaftEx.Server`**: Core Raft protocol logic (state transitions, RPC handling)
- **`RaftEx.Log`**: Persistent replicated log with WAL + mem table
- **`RaftEx.LogWal`**: Write-ahead log GenServer with batched fsync
- **`RaftEx.LogMeta`**: DETS + ETS backed metadata store
- **`RaftEx.Machine`**: Behaviour for pluggable state machines
- **`RaftEx.Server.Election`**: Leader election with pre-vote support
- **`RaftEx.Server.RpcHandler`**: AppendEntries, InstallSnapshot, Heartbeat handlers

## Configuration

```elixir
# 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

```bash
# 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:
- Mozilla Public License 2.0 (MPL-2.0)
- Apache License 2.0 (Apache-2.0)

See [LICENSE](LICENSE), [LICENSE-APACHE2](LICENSE-APACHE2), and [LICENSE-MPL-RabbitMQ](LICENSE-MPL-RabbitMQ) for details.

## Acknowledgments

- Original implementation: [RabbitMQ RA](https://github.com/rabbitmq/ra)
- Raft paper: [In Search of an Understandable Consensus Algorithm](https://raft.github.io/raft.pdf)