Sign in or create a free account to continue.

CMP-410 · Lesson 8 of 12 · 36 min

Consensus I: impossibility and Paxos

Lessons 6 and 7 kept arriving at the same problem. Safe failover needs replicas to agree on a new leader. State machine replication needs them to agree on the next command in the log. Atomic commit needs participants to agree on commit or abort, and lesson 5 showed that a single coordinator makes that agreement fragile. All of these are instances of consensus: getting a group of processes, some of which may fail, to agree on a single value. Consensus is the core problem of fault-tolerant distributed computing. This lesson defines it, explains a famous result showing that it cannot be solved deterministically in a purely asynchronous system, and then presents Paxos, the protocol that solves it in practice by keeping safety always and requiring timing assumptions only for progress. Lesson 9 presents Raft, a more recent protocol designed to be easier to understand and implement, and the coordination services built on these protocols.

The consensus problem

In the consensus problem, each of \(n\) processes may propose a value, and the processes must decide on one. A correct protocol satisfies three properties. Agreement: no two correct processes decide different values. Validity: the decided value was proposed by some process (so a trivial protocol that always decides 0 is ruled out). Termination: every correct process eventually decides. Agreement and validity are safety properties; termination is a liveness property, in the terminology of lesson 1. In the crash-recovery model, processes keep their promises across restarts by writing them to stable storage before acting on them.

Consensus looks easy until failures and asynchrony meet. If every process could reliably learn every other process's proposal, they could all pick the smallest. But a process that has not heard from another cannot tell whether that process has crashed or is merely slow. Waiting forever for a crashed process violates termination; deciding without a slow process risks it later deciding differently from information the others never saw.

The FLP impossibility result

In 1985 Michael Fischer, Nancy Lynch and Michael Paterson proved that in an asynchronous system, no deterministic protocol can solve consensus if even one process may crash. The result, known by the authors' initials as FLP, won the Dijkstra Prize and is one of the most important in the field. The proof shows that any protocol must have a bivalent initial configuration, one from which either value could still be decided depending on the order of events, and that from any bivalent configuration an adversary controlling message delays can always find a step that leads to another bivalent configuration, by delaying the one critical message that would settle the outcome. The protocol can therefore be kept undecided forever, without any process actually crashing; the mere possibility of a crash, which forbids waiting for everyone, is enough.

FLP does not mean consensus is unachievable in practice. It means that something in the model must be relaxed. Practical protocols relax it in one of three ways. They assume partial synchrony: the network eventually behaves within bounds for long enough, and the protocol guarantees termination only then. They use randomization: Ben-Or's 1983 protocol terminates with probability 1 by having processes flip coins when undecided. Or they use failure detectors: Chandra and Toueg showed in 1996 that consensus is solvable with a failure detector that is eventually accurate, even if it makes mistakes for a while. Paxos and Raft follow the first approach. Their safety never depends on timing: however unlucky the delays, two processes never decide different values. Only termination depends on the network eventually settling down.

Ways practical protocols escape the FLP impossibility
Way around FLPIdeaGuaranteeExample
Partial synchronyAssume timing bounds eventually holdAlways safe; terminates once the network is stablePaxos, Raft, PBFT
RandomizationBreak symmetry with coin flipsAlways safe; terminates with probability 1Ben-Or; some Byzantine protocols such as HoneyBadgerBFT
Failure detectorsUse an oracle that is eventually accurateSafe; terminates once the detector stabilizesChandra–Toueg consensus
Synchronous modelAssume known bounds always holdTerminates in f + 1 roundsSome aerospace and hardware protocols

Quorums and why majorities work

Fault-tolerant consensus protocols rely on a simple combinatorial fact. With \(n = 2f + 1\) acceptors, any two majorities of size \(f + 1\) share at least one acceptor. If a value is accepted by a majority, any later majority that is consulted includes at least one acceptor that knows about it. That shared acceptor is how information about a possibly chosen value survives failures and leadership changes. It also explains the replica count from lesson 1: tolerating \(f\) crash failures needs \(2f + 1\) acceptors, since the protocol must make progress with \(f\) of them down while still having its majorities overlap. Three acceptors tolerate one failure; five tolerate two.

\[|Q_1| + |Q_2| > n \quad\text{for every pair of quorums} \qquad\text{e.g. } n = 5,\ |Q| = 3\]

Paxos

Paxos was invented by Leslie Lamport in the late 1980s and described, as the parliament of a fictional Greek island, in The Part-Time Parliament, which was famously difficult to read and was not published until 1998. His 2001 note Paxos Made Simple restated it plainly. Paxos underlies Google's Chubby lock service, Spanner and many other systems. Single-decree Paxos, the basic protocol, decides one value. Processes play three roles, often all on the same machines: proposers suggest values, acceptors vote, and learners find out the outcome.

