Skip to content

Conversation

@iskettaneh
Copy link
Contributor

This PR 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.

Fixes: #156537

Release note: None

@cockroach-teamcity
Copy link
Member

This change is Reviewable

@iskettaneh iskettaneh force-pushed the rse3 branch 2 times, most recently from 9f7f91b to ed7f5cd Compare November 11, 2025 02:33
@iskettaneh iskettaneh requested a review from pav-kv November 11, 2025 02:33
@iskettaneh iskettaneh marked this pull request as ready for review November 11, 2025 02:34
@iskettaneh iskettaneh requested a review from a team as a code owner November 11, 2025 02:34
@iskettaneh iskettaneh force-pushed the rse3 branch 2 times, most recently from 4d115e0 to 4fbc791 Compare November 11, 2025 20:47
@github-actions
Copy link

Potential Bug(s) Detected

The three-stage Claude Code analysis has identified potential bug(s) in this PR that may warrant investigation.

Next Steps:
Please review the detailed findings in the workflow run.

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:

  • If the detected issue is real or was helpful in any way, please tag the issue with O-AI-Review-Real-Issue-Found
  • If the detected issue was not helpful in any way, please tag the issue with O-AI-Review-Not-Helpful

@github-actions github-actions bot added the o-AI-Review-Potential-Issue-Detected AI reviewer found potential issue. Never assign manually—auto-applied by GH action only. label Nov 11, 2025
@github-actions
Copy link

Potential Bug(s) Detected

The three-stage Claude Code analysis has identified potential bug(s) in this PR that may warrant investigation.

Next Steps:
Please review the detailed findings in the workflow run.

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:

  • If the detected issue is real or was helpful in any way, please tag the issue with O-AI-Review-Real-Issue-Found
  • If the detected issue was not helpful in any way, please tag the issue with O-AI-Review-Not-Helpful

@iskettaneh iskettaneh force-pushed the rse3 branch 2 times, most recently from f9daad1 to cdb4edc Compare November 12, 2025 14:35
Comment on lines 91 to 93
forbiddenSpansMatchers []func(roachpb.Span) error
allowUndeclared bool
allowForbidden bool
Copy link
Collaborator

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.

Copy link
Contributor Author

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

@iskettaneh iskettaneh force-pushed the rse3 branch 2 times, most recently from 6f5f0a9 to 8193ffc Compare November 19, 2025 00:08
@iskettaneh iskettaneh requested a review from pav-kv November 19, 2025 00:08
Copy link
Contributor Author

@iskettaneh iskettaneh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @pav-kv)

Comment on lines 91 to 93
forbiddenSpansMatchers []func(roachpb.Span) error
allowUndeclared bool
allowForbidden bool
Copy link
Contributor Author

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

Copy link
Collaborator

@pav-kv pav-kv left a 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.

Copy link
Contributor Author

@iskettaneh iskettaneh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @pav-kv)

@iskettaneh iskettaneh requested a review from pav-kv November 19, 2025 20:06
This commit introduces the concept of forbidden spans, which will
allow us to assert our batches don't modify Raft's engine keys.
Copy link
Contributor Author

@iskettaneh iskettaneh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @pav-kv)

Copy link
Contributor Author

@iskettaneh iskettaneh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @pav-kv)

Copy link
Collaborator

@pav-kv pav-kv left a 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.

Comment on lines 770 to 771
// TODO(ibrahim): The fields spans, spansOnly, and ts don't seem to be used.
// Consider removing them and performing the necessary clean ups.
Copy link
Collaborator

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.
Copy link
Collaborator

@pav-kv pav-kv left a 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.

Comment on lines +412 to +421
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])
}
Copy link
Collaborator

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 span

As 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()

Comment on lines +910 to +911
// are not completely within fullRangeIDLocalSpans, return an error as we
// collide with at least one unreplicated RangeIDLocal key.
Copy link
Collaborator

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)},
Copy link
Collaborator

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) {
Copy link
Collaborator

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.

Comment on lines +15312 to +15316
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)
}
Copy link
Collaborator

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())},
Copy link
Collaborator

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.
Copy link
Collaborator

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},
Copy link
Collaborator

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 {
Copy link
Collaborator

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.
Copy link
Collaborator

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

o-AI-Review-Potential-Issue-Detected AI reviewer found potential issue. Never assign manually—auto-applied by GH action only.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

kvserver: assert on log engine keys in eval

3 participants