raft: add entryID and logSlice types#145
Conversation
24e7750 to
3efb5cd
Compare
|
@ahrtr @nvanbenschoten This addresses one of the feedback points from #139 review. PTAL. Commit-by-commit review might be the most convenient. |
3efb5cd to
c6d6aad
Compare
|
@nvanbenschoten Addressed your comments, thank you. PTAL. |
|
@nvanbenschoten @ahrtr Things start stacking up on top of this PR. Please let me know if there is major objection to this clean-up. If not, I would like to merge and iterate/rebase. |
|
Thanks! |
038ad09 to
e23983f
Compare
5e4ca8e to
42a3fef
Compare
|
@nvanbenschoten I've changed this PR a little since the last review. Mainly, I reworked the @ahrtr @serathius Could you please take a final look at this PR, and merge if it looks good? Commit-by-commit review can be most convenient. I have follow-up work that propagates safe struct usage in other places (which already helped uncover bugs in tests), but I am not including them in this PR to keep it small and simple. Thank you. |
| type logSlice struct { | ||
| // term is the leader term containing the given entries in its log. | ||
| term uint64 | ||
| // prev is the ID of the entry immediately preceding the entries. | ||
| prev entryID | ||
| // entries contains the consecutive entries representing this slice. | ||
| entries []pb.Entry | ||
| } |
There was a problem hiding this comment.
I am thinking it might be better to replace the following fields in Message with this struct.
Lines 402 to 415 in 2f10997
This can be addressed in separate PRs. In order to be backward compatible, we might want to do it across multiple releases. e.g. add logSlice into protobuf firstly, and then replace the fields in Message in a following release.
There was a problem hiding this comment.
Yeah, we should probably leave this for some future consideration. The scope of this work currently is internal cleanup that doesn't affect public API / wire protocol.
There are a few additional considerations:
logSliceis intended as a code-level data structure. It's usually a bad practice to use protobuf-generated structs as data structures because we have limited control of its content, and also this is part of the public API.- The
LogTerm/Indexfields are used by a bunch of other message types (not onlyMsgApp). We can't easily deprecate them, rather it would have to be a newLogSlicefield or something. A broader refactoring could be: split theMessageinto message-type-specific sub-structs.
There is some argument for moving in a slightly opposite direction from protobufs:
raftdoesn't need to know everypb.Entry, it only internally cares about IDs of the entries, and config changes. Exposing application-specific entry contents to this package poses security and privacy risks. There could be a world in whichraftis purely a code-level algorithm/protocol, and messages are packaged at application level.raftnecessitates dependency on protobuf, which is not the most efficient format: it has to be marshaled/unmarshaled. Some applications may choose other wire formats, for instanceflatbuffers.
|
A generic comment, I prefer not to add too much small commits, as most of them are mechanical changes, e.g. |
| // Users of this struct can assume the invariants hold true. Exception is the | ||
| // "gateway" code that initially constructs logSlice, such as when its content | ||
| // is sourced from a message that was received via transport, or from Storage, | ||
| // or in a test code that manually hard-codes this struct. In these cases, the | ||
| // invariants should be validated using the valid() method. |
There was a problem hiding this comment.
Can you explain more about the exceptions? I think we should add a generic verification to verify the invariant properties in each test if possible.
There was a problem hiding this comment.
Agree with that. This PR adds a valid() check to TestLogMaybeAppend, which is at the moment the only user of the new struct. More tests will be converted in a follow-up PR. The valid() check in other tests helped to find a few places where these invariants are violated (so the tests are testing incorrect behaviour).
There was a problem hiding this comment.
The "exception" is basically any place which receives a log slice from an untrusted source (network, storage, etc). Example: see the handleAppendEntries call:
Lines 1748 to 1750 in 2c8980b
Once the invariants are validated, this struct can be passed around internally, and all the code can assume invariants are true. For instance, we know that the i-th entry in entries slice necessarily has entries[i].index == prev.index + 1 + i, so we don't have to additionally check for this.
I think the comment already explains this well, but please suggest a better phrasing if it doesn't.
I prefer to isolate mechanical changes to separate commits, so that all changes have a small scope and are obviously correct. Such a PR "tells a story" rather than dumps a large change onto a reviewer. Small changes make it possible to review one thing at a time, and not have to do mental work to decompose it. I do this both for the reviewer and for myself, to self-review and convince myself too. I usually communicate in the PR that commit-by-commit may be convenient / increase confidence in this being a mechanical change. This is not mandatory to review things this way though - if you prefer the other way around feel free to review the whole change rather than individual commits. Separate commits also help easily narrowing the scope of the PR when reviewers ask so. Then it becomes a simple commit removal, rather than rewriting the whole change manually. |
A lot of code in this repo manipulates (term, index) tuples as ints. Getting it wrong can be costly, since entry IDs are centrepiece of raft safety. This commit introduces a type that will be used to replace pairs of ints, to make the code more readable and safe. Signed-off-by: Pavel Kalinnikov <pavel@cockroachlabs.com>
Signed-off-by: Pavel Kalinnikov <pavel@cockroachlabs.com>
Signed-off-by: Pavel Kalinnikov <pavel@cockroachlabs.com>
The lastTerm() method is used in pair with lastIndex() almost in every occasion. Returning the whole pair from a method makes it cleaner and cheaper. The lastIndex() and lastTerm() methods in the worst case fall back to storage, so doing it once is better than twice. Signed-off-by: Pavel Kalinnikov <pavel@cockroachlabs.com>
Signed-off-by: Pavel Kalinnikov <pavel@cockroachlabs.com>
Signed-off-by: Pavel Kalinnikov <pavel@cockroachlabs.com>
Signed-off-by: Pavel Kalinnikov <pavel@cockroachlabs.com>
42a3fef to
8d19377
Compare
This is a type-safe wrapper for all kinds of log slices. We will use it for more readable and safe code. Usages will include: wrapping log append requests; unstable struct; possibly surface in a safer API. Signed-off-by: Pavel Kalinnikov <pavel@cockroachlabs.com>
8d19377 to
2c8980b
Compare
|
@ahrtr Addressed actionable comments. Thanks for the review! |
| if prev.index < r.raftLog.committed { | ||
| // TODO(pav-kv): construct logSlice up the stack next to receiving the | ||
| // message, and validate it before taking any action (e.g. bumping term). | ||
| a := logSlice{term: m.Term, prev: entryID{term: m.LogTerm, index: m.Index}, entries: m.Entries} |
There was a problem hiding this comment.
Probably we should add a function something like message2LogSlice to encapsulate the details.
There was a problem hiding this comment.
Added a logSliceFromMsgApp func, because it should specifically be a MsgApp message.
There was a problem hiding this comment.
because it should specifically be a
MsgAppmessage.
In that case, we should add a verification each time when the function is called in case it's misused.. The verification should only be executed in test. Let me add a generic verification framework/utility in a separate PR.
ahrtr
left a comment
There was a problem hiding this comment.
LGTM with a minor comment
Great work. Thanks.
Signed-off-by: Pavel Kalinnikov <pavel@cockroachlabs.com>
2c8980b to
bd5b421
Compare
This PR introduces type-safe wrappers for log entry IDs and raft log slices. We will use them for a more readable and safe code. Some usage is introduced in this PR, see individual commits.
A lot of code in this repo manipulates
(term, index)tuples as separate raw ints. Likewise, entry slices are handled without association with the leader term. Getting this usage wrong can be costly, since both entry IDs and log slices are centrepiece of raft safety.Related to: #142, #144