Paxos

From CS 260r 2014
Jump to: navigation, search

Here are four versions of Paxos, three believed correct and one known broken.

Paxos-A

persistent state
    proposer: n_p (initially 0) --- the latest proposal round
    acceptor: n_l (initially 0) --- the largest round received in a prepare()
    acceptor: n_a (initially 0) --- the largest round received in an accept()
    acceptor: v_a (initially NULL) --- the value received by the accept() for n_a

volatile state at proposer
    a --- number of responses received
    n_o --- the largest round received in a prepared()
    v_o --- the value received by the prepared() for n_o

propose: (at proposer)
    n_p := n_p + 1 + uniqueifier()
    a := 0         // volatile state
    n_o := 0
    send prepare(n_p) to a majority of acceptors

receive prepare(n): (at acceptor)
    n_l := max(n_l, n)
    send prepared(n_a, v_a) to proposer

receive prepared(n, v): (at proposer)
    if n > n_o:
        n_o := n
        v_o := v
    a := a + 1
    if a = f + 1:
        if v_o = NULL:
            v_o := choose value
        a := 0
        n_p := max(n_p, n_o)
        send accept(n_p, v_o) to a majority of acceptors

receive accept(n, v): (at acceptor)
    if n >= n_l:
        n_l := n_a := n
        v_a := v
    send accepted(n_a) to proposer

receive accepted(n): (at proposer)
    if n = n_p:
        a := a + 1
    if a = f + 1:
        send decided(v_o) to all

timeout: (at proposer)
    call propose() again

Bad Paxos

In Bad Paxos, we do not store n_l; instead we overload n_a and make n_a := max(n_l, n_a). This implementation is incorrect. Can you design an example execution that demonstrates this incorrectness?

persistent state
    proposer: n_p (initially 0) --- the latest proposal round
    acceptor: n_a (initially 0) --- the largest round received in an
    accept() or prepare()
    acceptor: v_a (initially NULL) --- the value received by the accept() for n_a

volatile state at proposer
    a --- number of responses received
    n_o --- the largest round received in a prepared()
    v_o --- the value received by the prepared() for n_o

propose: (at proposer)
    n_p := n_p + 1 + uniqueifier()
    a := 0         // volatile state
    n_o := 0
    send prepare(n_p) to a majority of acceptors

receive prepare(n): (at acceptor)
    n_a := max(n_a, n)
    send prepared(n_a, v_a) to proposer

receive prepared(n, v): (at proposer)
    n_o := max(n_o, n)
    if n = n_o and v != NULL:
        v_o := v
    a := a + 1
    if a = f + 1:
        if v_o = NULL:
            v_o := choose value
        a := 0
        send accept(n_o, v_o) to a majority of acceptors

receive accept(n, v): (at acceptor)
    if n >= n_a:
        n_a := n
        v_a := v
    send accepted(n_a) to proposer

receive accepted(n): (at proposer)
    if n = n_o:
        a := a + 1
    if a = f + 1:
        send decided(v_o) to all

timeout: (at proposer)
    call propose() again


Paxos-B

Many implementations of the Paxos idea are possible, and differences among them are useful to consider. Here’s an alternate choice, Paxos-B, which is closer to Robert Morris's pseudocode.

persistent state
    proposer/acceptor: n_l (initially 0) --- the largest round received in a prepare()
    acceptor: n_a (initially 0) --- the largest round received in an accept()
    acceptor: v_a (initially NULL) --- the value received by the accept() for n_a

volatile state at proposer
    n_p --- round of current proposal
    a --- number of prepared/accepted messages received
    n_o --- latest known prepared round
    v_o --- latest known prepared value

propose: (at proposer)
    n_p := n_l + 1 + uniqueifier()   // overlap between proposer state and acceptor state
    a := 0
    n_o := 0
    send prepare(n_p) to a majority of acceptors, including self

receive prepare(n): (at acceptor)
    if n > n_l:
        n_l := n
        send prepared(n_a, v_a) to acceptor

receive prepared(n, v): (at proposer)
    if n > n_o:
        n_o := n
        v_o := v
    a := a + 1
    if a = f + 1:
        if v_o = NULL:
            v_o := choose value
        a := 0
        send accept(n_p, v_o) to majority of servers

receive accept(n, v): (at acceptor)
    if n >= n_l:
        n_l := n_a := n
        v_a := v
        send accepted(n) to proposer

receive accepted(n): (at proposer)
    a := a + 1
    if a = f + 1:
        send decided(v_o) to all

Questions:

  • It is critically important that the propose step include the collocated acceptor in the set of acceptors to which it sends prepare. Why?
  • Paxos-B acceptors do not respond to failed prepare and accept messages. Paxos-A acceptors, in contrast, respond with enough information for the proposer to detect failure. What are the advantages and disadvantages of each approach?

Paxos-C

In this Paxos implementation, the proposer doesn’t pre-select a majority; instead, it sends prepare and accept messages to all acceptors, and takes the first majority that arrives. As a result, the pseudocode must handle prepared messages that arrive from stragglers while the protocol is accepting.

persistent state
    proposer/acceptor: n_l (initially 0) --- the largest round received in a prepare()
    acceptor: n_a (initially 0) --- the largest round received in an accept()
    acceptor: v_a (initially NULL) --- the value received by the accept() for n_a

volatile state
    proposer: n_p --- round of current proposal
    proposer: a --- number of prepared/accepted messages received
    proposer: n_o --- latest known prepared round
    proposer: v_o --- latest known prepared value

propose: (at proposer)
    n_p := n_l := n_l + 1 + uniqueifier()   // n_p is volatile; overlap between proposer state and acceptor state
    a := 1
    n_o := 0
    send prepare(n_p) to all acceptors except self

receive prepare(n): (at acceptor)
    if n > n_l:
        n_l := n
        send prepared(n_a, v_a) to acceptor

receive prepared(n, v): (at proposer)
    if n_p = n_l and n_p ≠ n_a:   // a straggler’s prepared will arrive when n_p = n_a already
        if n > n_o:
            n_o := n
            v_o := v
        a := a + 1
        if a = f + 1:
            if v_o = NULL:
                v_o := choose value
            a := 1
            n_a := n_p
            v_a := v_o
            send accept(n_p, v_o) to all acceptors except self

receive accept(n, v): (at acceptor)
    if n >= n_l:
        n_l := n_a := n
        v_a := v
        send accepted(n) to proposer

receive accepted(n): (at proposer)
    if n_p = n:
        a := a + 1
        if a = f + 1:
            send decided(v_o) to all

Questions:

  • What are the advantages and disadvantages of sending prepare and accept messages to more than the required majority of acceptors?
  • How does this pseudocode handle the overlap between proposers and acceptors (i.e., messages to self)?