Each attempt to choose a value is identified by a ballot number (Lamport calls it a proposal number). Ballot numbers are totally ordered and unique to each proposer, for example by pairing a counter with the proposer's identifier. Each acceptor stores, on stable storage, the highest ballot it has promised and the last ballot and value it has accepted. The protocol has two phases.

In phase 1, prepare, a proposer chooses a ballot \(b\) higher than any it has used and sends PREPARE(b) to the acceptors. An acceptor that has not promised a ballot higher than \(b\) promises not to accept any ballot lower than \(b\) and replies PROMISE(b), including the ballot and value it most recently accepted, if any. In phase 2, accept, if the proposer receives promises from a majority, it chooses a value: if any of the promises reported an accepted value, it must use the value from the promise with the highest ballot; only if none did may it use its own value. It sends ACCEPT(b, v) to the acceptors. An acceptor accepts unless it has since promised a higher ballot. When a majority of acceptors have accepted the same ballot, the value is chosen, and learners can be told.

A message sequence diagram with one proposer and three acceptors as vertical lines. Phase 1: the proposer sends PREPARE with ballot 5 to all three; two reply PROMISE, one reporting a previously accepted value v with ballot 3. Phase 2: the proposer, required to adopt v, sends ACCEPT with ballot 5 and value v; a majority reply ACCEPTED and the value is chosen. A note explains that adopting the highest-ballot accepted value is what preserves agreement.
The two phases of single-decree Paxos; the proposer must adopt the highest-ballot value reported in the promises

The rule that the proposer must adopt the highest-ballot accepted value is the heart of Paxos. Suppose value \(v\) was chosen at ballot \(b\), meaning a majority accepted it. Any proposer with a later ballot \(b'\) must first get promises from a majority, and that majority overlaps the one that accepted \(v\). At least one acceptor in the overlap reports having accepted \(v\) at ballot \(b\) or later, and by induction every ballot between \(b\) and \(b'\) that got as far as phase 2 also used \(v\). So the proposer adopts \(v\). Once a value is chosen, every later ballot proposes the same value: agreement holds regardless of timing, message loss or how many proposers compete.

Paxos with five acceptors, 25 percent message loss and two proposers with conflicting values: attempts fail and retry, but at most one value is ever chosen
# Single-decree Paxos with five acceptors, lossy messages and two competing proposers.
import random

class Acceptor:
    def __init__(self):
        self.promised = 0                       # highest ballot promised
        self.accepted = (0, None)               # (ballot, value) last accepted
    def prepare(self, b):                       # phase 1b
        if b > self.promised:
            self.promised = b
            return ("promise", self.accepted)
        return ("nack", None)
    def accept(self, b, v):                     # phase 2b
        if b >= self.promised:
            self.promised = b
            self.accepted = (b, v)
            return "accepted"
        return "nack"

def propose(acceptors, ballot, my_value, loss, rnd):
    majority = len(acceptors) // 2 + 1
    replies = [a.prepare(ballot) for a in acceptors if rnd.random() > loss]
    promises = [acc for kind, acc in replies if kind == "promise"]
    if len(promises) < majority:
        return None, "phase 1 failed"
    # Key rule: adopt the value with the highest ballot among the promises, if any.
    b_max, v_max = max(promises, key=lambda p: p[0])
    value = v_max if v_max is not None else my_value
    oks = sum(1 for a in acceptors if rnd.random() > loss and a.accept(ballot, value) == "accepted")
    return (value, "chosen") if oks >= majority else (None, "phase 2 failed")

def run(seed, verbose):
    rnd = random.Random(seed)
    acceptors = [Acceptor() for _ in range(5)]
    chosen = set()
    ballot = {"P1": 1, "P2": 2}                 # ballots are unique: P1 uses odd, P2 even
    for step in range(8):
        who = rnd.choice(["P1", "P2"])
        value = "pay" if who == "P1" else "refund"
        v, status = propose(acceptors, ballot[who], value, loss=0.25, rnd=rnd)
        if verbose:
            print(f"  {who} ballot {ballot[who]:2d} wants {value!r:9s} -> {status}{' ' + repr(v) if v else ''}")
        if v: chosen.add(v)
        ballot[who] += 2
    return chosen

