One of the challenges of teaching distributed systems is how to bridge between theoretical concepts and practical open source solutions such as Kafka. Using pattern structures makes those practical solutions easy to understand.
Distributed systems are inherently stateful systems because they manage data. They vary in size from a few nodes to a cluster of hundreds of servers.
Process crashes: These can happen at any time due to software or hardware faults, maintenance, unrelated events bringing the server down, etc,.
Network delays: There are two main problems at hand here:
Process pauses: If a leader is temporarily unavailable (eg. garbage collection pause), followers may elect a new leader. Generation clocks, a monotonically increasing number that keeps track of leader elections, can be used so that the old leader detects that they have to step down from leadership. The follower can detect that the old leader’s generation is too old and would discard any messages received from it.
System clocks are not guaranteed to be synchronized and thus can’t be used to order messages. Lamport Clock numbers are updated on writes only and are passed with the messages to solve conflicts by choosing the most recent update. This doesn’t help detect concurrent writes between different servers, in which case, version vectors are a more adequate solution.
Fault-Tolerant Consensus: Different algorithms like zab, Raft, and multi-Paxos are used to achieve the strongest consistency guarantees in distributed systems. Consensus refers to which data is stored, in what order, and when to make it visible to clients. Paxos relies on two-phase commit, quorum and generation clocks to achieve this. Achieving consensus on individual requests is not enough, as those requests need to be executed in the same order on all nodes.
Replicated Log: This is achieved with a Leader-Follower model, using a Quorum to agree on updating the high-water mark that decides which values are now visible to clients. Reading is possible from the followers if stale values are tolerated. Requests are processed in a strict order, using a single-socket channel. Pipelining (sending multiple requests, without waiting for responses in-between) can be used to improve latency and throughput.

Atomic Commit: When data is too big to fit on a single node, partitioning schemes (eg. fixed, key ranges, etc,.) are used. To maintain fault tolerance, each partition uses Replicated Log across multiple nodes. A two-phase commit allows locking data to achieve atomicity, at the cost of throughput. Versioned values can be used in two-phase commit implementations to solve conflicts without using locks.
Paxos is a family of algorithms for reaching consensus in Distributed Systems.
Consensus happens when a majority agrees on one proposed result, which will be eventually known by everyone.
Parties may agree on any result, not necessarily the one they proposed.
Consensus is needed in both leader-follower schema (consensus on leader election) and peer-to-peer schema (to guarantee consistency).
There are three roles: Proposers (Propose new values), Acceptors (Contribute to reaching consensus on new values), and Learners (Learn the agreed-upon value, eg. if they were late to the election). A node can play these roles at the same time.
Nodes can’t forget what they accepted (persistence) and they also need to know what a majority is.
A Paxos run aims to reach a single consensus. To reach another consensus, another Paxos run must happen.
The election goes as follows:

