1
0
mirror of https://github.com/facebook/rocksdb.git synced 2026-10-23 08:09:02 +03:00
Files
rocksdb/db/multi_scan.cc
Jay Huh e1ea04d9ef Add reverse order support to MultiScan (#14876)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14876

Add reverse scan support to `MultiScanArgs`/`MultiScan` so callers can iterate bounded scan ranges in descending order. Plumb reverse scan state through `DBIter`, `BlockBasedTableIterator`, and `MultiScanIndexIterator`, including reverse prefetch windowing and async I/O support. Update `db_bench` `multiscanrandom` to honor `--reverse_iterator` and report rows scanned so reverse MultiScan and iterator baselines can be compared on row throughput.

Add `db_stress --multiscan_reverse` coverage so the prepared MultiScan path can be stressed with `SeekForPrev()`/`Prev()` against a control iterator.

Also fix the table child iterator path to accept reverse `SeekForPrev()` targets above a file's prepared ranges. `MergingIterator` and `LevelIterator` can legally seek every child to the current range limit; a child file may only have lower prepared ranges, so treating that as invalid caused intermittent `SeekForPrev target is outside prepared ranges` exceptions in the benchmark.

Reviewed By: xingbowang

Differential Revision: D109357730

fbshipit-source-id: eccece5ca7d8f5d6760d51ebe76801a332a49061
2026-07-08 13:58:00 -07:00

167 lines
5.3 KiB
C++

// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#include <string>
#include <vector>
#include "file/file_util.h"
#include "rocksdb/db.h"
#include "rocksdb/file_system.h"
#include "rocksdb/iterator.h"
namespace ROCKSDB_NAMESPACE {
using MultiScanIterator = MultiScan::MultiScanIterator;
namespace {
Status ValidateScanOptionsForMultiScan(const MultiScanArgs& multiscan_opts) {
if (multiscan_opts.empty()) {
return Status::InvalidArgument("Empty MultiScanArgs");
}
const Comparator* comparator = multiscan_opts.GetComparator();
if (comparator == nullptr) {
return Status::InvalidArgument("MultiScanArgs requires comparator");
}
const std::vector<ScanOptions>& scan_opts = multiscan_opts.GetScanRanges();
for (size_t i = 0; i < scan_opts.size(); ++i) {
const auto& scan_range = scan_opts[i].range;
if (!scan_range.start.has_value()) {
return Status::InvalidArgument("Scan has no start key at index " +
std::to_string(i));
}
if (multiscan_opts.reverse && !scan_range.limit.has_value()) {
return Status::InvalidArgument(
"Reverse MultiScan requires limit at index " + std::to_string(i));
}
if (scan_range.limit.has_value() &&
comparator->CompareWithoutTimestamp(
scan_range.start.value(), /*a_has_ts=*/false,
scan_range.limit.value(), /*b_has_ts=*/false) >= 0) {
return Status::InvalidArgument(
"Scan start key is large or equal than limit at index " +
std::to_string(i));
}
}
return Status::OK();
}
} // namespace
MultiScan::MultiScan(const ReadOptions& read_options,
const MultiScanArgs& scan_opts, DB* db,
ColumnFamilyHandle* cfh)
: read_options_(read_options),
scan_opts_([&] {
// Disable async IO if the filesystem does not support it, consistent
// with how ArenaWrappedDBIter::Init() handles ReadOptions::async_io.
auto opts = scan_opts;
if (opts.use_async_io &&
!CheckFSFeatureSupport(db->GetFileSystem(),
FSSupportedOps::kAsyncIO)) {
opts.use_async_io = false;
}
return opts;
}()),
db_(db),
cfh_(cfh) {
Status validate_status = ValidateScanOptionsForMultiScan(scan_opts_);
if (!validate_status.ok()) {
db_iter_.reset(NewErrorIterator(validate_status));
return;
}
bool slow_path = false;
// Setup read_options bounds based on the first scan. Subsequent scans will
// update the bounds and allocate a new DB iterator as necessary.
{
const ScanOptions& first_scan = scan_opts_.GetScanRanges()[0];
if (scan_opts_.reverse) {
lower_bound_ = *first_scan.range.start;
read_options_.iterate_lower_bound = &lower_bound_;
upper_bound_ = *first_scan.range.limit;
read_options_.iterate_upper_bound = &upper_bound_;
} else if (first_scan.range.limit) {
upper_bound_ = *first_scan.range.limit;
read_options_.iterate_upper_bound = &upper_bound_;
} else {
read_options_.iterate_upper_bound = nullptr;
}
}
for (const auto& opts : scan_opts_.GetScanRanges()) {
// Check that all the ScanOptions either specify an upper bound or not. If
// its mixed we take the slow path which avoids calling Prepare: we have to
// reallocate the Iterator with updated read_options everytime we switch
// between upper bound or no upper bound, which complicates Prepare.
if (opts.range.limit.has_value() !=
scan_opts_.GetScanRanges()[0].range.limit.has_value()) {
slow_path = true;
break;
}
}
db_iter_.reset(db->NewIterator(read_options_, cfh));
if (!slow_path) {
db_iter_->Prepare(scan_opts_);
}
}
void MultiScanIterator::SeekCurrentRange() {
const ScanOptions& scan_opt = scan_opts_[idx_];
if (scan_opt.range.limit) {
*upper_bound_ = *scan_opt.range.limit;
read_options_.iterate_upper_bound = upper_bound_;
} else {
read_options_.iterate_upper_bound = nullptr;
}
if (reverse_) {
*lower_bound_ = *scan_opt.range.start;
read_options_.iterate_lower_bound = lower_bound_;
assert(scan_opt.range.limit.has_value());
db_iter_->SeekForPrev(*scan_opt.range.limit);
} else {
db_iter_->Seek(*scan_opt.range.start);
}
}
MultiScanIterator& MultiScanIterator::operator++() {
status_ = db_iter_->status();
if (!status_.ok()) {
throw MultiScanException(status_);
}
if (!valid_) {
throw std::logic_error("Incrementing exhausted MultiScan iterator");
}
++idx_;
if (idx_ >= scan_opts_.size()) {
valid_ = false;
return *this;
}
// Check if we need to update read_options_
if (scan_opts_[idx_].range.limit.has_value() !=
(read_options_.iterate_upper_bound != nullptr)) {
if (scan_opts_[idx_].range.limit) {
read_options_.iterate_upper_bound = upper_bound_;
} else {
read_options_.iterate_upper_bound = nullptr;
}
db_iter_.reset(db_->NewIterator(read_options_, cfh_));
scan_.Reset(db_iter_.get(), reverse_);
}
SeekCurrentRange();
status_ = db_iter_->status();
if (!status_.ok()) {
throw MultiScanException(status_);
}
return *this;
}
} // namespace ROCKSDB_NAMESPACE