print("one run (seed 2):")
print("  values chosen:", run(2, True))
conflicts = sum(len(run(s, False)) > 1 for s in range(1000))
print(f"runs out of 1000 in which two different values were chosen: {conflicts}")
Output
one run (seed 2):
  P1 ballot  1 wants 'pay'     -> chosen 'pay'
  P2 ballot  2 wants 'refund'  -> chosen 'pay'
  P2 ballot  4 wants 'refund'  -> phase 1 failed
  P1 ballot  3 wants 'pay'     -> chosen 'pay'
  P2 ballot  6 wants 'refund'  -> chosen 'pay'
  P2 ballot  8 wants 'refund'  -> chosen 'pay'
  P1 ballot  5 wants 'pay'     -> phase 1 failed
  P2 ballot 10 wants 'refund'  -> chosen 'pay'
  values chosen: {'pay'}
runs out of 1000 in which two different values were chosen: 0

The simulation shows the safety argument at work. Attempts fail when too many messages are lost or when the other proposer has moved to a higher ballot. Once P1's value "pay" is chosen, P2's later successful attempts also choose "pay", although P2 wanted "refund": its promises reported the accepted value and the rule forced P2 to adopt it. Across a thousand randomized runs, no run ever chose two different values.

Liveness and the distinguished proposer

Paxos guarantees safety unconditionally but not progress. Two proposers can pre-empt each other forever: P1 completes phase 1 with ballot 1; P2 completes phase 1 with ballot 2, so P1's accepts are rejected; P1 retries with ballot 3, pre-empting P2's accepts; and so on. This is FLP in action, an adversarial schedule keeping the protocol undecided.

Two proposers alternating prepares can each block the other's accept forever: safety holds but nothing is decided
# Dueling proposers: each new prepare pre-empts the other's accept, so nothing is chosen.
class Acceptor:
    def __init__(self): self.promised, self.accepted = 0, (0, None)

acceptors = [Acceptor() for _ in range(3)]
def prepare(b):
    ok = 0
    for a in acceptors:
        if b > a.promised: a.promised = b; ok += 1
    return ok >= 2
def accept(b, v):
    ok = 0
    for a in acceptors:
        if b >= a.promised: a.promised = b; a.accepted = (b, v); ok += 1
    return ok >= 2

b1, b2 = 1, 2
for rnd in range(4):
    p1 = prepare(b1)                   # P1 prepares
    p2 = prepare(b2)                   # P2 prepares with a higher ballot before P1 accepts
    a1 = accept(b1, "X")               # P1's accept is rejected: promised b2
    b1 += 2
    p1b = prepare(b1)                  # P1 retries with a higher ballot...
    a2 = accept(b2, "Y")               # ...which pre-empts P2's accept
    b2 += 2
    print(f"round {rnd}: P1 accept {'ok' if a1 else 'rejected'}, P2 accept {'ok' if a2 else 'rejected'}")
print("No value chosen. Fix: elect a single distinguished proposer (a leader), with randomized backoff.")
Output
round 0: P1 accept rejected, P2 accept rejected
round 1: P1 accept rejected, P2 accept rejected
round 2: P1 accept rejected, P2 accept rejected
round 3: P1 accept rejected, P2 accept rejected
No value chosen. Fix: elect a single distinguished proposer (a leader), with randomized backoff.

The practical remedy is to designate a single distinguished proposer, a leader, through which all proposals pass, and to have competing proposers back off for random intervals. Leader election itself needs only to be eventually accurate: if two processes briefly both believe they are leader, Paxos stays safe and merely wastes a few rounds. Once the network is stable and one leader is recognized, every proposal succeeds in two round trips.

Multi-Paxos and replicated logs

A replicated log needs a sequence of decisions, one per log slot. Running full single-decree Paxos for every slot would cost two round trips per command. Multi-Paxos optimizes this. A stable leader runs phase 1 once for all future slots, using a single ballot; acceptors promise not to accept lower ballots for any slot. Thereafter, each new command needs only phase 2: one round trip from the leader to a majority. If the leader fails, a new leader runs phase 1 with a higher ballot, learns any values that may have been accepted in unfinished slots, completes them, and continues. With a stable leader, Multi-Paxos commits a command in one round trip to a majority, the minimum possible for a crash-tolerant replicated log.

The gap between the elegant single-decree protocol and a production Multi-Paxos system is large. Google's paper Paxos Made Live (Chandra, Griesemer and Redstone, 2007) described building Chubby's replication layer and listed the details the theory leaves out: disk corruption, leader leases for fast reads, snapshots to keep the log from growing forever, membership changes, and extensive testing. That gap is part of what motivated Raft, which specifies these details as part of the protocol.

