Section 6: Lab 3 (contd.)
CSE 452 Spring 2021
Basic Paxos -> Multi-Paxos
Last week:
This week (and what we’re doing in lab 3):
Slot 1
Slot 2
Slot 3
Log
Paxos!
Paxos!
Paxos!
...
Problem: Contention
If many proposers are proposing values for the same slot at the same time, problems can occur
p1
p2
A1
A2
A3
HP#, HA#, HAV
-1, -1, null
-1, -1, null
-1, -1, null
p1
p2
A1
A2
A3
HP#, HA#, HAV
-1, -1, null
-1, -1, null
-1, -1, null
P(1)
P(1)
p1
p2
A1
A2
A3
HP#, HA#, HAV
1, -1, null
-1, -1, null
1, -1, null
PR(-1, null)
PR(-1, null)
p1
p2
A1
A2
A3
HP#, HA#, HAV
1, -1, null
-1, -1, null
1, -1, null
A(1, v1)
A(1, v1)
p1
p2
A1
A2
A3
HP#, HA#, HAV
1, 1, v1
-1, -1, null
1, -1, null
AR(accept)
A(1, v1)
p1
p2
A1
A2
A3
HP#, HA#, HAV
1, 1, v1
-1, -1, null
1, -1, null
A(1, v1)
P(2)
P(2)
p1
p2
A1
A2
A3
HP#, HA#, HAV
1, 1, v1
2, -1, null
2, -1, null
A(1, v1)
PR(-1, null)
PR(-1, null)
p1
p2
A1
A2
A3
HP#, HA#, HAV
1, 1, v1
2, -1, null
2, -1, null
A(1, v1)
A(2, v2)
A(2, v2)
p1
p2
A1
A2
A3
HP#, HA#, HAV
1, 1, v1
2, 2, v2
2, -1, null
A(1, v1)
A(2, v2)
AR(accept)
p1
p2
A1
A2
A3
HP#, HA#, HAV
1, 1, v1
2, 2, v2
2, -1, null
A(1, v1)
A(2, v2)
p1
p2
A1
A2
A3
HP#, HA#, HAV
1, 1, v1
2, 2, v2
2, -1, null
AR(reject)
A(2, v2)
p1
p2
A1
A2
A3
HP#, HA#, HAV
1, 1, v1
2, 2, v2
2, -1, null
A(2, v2)
P(3)
P(3)
p1
p2
A1
A2
A3
HP#, HA#, HAV
3, 1, v1
2, 2, v2
3, -1, null
A(2, v2)
PR(1, v1)
PR(-1, null)
p1
p2
A1
A2
A3
HP#, HA#, HAV
3, 1, v1
2, 2, v2
3, -1, null
A(2, v2)
A(3, v1)
A(3, v1)
p1
p2
A1
A2
A3
HP#, HA#, HAV
3, 3, v1
2, 2, v2
3, -1, null
A(2, v2)
AR(accept)
A(3, v1)
p1
p2
A1
A2
A3
HP#, HA#, HAV
3, 3, v1
2, 2, v2
3, -1, null
A(2, v2)
A(3, v1)
p1
p2
A1
A2
A3
HP#, HA#, HAV
3, 3, v1
2, 2, v2
3, -1, null
AR(reject)
A(3, v1)
Problem: Contention
Stable Leader - Multi-Paxos (PMMC)
Terminology (Basic Paxos -> PMMC)
Recommendation: Ballots
Even though doing something like having ballots being 1.1, 1.2, 1.3, 2.1, ... would work, we recommend making ballot a class like:
E.g. (3, server1)
Then making that comparable and defining a compareTo.
We recommend using a single ballot on each server (not for each role).
If you define the Ballot as an inner class, remember to make it static! If not, it might lead to failed garbage collection or lead to inefficiencies in testing and cause you to fail tests you would otherwise pass. Also make sure it’s serializable and has equals and hashcode!
PMMC vs. Lab 3
PMMC (Multi-Paxos) Properties
Recommendation: Logs
For your logs, store it as
Map<Integer, LogEntry> log; // Where slot number -> log entry
Where LogEntry contains:
If you define the LogEntry as an inner class, remember to make it static! If not, it might lead to failed garbage collection or lead to inefficiencies in testing and cause you to fail tests you would otherwise pass. Also make sure it’s serializable and has equals and hashcode!
Phase 1: Leader election
Phase 1: How to get elected
Phase 1: I’m elected, now what?
Server1
Ballot: 1.2
Accepted: {1->(A, 1.2)}
Server2
Ballot: 1.2
Accepted: {1->(A, 1.2)}
Server3
Ballot: 1.3
Accepted: {}
Server1
Ballot: 1.2
Accepted: {1->(A, 1.2)}
Server2
Ballot: 1.2
Accepted: {1->(A, 1.2)}
Server3
Ballot: 1.3
Accepted: {}
Server 3 wants to become leader
Server1
Ballot: 1.2
Accepted: {1->(A, 1.2)}
Server2
Ballot: 1.2
Accepted: {1->(A, 1.2)}
Server3
Ballot: 1.3
Accepted: {}
Server 3 wants to become leader
P1A:
ballot: 1.3
Server1
Ballot: 1.3
Accepted: {1->(A, 1.2)}
Server2
Ballot: 1.2
Accepted: {1->(A, 1.2)}
Server3
Ballot: 1.3
Accepted: {}
Server 3 wants to become leader
P1B:
ballot: 1.3
accepted: {1->(A, 1.2)}
Server1
Ballot: 1.3
Accepted: {1->(A, 1.2)}
Server2
Ballot: 1.2
Accepted: {1->(A, 1.2)}
Server3
Ballot: 1.3
Accepted: {}
Server 3 wants to become leader
P1B:
ballot: 1.3
accepted: {1->(A, 1.2)}
P1B:
ballot: 1.3
accepted: {}
Server1
Ballot: 1.3
Accepted: {1->(A, 1.2)}
Server2
Ballot: 1.2
Accepted: {1->(A, 1.2)}
Server3
Ballot: 1.3
Proposals: {1->(A, 1.2)}
Accepted: {}
Leader: true
Server 3 is now leader
Delayed P1A
Server1
Ballot: 1.3
Accepted: {1->(A, 1.2)}
Server2
Ballot: 1.2
Accepted: {1->(A, 1.2)}
Server3
Ballot: 1.3
Proposals: {1->(A, 1.2)}
Accepted: {}
Leader: true
Server 3 is now leader
P2A
ballot: 1.3
slot: 1
command: A
Delayed P1A
Phase 2: Leader
Recommendation: Roles
Garbage Collection
Garbage Collection: 3 servers in the system
Server1 (leader):
Latest executed slot: 4
Server2:
Latest executed slot: 4
Server3:
Latest executed slot: 3
Hasn’t heard about slot 4
Can garbage collect slots 1-3
Garbage Collection: 5 servers in the system
Server1 (leader):
Latest executed slot: 4
Server2:
Latest executed slot: 4
Server3:
Latest executed slot: 3
Hasn’t heard about slot 4
Cannot garbage collect anything until all servers have executed a slot
Server4:
Latest executed slot: N/A
Server5:
Latest executed slot: N/A
Recommendation: slot_in and slot_out
Keep track of slot_in and slot_out from PMMC:
Lab 3 Questions, q1
Question: What’s the difference between a ballot number and a log slot?�
Answer:
Lab 3 Questions, q2
Question: When are you done with a log slot? How can you make sure that they’re garbage-collected efficiently?�
Answer: You are done when all servers in the Paxos group have executed the request. You can piggyback messages to let everyone know not only what you are done with, but what you think everyone else is done with.
Dealing with Log “Holes”/Proposing no-ops
If we have in our log:
put(a, 1), ______, ______, append(a, 2)
Do we know those empty log slots are decided? How can we push these log slots to completion and execute? Why do we need no-ops?
Your implementation needs to be able to handle "holes" in the Paxos log. That is, at certain points, a server might see agreement being reached on a slot but not previous slots. Your implementation should still make progress in this case.
Server1
Ballot: 1.2
Accepted: {1->(A, 1.2), 2->(B, 1.2)}
Server2
Ballot: 1.2
Accepted: {2->(B, 1.2)}
Server3
Ballot: 1.3
Active: false
Accepted: {}
Log: {}
Server 3 log:
Not started
In progress
Value chosen
1
2
3
4
...
*Log only contains chosen slots in this example
Server1
Ballot: 1.2
Accepted: {1->(A, 1.2), 2->(B, 1.2)}
Server2
Ballot: 1.2
Accepted: {2->(B, 1.2)}
Server3
Ballot: 1.3
Active: false
Accepted: {}
Log: {}
P1A
ballot: 1.3
Server 3 log:
Not started
In progress
Value chosen
1
2
3
4
...
*Log only contains chosen slots in this example
Server1
Ballot: 1.2
Accepted: {1->(A, 1.2), 2->(B, 1.2)}
Server2
Ballot: 1.3
Accepted: {2->(B, 1.2)}
Server3
Ballot: 1.3
Active: false
Accepted: {}
Log: {}
P1B
ballot: 1.3
accepted: {2->(B, 1.2)}
Server 3 log:
Not started
In progress
Value chosen
1
2
3
4
...
*Log only contains chosen slots in this example
Server1
Ballot: 1.2
Accepted: {1->(A, 1.2), 2->(B, 1.2)}
Server2
Ballot: 1.3
Accepted: {2->(B, 1.2)}
Server3
Ballot: 1.3
Active: false
Accepted: {}
Log: {}
P1B
ballot: 1.3
accepted: {2->(B, 1.2)}
P1B
ballot: 1.3
accepted: {}
Server 3 log:
Not started
In progress
Value chosen
1
2
3
4
...
*Log only contains chosen slots in this example
Server1
Ballot: 1.2
Accepted: {1->(A, 1.2), 2->(B, 1.2)}
Server2
Ballot: 1.3
Accepted: {2->(B, 1.2)}
Server3
Ballot: 1.3
Active: true
Proposals: {2->(B, 1.2)}
Log: {}
Server 3 log:
Not started
In progress
Value chosen
1
2
3
4
...
*Log only contains chosen slots in this example
Server1
Ballot: 1.2
Accepted: {1->(A, 1.2), 2->(B, 1.2)}
Server2
Ballot: 1.3
Accepted: {2->(B, 1.2)}
Server3
Ballot: 1.3
Active: true
Proposals: {2->(B, 1.2)}
Received P2B from: {}
Log: {}
Server 3 log:
Not started
In progress
Value chosen
1
2
3
4
...
P2A
ballot: 1.3
slot number: 1
command: no-op
*Log only contains chosen slots in this example
Server1
Ballot: 1.2
Accepted: {1->(A, 1.2), 2->(B, 1.2)}
Server2
Ballot: 1.3
Accepted: {2->(B, 1.2)}
Server3
Ballot: 1.3
Active: true
Proposals: {2->(B, 1.2)}
Received P2B from: {}
Log: {}
Server 3 log:
Not started
In progress
Value chosen
1
2
3
4
...
P2A
ballot: 1.3
slot number: 2
command: B
*Log only contains chosen slots in this example
Server1
Ballot: 1.2
Accepted: {1->(A, 1.2), 2->(B, 1.2)}
Server2
Ballot: 1.3
Accepted: {2->(B, 1.3)}
Server3
Ballot: 1.3
Active: true
Proposals: {2->(B, 1.2)}
Received P2B from: {}
Log: {}
Server 3 log:
Not started
In progress
Value chosen
1
2
3
4
...
P2B
ballot: 1.3
slot number: 2
*Log only contains chosen slots in this example
Server1
Ballot: 1.2
Accepted: {1->(A, 1.2), 2->(B, 1.2)}
Server2
Ballot: 1.3
Accepted: {2->(B, 1.3)}
Server3
Ballot: 1.3
Active: true
Proposals: {2->(B, 1.2)}
Received P2B from: {2->[2]}
Log: {}
Server 3 log:
Not started
In progress
Value chosen
1
2
3
4
...
P2B
ballot: 1.3
slot number: 2
*Log only contains chosen slots in this example
Server1
Ballot: 1.2
Accepted: {1->(A, 1.2), 2->(B, 1.2)}
Server2
Ballot: 1.3
Accepted: {2->(B, 1.3)}
Server3
Ballot: 1.3
Active: true
Proposals: {2->(B, 1.2)}
Received P2B from: {2->[2,3]}
Log: {2->B}
Server 3 log:
Not started
In progress
Value chosen
1
2
3
4
...
P2A
ballot: 1.3
slot number: 1
command: no-op
*Log only contains chosen slots in this example
Server1
Ballot: 1.2
Accepted: {1->(A, 1.2), 2->(B, 1.2)}
Server2
Ballot: 1.3
Accepted: {1->(no-op, 1.3), 2->(B, 1.3)}
Server3
Ballot: 1.3
Active: true
Proposals: {2->(B, 1.2)}
Received P2B from: {2->[2,3]}
Log: {2->B}
Server 3 log:
Not started
In progress
Value chosen
1
2
3
4
...
P2B
ballot: 1.3
slot number: 1
*Log only contains chosen slots in this example
Server1
Ballot: 1.2
Accepted: {1->(A, 1.2), 2->(B, 1.2)}
Server2
Ballot: 1.3
Accepted: {1->(no-op, 1.3), 2->(B, 1.3)}
Server3
Ballot: 1.3
Active: true
Proposals: {2->(B, 1.2)}
Received P2B from: {1->[2], 2->[2,3]}
Log: {2->B}
Server 3 log:
Not started
In progress
Value chosen
1
2
3
4
...
P2B
ballot: 1.3
slot number: 1
*Log only contains chosen slots in this example
Server1
Ballot: 1.2
Accepted: {1->(A, 1.2), 2->(B, 1.2)}
Server2
Ballot: 1.3
Accepted: {1->(no-op, 1.3), 2->(B, 1.3)}
Server3
Ballot: 1.3
Active: true
Proposals: {2->(B, 1.2)}
Received P2B from: {1->[2, 3], 2->[2,3]}
Log: {1->no-op, 2->B}
Server 3 log:
Not started
In progress
Value chosen
1
2
3
4
...
*Log only contains chosen slots in this example
Slot # | 1 | 2 | 3 | 4 | 5 | 6 |
Candidate (s4) | (C, _, v) | (A, (2, s4), h) | | (A, (3, s2), x) | | |
Follower 1 (s3) | | (A, (2, s5), y) | | (C, _, x) | (A, (3, s5), b) | |
Follower 2 (s1) | (C, _, v) | (C, _, y) | | (A, (3, s5), x) | (A, (4, s2), g) | |
Slot # | 1 | 2 | 3 | 4 | 5 | 6 |
Leader (s4) | Chosen: v | Chosen: y | Propose: NO-OP with ballot (5, s4) | Chosen: x | Propose: g with ballot �(5, s4) | |
Example of merging logs (from P1Bs, with optimization of also sending chosen slots)�Suppose there are 5 servers. �(s4 trying to become leader with ballot number (5, s4).
([C]hosen/[A]ccepted, (Ballot), value)�Grey is executed
What was/is slot_in?
What was/is slot_out?
What should the leader do next?
How does s3 (+ other nodes) learn that slot 1 and 2 were chosen?
Cycling Leaders/Late Rejections*
Consider the case with 5 servers:
Server 4 is leader.
Server 4 sends P2A for value v in slot 1 with ballot number ⟨1, 4⟩ to every server. ← delayed
Server 5 sends P1A to every server with ballot ⟨1, 5⟩.
Servers 1 and 2 accept ballot ⟨1, 5⟩ and send P1B.
Server 5 then goes down.
Server 4 sees this and starts scouting sending P1A with ballot ⟨2, 4⟩.
Servers 1 and 2 accept ballot ⟨2, 4⟩ and send P1B.
Server 4 is now the active leader.
Server 4 sends P2A for value v in slot 1 with ballot ⟨2, 4⟩ to every server.
Server 2 then receives the old P2A with value v in slot 1 and ballot ⟨1, 4⟩.
Server 2 rejects this P2A and then sends back a P2B with the ballot ⟨2, 4⟩ without accepting the P2A.
Server 4 gets the P2B with ballot ⟨2, 4⟩ and believes server 2 has accepted the new P2A, when it has not.
*=For people who follow PMMC REALLY closely
Possible order for implementation of Paxos
Some cases/questions to consider when writing your code
Misc. tips
Tips for debugging
If you fail test 8 and...
Client workload wasn’t finished
It’s due to a timeout
If you fail test 10 and it takes too long...
If you fail 16,17,18
Some other tips for debugging 17,18
You can add print statements and run 17 and 18 (not writing it to a file) to see where it gets stuck.
Suggested positions:
If you fail 23 because...
Search space exhausted:
Could not find state matching [...]:
If you see servers that are supposed to be dead with timers triggering...
If you see that a handler isn’t idempotent
You can run the visualizer by itself and try to get to a state in which you send the message with the handler that it says isn’t idempotent, then right-click the message and click “duplicate”. Then process those messages in order. Note that the network model uses a set of messages, so handling a message again shouldn’t create a brand new message/timer.
To run the visualizer by itself, do something like this:
Miscellaneous design things we saw that we thought were good ideas
Some memes to remind you that your struggle was also experienced by students in previous quarters
New meme proposals
We are accepting more memes, plz & tks