-
Notifications
You must be signed in to change notification settings - Fork 4k
spanset: assert that batches don't access store local and unreplicated RangeID local keys #157153
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
base: master
Are you sure you want to change the base?
Conversation
9f7f91b to
ed7f5cd
Compare
4d115e0 to
4fbc791
Compare
Potential Bug(s) DetectedThe three-stage Claude Code analysis has identified potential bug(s) in this PR that may warrant investigation. Next Steps: Note: When viewing the workflow output, scroll to the bottom to find the Final Analysis Summary. After you review the findings, please tag the issue as follows:
|
Potential Bug(s) DetectedThe three-stage Claude Code analysis has identified potential bug(s) in this PR that may warrant investigation. Next Steps: Note: When viewing the workflow output, scroll to the bottom to find the Final Analysis Summary. After you review the findings, please tag the issue as follows:
|
f9daad1 to
cdb4edc
Compare
pkg/kv/kvserver/spanset/spanset.go
Outdated
| forbiddenSpansMatchers []func(roachpb.Span) error | ||
| allowUndeclared bool | ||
| allowForbidden bool |
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.
Pre-existing, but made stronger in this PR: it seems that this type shouldn't mix the job of representing a set of spans and that of [test-only] verifying it / forbidding keys. There seems to be a clear subset of methods that add/canonicalize/access spans without having an opinion on them, and then the "check" methods that verify stuff. I would make the latter the responsibility of a wrapper (either existing one like spanSetBatch, or make a new one).
Ideally need a prereq PR/commit that does that separation.
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.
Will do it in another PR
6f5f0a9 to
8193ffc
Compare
iskettaneh
left a comment
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.
Reviewable status:
complete! 0 of 0 LGTMs obtained (waiting on @pav-kv)
pkg/kv/kvserver/spanset/spanset.go
Outdated
| forbiddenSpansMatchers []func(roachpb.Span) error | ||
| allowUndeclared bool | ||
| allowForbidden bool |
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.
Will do it in another PR
pav-kv
left a comment
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 nearing the LGTM, thanks for making it cleaner each time.
iskettaneh
left a comment
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.
Reviewable status:
complete! 0 of 0 LGTMs obtained (waiting on @pav-kv)
This commit introduces the concept of forbidden spans, which will allow us to assert our batches don't modify Raft's engine keys.
iskettaneh
left a comment
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.
Reviewable status:
complete! 0 of 0 LGTMs obtained (waiting on @pav-kv)
iskettaneh
left a comment
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.
Reviewable status:
complete! 0 of 0 LGTMs obtained (waiting on @pav-kv)
pav-kv
left a comment
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.
The non-test parts LGTM, minor suggestions. Request to clean up the tests a bit, so that I can make a final (self-convincing) pass on them and LGTM tomorrow.
pkg/kv/kvserver/spanset/batch.go
Outdated
| // TODO(ibrahim): The fields spans, spansOnly, and ts don't seem to be used. | ||
| // Consider removing them and performing the necessary clean ups. |
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.
or making them used as intended
Analogous to how we have a spanset helper contains() that understands the special span representation: [x-eps,x). This commit adds Overlaps that expects the same span representation. Moreover, this commit makes this special span representation explicit by introducing a new type called `TrickySpan`.
…d RangeID local keys This commit adds the following test-only assertions: 1) Generated batches don't touch store-local keys. 2) Generated batches don't touch unreplicated RangeID local keys. We disable the check in exactly 3 locations we know that we currently touch those keys.
pav-kv
left a comment
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.
LGTM, thanks for this change! Some last suggestions included, apply as many as makes sense.
| var start roachpb.Key | ||
| var end roachpb.Key | ||
|
|
||
| if parts[0] != "X" { | ||
| start = roachpb.Key(parts[0]) | ||
| } | ||
|
|
||
| if parts[1] != "X" { | ||
| end = roachpb.Key(parts[1]) | ||
| } |
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.
style nit / opinion (feel free to ignore, just noticed the pattern in this PR, e.g. in overlapsUnreplicatedRangeIDLocalKeys too):
In simple code, vertical sparseness / empty lines is often not needed (makes "value" per screen lower). Empty lines is a tool to communicate/separate slightly more medium-size blocks of logically related code, offering some "structure" to the reader.
E.g. here I would recommend making it one block, because it's all about converting parts to span:
var span roachpb.Span
if parts[0] != "X" {
span.Key = roachpb.Key(parts[0])
}
if parts[1] != "X" {
span.EndKey = roachpb.Key(parts[1])
}
return spanAs an extension to this (more applies to other places: Contains/Overlaps and the two matcher funcs), comments are another "separator" which we can play with to show structure:
// Need to do this and that for some reasons.
this()
that()
// And do something different, but still related to this block.
still()
related()
// And here is a comment after a break. Which emphasizes the
// 2-level structure. Above, there is a "block" with 2 sub-blocks.
// Now, it's a new "higher-level" block.
totally()
different()
thing()
| // are not completely within fullRangeIDLocalSpans, return an error as we | ||
| // collide with at least one unreplicated RangeIDLocal key. |
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.
Technically, it's possible that we don't collide. E.g. you can imagine a little span that begins before the RangeID-local space, and slightly moves into it without touching the first range's unreplicated.
We just make it a "policy" here that it shouldn't be possible, i.e. the spans are sensibly respecting the "tree" structure, and a span that begins outside the subtree and ends inside (or vice versa) doesn't make logical sense.
Dunno it there is a concise way to say this, see if you can compress it :)
| // Point spans not overlapping with Store-local span. | ||
| {span: s(roachpb.KeyMin, nil)}, | ||
| {span: s(roachpb.Key(keys.LocalStorePrefix).Prevish(1), nil)}, | ||
| {span: s(keys.LocalStoreMax.Next(), nil)}, |
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 LocalStoreMax here too, since it's not inclusive?
| // TestOverlapsStoreLocalKeys verifies that the function | ||
| // overlapsStoreLocalKeys() successfully catches any overlap with | ||
| // store local keys. | ||
| func TestOverlapsStoreLocalKeys(t *testing.T) { |
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.
Nice, thanks a lot for the clean up! Was able to review this test in a minute and spot an edge case to add.
| if tc.notOK { | ||
| require.Errorf(t, err, "expected error for span %s", tc.span) | ||
| } else { | ||
| require.NoErrorf(t, err, "expected no error for span %s", tc.span) | ||
| } |
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.
optional (2 patterns too):
require.Equal(t, tc.notOk, err != nil, tc.span)Same above.
| // Tricky spans not overlapping with unreplicated local RangeID spans. | ||
| {span: s(nil, keys.LocalRangeIDPrefix.AsRawKey())}, | ||
| {span: s(nil, keys.RangeForceFlushKey(1))}, | ||
| {span: s(nil, keys.LocalRangeIDPrefix.AsRawKey().PrefixEnd().Next())}, |
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.
Can add one without Next(), it should be legit too (IIUC it will take a different branch in the func).
| {span: s(nil, keys.RangeForceFlushKey(1))}, | ||
| {span: s(nil, keys.LocalRangeIDPrefix.AsRawKey().PrefixEnd().Next())}, | ||
|
|
||
| // Tricky spans overlapping with unreplicated local RangeID spans. |
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.
A couple of corner cases would be good: for the beginning and ending of the unreplicated span.
Applies to a few blocks here. Could be convenient to pre-compute a couple of vars for some unreplicated span and use it in a few places:
r1Unrepl := roachpb.Span{...}
{span: s(r1Unrepl.Key.Prev(), nil)},
{span: s(r1Unrepl.EndKey, nil)},
{span: s(nil, r1Unrepl.EndKey), notOk: true},
// etc| }{ | ||
| // s1 is a full span, and s2 is a tricky span with nil StartKey. | ||
| {s1: "b-d", s2: "X-a", contains: false, overlaps: false}, | ||
| {s1: "b-d", s2: "X-b", contains: false, overlaps: 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
{s1: "b-d", s2: "X-b\x00", contains: true, overlaps: true},
Maybe a few more corner cases with \x00 in other blocks below. Those are interesting cases, when we have a key and its Next(). Perhaps not in all groups, but in the point tricky spans for sure because there is a line checking IsPrev in the func.
| // SpanSet wrapper with the forbidden span assertion disabled. | ||
| // TODO(ibrahim): We eventually want to eliminate all the users of this | ||
| // function. | ||
| func DisableForbiddenSpanAssertionsOnBatch(rw storage.ReadWriter) storage.ReadWriter { |
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: the two "disable" methods could be named in a similar style e.g.
DisableUndeclaredSpanAssertions
DisableForbiddenSpanAssertions
or if you fancy brevity:
NoUndeclaredSpanAssertions
NoForbiddenSpanAssertions
| s.allowUndeclared = true | ||
| } | ||
|
|
||
| // DisableForbiddenAssertions disables forbidden spans assertions. |
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.
Sync the comment with the name?
This PR adds the following test-only assertions:
We disable the check in exactly 3 locations we know that we currently
touch those keys.
Fixes: #156537
Release note: None