Single-decree Paxos at a glance
PhaseMessageAcceptor rulePurpose
1aPREPARE(b)—Proposer claims ballot b
1bPROMISE(b, last accepted)Reply only if b exceeds every ballot promised; then promise bLearn any value that may already be chosen; block older ballots
2aACCEPT(b, v)—Propose v, which must be the highest-ballot value reported, if any
2bACCEPTED(b, v)Accept unless a higher ballot has been promisedA majority of acceptances means v is chosen

Variants of Paxos

Paxos has spawned a large family of variants. Fast Paxos (Lamport, 2006) lets clients send directly to acceptors, saving a message delay when there is no contention, at the cost of larger quorums. EPaxos (Moraru, Andersen and Kaminsky, 2013) is leaderless: any replica can commit a command in one round trip if it does not conflict with concurrent commands, which suits geo-replication. Flexible Paxos (Howard, Malkhi and Spiegelman, 2016) observed that phase 1 and phase 2 quorums need only intersect each other, not themselves, so a system can use small phase 2 quorums for fast commits and large phase 1 quorums for rare leader changes. Vertical Paxos reconfigures membership through an auxiliary master. Each trades simplicity for performance in some regime.

Byzantine fault tolerance

Paxos tolerates crashes, not lies. If acceptors may behave arbitrarily, for example because of corrupted memory, compromised software or a malicious operator, stronger protocols are needed. Lamport, Shostak and Pease showed in 1982 that Byzantine agreement among \(n\) processes is possible only if \(n \ge 3f + 1\). The intuition is that a correct process must be able to decide after hearing from \(n - f\) processes (it cannot wait for the \(f\) that may be silent), and among those \(n - f\), up to \(f\) may be lying, so the honest ones must outnumber them: \(n - 2f > f\).

Castro and Liskov's Practical Byzantine Fault Tolerance (PBFT, 1999) was the first protocol efficient enough for real systems. It uses a primary and three phases (pre-prepare, prepare, commit) with quorums of \(2f + 1\) out of \(3f + 1\) replicas, and signed or authenticated messages so that a lying primary can be detected and replaced through a view change. Its message complexity is quadratic in the number of replicas, which limits it to tens of nodes. Later protocols such as Tendermint (2014) and HotStuff (2019), which powered Facebook's planned Diem payment network, reduced the communication cost. Bitcoin's proof-of-work consensus (2008) takes a different approach for open, permissionless membership, replacing votes by computational work and offering probabilistic rather than absolute finality; Ethereum moved in 2022 from proof-of-work to a proof-of-stake protocol with BFT-style finality. Inside a single organization's data centers, where machines are trusted and failures are crashes, crash-tolerant protocols such as Paxos and Raft are almost always used, because Byzantine tolerance costs more replicas, more messages and more cryptography.

Crash-tolerant and Byzantine-tolerant consensus compared
PropertyCrash-tolerant (Paxos, Raft)Byzantine-tolerant (PBFT, HotStuff)
Replicas to tolerate f faults2f + 13f + 1
Quorum sizef + 1 (a majority)2f + 1
Message cost per decisionLinear in n with a stable leaderQuadratic in PBFT; linear in HotStuff
CryptographyNot requiredMessage authentication or signatures
Typical useDatabases, coordination services, logs inside one organizationBlockchains, systems spanning mutually distrusting parties

Worked examples

Worked problem

Which value must the proposer use?

With five acceptors, a proposer running ballot 9 receives promises from three: A1 accepted (ballot 4, "pay"), A2 accepted nothing, A3 accepted (ballot 7, "refund"). Which value must it propose?

Show solution
  1. The proposer has a majority of promises (3 of 5), so it may proceed.
  2. Some promises report accepted values; it must take the one with the highest ballot.
  3. Ballot 7 > ballot 4, so the value is "refund".

Answer: It must propose "refund" with ballot 9, whatever value it originally wanted.

Worked problem

How many replicas?

Ledgerline wants its ledger to survive two simultaneous machine crashes. How many replicas does a Raft or Paxos group need? What if the machines might be compromised and lie?

Show solution
  1. Crash tolerance: \(n = 2f + 1 = 5\).
  2. Byzantine tolerance: \(n = 3f + 1 = 7\).
  3. With 5 replicas a majority is 3, so the group keeps working with any 2 down.

Answer: Five replicas for crashes; seven if replicas might behave arbitrarily.

Worked problem

Why not decide with a minority?

A team proposes committing log entries once 2 of 5 acceptors accept them, to cut latency. Show how agreement can fail.

