-
Notifications
You must be signed in to change notification settings - Fork 9.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
raft: centralize configuration change application #10865
Conversation
cc @nvanbenschoten (sorry - can't request reviews from you here -- @gyuho could you add @nvanbenschoten to the repo?) |
Codecov Report
@@ Coverage Diff @@
## master #10865 +/- ##
==========================================
+ Coverage 62.81% 63.16% +0.34%
==========================================
Files 398 398
Lines 37447 37483 +36
==========================================
+ Hits 23523 23675 +152
+ Misses 12336 12225 -111
+ Partials 1588 1583 -5
Continue to review full report at Codecov.
|
c143dd7
to
6e4f0a8
Compare
// follower state. This should never fire, but if it did, we'd have | ||
// prevented damage by returning early, so log only a loud warning. | ||
r.logger.Warningf("%x attempted to restore snapshot as leader; should never happen", r.id) | ||
return false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a comment to this function explaining the bool return value. Should we also step down to follower in this case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
745431f
to
87d0330
Compare
raft/raft.go
Outdated
} | ||
|
||
// More defense-in-depth: throw away snapshot if recipient is not in the | ||
// config. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you describe what could go wrong if we didn't do this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't really have a good answer here, but I added something. The "am I currently in the config" checks show up in random places and it doesn't seem there's a system for them. I mostly just don't want to have to even think about it in this method.
} | ||
|
||
pr := r.prs.Progress[r.id] | ||
pr.MaybeUpdate(pr.Next - 1) // TODO(tbg): this is untested and likely unneeded |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks new. What prompted you to add it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it's not new, just looks different now. I've traced this back to basically the age of the dinosaurs but there was no reason given for doing this. I don't think it matters, but am not sure enough to rip it out via a drive-by.
if isLearner && !pr.IsLearner { | ||
// Can only change Learner to Voter. | ||
// | ||
// TODO(tbg): why? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did we come to a decision on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think there's a good reason, but we're waiting for @xiang90 and @siddontang for more input. My plan so far is to implement everything as if we wanted to allow demotions (incl unit tests etc) but then not actually expose them to the outside world. We certainly don't need them in CRDB.
@@ -356,8 +356,8 @@ func TestLearnerPromotion(t *testing.T) { | |||
|
|||
nt.send(pb.Message{From: 1, To: 1, Type: pb.MsgBeat}) | |||
|
|||
n1.addNode(2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: for the purpose of avoiding verbosity and the diff, you might consider adding addNode
, removeNode
, and addLearner
as test-only (in raft_test.go
) methods. They would be one-liners.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a bad idea, but I'll keep as is.
raft/quorum/majority.go
Outdated
if i > 0 { | ||
buf.WriteByte(' ') | ||
} | ||
buf.WriteString(fmt.Sprintf("%d", sl[i])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fmt.Fprintf(&buf, ...
? Are you trying to prevent buf
from escaping?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, just some confused code. Fixed.
// Config reflects the configuration tracked in a ProgressTracker. | ||
type Config struct { | ||
Voters quorum.JointConfig | ||
Learners map[uint64]struct{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a little surprising to me. I would have expected Learners to live in MajorityConfig
. How would a joint configuration where three learners are all atomically upgraded to voting replicas be represented in this model?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would a joint configuration where three learners are all atomically upgraded to voting replicas be represented in this model?
You start with voters=(1) learners=(2 3)
and transition to voters=(1)&(1 2 3) learners=()
and then transition to voters=(1 2 3) learners=()
It gets more interesting when you add learners by demoting a voter because we don't at any given point want a peer to be tracked as both a voter and a learner, but naively you need this because you go from
voters=(1 2 3) learners=()
to voters=(1 2 3)&(1 2) learners=(3)
so (and this isn't in this PR yet because it's not needed) you get another map NextLearners
which stashes the learners that will be added when transitioning out of the joint config rather than in. I.e you really do
voters=(1 2 3) learners=()
to voters=(1 2 3) & (1 2) learners=() next_learners=(3)
and then to voters=(1 2) learners=(3)
Basically whenever you see that you need a new learner but in the joint config it's also a voter it goes to next_learners, otherwise to learners. It's more efficient to use learners directly (instead of unconditionally pushing into next_learners) so the peer gets caught up earlier.
I would have expected Learners to live in MajorityConfig
This could've gone either way, but so far the quorum
package is really only concerned with quorum computations at the moment. Learners don't influence the quorum.
I don't know if this is relevant to your question, but the ConfState will have to contain nodes, joint nodes, learners, and next learners, i.e. corresponds to a Config
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You start with voters=(1) learners=(2 3) and transition to voters=(1)&(1 2 3) learners=() and then transition to voters=(1 2 3) learners=()
But right now the second state would necessarily be voters=(1)&(1 2 3) learners=(2 3)
so (and this isn't in this PR yet because it's not needed) you get another map NextLearners which stashes the learners that will be added when transitioning out of the joint config rather than in
Yeah, so I guess this is what I'm getting at. You'd want to associate a learners map with each side of the joint config. I'm ok with this living outside of the MajorityConfig
, but we should make it clear that Learners
only refers to the left side of the joint config.
On that note, would we want to represent this like:
type Config struct {
Voters quorum.JointConfig
Learners [2]map[uint64]struct{}
}
to mirror the structure in quorum.JointConfig
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'd want to associate a learners map with each side of the joint config.
Sort of, but it's much less symmetric than the joint config (a learner added in the next config may end up in Learners instead of NextLearners), so I'm more comfortable keeping this more explicit. I plan to call this Learners
and NextLearners
(added roughly the comment above into the code to make the plan clear).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But right now the second state would necessarily be voters=(1)&(1 2 3) learners=(2 3)
Yep, but also we don't support joint config changes yet :-) I agree that this is awkward but the in-code comment now explains exactly how this will work (and it'll also be the next PR).
Config: Config{ | ||
Voters: quorum.JointConfig{ | ||
quorum.MajorityConfig{}, | ||
// TODO(tbg): this will be mostly empty, so make it a nil pointer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Put all the logic related to applying a configuration change in one place in preparation for adding joint consensus. This inspired various TODOs. I had to rewrite TestSnapshotSucceedViaAppResp since it was relying on a snapshot applied to the leader, which is now prevented.
This is helpful to quickly print the configuration log messages without having to specify Voters and Learners separately. It will also come in handy for joint quorums because it allows holding on to voters and learners as a unit, which is useful for unit testing.
Thanks for the reviews! I think I got everything addressed, but anything I missed I'll follow up on when pinged. |
Put all the logic related to applying a configuration change in one
place in preparation for adding joint consensus.
This inspired various TODOs.
I had to rewrite TestSnapshotSucceedViaAppResp since it was relying
on a snapshot applied to the leader, which is now prevented.