Make(peers,me)
to create a Paxos peer.
peers
argument contains the ports of all the peers (including this one), me
argument is the index of this peer in the peers array. Start(seq,v)
asks Paxos to start agreement on instance seq, with proposed value v;
Start()
should return immediately, without waiting for agreement to complete. Status(seq)
to find out whether the Paxos peer thinks the instance
has reached agreement,
Status()
should consult the local Paxos peer's state and return immediately;
Status()
for old instances (but see the discussion of Done()
below).Start()
with different sequence numbers at about the same time,
your implementation should run the Paxos protocol concurrently for all of them. i
before starting the protocol
for instance i+1
. Each instance should have its own separate execution of the Paxos protocol.Status()
for it
Status()
for any instance
<= x
, it should call Done(x)
.
Done()
argument supplied by
its local application. Done()
value from each other peer. <= that
minimum
. Min()
method returns this minimum sequence number plus one.Done()
value in the agreement protocol packets; Done()
value the next time that
P2 sends an agreement message to P1. Start()
is called with a sequence number less than Min()
, the Start()
call should
be ignored. Status()
is called with a sequence number less than Min()
, Status()
should return Forgotten
.Here's a reasonable plan of attack:
paxos.go
to hold the state you'll need, according
to the lecture pseudo-code.
Start()
).Done: more than one Paxos instance may be executing at a given time, and they may be Start()ed and/or decided out of order (e.g. seq 10 may be decided before seq 5).
i
, wait for log entries < i
to be decidedDone: in order to pass tests assuming unreliable network, your paxos should call the local acceptor through a function call rather than RPC.
Hint: remember that multiple application peers may call Start() on the same instance, perhaps with different proposed values. An application may even call Start() for an instance that has already been decided (maybe because it could race when issuing a NoOp for a hole in the log).
Hint: think about how your paxos will forget (discard) information about old instances before you start writing code. Each Paxos peer will need to store instance information in some data structure that allows individual instance records to be deleted (so that the Go garbage collector can free / re-use the memory).
Hint: you do not need to write code to handle the situation where a Paxos peer needs to re-start after a crash. If one of your Paxos peers crashes, it will never be re-started.
Hint: have each Paxos peer start a thread per un-decided instance whose job is to eventually drive the instance to agreement, by acting as a proposer.
Hint: a single Paxos peer may be acting simultaneously as acceptor and proposer for the same instance. Keep these two activities as separate as possible.
Hint: a proposer needs a way to choose a higher proposal number than any seen so far. This is a reasonable exception to the rule that proposer and acceptor should be separate. It may also be useful for the propose RPC handler to return the highest known proposal number if it rejects an RPC, to help the caller pick a higher one next time. The px.me value will be different in each Paxos peer, so you can use px.me to help ensure that proposal numbers are unique.
Hint: figure out the minimum number of messages Paxos should use when reaching agreement in non-failure cases and make your implementation use that minimum.
Hint: the tester calls Kill() when it wants your Paxos to shut down; Kill() sets px.dead. You should check px.dead in any loops you have that might run for a while, and break out of the loop if px.dead is true. It's particularly important to do this in any long-running threads you create.
=>
should eventually catch up (learn about operations that it missed)Clerk.Get()
, Clerk.Put()
, and Clerk.Append()
methods in kvpaxos/client.go
must appear to have affected all replicas in the same
order and have at-most-once semanticsClerk.Get()
should see the value written by the most recent Clerk.Put()
or
Clerk.Append()
(in that order) to the same key. Clerk.Put()
or Clerk.Append()
must appear in that order just once (i.e., write the key/value database just once),
even though internally your client.go
may have to send RPCs multiple times until
it finds a kvpaxos server replica that replies.Here's a reasonable plan:
Op
struct in server.go
with the "value" information that kvpaxos will
use Paxos to agree on, for each client request.
Op
field names must start with capital letters. Op
structs as the agreed-on values
Op
structs to Paxos.Start()
.Op
structsgob.Register()
in StartServer()
teaches it how.PutAppend()
handler in server.go.
Put
or Append
Op
in the Paxos log (i.e., use Paxos to allocate a
Paxos instance, whose value includes the key and value (so that other kvpaxoses know
about the Put()
or Append()
)). Append
Paxos log entry should contain the Append
's
arguments, but not the resulting value, since the result might be large.Get()
handler.
Get
Op
in the Paxos log, and then "interpret"
the log before that point to make sure its key/value database reflects all recent Put()
s.Hint: your server should try to assign the next available Paxos instance (sequence number) to each incoming client RPC. However, some other kvpaxos replica may also be trying to use that instance for a different client's operation. So the kvpaxos server has to be prepared to try different instances.
Hint: your kvpaxos servers should not directly communicate; they should only interact with each other through the Paxos log.
Hint: as in Lab 2, you will need to uniquely identify client operations to ensure that they execute just once. Also as in Lab 2, you can assume that each clerk has only one outstanding Put
,Get
, or Append
.
Hint: a kvpaxos server should not complete a Get()
RPC if it is not part of a majority (so that it does not serve stale data). This means that each Get()
(as well as each Put()
and Append()
) must involve Paxos agreement.
Hint: don't forget to call the Paxos Done()
method when a kvpaxos has processed an instance and will no longer need it or any previous instance.
Hint: your code will need to wait for Paxos instances to complete agreement. The only way to do this is to periodically call Status()
, sleeping between calls. How long to sleep? A good plan is to check quickly at first, and then more slowly:
to := 10 * time.Millisecond
for {
status, _ := kv.px.Status(seq)
if status == paxos.Decided {
...
return
}
time.Sleep(to)
if to < 10 * time.Second {
to *= 2
}
}
Hint: if one of your kvpaxos servers falls behind (i.e. did not participate in the agreement for some instance), it will later need to find out what (if anything) was agreed to. A reasonable way to to this is to call Start()
, which will either discover the previously agreed-to value, or cause agreement to happen. Think about what value would be reasonable to pass to Start()
in this situation.
NoOp
for this, In case the fallen-behind node who wanted to discover previously agreed-to value actually succeeds in proposing one[DONE] Hint: When the test fails, check for gob error (e.g. "rpc: writing response: gob: type not registered for interface ...") in the log because go doesn't consider the error fatal, although it is fatal for the lab.
Each KVP server has a copy of the DB and of the log
Note: We will not reimplement a log, because we should not have to!
The log is already implicitly implemented in the paxos
library from Part A. And a server will know how much of the log it can apply to its local DB by calling Min()
on its Paxos peer.
Q: What's the high-level algorithm
A: A sketch:
nextSeq
number that it thinks the next request should get
S
creates an Op
op
from the request and calls Paxos.Start(nextSeq, op)
to get other
peers to agree on this op
Op
to be agreed
uponS
who got the majority to agree will not finish his Start()
loop
until everyone receives the Decided
message, so it seems that we do not need
to do anything?S
is down and there is still a majority up that can
inform the minority about the agreement?
S
's value (i.e. S
's proposal failed), then S
needs to
retry with a different nextSeq
number.
S
can definitely increment his nextSeq
number because he knows
a value was agreed on nextSeq
number that S
picks if he fails proposing?
Q: Do we need to apply the log to the DB?
A: Yes, from the memory test cases and from the Piazza answers.
Q: When do we apply the log to the DB?
A: Seems like we can call Paxos.Min()
and see which log entries everyone agreed on AND everyone HAS and apply those to our DB?
Q: Duplicate detection?
A: Only one outstanding client call =>
next client call can refer to previous call's XID and tell the server to get rid of it?