Fix a perf regression that caused every key to go through upper bound check (#7209)

Summary:
https://github.com/facebook/rocksdb/pull/5289 introduces a performance regression that caused an upper bound check within every BlockBasedTableIterator::Next(). This is unnecessary if we've checked the boundary key for current block and it is within upper bound.

Fix the bug. Also rename the boolean to a enum so that the code is slightly better readable. The original regression was probably to fix a bug that the block upper bound check status is not reset after a new block is created. Fix it bug so that the regression can be avoided without hitting the bug.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/7209

Test Plan: Run all existing tests. Will run atomic black box crash test for a while.

Reviewed By: anand1976

Differential Revision: D22859246

fbshipit-source-id: cbdad1f5e656c55fd8b71726d5a4f6cb53ff9140
This commit is contained in:
sdong 2020-08-04 11:28:02 -07:00 committed by Facebook GitHub Bot
parent fea286d914
commit 41c328fe57
3 changed files with 48 additions and 10 deletions

View File

@ -1,5 +1,8 @@
# Rocksdb Change Log # Rocksdb Change Log
## Unreleased ## Unreleased
### Bug fixes
* Fix a performance regression introduced in 6.4 that makes a upper bound check for every Next() even if keys are within a data block that is within the upper bound.
## 6.12 (2020-07-28) ## 6.12 (2020-07-28)
### Public API Change ### Public API Change

View File

@ -300,7 +300,8 @@ void BlockBasedTableIterator::FindBlockForward() {
// Whether next data block is out of upper bound, if there is one. // Whether next data block is out of upper bound, if there is one.
const bool next_block_is_out_of_bound = const bool next_block_is_out_of_bound =
read_options_.iterate_upper_bound != nullptr && read_options_.iterate_upper_bound != nullptr &&
block_iter_points_to_real_block_ && !data_block_within_upper_bound_; block_iter_points_to_real_block_ &&
block_upper_bound_check_ == BlockUpperBound::kUpperBoundInCurBlock;
assert(!next_block_is_out_of_bound || assert(!next_block_is_out_of_bound ||
user_comparator_.CompareWithoutTimestamp( user_comparator_.CompareWithoutTimestamp(
*read_options_.iterate_upper_bound, /*a_has_ts=*/false, *read_options_.iterate_upper_bound, /*a_has_ts=*/false,
@ -358,7 +359,9 @@ void BlockBasedTableIterator::FindKeyBackward() {
} }
void BlockBasedTableIterator::CheckOutOfBound() { void BlockBasedTableIterator::CheckOutOfBound() {
if (read_options_.iterate_upper_bound != nullptr && Valid()) { if (read_options_.iterate_upper_bound != nullptr &&
block_upper_bound_check_ != BlockUpperBound::kUpperBoundBeyondCurBlock &&
Valid()) {
is_out_of_bound_ = is_out_of_bound_ =
user_comparator_.CompareWithoutTimestamp( user_comparator_.CompareWithoutTimestamp(
*read_options_.iterate_upper_bound, /*a_has_ts=*/false, user_key(), *read_options_.iterate_upper_bound, /*a_has_ts=*/false, user_key(),
@ -369,11 +372,12 @@ void BlockBasedTableIterator::CheckOutOfBound() {
void BlockBasedTableIterator::CheckDataBlockWithinUpperBound() { void BlockBasedTableIterator::CheckDataBlockWithinUpperBound() {
if (read_options_.iterate_upper_bound != nullptr && if (read_options_.iterate_upper_bound != nullptr &&
block_iter_points_to_real_block_) { block_iter_points_to_real_block_) {
data_block_within_upper_bound_ = block_upper_bound_check_ = (user_comparator_.CompareWithoutTimestamp(
(user_comparator_.CompareWithoutTimestamp( *read_options_.iterate_upper_bound,
*read_options_.iterate_upper_bound, /*a_has_ts=*/false, /*a_has_ts=*/false, index_iter_->user_key(),
index_iter_->user_key(), /*b_has_ts=*/true) > 0)
/*b_has_ts=*/true) > 0); ? BlockUpperBound::kUpperBoundBeyondCurBlock
: BlockUpperBound::kUpperBoundInCurBlock;
} }
} }
} // namespace ROCKSDB_NAMESPACE } // namespace ROCKSDB_NAMESPACE

View File

@ -104,7 +104,8 @@ class BlockBasedTableIterator : public InternalIteratorBase<Slice> {
inline bool MayBeOutOfUpperBound() override { inline bool MayBeOutOfUpperBound() override {
assert(Valid()); assert(Valid());
return !data_block_within_upper_bound_; return block_upper_bound_check_ !=
BlockUpperBound::kUpperBoundBeyondCurBlock;
} }
void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) override { void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) override {
@ -134,6 +135,7 @@ class BlockBasedTableIterator : public InternalIteratorBase<Slice> {
block_iter_.Invalidate(Status::OK()); block_iter_.Invalidate(Status::OK());
block_iter_points_to_real_block_ = false; block_iter_points_to_real_block_ = false;
} }
block_upper_bound_check_ = BlockUpperBound::kUnknown;
} }
void SavePrevIndexValue() { void SavePrevIndexValue() {
@ -149,6 +151,34 @@ class BlockBasedTableIterator : public InternalIteratorBase<Slice> {
kForward, kForward,
kBackward, kBackward,
}; };
// This enum indicates whether the upper bound falls into current block
// or beyond.
// +-------------+
// | cur block | <-- (1)
// +-------------+
// <-- (2)
// --- <boundary key> ---
// <-- (3)
// +-------------+
// | next block | <-- (4)
// ......
//
// When the block is smaller than <boundary key>, kUpperBoundInCurBlock
// is the value to use. The examples are (1) or (2) in the graph. It means
// all keys in the next block or beyond will be out of bound. Keys within
// the current block may or may not be out of bound.
// When the block is larger or equal to <boundary key>,
// kUpperBoundBeyondCurBlock is to be used. The examples are (3) and (4)
// in the graph. It means that all keys in the current block is within the
// upper bound and keys in the next block may or may not be within the uppder
// bound.
// If the boundary key hasn't been checked against the upper bound,
// kUnknown can be used.
enum class BlockUpperBound {
kUpperBoundInCurBlock,
kUpperBoundBeyondCurBlock,
kUnknown,
};
const BlockBasedTable* table_; const BlockBasedTable* table_;
const ReadOptions& read_options_; const ReadOptions& read_options_;
@ -169,8 +199,9 @@ class BlockBasedTableIterator : public InternalIteratorBase<Slice> {
bool block_iter_points_to_real_block_; bool block_iter_points_to_real_block_;
// See InternalIteratorBase::IsOutOfBound(). // See InternalIteratorBase::IsOutOfBound().
bool is_out_of_bound_ = false; bool is_out_of_bound_ = false;
// Whether current data block being fully within iterate upper bound. // How current data block's boundary key with the next block is compared with
bool data_block_within_upper_bound_ = false; // iterate upper bound.
BlockUpperBound block_upper_bound_check_ = BlockUpperBound::kUnknown;
// True if we're standing at the first key of a block, and we haven't loaded // True if we're standing at the first key of a block, and we haven't loaded
// that block yet. A call to PrepareValue() will trigger loading the block. // that block yet. A call to PrepareValue() will trigger loading the block.
bool is_at_first_key_from_index_ = false; bool is_at_first_key_from_index_ = false;