A raft implementation to learn the famous consensus algorithm and Go.
It is an incomplete implementation and contains many anti-patterns as this was personal learning project.
- gRPC is used for node to node communication, but can be replaced by any object implementing the RaftRPC interface in rpc.go
- any state machine can be passed to a raft node as long as it implements the interface defined in state_machine.go for applying, serializing, deserializing, and reading state machine commands
Leader election and log replication are implemented. Snapshotting and membership changes are not stable enough to be considered implemented.
A demo is available under demo/. The demo gives an example of a simple key/value state machine and where network failures can be simulated.
Two types of network failures are simulated: packet loss and partitions.
To configure packet loss, you can modify the DROP_RATE environment variable in the docker compose.
At every packet send / RPC, if a random number between 0 and 1 is less than the defined DROP_RATE, an error is returned to the sender / caller.
To configure network partions, two environment variables can be used:
PARTITION_INTERVAL_MS: the next partiton will be scheduled at random time in miliseconds between 0 andPARTITION_INTERVAL_MS.PARTITION_OUTAGE_MS: the maximum time to partion a node and make all its RPCs fail
The following raft related configurations are also available through environment variables in the docker compose.
ELECTION_TIMEOUT_MIN: the number of miliseconds after which a follower becomes a candidate after not receiving heartbeats from the leaderELECTION_TIMEOUT_MAX: when starting an election, a candidate will wait at most ELECTION_TIMEOUT_MAX miliseconds before losing a termHEARTBEAT_TIMEOUT_LEADER: the number of miliseconds after which a leader sends heartbeats. For a leader to be elected,HEARTBEAT_TIMEOUT_LEADERshould be smaller thanELECTION_TIMEOUT_MIN
The docker compose defines 3 raft nodes and a container that can be used as client. Since no HTTP API is available to communicate with the cluster, the easiest way to send commands is to run shell commands in the client container. Since the client accepts only read or write commands, here are the two types of shell commands you can run:
docker exec raft-client /app/raftbin write KEY VALUE
docker exec raft-client /app/raftbin read KEYstart the raft nodes and client container:
docker compose upin another terminal, send a write command to the cluster:
docker exec raft-client /app/raftbin write hello worldonce that's done, read the value:
docker exec raft-client /app/raftbin read hellochange the key
docker exec raft-client /app/raftbin write hello thereread the key
docker exec raft-client /app/raftbin read helloset another key
docker exec raft-client /app/raftbin write specialkey specialvalueread it back:
docker exec raft-client /app/raftbin read specialkeyand so on...