Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

raft-py

Pure-Python implementation of the Raft consensus algorithm (Ongaro, 2014) with a deterministic simulated network, partition-injection primitives, and a correctness test suite that exercises election safety, log matching, and partition tolerance.

Follows In Search of an Understandable Consensus Algorithm, Figure 2, section-by-section. No external dependencies.

$ pytest tests/                  -> 12 passed in 0.08s
$ python examples/demo.py
  [1] Cold start: leader elected -> node 4 (term 1)
  [3] Network partition: isolate node 4
      -> new leader -> node 1 (term 2)
  [5] Heal the partition -> isolated node catches up
      State-machine consistency verified across all 5 nodes

Why this exists

Raft powers etcd, Consul, CockroachDB, TiKV, and most production distributed databases shipped after 2014. It's the canonical exam in distributed systems interviews. A clean from-scratch implementation, with deterministic tests of failure scenarios, is the best way to internalize:

  • Election safety — at most one leader per term (section 5.2)
  • Log matching — if two logs have the same (index, term), all prior entries are identical (section 5.3)
  • Leader completeness — committed entries survive future elections (section 5.4.1, up-to-date vote requirement)
  • Current-term commit rule — leaders only commit entries from their own term directly; older-term entries commit transitively (section 5.4.2, Figure 8 anomaly)
  • State-machine safety — applied entries on different nodes match

All of the above are tested below.

Architecture

raft-py/
├── raft/
│   ├── __init__.py     - public surface
│   ├── rpc.py          - RequestVote, AppendEntries dataclasses (Fig 2)
│   ├── log.py          - 1-based replicated log with matching helpers
│   ├── network.py      - tick-driven simulated network with partitions
│   └── node.py         - the state machine (Follower / Candidate / Leader)
├── tests/
│   ├── helpers.py
│   ├── test_election.py
│   ├── test_replication.py
│   └── test_partition.py
└── examples/
    └── demo.py         - observable 5-node walkthrough

The simulated network

Time is virtual. Nothing happens until tick() is called. Each tick advances the world by one unit; messages have configurable latency and may be dropped at a configurable rate. This makes every scenario perfectly reproducible (same seed -> same election outcome, byte-for-byte).

Partition injection is one call:

net.isolate(4)                  # node 4 alone, rest together
net.partition([[1, 2], [3, 4, 5]])
net.heal()

Cross-partition messages are silently dropped (modelling unreachability), and reachability is re-checked at delivery time: a partition installed while messages are in flight still drops them.

The state machine

Role is one of FOLLOWER / CANDIDATE / LEADER. Transitions follow Ongaro Figure 2:

   ─── timeout ───►          ─── majority ───►
FOLLOWER       CANDIDATE                      LEADER
   ◄─── higher-term AE ───   ◄─── higher-term ───

Each node carries:

Field Persistent? Role
current_term yes all
voted_for yes all
log[] yes all
commit_index no all
last_applied no all
next_index[peer], match_index[peer] no leader only

(persistence is wired up but currently in-memory; v0.2.0 adds disk durability.)

Tests

pytest tests/ -> 12/12 passing on this run. Categories:

Election (5 tests)

  • single_leader_emerges_in_quiet_cluster — 5 nodes, no failures, exactly one leader emerges
  • election_safety_at_most_one_leader_per_term — invariant checked every tick for 2000 ticks
  • 3_node_cluster_elects — smallest viable cluster
  • new_election_when_leader_dies — isolating the leader provokes a fresh election in a higher term
  • term_monotonically_increases — terms never go backwards

Replication (4 tests)

  • committed_entry_replicated_to_all — leader submit → all 5 state machines apply it
  • many_entries_consistent_ordering — 20 commands, every node's state-machine prefix matches
  • follower_logs_match_leader_log_matching_property — for any common (idx, term), all prior entries are byte-identical
  • submit_to_follower_rejected — only the leader accepts client writes

Partition tolerance (3 tests)

  • minority_cannot_elect — 2-of-5 partition fails to find a majority
  • majority_continues_to_make_progress — 4-of-5 commits new entries while 1 is isolated
  • partition_heal_catches_up_lagging_node — formerly-isolated node syncs from the leader after heal

Demo walkthrough (real run)

t= 156  leader elected -> node 4 (term 1)
t= 356  after 5 client submits, all 5 nodes: log=5, commit=5, applied=5
t= 468  node 4 isolated; new leader -> node 1 (term 2)
        (note: node 4 still believes it is leader of term 1 - correct,
         it can't know it has been replaced while isolated)
t= 768  majority commits 5 more entries; isolated node 4 stuck at 5
t=1268  partition healed; node 4 catches up, steps down to follower
        ALL FIVE NODES: log=10, commit=10, applied=10, term=2

Run it

pytest tests/                  # 12 passed in ~0.1s
python examples/demo.py        # observable walkthrough

No pip install needed (stdlib only).

What's not here (yet)

The implementation is intentionally compact. Known production-grade extensions on the roadmap:

  • Persistence — flush current_term, voted_for, log[] to disk on every change
  • Log compaction — install snapshots when the log gets too large
  • Cluster membership changes — single-server AddServer / RemoveServer
  • Pre-vote — avoid disruptive elections from partitioned-then-reconnected nodes
  • Leader transfer extension — graceful handoff for maintenance
  • Faster log catch-up — fast-backoff nextIndex hint (currently decrements by 1)
  • Real networkasyncio+TCP transport (the current Network is in-process; swapping it is a single class)

v0.2.0 will pick the first 2-3.

References

  • Ongaro, D. & Ousterhout, J. (2014). In Search of an Understandable Consensus Algorithm. USENIX ATC. paper
  • Ongaro, D. (2014). Consensus: Bridging Theory and Practice. PhD thesis, Stanford. (The 250-page version — covers everything in the v0.2.0 roadmap.)
  • The Raft visualization — recommended for building intuition before reading code.

About

Raft consensus algorithm in pure Python with simulated network, partition injection, and correctness test suite (Ongaro 2014, Fig 2)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages