-
Notifications
You must be signed in to change notification settings - Fork 4.1k
fix(store/v2): Fix PebbleDB Iteration Edge Cases #18948
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
Changes from 8 commits
c00509e
1603129
c1f8dfe
ffa58c6
252b87e
786827a
525382b
a2c09d9
c730f29
ced49f3
d7ac611
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -116,15 +116,15 @@ func (itr *iterator) Value() []byte { | |||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| func (itr *iterator) Next() bool { | ||||||||||||||||||||||||||||
| currKey, _, ok := SplitMVCCKey(itr.source.Key()) | ||||||||||||||||||||||||||||
| if !ok { | ||||||||||||||||||||||||||||
| // XXX: This should not happen as that would indicate we have a malformed | ||||||||||||||||||||||||||||
| // MVCC key. | ||||||||||||||||||||||||||||
| panic(fmt.Sprintf("invalid PebbleDB MVCC key: %s", itr.source.Key())) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+116
to
+121
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use of - panic(fmt.Sprintf("invalid PebbleDB MVCC key: %s", itr.source.Key()))
+ // Handle error properly instead of panic
+ return fmt.Errorf("invalid PebbleDB MVCC key: %s", itr.source.Key())Committable suggestion
Suggested change
The panic in the |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| var next bool | ||||||||||||||||||||||||||||
| if itr.reverse { | ||||||||||||||||||||||||||||
| currKey, _, ok := SplitMVCCKey(itr.source.Key()) | ||||||||||||||||||||||||||||
| if !ok { | ||||||||||||||||||||||||||||
| // XXX: This should not happen as that would indicate we have a malformed | ||||||||||||||||||||||||||||
| // MVCC key. | ||||||||||||||||||||||||||||
| panic(fmt.Sprintf("invalid PebbleDB MVCC key: %s", itr.source.Key())) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Since PebbleDB has no PrevPrefix API, we must manually seek to the next | ||||||||||||||||||||||||||||
| // key that is lexicographically less than the current key. | ||||||||||||||||||||||||||||
| next = itr.source.SeekLT(MVCCEncode(currKey, 0)) | ||||||||||||||||||||||||||||
|
|
@@ -135,7 +135,7 @@ func (itr *iterator) Next() bool { | |||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // First move the iterator to the next prefix, which may not correspond to the | ||||||||||||||||||||||||||||
| // desired version for that key, e.g. if the key was written at a later version, | ||||||||||||||||||||||||||||
| // so we seek back to the latest desired version, s.t. the version is <= itr.version. | ||||||||||||||||||||||||||||
| // so we seek back to the latest desired version, s.t. the version <= itr.version. | ||||||||||||||||||||||||||||
| if next { | ||||||||||||||||||||||||||||
| nextKey, _, ok := SplitMVCCKey(itr.source.Key()) | ||||||||||||||||||||||||||||
| if !ok { | ||||||||||||||||||||||||||||
|
Comment on lines
133
to
138
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The logic for handling reverse iteration and seeking to the next prefix seems to be missing the actual implementation for reverse iteration, as the |
||||||||||||||||||||||||||||
|
|
@@ -150,10 +150,31 @@ func (itr *iterator) Next() bool { | |||||||||||||||||||||||||||
| return itr.valid | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Move the iterator to the closest version to the desired version, so we | ||||||||||||||||||||||||||||
| // Move the iterator to the closest version of the desired version, so we | ||||||||||||||||||||||||||||
| // append the current iterator key to the prefix and seek to that key. | ||||||||||||||||||||||||||||
| itr.valid = itr.source.SeekLT(MVCCEncode(nextKey, itr.version+1)) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| tmpKey, _, ok := SplitMVCCKey(itr.source.Key()) | ||||||||||||||||||||||||||||
| if !ok { | ||||||||||||||||||||||||||||
| // XXX: This should not happen as that would indicate we have a malformed | ||||||||||||||||||||||||||||
| // MVCC key. | ||||||||||||||||||||||||||||
| itr.valid = false | ||||||||||||||||||||||||||||
| return itr.valid | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // There exists cases where the SeekLT() call moved us back to the same key | ||||||||||||||||||||||||||||
| // we started at, so we must move to next key, i.e. two keys forward. | ||||||||||||||||||||||||||||
| if bytes.Equal(tmpKey, currKey) { | ||||||||||||||||||||||||||||
| if itr.source.SeekGE(MVCCEncode(nextKey, 0)) { | ||||||||||||||||||||||||||||
| if itr.source.NextPrefix() { | ||||||||||||||||||||||||||||
| return itr.Next() | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| itr.valid = false | ||||||||||||||||||||||||||||
| return itr.valid | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
alexanderbez marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // The cursor might now be pointing at a key/value pair that is tombstoned. | ||||||||||||||||||||||||||||
| // If so, we must move the cursor. | ||||||||||||||||||||||||||||
| if itr.valid && itr.cursorTombstoned() { | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -393,10 +393,54 @@ func (s *StorageTestSuite) TestDatabase_IteratorMultiVersion() { | |
| i = (i + 1) % 10 | ||
| count++ | ||
| } | ||
|
|
||
| s.Require().Equal(10, count) | ||
| s.Require().NoError(itr.Error()) | ||
| } | ||
|
|
||
| func (s *StorageTestSuite) TestDatabaseIterator_SkipVersion() { | ||
| db, err := s.NewDB(s.T().TempDir()) | ||
| s.Require().NoError(err) | ||
|
|
||
| defer db.Close() | ||
|
|
||
| cs := store.NewChangeset(map[string]store.KVPairs{storeKey1: { | ||
| {Key: []byte("keyC"), Value: []byte("value003")}, | ||
| }}) | ||
| s.Require().NoError(db.ApplyChangeset(58827506, cs)) | ||
|
|
||
| cs = store.NewChangeset(map[string]store.KVPairs{storeKey1: { | ||
| {Key: []byte("keyE"), Value: []byte("value000")}, | ||
| }}) | ||
| s.Require().NoError(db.ApplyChangeset(58827506, cs)) | ||
|
|
||
| cs = store.NewChangeset(map[string]store.KVPairs{storeKey1: { | ||
| {Key: []byte("keyF"), Value: []byte("value000")}, | ||
| }}) | ||
| s.Require().NoError(db.ApplyChangeset(58827506, cs)) | ||
|
|
||
| cs = store.NewChangeset(map[string]store.KVPairs{storeKey1: { | ||
| {Key: []byte("keyC"), Value: []byte("value004")}, | ||
| }}) | ||
| s.Require().NoError(db.ApplyChangeset(58833605, cs)) | ||
|
|
||
| cs = store.NewChangeset(map[string]store.KVPairs{storeKey1: { | ||
| {Key: []byte("keyD"), Value: []byte("value006")}, | ||
| }}) | ||
| s.Require().NoError(db.ApplyChangeset(58833606, cs)) | ||
|
|
||
| itr, err := db.Iterator(storeKey1, 58831525, []byte("key"), nil) | ||
| s.Require().NoError(err) | ||
| defer itr.Close() | ||
|
|
||
| count := make(map[string]struct{}) | ||
| for ; itr.Valid(); itr.Next() { | ||
| count[string(itr.Key())] = struct{}{} | ||
| } | ||
|
|
||
| s.Require().Equal(3, len(count)) | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The ast-grep command has provided the loop's body within the Analysis chainThe new test
The ast-grep command to verify the application of multiple changesets to the same version number The ast-grep command has confirmed the presence of The ast-grep command did not produce the expected output, which suggests a potential issue with the complexity of the pattern. To capture the body of the loop within the The attempt to use the Scripts ExecutedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Verify that applying multiple changesets to the same version is correct.
ast-grep --lang go --pattern $'db.ApplyChangeset($_, $_)' | grep '58827506'
# Verify that the iterator is correctly skipping versions by checking the presence of specific key-value pairs.
ast-grep --lang go --pattern $'itr.Valid(); itr.Next() { $$$ }' | grep 'TestDatabaseIterator_SkipVersion'
Length of output: 921 Script: #!/bin/bash
# Correct the ast-grep pattern to verify the iterator's behavior in the test.
ast-grep --lang go --pattern $'func ($_ *StorageTestSuite) TestDatabaseIterator_SkipVersion() { $$$ }' | grep 'itr.Valid'
Length of output: 195 Script: #!/bin/bash
# Adjust the ast-grep pattern to capture the body of the loop within the TestDatabaseIterator_SkipVersion function.
ast-grep --lang go --pattern $'func ($_ *StorageTestSuite) TestDatabaseIterator_SkipVersion() { $$$ for ; itr.Valid(); itr.Next() { $$$ } $$$ }'
Length of output: 145 Script: #!/bin/bash
# Simplify the ast-grep pattern to focus on the loop's body within the TestDatabaseIterator_SkipVersion function.
ast-grep --lang go --pattern $'for ; itr.Valid(); itr.Next() { $$$ }' --within $'func ($_ *StorageTestSuite) TestDatabaseIterator_SkipVersion() { $$$ }'
Length of output: 352 Script: #!/bin/bash
# Attempt a simpler ast-grep pattern to directly target the loop's body within the TestDatabaseIterator_SkipVersion function.
ast-grep --lang go --pattern $'for ; itr.Valid(); itr.Next() { $$$ }'
Length of output: 16366 |
||
|
|
||
| func (s *StorageTestSuite) TestDatabase_IteratorNoDomain() { | ||
| db, err := s.NewDB(s.T().TempDir()) | ||
| s.Require().NoError(err) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.