Skip to content

Commit d3713c0

Browse files
author
Xingbo Wang
committed
Move the MultiScan seek key check to upper layer (facebook#14040)
Summary: The current seek key validation is too strict. This change relaxes it at block iterator level, and add additional check at DB iterator level. The new contract is that when MultiScan is used, after prepared is called, each following seek must seek the start key of the prepared scan range in order. Otherwise, the iterator is set with error status. Pull Request resolved: facebook#14040 Test Plan: Unit test Reviewed By: anand1976 Differential Revision: D84292297 Pulled By: xingbowang fbshipit-source-id: 7b31f727e67e7c0bfc53c2f9a6552e0c3d324869
1 parent d18c290 commit d3713c0

11 files changed

Lines changed: 1144 additions & 269 deletions

File tree

db/db_iter.cc

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1565,11 +1565,115 @@ void DBIter::SetSavedKeyToSeekForPrevTarget(const Slice& target) {
15651565
}
15661566
}
15671567

1568+
Status DBIter::ValidateScanOptions(const MultiScanArgs& multiscan_opts) const {
1569+
if (multiscan_opts.empty()) {
1570+
return Status::InvalidArgument("Empty MultiScanArgs");
1571+
}
1572+
1573+
const std::vector<ScanOptions>& scan_opts = multiscan_opts.GetScanRanges();
1574+
const bool has_limit = scan_opts.front().range.limit.has_value();
1575+
if (!has_limit && scan_opts.size() > 1) {
1576+
return Status::InvalidArgument("Scan has no upper bound");
1577+
}
1578+
1579+
for (size_t i = 0; i < scan_opts.size(); ++i) {
1580+
const auto& scan_range = scan_opts[i].range;
1581+
if (!scan_range.start.has_value()) {
1582+
return Status::InvalidArgument("Scan has no start key at index " +
1583+
std::to_string(i));
1584+
}
1585+
1586+
if (scan_range.limit.has_value()) {
1587+
if (user_comparator_.CompareWithoutTimestamp(
1588+
scan_range.start.value(), /*a_has_ts=*/false,
1589+
scan_range.limit.value(), /*b_has_ts=*/false) >= 0) {
1590+
return Status::InvalidArgument(
1591+
"Scan start key is large or equal than limit at index " +
1592+
std::to_string(i));
1593+
}
1594+
}
1595+
1596+
if (i > 0) {
1597+
if (!scan_range.limit.has_value()) {
1598+
// multiple scan without limit scan ranges
1599+
return Status::InvalidArgument("Scan has no upper bound at index " +
1600+
std::to_string(i));
1601+
}
1602+
1603+
const auto& last_end_key = scan_opts[i - 1].range.limit.value();
1604+
if (user_comparator_.CompareWithoutTimestamp(
1605+
scan_range.start.value(), /*a_has_ts=*/false, last_end_key,
1606+
/*b_has_ts=*/false) < 0) {
1607+
return Status::InvalidArgument("Overlapping ranges at index " +
1608+
std::to_string(i));
1609+
}
1610+
}
1611+
}
1612+
return Status::OK();
1613+
}
1614+
1615+
void DBIter::Prepare(const MultiScanArgs& scan_opts) {
1616+
status_ = ValidateScanOptions(scan_opts);
1617+
if (!status_.ok()) {
1618+
return;
1619+
}
1620+
std::optional<MultiScanArgs> new_scan_opts;
1621+
new_scan_opts.emplace(scan_opts);
1622+
scan_opts_.swap(new_scan_opts);
1623+
scan_index_ = 0;
1624+
if (!scan_opts.empty()) {
1625+
iter_.Prepare(&scan_opts_.value());
1626+
} else {
1627+
iter_.Prepare(nullptr);
1628+
}
1629+
}
1630+
15681631
void DBIter::Seek(const Slice& target) {
15691632
PERF_COUNTER_ADD(iter_seek_count, 1);
15701633
PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, clock_);
15711634
StopWatch sw(clock_, statistics_, DB_SEEK);
15721635

