-
Notifications
You must be signed in to change notification settings - Fork 6.3k
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
Option to fail a request as incomplete when skipping too many internal keys #2000
Conversation
@sagar0 make sure you add an entry in HISTORY.md. That will convert to release notes. |
@siying sure, will do. |
@sagar0 updated the pull request - view changes |
@sagar0 updated the pull request - view changes |
@sagar0 updated the pull request - view changes |
@sagar0 has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator. |
db/db_iter.cc
Outdated
} else { | ||
num_tombstones_skipped_++; | ||
} | ||
|
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.
Since the option name is max_tombstones_skip_in_iterations, as discussed offline, we should have a better name for this since it includes every iterator no matter whether it is tombstone or not.
db/db_iter.cc
Outdated
// } else { | ||
// num_tombstones_skipped_++; | ||
// } | ||
|
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.
Same here, which should be moved to line 769 I think.
db/db_iter.cc
Outdated
} else { | ||
num_internal_keys_skipped_++; | ||
} | ||
|
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 think this part should be on line 789. The first time we enter this loop we don't know whether the key will be skipped.
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.
Also, how to deal with too many merge_operands in this case?
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.
That's a reasonable suggestion. I believe I added this code snippet here as the other check (in line 742) is also here, and I wanted to keep both of them close together.
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.
@lightmark should there be something different done for merges? I am assuming that they should also be handled in the same way. We should still fail and return an incomplete status if too many keys are being looked for getting the value of a merge. Thoughts?
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 sounds like a reasonable behavior for merge operands
db/db_iter.cc
Outdated
if (TooManyInternalKeysSkipped()) { | ||
return; | ||
} | ||
|
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.
These lines are not necessary since you set valid_ = false;
already in TooManyInternalKeysSkipped
. So line 700 will break;
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.
If I understand the code correctly, valid_'s value need not always be the same as iter_->Valid() value. In this code snippet, if valid_ is set to false in TooManyInternalKeysSkipped(), we will not break in 701 as we never enter the if block (iter_->Valid() need not be false). Hence a return here.
In fact, some of my unit tests check this scenario.
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 are right. I thought it was Valid() not iter_->Valid()
db/db_iter.cc
Outdated
} | ||
return 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.
maybe if would be better to move num_internal_keys_skipped_++;
before line 969.
Then you can do this instead an if...else...
if (TooManyInternalKeysSkipped()) {
return;
}
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 definitely did consider this, and I would have loved to done the exact same thing which you suggested, but it will not work in the reverse iteration flow as we will end up double counting in a few cases.
The Reason being:
TooManyInternalKeysSkipped is called from both PrevInternal and FindValueForCurrentKey. FindValueForCurrentKey is in turn called from PrevInternal. If the counter is incremented in FindValueForCurrentKey, it should not be incremented again in PrevInternal. One of my unit tests ran into this problem of double counting, and I had to fix it.
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.
Sure. This comment has the prerequisite that the first comment is valid.
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. Just consider how to cope with merge in FindValueForCurrentKey
db/db_iter.cc
Outdated
if (TooManyInternalKeysSkipped()) { | ||
return; | ||
} | ||
|
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 are right. I thought it was Valid() not iter_->Valid()
db/db_iter.cc
Outdated
} | ||
return 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.
Sure. This comment has the prerequisite that the first comment is valid.
@@ -106,7 +106,8 @@ class DBIter: public Iterator { | |||
uint64_t max_sequential_skip_in_iterations, uint64_t version_number, | |||
const Slice* iterate_upper_bound = nullptr, | |||
bool prefix_same_as_start = false, bool pin_data = false, | |||
bool total_order_seek = false) | |||
bool total_order_seek = false, | |||
uint64_t max_skippable_internal_keys = 0) |
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.
We keep passing new arguments to the Iterator, I think we should simply pass the ReadOptions. this should be a refactor that we should consider to do in the future
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.
Addressed in #2116.
@@ -390,6 +400,12 @@ void DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) { | |||
break; | |||
} | |||
|
|||
if (TooManyInternalKeysSkipped()) { |
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 look into if it's possible to have it look like this
if (BumpInternalKeysSkipped()) {
return false;
}
we can even have a statistics counter inside this function that will report the total number of internal keys that we need to skip
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.
Moved counter-incrementing to be inside the function.
I have not added statistics counters in this PR, but I'll look at adding them in a separate PR.
db/db_iter.cc
Outdated
} else { | ||
num_internal_keys_skipped_++; | ||
} | ||
|
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 sounds like a reasonable behavior for merge operands
@sagar0 updated the pull request - view changes - changes since last import |
@sagar0 has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator. |
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, let's wait for the tests to pass then land it
Operations like Seek/Next/Prev sometimes take too long to complete when there are many internal keys to be skipped. Adding an option, max_skippable_internal_keys -- which could be used to set a threshold for the maximum number of keys that can be skipped, will help to address these cases where it is much better to fail a request (as incomplete) than to wait for a considerable time for the request to complete.
This feature -- to fail an iterator seek request as incomplete, is disabled by default when max_skippable_internal_keys = 0. It is enabled only when max_skippable_internal_keys > 0.
This feature is based on the discussion mentioned in the PR #1084.