-
Notifications
You must be signed in to change notification settings - Fork 3.4k
HBASE-29254 StoreScanner returns incorrect row after flush due to topChanged behavior #6900
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
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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
!outResult.isEmptyis there from the beginning. So would you mind explaining about why here we need to remove this check?Uh oh!
There was an error while loading. Please reload this page.
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.
Let’s assume the data stored in HBase is as follows:
(1) row0/family2:qf1/DeleteColumn
(2) row0/family2:qf1/Put/value2
(3) row1/family1:qf1/Put/value2
(4) row1/family2:qf1/Put/value2
Now, suppose a user starts scanning from row0.
In
RegionScannerImpl#nextInternal, when the current cell’s row isrow0, after reading entry (2) inStoreScanner, if a flush happens, atopChangedoccurs (Storescanner.peek() is changed where before ...), and the value ofStoreScanner’sheap.peek()becomes (4)row1/family2:qf1/Put/value2.Since it is the next row,
StoreScannershould return at that point — but it fails to recognize that it has moved to the next row because outResult is empty, and ends up including the new row in the result.Then, in
RegionScannerImpl, it sees thatnextKv’s row is different from the current cell’s row, and returns (since it has moved to a different row).As a result, even though (3) and (4) belong to the same row (
row1), they are returned to the client as if they were from different rows.(3) and (4) should be combined into a single Result, but they end up being returned as separate Result instances.
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 was the root cause of the issue described in this thread.
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.
OK, I think I understand the problem.
Let's see how to reproduce the problem in UT.
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.
Thanks for reviewing the code!
let me know if there's anything I should take care of.
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 can confirm that there is a behavior change, but if we can still get 3 cells at once, it does not make client get partial rows?
Uh oh!
There was an error while loading. Please reload this page.
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're right that at the
StoreScannerlevel, the behavior doesn't seem immediately problematic.However, the issue becomes visible on the client side when multiple column families are involved — the row ends up getting split.
This happens because the result from
row2is included in the first next() call, which is supposed to return onlyrow1.As a result, the client receives fragmented results for
row2.To help illustrate the issue, I’ve added a reproducible test case.
If you run TestTopChanged.testTopChanged on the feature/bug-demo-HBASE-29254 branch, you can observe the behavior directly.
Here’s what you’ll see:
!outResult.isEmpty()condition inneedToReturn(current behavior):!outResult.isEmpty()condition (modified behavior):It would be great if you could take a look — I know it's a bit of a hassle, but your feedback would be really helpful.
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.
What if there are no data for row1 in cf2? What is the current way to prevert loading row2?
I mean we have delete row1 in cf1, and row2 in cf1 and cf2, how can we make sure that we do not return row2 when calling cf2.next at the first time?
Uh oh!
There was an error while loading. Please reload this page.
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.
Sorry, I didn’t fully understand the question.
If there is no data for
cf2inrow1, loading ofrow2is prevented bymoreCellsInRowinRegionScannerImpl.This is because:
row1/cf1:cq1/1745317264666/Put/vlen=10/seqid=4row2/cf1:cq1/1745317264674/Put/vlen=10/seqid=5By "What if there are no data for row1 in cf2? What is the current way to prevent loading row2?", are you asking about the current mechanism that prevents row2 from being included in the result while the StoreScanner is reading row1 and
topChangedhas not occurred?This is handled in
ScanQueryMatcher.preCheckusing therowComparator.When the
currentRowisrow1/cf1:cq1/1745314001315/DeleteColumn/vlen=0/seqid=5and thecellisrow2/cf1:cq1/1745314001320/Put/vlen=10/seqid=7, the MatchCode becomes DONE.However, when
topChangedoccurs (which is the problematic condition), thecurrentRowbecomesrow2/cf2:cq1/1745314108845/Put/vlen=10/seqid=7and the cell isrow2/cf2:cq1/1745314108845/Put/vlen=10/seqid=7.In
ScanQueryMatcher.setToNewRow, thecurrentRowis set torow2/cf2:cq1/1745314108845/Put/vlen=10/seqid=7.In this condition, "I mean we have delete row1 in cf1, and row2 in cf1 and cf2, how can we make sure that we do not return row2 when calling cf2.next at the first time?", it is determined as a different row by ScanQueryMatcher.preCheck.
When using a
currentRowthat hasn't been flushed to disk yet, it may change after a flush when the scanner is reopened (topChanged).If
outResultcontains elements, there is no issue. However, ifoutResultis empty, the return does not occur, and the loop iterates again, which causes a problem.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.
Thanks again for the detailed review and your follow-up questions — I really appreciate it.
I feel that my earlier explanation may not have been clear enough.
To clarify: the main issue is not with partial results, but rather with a single row being split into two separate results.
This split happens because after a topChanged event, if outResult is empty, StoreScanner doesn't correctly detect that the row has changed.
(Upon reflecting on our previous discussion, I realized that my initial explanation may not have been as clear as it should have been. When I first encountered this issue, I thought it was related to partial results, which is why I initially mentioned that in thread. However, after further debugging, the core issue is actually that a single row is being split into two separate Results. I apologize if my earlier explanation was confusing, and I'm sorry for not making this clearer sooner.)
I’ve already added a reproducible test case (TestTopChanged.testTopChanged in the feature/bug-demo-HBASE-29254 branch) to demonstrate this behavior.
Please let me know if there’s anything else you would like me to work on to help move this forward.
Thanks again for your time and support!