Raftra Connecting Star

v0.2.2Raft written from scratch in Go, 71 tests passing

Raftra

A fault-tolerant key-value store.

We kill its leader every ten minutes. Your writes survive.

Three servers in Mumbai agree on every write using Raft, a consensus algorithm implemented here by hand in Go. Lose any one of them, leader included, and nothing that was acknowledged is lost. The bars next to the name are those three servers, live.

curl -fsSL https://raw.githubusercontent.com/shantanu-1607/Raftra/main/site/install.sh | sh

The recorder

Every two seconds this page asks each server who it thinks leads. Three minutes of answers scroll right to left. Orange is the leader, bone is a follower, red is a dead node. Hover or tap the tape to read any moment.

What happened

    Now break one yourself

    Five simulated servers running textbook Raft in your browser, slowed down ten times so you can follow every message. The log shows simulated time. Click a server to crash it, click again to bring it back.

    Waiting for the first election.

      • Vote request
      • Append entries or heartbeat
      • Reply
      • Committed entry
      • Not committed yet

      Try this: split the network while the leader sits on the small side, send a write, then heal. The stranded leader accepts the write but can never commit it. The real cluster also runs pre-vote, which this simulator leaves out so elections are easier to see.

      Get the CLI

      One small binary, no dependencies. It talks to the live cluster out of the box and follows the leader when it moves.

      Install from your terminalOne line on every system. Each script picks the right build for your CPU and checks it against checksums.txt before installing.

      Terminal

      curl -fsSL https://raw.githubusercontent.com/shantanu-1607/Raftra/main/site/install.sh | sh

      Installs to ~/.local/bin. Apple silicon and Intel. Installing this way also avoids the Gatekeeper warning a browser download gets.

      Any shell

      curl -fsSL https://raw.githubusercontent.com/shantanu-1607/Raftra/main/site/install.sh | sh

      Installs to ~/.local/bin and tells you if that folder isn’t on your PATH yet. x86-64 and ARM64.

      PowerShell

      irm https://raw.githubusercontent.com/shantanu-1607/Raftra/main/site/install.ps1 | iex

      Command Prompt

      powershell -ExecutionPolicy Bypass -c "irm https://raw.githubusercontent.com/shantanu-1607/Raftra/main/site/install.ps1 | iex"

      Installs to %LOCALAPPDATA%\Programs\raftra and adds it to your user PATH, so open a new terminal afterwards. x64 and ARM64.

      Every build, straight from the latest GitHub release
      SystemIntel or AMD (x86-64)ARM (64-bit)
      macOS Intel Mac.tar.gz Apple silicon.tar.gz
      Linux x86-64.tar.gz ARM64.tar.gz
      Windows x64.zip ARM64.zip

      macOS, downloaded in a browser: the binary isn’t signed, so Gatekeeper blocks it. Clear the flag with xattr -d com.apple.quarantine raftra-cli, or use the script above, which avoids it.

      Windows, downloaded as a .zip: unzip it, open a terminal in that folder and run .\raftra-cli.exe status. SmartScreen may warn about an unknown publisher the first time, because the binary isn’t signed.

      Verify by hand: compare against checksums.txt. Older versions are on the releases page.

      Try it in thirty seconds

      Every command goes to the live cluster. Writes land on the leader wherever it is, and the CLI moves on to the next server if one is mid-crash. Run raftra-cli with no arguments for an interactive shell.

      Playground rules

      • Keys up to 128 bytes, values up to 1 KiB, 10,000 keys in total.
      • 5 writes a second per IP address, with bursts of up to 10.
      • Everything is wiped at the top of every hour.
      • It’s public. Don’t store anything you wouldn’t post on a wall.
      $ raftra-cli status
      ● node2 leads term 2
        ⎿  raftra-n1.duckdns.org  follower   term 2    commit 4
           raftra-n2.duckdns.org  leader     term 2    commit 4
           raftra-n3.duckdns.org  follower   term 2    commit 4
      
      $ raftra-cli set city mumbai
      ● city = mumbai
        ⎿  Committed: a majority of nodes stored it.
           Followed the leader to raftra-n2.duckdns.org.
      
      $ raftra-cli get city
      ● city = mumbai
        ⎿  Read from raftra-n1.duckdns.org. Reads on a
           follower can briefly lag the leader.
      
      $ raftra-cli delete city
      ● Deleted city
        ⎿  Committed: a majority of nodes stored the delete.
           Followed the leader to raftra-n2.duckdns.org.
      
      $ raftra-cli get city
      ● city isn’t set
      Real output from CLI v0.2.2, recorded against the playground. Node 1 was a follower, so writes followed the leader to node 2.

      How a write survives

      Nothing counts as written until a majority of servers has it on disk. Lose any one machine, including the leader, and every acknowledged write is still there.

      Write path: the client asks a follower, is redirected to the leader, the leader stores the entry and replicates it to the followers, and replies once a majority has it. raftra-cli, curl or your app 1 PUT /kv/city 2 307: ask node1 3 PUT again node2follower node1leader node3follower HTTPS (Caddy)HTTPS (Caddy)HTTPS (Caddy) HTTP gatewayHTTP gatewayHTTP gateway RaftRaftRaft Log on disk (bbolt)Log on disk (bbolt)Log on disk (bbolt) Key-value mapKey-value mapKey-value map 4 append 4 append stored stored
      1. Any server takes the request. Clients can talk to whichever node answers first.
      2. Followers point to the leader. They reply with a 307 redirect to the leader’s address.
      3. The leader writes it to its own log and syncs the disk before going further.
      4. The leader sends it to the followers over gRPC on the private network. Each one stores it and replies.
      5. A majority has it, so it’s committed. The leader applies it to the key-value map and answers 200. Followers apply it on the next heartbeat.

      What it guarantees

      • At most one leader per term, and votes survive restarts.
      • Every server applies the same writes in the same order, and only after a majority stored them.
      • Any single server can die and no acknowledged write is lost.
      • A server on the losing side of a network split can’t commit anything.
      • Logs that disagree are repaired from the leader’s copy.
      • Pre-vote stops a returning server from disrupting a healthy leader.
      • A new leader commits an empty entry at once, so earlier writes become visible without waiting for traffic.

      What it doesn’t do (yet)

      • Reads on followers can be stale. There’s no ReadIndex or lease read.
      • No snapshots, so the log grows until the hourly wipe.
      • Cluster membership is fixed at three nodes.
      • No batching: every write is its own disk sync, which is why durable throughput is low.
      • No TLS or auth between nodes. The Raft port is only reachable on the private network.

      Measured, including the slow parts

      Three nodes on one Apple M2 laptop (macOS, Go 1.26.4), 100 concurrent clients, 80% writes. Full logs are in benchmark_results.md.

      Throughput

      Operations per second through the HTTP API.

      5,842 ops/s without fsync (-nosync, benchmark only)

      Write latency p50 20.84 ms, p99 40.23 ms. 0 errors in 1,000,000 operations.

      93.7 ops/s with fsync (the default, crash-safe)

      Write latency p50 1,323.76 ms. 0 errors in 10,000 operations.

      Why the gap: each write is its own synced bbolt transaction, taken while the node holds its lock, and on macOS a sync is a full drive-cache flush (F_FULLFSYNC). Group commit would fix it. It isn’t built yet.

      Failover

      Leader crash to the first write committed by the new leader, 20 trials.

      245 ms average

      Median 243 ms, fastest 205 ms, slowest 291 ms. The target was an average under 2,000 ms.

      Go microbenchmarks

      In-process cluster with real fsync, -count=3.

      Read from the state machine29.36 ns
      Mixed, 80% reads2.69 ms
      One write, quorum commit14.83 ms
      Proposal to quorum commit15.58 ms

      Tests

      Run with Go’s race detector on every push.

      Unit, persistence and chaos tests71 / 71
      Chaos scenarios, 10 runs each80 / 80
      Data races found0

      Scenarios: leader crash, follower crash, minority partition, split brain, rapid repeated leader kills, every node down at once, data readable after a full restart, and a follower that can’t hear the leader.

      How it was built

      Eight phases from an empty folder to a tested cluster, then a public one.

      1. FoundationProtobuf and gRPC interfaces, the node skeleton and its event loop.
      2. Leader electionRandomized timeouts, terms and votes. Exactly one leader per term.
      3. Log replicationAppendEntries, majority commit and the HTTP gateway for clients.
      4. PersistenceTerm, vote and log in bbolt, so a crashed node rejoins without losing anything.
      5. Failure testingAn in-memory network that can cut links and split groups, plus eight fault scenarios.
      6. Deployment and CLIDocker Compose for three nodes and a CLI that follows leader redirects.
      7. Benchmarks and metricsA load generator, failover timing and Prometheus metrics.
      8. Docs and polishREADME, a demo walkthrough and a full test pass.
      9. Then it went publicAbuse limits, release binaries for six platforms, three AWS servers, the ten-minute kill timer and this page.

      Two bugs the public cluster found

      A restarted server kept knocking out a healthy leader

      When the kill timer’s victim came back, it had been alone long enough to start an election and raise its term. Its first message carried that higher term, and the working leader stepped down for nothing. Every kill caused a second, pointless election.

      Fix: pre-vote (Raft thesis §9.6). A server now asks whether it could win before raising its term, and peers that heard from a leader recently say no. In three real-process test runs, the leader was knocked out three times before the fix and zero times after. Rejoining now takes 0.2–0.6 s.

      Committed data was invisible after a failover

      Raft lets a new leader commit entries from earlier terms only indirectly (§5.4.2). Until somebody wrote something new, the last writes before the crash stayed unapplied on the survivors.

      Fix: a no-op entry. Each new leader appends an empty entry in its own term as soon as it wins (§8). Committing it commits everything before it, so the data is back within one heartbeat. Ignoring stale election-timer fires in the same change cut the average failover from 322 ms to 245 ms, and the worst case from 611 ms to 291 ms.