SE2062 Distributed Systems Β· Group 30
- Project Overview
- Team Members
- Architecture
- Module Breakdown
- API Reference
- Getting Started
- Running the CLI Tools
- Key Concepts
- References
NexusChat is a high-availability distributed messaging system built for the SE2062 Distributed Systems module. It simulates a real-world cluster of three nodes that replicate messages using the Raft consensus algorithm.
Core guarantees:
- Messages are committed only when a majority (quorum) of nodes confirms replication
- If a minority of nodes crash, the system stays online and continues accepting messages
- If quorum is lost, messages are queued locally and automatically committed once enough nodes recover
- Recovering nodes are synced via anti-entropy so they catch up to the current log
| # | Name | registration numbers | emails | Responsibility |
|---|---|---|---|---|
| 01 | Supun Dharmaratne | IT24101504 | IIT24101504@my.sliit.lk | Fault Tolerance β Failure Detection & Automatic Recovery |
| 02 | Ruchira Lakshan | IT24101673 | IT24101673@my.sliit.lk | Data Replication β Quorum-Based Consistency |
| 03 | Sasiru Sithujaya | IT24100722 | IT24100722@my.sliit.lk | Time Synchronisation β Berkeley Algorithm |
| 04 | Sachith Asmadala | IT24103430 | IT24103430@my.sliit.lk | Consensus & Agreement β Raft Leader Election |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Flask REST API (api.py) β
β /api/status /api/messages /api/servers/:id/crash|recover β
β /api/logs /api/time/sync /api/time/report β
βββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββΌβββββββββββββββββ
β RaftCluster β
β (raft_consensus.py) β
β β
β ββββββββββββ ββββββββββββ ββββββββββββ β
β β Node-01 β β Node-02 β β Node-03 β β
β β (Leader) β β(Follower)β β(Follower)β β
β ββββββ¬ββββββ βββββββ¬βββββ βββββββ¬βββββ β
β ββββββββββββββββ΄βββββββββββββββ β
β InMemoryTransport (RPC) β
βββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββΌββββββββββββββββββββββββββββ
β β β
ββββββΌββββββ ββββββββΌβββββββ βββββββββΌβββββββ
β Failure β β Replication β β Time Sync β
β Detector β β Manager β β Manager β
β β β β β (Berkeley) β
ββββββββββββ βββββββββββββββ ββββββββββββββββ
β β
ββββββΌββββββ ββββββββΌβββββββ
β Failover β β Recovery β
β Manager β β Sync Mgr β
ββββββββββββ βββββββββββββββ
All nodes communicate through InMemoryTransport, which simulates network RPC calls and supports injecting partitions for testing.
The base Server class. Each instance represents one distributed node.
| Attribute | Description |
|---|---|
message_store |
Dict of committed messages keyed by message_id |
pending_messages |
List of messages queued when no quorum exists |
is_alive |
Boolean; set to False on crash |
last_heartbeat |
Timestamp of last heartbeat, used by failure detector |
Key methods: store_message(), queue_pending_message(), simulate_crash(), simulate_recovery()
Implements the full Raft consensus algorithm across three nodes.
Classes:
RaftNodeβ Per-node Raft state machine (follower / candidate / leader)RaftClusterβ Cluster coordinator; exposes the API used byapi.pyInMemoryTransportβ Simulated in-process RPC layer
How a message is committed:
- Client sends POST to
/api/messages api.pycallsraft_cluster.append_message()- The leader appends the entry to its log and replicates to all followers via
AppendEntriesRPC - Once a majority (
βN/2β + 1) acknowledges, the leader advancescommit_index - Each node calls
apply_committed_entries(), which writes toserver.message_store
No-quorum path:
If fewer than majority nodes are alive, the leader's append_client_message() detects this before writing and calls server.queue_pending_message(). The background worker (_background_worker) retries every 2 seconds until quorum is restored.
Leader election:
Raft uses randomised election timeouts (1.5β3.0 s). A follower that times out increments its term, becomes a candidate, and sends RequestVote RPCs to peers. The first candidate to collect votes from a majority becomes leader.
Polls each node's last_heartbeat timestamp and marks nodes as suspected-failed if they exceed the configured timeout (default 5 s).
Key methods: check_all_servers(), get_alive_servers(), is_server_failed(name)
Maintains a reference to the current primary and automatically promotes a backup if the primary is detected as failed. Used for direct message delivery outside the Raft path.
Key methods: get_active_server(), send_with_failover(message_id, content, sender)
Manages replication factor (RF=3), write/read quorums, deduplication, and read-repair.
| Feature | Detail |
|---|---|
| Write quorum | W = βRF/2β + 1 β majority must acknowledge before commit |
| Read quorum | R = RF - W + 1 β sufficient copies to guarantee latest value |
| Deduplication | Tracks sender:content fingerprint to prevent double-writes |
| Read-repair | On a successful read, any replica missing the message is automatically updated |
When a node rejoins, RecoverySyncManager.sync_server() identifies the gap between the recovering node's store and the union of all donor stores, then transfers every missing message.
TimeSyncManager coordinates clock synchronisation using a simplified Berkeley Algorithm:
- Master polls every alive node for its local (skewed) time
- Computes the average as the reference ("master") time
- Each node receives its correction offset (
master_time β local_time) - Messages are re-sorted by corrected timestamp to restore causal ordering
ClockSkewSimulator assigns random Β±2 s offsets to each node at startup to simulate real-world clock drift.
Flask app that wires all modules together and exposes the endpoints consumed by the frontend.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/status |
Cluster status: nodes, leader, term, quorum, pending count |
GET |
/api/messages |
All committed + pending messages, sorted by corrected timestamp |
POST |
/api/messages |
Send a message; returns 201 on commit, 202 if queued (no quorum) |
POST |
/api/servers/<id>/crash |
Crash node by ID (1β3) |
POST |
/api/servers/<id>/recover |
Recover a crashed node and trigger sync |
POST |
/api/time/sync |
Run Berkeley clock synchronisation |
GET |
/api/time/report |
Clock skews and timestamp metadata |
GET |
/api/logs |
Event log (boot, store, crash, recovery, leader, etc.) |
POST /api/messages β request body:
{ "sender": "Supun", "content": "Hello cluster!" }Response 201 (committed):
{ "id": "msg_abc123", "server": "Node-01", "term": 2, "index": 5, "replicas": ["Node-01","Node-02"] }Response 202 (queued β no quorum):
{ "id": "msg_xyz", "status": "pending", "stored_on": ["Node-01"], "message": "No Raft quorum β message queued." }- Python 3.10+
- pip
pip install flask flask-corsNo other third-party packages are required. All distributed system logic uses the Python standard library.
python api.pyThe server starts on http://localhost:8000. You should see:
[API] NexusChat backend running at http://localhost:8000
[API] Endpoints:
GET /api/status
GET /api/messages
POST /api/messages
POST /api/servers/<id>/crash
POST /api/servers/<id>/recover
GET /api/logs
The cluster auto-elects a leader and seeds two initial messages on startup.
python nchat_cli.pyRuns all three nodes in a single process β useful for quickly testing leader election, replication, and crash/recovery without the full HTTP stack.
# Terminal 1 β start the backend
python api.py
# Terminal 2 β interactive CLI
python nexuschat_cli.pyAvailable commands:
| Command | Action |
|---|---|
m <text> |
Broadcast a message to the cluster |
c1 / c2 / c3 |
Crash Node-01 / Node-02 / Node-03 |
r1 / r2 / r3 |
Recover the specified node |
s or status |
Print leader, term, and quorum info |
sync |
Trigger Berkeley time synchronisation |
logs |
Display recent system events |
q or quit |
Exit |
Raft divides consensus into three sub-problems: leader election, log replication, and safety. A leader is elected per term; all writes go through the leader. An entry is committed once the leader confirms replication to a majority.
For a 3-node cluster, quorum = 2. The cluster tolerates 1 node failure and remains fully operational. With only 1 node alive, it loses quorum β writes are queued and the cluster becomes read-unavailable until a second node recovers.
Messages sent during a quorum outage are stored in server.pending_messages. The background thread (_retry_pending_messages) polls every 2 seconds and retries each pending message through Raft once quorum is restored.
Addresses clock skew across distributed nodes. The master collects all node times, computes the average, and sends each node its correction offset. Messages are then re-sorted by corrected timestamp to maintain causal ordering.
- Ongaro, D. & Ousterhout, J. (2014). In Search of an Understandable Consensus Algorithm (Raft)
- Gifford, D. (1979). Weighted Voting for Replicated Data
- Fischer, M., Lynch, N. & Paterson, M. (1985). Impossibility of Distributed Consensus with One Faulty Process (FLP)
- Gusella, R. & Zatti, S. (1989). The Accuracy of the Clock Synchronization Achieved by TEMPO in Berkeley UNIX 4.3BSD
SE2062 Distributed Systems Β· Group 30 Β· Β© 2026