1636+
if (scan_opts_.has_value()) {
1637+
// Validate the seek target is as expected in the previously prepared range
1638+
auto const& scan_ranges = scan_opts_.value().GetScanRanges();
1639+
if (scan_index_ >= scan_ranges.size()) {
1640+
status_ = Status::InvalidArgument(
1641+
"Seek called after exhausting all of the scan ranges");
1642+
valid_ = false;
1643+
return;
1644+
}
1645+
1646+
// Validate start key of next prepare range matches the seek target
1647+
auto const& range = scan_ranges[scan_index_];
1648+
auto const& start = range.range.start;
1649+
assert(start.has_value());
1650+
if (user_comparator_.CompareWithoutTimestamp(target, *start) != 0) {
1651+
status_ = Status::InvalidArgument(
1652+
"Seek target does not match the start of the next prepared range at "
1653+
"index " +
1654+
std::to_string(scan_index_));
1655+
valid_ = false;
1656+
return;
1657+
}
1658+
1659+
// validate the upper bound is set to the same value of limit, if limit
1660+
// exists
1661+
auto const& limit = range.range.limit;
1662+
if (limit.has_value()) {
1663+
if (iterate_upper_bound_ == nullptr ||
1664+
user_comparator_.CompareWithoutTimestamp(
1665+
limit.value(), *iterate_upper_bound_) != 0) {
1666+
status_ = Status::InvalidArgument(
1667+
"Upper bound is not set to the same limit value of the next "
1668+
"prepared range at index " +
1669+
std::to_string(scan_index_));
1670+
valid_ = false;
1671+
return;
1672+
}
1673+
}
1674+
scan_index_++;
1675+
}
1676+
15731677
if (cfh_ != nullptr) {
15741678
// TODO: What do we do if this returns an error?
15751679
Slice lower_bound, upper_bound;

db/db_iter.h

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -240,16 +240,8 @@ class DBIter final : public Iterator {
240240

241241
bool PrepareValue() override;
242242

243-
void Prepare(const MultiScanArgs& scan_opts) override {
244-
std::optional<MultiScanArgs> new_scan_opts;
245-
new_scan_opts.emplace(scan_opts);
246-
scan_opts_.swap(new_scan_opts);
247-
if (!scan_opts.empty()) {
248-
iter_.Prepare(&scan_opts_.value());
249-
} else {
250-
iter_.Prepare(nullptr);
251-
}
252-
}
243+
void Prepare(const MultiScanArgs& scan_opts) override;
244+
Status ValidateScanOptions(const MultiScanArgs& multiscan_opts) const;
253245

254246
private:
255247
DBIter(Env* _env, const ReadOptions& read_options,
@@ -506,6 +498,7 @@ class DBIter final : public Iterator {
506498
const size_t timestamp_size_;
507499
std::string saved_timestamp_;
508500
std::optional<MultiScanArgs> scan_opts_;
501+
size_t scan_index_{0};
509502
ReadOnlyMemTable* const active_mem_;
510503
SequenceNumber memtable_seqno_lb_;
511504
uint32_t memtable_op_scan_flush_trigger_;

db/db_iterator_test.cc

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4382,18 +4382,6 @@ TEST_P(DBMultiScanIteratorTest, FailureTest) {
43824382
iter->Seek(key_ranges[2]);
43834383
ASSERT_NOK(iter->status());
43844384
iter.reset();
4385-
4386-
// Test the case of overlapping ranges
4387-
iter.reset(dbfull()->NewIterator(ro, cfh));
4388-
ASSERT_NE(iter, nullptr);
4389-
(*scan_options).clear();
4390-
scan_options.insert(key_ranges[0]);
4391-
scan_options.insert(key_ranges[2], key_ranges[3]);
4392-
iter->Prepare(scan_options);
4393-
ub = key_ranges[3];
4394-
iter->Seek(key_ranges[2]);
4395-
ASSERT_NOK(iter->status());
4396-
iter.reset();
43974385
}
43984386

43994387
TEST_P(DBMultiScanIteratorTest, RangeBetweenFiles) {

db/multi_scan.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ MultiScan::MultiScan(const ReadOptions& read_options,
4040
}
4141

4242
MultiScanIterator& MultiScanIterator::operator++() {
43+
status_ = db_iter_->status();
44+
if (!status_.ok()) {
45+
throw MultiScanException(status_);
46+
}
47+
4348
if (idx_ >= scan_opts_.size()) {
4449
throw std::logic_error("Index out of range");
4550
}

db/version_set.cc

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1668,8 +1668,6 @@ void LevelIterator::SetFileIterator(InternalIterator* iter) {
16681668
if (FileHasMultiScanArg(file_index_)) {
16691669
const MultiScanArgs& new_opts = GetMultiScanArgForFile(file_index_);
16701670
file_iter_.Prepare(&new_opts);
1671-
} else {
1672-
file_iter_.Prepare(scan_opts_);
16731671
}
16741672
}
16751673

include/rocksdb/multi_scan.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,10 @@ class MultiScan {
191191
if (scan_opts_.empty()) {
192192
throw std::logic_error("Zero scans in multi-scan");
193193
}
194+
status_ = db_iter_->status();
195+
if (!status_.ok()) {
196+
throw MultiScanException(status_);
197+
}
194198
db_iter_->Seek(*scan_opts_[idx_].range.start);
195199
status_ = db_iter_->status();
196200
if (!status_.ok()) {

0 commit comments

Comments
 (0)