Skip to content

Commit fa143aa

Browse files
cbi42anand1976
authored andcommitted
Add option to limit max prefetching in MultiScan (facebook#13920)
Summary: Add a new option `MultiScanArgs::max_prefetch_size` that limits the memory usage of per file pinning of prefetched blocks. Note that this only accounts for compressed block size. This is intended to be a stopgap until we implement some kind of global prefetch manager that limits the global multiscan memory usage. Pull Request resolved: facebook#13920 Test Plan: new unit test `./block_based_table_reader_test --gtest_filter="*MultiScanPrefetchSizeLimit/*"` Reviewed By: xingbowang Differential Revision: D81630629 Pulled By: cbi42 fbshipit-source-id: 9f66678915242fe1220620531a4b9fd22747cdea
1 parent 6ea50e6 commit fa143aa

7 files changed

Lines changed: 294 additions & 16 deletions

File tree

include/rocksdb/options.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1790,16 +1790,19 @@ class MultiScanArgs {
17901790
comp_ = other.comp_;
17911791
original_ranges_ = other.original_ranges_;
17921792
io_coalesce_threshold = other.io_coalesce_threshold;
1793+
max_prefetch_size = other.max_prefetch_size;
17931794
}
17941795
MultiScanArgs(MultiScanArgs&& other) noexcept
17951796
: io_coalesce_threshold(other.io_coalesce_threshold),
1797+
max_prefetch_size(other.max_prefetch_size),
17961798
comp_(other.comp_),
17971799
original_ranges_(std::move(other.original_ranges_)) {}
17981800

17991801
MultiScanArgs& operator=(const MultiScanArgs& other) {
18001802
comp_ = other.comp_;
18011803
original_ranges_ = other.original_ranges_;
18021804
io_coalesce_threshold = other.io_coalesce_threshold;
1805+
max_prefetch_size = other.max_prefetch_size;
18031806
return *this;
18041807
}
18051808

@@ -1808,6 +1811,7 @@ class MultiScanArgs {
18081811
comp_ = other.comp_;
18091812
original_ranges_ = std::move(other.original_ranges_);
18101813
io_coalesce_threshold = other.io_coalesce_threshold;
1814+
max_prefetch_size = other.max_prefetch_size;
18111815
}
18121816
return *this;
18131817
}
@@ -1849,6 +1853,18 @@ class MultiScanArgs {
18491853

18501854
uint64_t io_coalesce_threshold = 16 << 10; // 16KB by default
18511855

1856+
// Maximum size (in bytes) for the data blocks loaded by a MultiScan.
1857+
// This limits the amount of I/O and memory usage by pinned data blocks.
1858+
//
1859+
// When set to 0 (the default), there is no limit. When the limit is reached,
1860+
// the iterator will start returning Status::PrefetchLimitReached().
1861+
//
1862+
// Note that prefetching happens only once in Prepare(), which is different
1863+
// from ReadOptions::readahead_size, which applies any time the iterator does
1864+
// I/O.
1865+
// Note that this limit is per file and applies to compressed block size.
1866+
uint64_t max_prefetch_size = 0;
1867+
18521868
private:
18531869
// The comparator used for ordering ranges
18541870
const Comparator* comp_;

include/rocksdb/status.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ class Status {
115115
kIOFenced = 14,
116116
kMergeOperatorFailed = 15,
117117
kMergeOperandThresholdExceeded = 16,
118+
kPrefetchLimitReached = 17,
118119
kMaxSubCode
119120
};
120121

@@ -318,6 +319,10 @@ class Status {
318319

319320
static Status LockLimit() { return Status(kAborted, kLockLimit); }
320321

322+
static Status PrefetchLimitReached() {
323+
return Status(kIncomplete, kPrefetchLimitReached);
324+
}
325+
321326
// Returns true iff the status indicates success.
322327
bool ok() const {
323328
MarkChecked();
@@ -486,6 +491,13 @@ class Status {
486491
return (code() == kIOError) && (subcode() == kIOFenced);
487492
}
488493

494+
// Returns true iff the status indicates prefetch limit reached during
495+
// MultiScan.
496+
bool IsPrefetchLimitReached() const {
497+
MarkChecked();
498+
return (code() == kIncomplete) && (subcode() == kPrefetchLimitReached);
499+
}
500+
489501
// Return a string representation of this status suitable for printing.
490502
// Returns the string "OK" for success.
491503
std::string ToString() const;

table/block_based/block_based_table_iterator.cc

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -982,7 +982,6 @@ void BlockBasedTableIterator::Prepare(const MultiScanArgs* multiscan_opts) {
982982

983983
// Gather all relevant data block handles
984984
std::vector<BlockHandle> blocks_to_prepare;
985-
Status s;
986985
std::vector<std::tuple<size_t, size_t>> block_ranges_per_scan;
987986
for (const auto& scan_opt : *scan_opts) {
988987
size_t num_blocks = 0;
@@ -1042,11 +1041,26 @@ void BlockBasedTableIterator::Prepare(const MultiScanArgs* multiscan_opts) {
10421041
// Look up entries in cache and pin if exist.
10431042
// Store indices of blocks to read.
10441043
std::vector<size_t> blocks_to_read;
1045-
std::vector<CachableEntry<Block>> pinned_data_blocks_guard;
1046-
pinned_data_blocks_guard.resize(blocks_to_prepare.size());
1044+
std::vector<CachableEntry<Block>> pinned_data_blocks_guard(
1045+
blocks_to_prepare.size());
1046+
uint64_t total_prefetch_size = 0;
1047+
10471048
for (size_t i = 0; i < blocks_to_prepare.size(); ++i) {
10481049
const auto& data_block_handle = blocks_to_prepare[i];
1049-
s = table_->LookupAndPinBlocksInCache<Block_kData>(
1050+
1051+
// Check if we would exceed the prefetch size limit with this block
1052+
total_prefetch_size +=
1053+
BlockBasedTable::BlockSizeWithTrailer(data_block_handle);
1054+
if (multiscan_opts->max_prefetch_size > 0 &&
1055+
total_prefetch_size > multiscan_opts->max_prefetch_size) {
1056+
// All remaining blocks are by default empty.
1057+
for (size_t j = i; j < blocks_to_prepare.size(); ++j) {
1058+
assert(pinned_data_blocks_guard[j].IsEmpty());
1059+
}
1060+
break;
1061+
}
1062+
1063+
Status s = table_->LookupAndPinBlocksInCache<Block_kData>(
10501064
read_options_, data_block_handle,
10511065
&pinned_data_blocks_guard[i].As<Block_kData>());
10521066

@@ -1088,10 +1102,13 @@ void BlockBasedTableIterator::Prepare(const MultiScanArgs* multiscan_opts) {
10881102

10891103
// do IO
10901104
IOOptions io_opts;
1091-
s = table_->get_rep()->file->PrepareIOOptions(read_options_, io_opts);
1092-
if (!s.ok()) {
1093-
// Abort: PrepareIOOptions failed
1094-
return;
1105+
{
1106+
Status s =
1107+
table_->get_rep()->file->PrepareIOOptions(read_options_, io_opts);
1108+
if (!s.ok()) {
1109+
// Abort: PrepareIOOptions failed
1110+
return;
1111+
}
10951112
}
10961113

10971114
// Init read requests for Multi-Read
@@ -1163,11 +1180,13 @@ void BlockBasedTableIterator::Prepare(const MultiScanArgs* multiscan_opts) {
11631180
}
11641181

11651182
AlignedBuf aligned_buf;
1166-
s = table_->get_rep()->file.get()->MultiRead(
1167-
io_opts, read_reqs.data(), read_reqs.size(),
1168-
direct_io ? &aligned_buf : nullptr);
1169-
if (!s.ok()) {
1170-
return;
1183+
{
1184+
Status s = table_->get_rep()->file.get()->MultiRead(
1185+
io_opts, read_reqs.data(), read_reqs.size(),
1186+
direct_io ? &aligned_buf : nullptr);
1187+
if (!s.ok()) {
1188+
return;
1189+
}
11711190
}
11721191
for (auto& req : read_reqs) {
11731192
if (!req.status.ok()) {
@@ -1181,7 +1200,8 @@ void BlockBasedTableIterator::Prepare(const MultiScanArgs* multiscan_opts) {
11811200
table_->get_rep()->decompressor.get();
11821201
CachableEntry<DecompressorDict> cached_dict;
11831202
if (table_->get_rep()->uncompression_dict_reader) {
1184-
s = table_->get_rep()
1203+
Status s =
1204+
table_->get_rep()
11851205
->uncompression_dict_reader->GetOrReadUncompressionDictionary(
11861206
/* prefetch_buffer= */ nullptr, read_options_,
11871207
/* get_context= */ nullptr, /* lookup_context= */ nullptr,
@@ -1226,7 +1246,7 @@ void BlockBasedTableIterator::Prepare(const MultiScanArgs* multiscan_opts) {
12261246
table_->get_rep()->footer.GetBlockTrailerSize() > 0;
12271247
#endif
12281248
assert(pinned_data_blocks_guard[block_idx].IsEmpty());
1229-
s = table_->CreateAndPinBlockInCache<Block_kData>(
1249+
Status s = table_->CreateAndPinBlockInCache<Block_kData>(
12301250
read_options_, block, decompressor, &tmp_contents,
12311251
&(pinned_data_blocks_guard[block_idx].As<Block_kData>()));
12321252
if (!s.ok()) {
@@ -1290,6 +1310,16 @@ bool BlockBasedTableIterator::SeekMultiScan(const Slice* target) {
12901310
}
12911311

12921312
ResetDataIter();
1313+
1314+
// Check if we've hit an empty entry indicating prefetch limit reached
1315+
if (multi_scan_->pinned_data_blocks[cur_scan_start_idx].IsEmpty()) {
1316+
multi_scan_->cur_data_block_idx = cur_scan_start_idx;
1317+
multi_scan_->prefetch_limit_reached = true;
1318+
assert(!Valid());
1319+
assert(status().IsPrefetchLimitReached());
1320+
return true;
1321+
}
1322+
12931323
// Note that the block_iter_ takes ownership of the pinned data block
12941324
// TODO: we can delegate the clean up like with pinned_iters_mgr_ if
12951325
// need to pin blocks longer.
@@ -1346,6 +1376,16 @@ void BlockBasedTableIterator::FindBlockForwardInMultiScan() {
13461376
// Move to the next pinned data block
13471377
ResetDataIter();
13481378
++multi_scan_->cur_data_block_idx;
1379+
1380+
// Check if we've hit an empty entry indicating prefetch limit reached
1381+
if (multi_scan_->pinned_data_blocks[multi_scan_->cur_data_block_idx]
1382+
.IsEmpty()) {
1383+
multi_scan_->prefetch_limit_reached = true;
1384+
assert(!Valid());
1385+
assert(status().IsPrefetchLimitReached());
1386+
return;
1387+
}
1388+
13491389
table_->NewDataBlockIterator<DataBlockIter>(
13501390
read_options_,
13511391
multi_scan_->pinned_data_blocks[multi_scan_->cur_data_block_idx],

table/block_based/block_based_table_iterator.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,14 @@ class BlockBasedTableIterator : public InternalIteratorBase<Slice> {
145145
assert(!multi_scan_);
146146
return index_iter_->status();
147147
} else if (block_iter_points_to_real_block_) {
148+
// This is the common case.
148149
return block_iter_.status();
149150
} else if (async_read_in_progress_) {
150151
assert(!multi_scan_);
151152
return Status::TryAgain("Async read in progress");
153+
} else if (multi_scan_ && multi_scan_->prefetch_limit_reached) {
154+
assert(!Valid());
155+
return Status::PrefetchLimitReached();
152156
} else {
153157
return Status::OK();
154158
}
@@ -385,6 +389,10 @@ class BlockBasedTableIterator : public InternalIteratorBase<Slice> {
385389
size_t next_scan_idx;
386390
size_t cur_data_block_idx;
387391

392+
// When true, the iterator will return
393+
// Status::Incomplete(Status::kPrefetchLimitReached).
394+
bool prefetch_limit_reached;
395+
388396
MultiScanState(
389397
const MultiScanArgs* _scan_opts,
390398
std::vector<CachableEntry<Block>>&& _pinned_data_blocks,
@@ -393,7 +401,8 @@ class BlockBasedTableIterator : public InternalIteratorBase<Slice> {
393401
pinned_data_blocks(std::move(_pinned_data_blocks)),
394402
block_ranges_per_scan(std::move(_block_ranges_per_scan)),
395403
next_scan_idx(0),
396-
cur_data_block_idx(0) {}
404+
cur_data_block_idx(0),
405+
prefetch_limit_reached(false) {}
397406
};
398407

399408
std::unique_ptr<MultiScanState> multi_scan_;

0 commit comments

Comments
 (0)