Show solution
  1. A partition separates {A1, A2} from {A3, A4, A5}.
  2. Leader L1 on the first side gets 2 acceptances for value X in slot 10 and treats it as chosen.
  3. Leader L2 on the other side gets 3 acceptances for value Y in slot 10.
  4. The two quorums do not intersect, so neither leader learns of the other's value.

Answer: Two different values are chosen for slot 10; quorums must intersect (here, at least 3 of 5) to preserve agreement.

Common mistakes

  • MistakeRead FLP as "consensus is impossible in practice".

    InsteadFLP rules out deterministic, guaranteed termination in a fully asynchronous model; protocols stay safe always and terminate under partial synchrony.

  • MistakeLet a Paxos proposer use its own value after phase 1 regardless of the promises.

    InsteadIt must adopt the highest-ballot value reported; that rule is what preserves agreement.

  • MistakeKeep promises and accepted values only in memory.

    InsteadAcceptors must persist them before replying, or a restart could break promises.

  • MistakeRun consensus with an even number of replicas to save a machine.

    InsteadFour replicas tolerate only one failure, like three; use odd sizes.

  • MistakeUse Byzantine protocols for ordinary data-center replication.

    InsteadThey need 3f + 1 replicas and much more communication; crash-tolerant protocols suffice when replicas are trusted.

Summary and key terms

  • Consensus requires agreement, validity and termination despite failures; it underlies leader election, replicated logs and atomic commit.
  • FLP: no deterministic protocol guarantees consensus in an asynchronous system with even one crash; practical protocols assume partial synchrony, randomize or use failure detectors.
  • Majority quorums of 2f + 1 acceptors always intersect, so knowledge of a possibly chosen value survives failures.
  • Single-decree Paxos uses ballots, a prepare phase that collects promises and prior acceptances, and an accept phase; adopting the highest-ballot value preserves agreement.
  • Paxos is always safe but can livelock with competing proposers; a distinguished leader with randomized backoff restores progress.
  • Multi-Paxos runs phase 1 once per leader and commits each log entry with one round trip to a majority.
  • Byzantine fault tolerance needs 3f + 1 replicas and authenticated messages; PBFT and its successors serve settings with mutually distrusting participants.
Consensus
Agreement by a set of processes on a single value despite failures.
Agreement
The property that no two correct processes decide differently.
Validity
The property that the decided value was proposed by some process.
FLP impossibility
The result that deterministic consensus cannot be guaranteed in an asynchronous system with one crash failure.
Ballot number
A unique, totally ordered identifier for a Paxos proposal attempt.
Proposer
A Paxos role that suggests values and drives the protocol.
Acceptor
A Paxos role that votes on proposals and stores promises and accepted values durably.
Learner
A Paxos role that finds out which value was chosen.
Multi-Paxos
An optimization of Paxos for a sequence of decisions using a stable leader and one round trip per entry.
Byzantine fault tolerance
Tolerance of processes that behave arbitrarily, requiring 3f + 1 replicas.
PBFT
Practical Byzantine Fault Tolerance, Castro and Liskov's three-phase Byzantine consensus protocol.

Practice problems

Work each problem on paper first, then reveal the answer to check.

  1. Which consensus properties are safety properties?

    Show answer

    Agreement and validity.

  2. What does FLP prove, and under what assumptions?

    Show answer

    That no deterministic protocol can guarantee termination of consensus in an asynchronous system if one process may crash.

  3. Name three ways practical systems work around FLP.

    Show answer

    Partial synchrony, randomization and eventually accurate failure detectors.

  4. With 7 acceptors, how many failures can Paxos tolerate, and what is the quorum size?

    Show answer

    Three failures; quorums of 4.

  5. Why must ballot numbers be unique to each proposer?

    Show answer

    So that two proposers never use the same ballot with different values.

  6. What must an acceptor do before replying to PREPARE?

    Show answer

    Record its promise durably.

  7. How can two proposers prevent each other from making progress?

    Show answer

    By alternately issuing higher prepares that invalidate each other's accepts.

  8. How many round trips does Multi-Paxos need per command with a stable leader?

    Show answer

    One round trip from the leader to a majority.

  9. Why does Byzantine agreement require n ≥ 3f + 1?

    Show answer

    After hearing from n − f processes, up to f of them may lie, so honest replies must still outnumber liars: n − 2f > f.

  10. Why is Raft or Paxos usually preferred to PBFT within one company's data centers?

    Show answer

    Replicas are trusted, so crash tolerance suffices with fewer replicas, messages and cryptography.

Lesson quiz · 5 questions

Score 60% or more to complete this lesson.

Take the quiz