Visualizing the 6 rules of Primary-Backup
CSE 452 Spring 2026
Announcements
Motivating Primary-Backup:
What will we have accomplished in lab 1?
What do we aim to achieve in lab 2?
General Flow
Pool of Idle Servers
Client
Primary
Backup
View Server
Request
Forward Request
Forward
Ack
Response
Ping
Ping
Ping
GetView
What messages do they send/receive?
General Flow
Pool of Idle Servers
Client
Primary
Backup
View Server
Request
Forward Request
Forward
Ack
Response
Ping
Ping
Ping
GetView
What messages do they send/receive?
ViewReply (VR)
VR
VR
VR
Rules:
Rule 1: What if we did not forward requests before executing them? (primary changes)
Client
Server A
Server B
{}
Server C
{}
Append(foo->bar)
AppendOk(bar)
View 2
Primary: A
Backup: B
{foo: bar}
Append(foo->bar)
Rule 1: What if we did not forward requests before executing them? (primary changes)
Client
Server A
{foo: bar}
Server B
{}
Server C
{}
Get(foo)
View 3
Primary: B
Backup: C
KeyNotFound
Rule 1: What if we did not forward requests before executing them? (different views)
Client
Server A
Server B
{foo: bar}
Server C
{foo: bar}
Append(foo->baz)
View 3
Primary: B
Backup: C
AppendRes(barbaz)
Suppose both the client and Server A do not get the new View…
Append(foo->baz)
View 2
Primary: A
Backup: B
View 2
Primary: A
Backup: B
View 3
Primary: B
Backup: C
{foo: bar}
{foo: barbaz}
Rule 1: What if we did not forward requests before executing them? (different views)
Client
Server A
{foo: barbaz}
Server B
{foo: bar}
Server C
{foo: bar}
View 3
Primary: B
Backup: C
When the client eventually
gets the new view…
Get(foo)
GetRes(bar)
View 3
Primary: B
Backup: C
View 3
Primary: B
Backup: C
Rule 1: Solution
Primary must wait for backup to execute the command before executing it and replying to client.
…could there be an optimization on read commands…?
Rule 1: Solution
Primary must wait for backup to execute the command before executing it and replying to client.
…could there be an optimization on read commands…?
Yes, as long as you are okay with stale reads.
Rule 1: Takeaway
Forwarding!
Aside: What happens if you try to process more than one client request at a time?
Primary
Backup
Put(“a”, “foo”)
Put(“a”, “foo”)
“a” -> “foo”
Client 1
Put(“a”, “foo”)
Client 2
Put(“a”, “bar”)
Put(“a”, “bar”)
Put(“a”, “bar”)
“a” -> “bar”
“a” -> “bar”
“a” -> “foo”
Rules:
Rule 2: Problem & Takeaway
View 2
Primary: A
Backup: B
View 3
Primary: B
Backup: C
View 4
Primary: C
Backup: D
View 5
Primary: D
Backup: B
Delayed Forward or a
Duplicated Forward
Rule 2: Problem & Takeaway
Takeaway: Have the view number on all messages
View 2
Primary: A
Backup: B
View 3
Primary: B
Backup: C
View 4
Primary: C
Backup: D
View 5
Primary: D
Backup: B
Delayed Forward or a
Duplicated Forward
Rules:
Rule 3: What if the new Primary was not in the previous view?
Client
Server A
{}
Server B
{}
Server C
{}
Server D
{}
Append(foo->bar)
Append(foo->bar)
AppendRes(bar)
AppendRes(bar)
View 2
Primary: A
Backup: B
Server A
{foo: bar}
Server B
{foo: bar}
Rule 3: What if the new Primary was not in the previous view?
Client
Server A
{foo: bar}
Server B
{foo: bar}
Server C
{}
Server D
{}
View 3
Primary: C
Backup: D
Get(foo)
KeyNotFound()
Get(foo)
KeyNotFound()
Rule 3: Takeaway
Possible Transitions?
The new primary MUST have been in the previous view
View i
Primary: A
Backup: B
View i+1
Primary: A
Backup: NULL
View i
Primary: A
Backup: B
View i+1
Primary: B
Backup: NULL
View i
Primary: A
Backup: B
View i+1
Primary: B
Backup: A
Some Possible? Transitions
Rules:
Rule 4: What if a non-primary responds to a client?
Server C
{}
View 4
Primary: B
Backup: null
Client
Server A
{}
Server B
{}
Put(a->bar)
View 2
Primary: A
Backup: B
View 4
Primary: B
Backup: null
View 4
Primary: B
Backup: null
View 3
Primary: B
Backup: C
Rule 4: What if a non-primary responds to a client?
Server C
{}
View 4
Primary: B
Backup: null
Client
Server A
{a:bar}
Server B
{}
Put(a->bar)
View 2
Primary: A
Backup: B
View 4
Primary: B
Backup: null
View 4
Primary: B
Backup: null
View 3
Primary: B
Backup: C
Rule 4: What if a non-primary responds to a client?
Server C
{}
View 4
Primary: B
Backup: null
Client
Server A
{a:bar}
Server B
{}
PutOk()
View 2
Primary: A
Backup: B
View 4
Primary: B
Backup: null
View 4
Primary: B
Backup: null
View 3
Primary: B
Backup: C
Rule 4: What if a non-primary responds to a client?
Server C
{}
View 4
Primary: B
Backup: null
Client
Server A
{a:bar}
Server B
{}
View 4
Primary: B
Backup: null
View 4
Primary: B
Backup: null
View 4
Primary: B
Backup: null
View 3
Primary: B
Backup: C
Rule 4: What if a non-primary responds to a client?
Server C
{}
View 4
Primary: B
Backup: null
Client
Server A
{a:bar}
Server B
{}
View 4
Primary: B
Backup: null
View 4
Primary: B
Backup: null
View 4
Primary: B
Backup: null
View 3
Primary: B
Backup: C
Get(a)
Rule 4: What if a non-primary responds to a client?
Server C
{}
View 4
Primary: B
Backup: null
Client
Server A
{a:bar}
Server B
{}
View 4
Primary: B
Backup: null
View 4
Primary: B
Backup: null
View 4
Primary: B
Backup: null
View 3
Primary: B
Backup: C
KeyNotFound()
Rule 4: Takeaway
Rules:
Recap: State Transfer (State Machine Replication)
Rule 5: Problem
View 1
Primary: A
Backup: B
Server A
{a:bar}
View 1
Primary: A
Backup: B
Everyone is in view 1…
Server B
{a:bar}
View 1
Primary: A
Backup: B
Server C
{}
View 1
Primary: A
Backup: B
VS
Rule 5: Problem
View 1
Primary: A
Backup: B
Server A
{a:bar}
Server B
{a:bar}
View 1
Primary: A
Backup: B
View 1
Primary: A
Backup: B
VS
Server B dies, ViewServer moves on to view 2. Server C is the “new” backup
View 2
Primary: A
Backup: C
Server C
{}
View 1
Primary: A
Backup: B
Rule 5: Problem
Server A
{a:bar}
View 1
Primary: A
Backup: B
VS
Server A dies and Server C is made primary (without view 2 ever being acked by A)
View 2
Primary: A
Backup: C
View 3
Primary: C
Backup: null
Server C
{}
View 1
Primary: A
Backup: B
Server B
{a:bar}
View 1
Primary: A
Backup: B
View 3
Primary: C
Backup: null
Rule 5: Problem
Server A
{a:bar}
View 1
Primary: A
Backup: B
VS
But when client shows up again..its not looking good:/
View 3
Primary: C
Backup: null
Server C
{}
Server B
{a:bar}
View 1
Primary: A
Backup: B
View 3
Primary: C
Backup: null
Client
View 3
Primary: C
Backup: null
KeyNotFound()
Get(a)
Rule 5: Problem Explained
Rule 5: Ideal Flow
Server A
{a:bar}
Server B
{a:bar}
View 1
Primary: A
Backup: B
View 1
Primary: A
Backup: null
VS
Ideally, right after B dies, Server A completes a state transfer
View 2
Primary: A
Backup: C
Server C
{}
View 1
Primary: A
Backup: B
StateTransfer((“a” -> “bar”))
Server C
{a:bar}
View 2
Primary: A
Backup: C
View 2
Primary: A
Backup: C
Rule 5: Ideal Flow
Server A
{a:bar}
Server B
{a:bar}
View 2
Primary: A
Backup: C
View 1
Primary: A
Backup: null
VS
And send an ack to ViewServer, to indicate state sync between all involved servers in the current view
View 2
Primary: A
Backup: C
Server C
{a:bar}
View 2
Primary: A
Backup: C
View2 ack
Rule 5: Takeaway
Server A
{a:bar}
View 1
Primary: A
Backup: null
VS
So that when views change and C becomes primary, everything remains consistent
View 3
Primary: C
Backup: null
Server C
{a:bar}
Server B
{a:bar}
View 1
Primary: A
Backup: null
View 3
Primary: C
Backup: null
Client
View 3
Primary: C
Backup: null
GetResult(bar)
Get(a)
Recap: State Transfer (Subtle details)
Executing Duplicate State Transfer Requests
Primary
Backup
StateTransfer((“a” -> “foo”))
StateTransfer((“a” -> “foo”))
StateTransferAck
“a” -> “foo”
“a” -> “foo”
Executing Duplicate State Transfer Requests
Primary
Backup
StateTransfer((“a” -> “foo”))
“a” -> “foobarbaz”
“a” -> “foobarbaz”
“a” -> “foo”
StateTransferAck
Once backup becomes the next primary, clients could get unexpected results.
Takeaway: Execute a state transfer once per view. The backup should still always respond to a state transfer request acknowledging the request.
Rules:
Rule 6: What if we processed requests during state transfer?
Primary
Backup
StateTransfer((“a” -> “foo”))
“a” -> “foo”
Client 1
Put(“a”, “bar”)
Put(“a”, “bar”)
Put(“a”, “bar”)
“a” -> “foo”
“a” -> “bar”
“a” -> “bar”
StateTransferAck
Rule 6: Takeaway
Rules: