Skip to content

Commit 4b45dca

Browse files
committed
Add prefetch support for reverse scan
1 parent ad43a5f commit 4b45dca

6 files changed

Lines changed: 145 additions & 39 deletions

File tree

file/file_prefetch_buffer.cc

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -259,11 +259,17 @@ void FilePrefetchBuffer::CopyDataToOverlapBuffer(BufferInfo* src,
259259
// Clear the buffers if it contains outdated data. Outdated data can be because
260260
// previous sequential reads were read from the cache instead of these buffer.
261261
// In that case outdated IOs should be aborted.
262-
void FilePrefetchBuffer::AbortOutdatedIO(uint64_t offset) {
262+
void FilePrefetchBuffer::AbortOutdatedIO(uint64_t offset, size_t length) {
263263
std::vector<void*> handles;
264264
std::vector<BufferInfo*> tmp_buf;
265265
for (auto& buf : bufs_) {
266-
if (buf->IsBufferOutdatedWithAsyncProgress(offset)) {
266+
bool outdated = false;
267+
if (direction_ == PrefetchDirection::kForward) {
268+
outdated = buf->IsBufferOutdatedWithAsyncProgress(offset);
269+
} else {
270+
outdated = buf->IsBufferOutdatedBackwardWithAsyncProgress(offset, length);
271+
}
272+
if (outdated) {
267273
handles.emplace_back(buf->io_handle_);
268274
tmp_buf.emplace_back(buf);
269275
}
@@ -315,8 +321,15 @@ void FilePrefetchBuffer::AbortAllIOs() {
315321
void FilePrefetchBuffer::ClearOutdatedData(uint64_t offset, size_t length) {
316322
while (!IsBufferQueueEmpty()) {
317323
BufferInfo* buf = GetFirstBuffer();
318-
// Offset is greater than this buffer's end offset.
319-
if (buf->IsBufferOutdated(offset)) {
324+
// Offset is greater than this buffer's end offset for forward,
325+
// or offset+length <= buf start for backward.
326+
bool outdated = false;
327+
if (direction_ == PrefetchDirection::kForward) {
328+
outdated = buf->IsBufferOutdated(offset);
329+
} else {
330+
outdated = buf->IsBufferOutdatedBackward(offset, length);
331+
}
332+
if (outdated) {
320333
FreeFrontBuffer();
321334
} else {
322335
break;
@@ -339,11 +352,22 @@ void FilePrefetchBuffer::ClearOutdatedData(uint64_t offset, size_t length) {
339352

340353
if (buf->DoesBufferContainData() && buf->IsOffsetInBuffer(offset)) {
341354
BufferInfo* next_buf = bufs_[1];
342-
if (/* next buffer doesn't align with first buffer and requested data
343-
overlaps with next buffer */
344-
((buf->offset_ + buf->CurrentSize() != next_buf->offset_) &&
345-
(offset + length > buf->offset_ + buf->CurrentSize()))) {
346-
abort_io = true;
355+
if (direction_ == PrefetchDirection::kForward) {
356+
if (/* next buffer doesn't align with first buffer and requested data
357+
overlaps with next buffer */
358+
((buf->offset_ + buf->CurrentSize() != next_buf->offset_) &&
359+
(offset + length > buf->offset_ + buf->CurrentSize()))) {
360+
abort_io = true;
361+
}
362+
} else {
363+
// Backward: next buffer should start where current ends in reverse
364+
// direction? For backward, buffers are ordered with decreasing offsets in
365+
// queue front to back? Simplistic check: if requested range extends
366+
// before current buffer start, and next buffer doesn't align.
367+
if (((next_buf->offset_ + next_buf->CurrentSize() != buf->offset_) &&
368+
(offset < buf->offset_))) {
369+
abort_io = true;
370+
}
347371
}
348372
} else {
349373
// buffer with offset doesn't contain data or offset doesn't lie in this
@@ -636,7 +660,7 @@ Status FilePrefetchBuffer::PrefetchInternal(const IOOptions& opts,
636660

637661
// Abort outdated IO.
638662
if (!explicit_prefetch_submitted_) {
639-
AbortOutdatedIO(offset);
663+
AbortOutdatedIO(offset, length);
640664
FreeEmptyBuffers();
641665
}
642666
ClearOutdatedData(offset, length);

file/file_prefetch_buffer.h

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ namespace ROCKSDB_NAMESPACE {
3434
struct IOOptions;
3535
class RandomAccessFileReader;
3636

37+
enum class FilePrefetchBufferUsage {
38+
kTableOpenPrefetchTail,
39+
kUserScanPrefetch,
40+
kCompactionPrefetch,
41+
kUnknown,
42+
};
43+
44+
enum class PrefetchDirection {
45+
kForward,
46+
kBackward,
47+
};
48+
3749
struct ReadaheadParams {
3850
ReadaheadParams() {}
3951

@@ -58,6 +70,10 @@ struct ReadaheadParams {
5870
// Number of buffers to maintain that contains prefetched data. If num_buffers
5971
// > 1 then buffers will be filled asynchronously whenever they get emptied.
6072
size_t num_buffers = 1;
73+
74+
// Direction of prefetch for sequential scans. Forward is default for backward
75+
// compatibility. Backward is used for reverse iteration.
76+
PrefetchDirection direction = PrefetchDirection::kForward;
6177
};
6278

6379
struct BufferInfo {
@@ -118,11 +134,22 @@ struct BufferInfo {
118134
offset >= offset_ + buffer_.CurrentSize());
119135
}
120136

137+
bool IsBufferOutdatedBackward(uint64_t offset, size_t length) {
138+
return (!async_read_in_progress_ && DoesBufferContainData() &&
139+
offset + length <= offset_);
140+
}
141+
121142
bool IsBufferOutdatedWithAsyncProgress(uint64_t offset) {
122143
return (async_read_in_progress_ && io_handle_ != nullptr &&
123144
offset >= offset_ + async_req_len_);
124145
}
125146

147+
bool IsBufferOutdatedBackwardWithAsyncProgress(uint64_t offset,
148+
size_t length) {
149+
return (async_read_in_progress_ && io_handle_ != nullptr &&
150+
offset + length <= offset_);
151+
}
152+
126153
bool IsOffsetInBufferWithAsyncProgress(uint64_t offset) {
127154
return (async_read_in_progress_ && offset >= offset_ &&
128155
offset < offset_ + async_req_len_);
@@ -131,13 +158,6 @@ struct BufferInfo {
131158
size_t CurrentSize() { return buffer_.CurrentSize(); }
132159
};
133160

134-
enum class FilePrefetchBufferUsage {
135-
kTableOpenPrefetchTail,
136-
kUserScanPrefetch,
137-
kCompactionPrefetch,
138-
kUnknown,
139-
};
140-
141161
// Implementation:
142162
// FilePrefetchBuffer maintains a dequeu of free buffers (free_bufs_) with no
143163
// data and bufs_ which contains the prefetched data. Whenever a buffer is
@@ -208,7 +228,8 @@ class FilePrefetchBuffer {
208228
stats_(stats),
209229
usage_(usage),
210230
readaheadsize_cb_(cb),
211-
num_buffers_(readahead_params.num_buffers) {
231+
num_buffers_(readahead_params.num_buffers),
232+
direction_(readahead_params.direction) {
212233
assert((num_file_reads_ >= num_file_reads_for_auto_readahead_ + 1) ||
213234
(num_file_reads_ == 0));
214235

@@ -424,7 +445,7 @@ class FilePrefetchBuffer {
424445
size_t roundup_len, bool refit_tail,
425446
bool use_fs_buffer, uint64_t& aligned_useful_len);
426447

427-
void AbortOutdatedIO(uint64_t offset);
448+
void AbortOutdatedIO(uint64_t offset, size_t length);
428449

429450
void AbortAllIOs();
430451

@@ -711,5 +732,8 @@ class FilePrefetchBuffer {
711732
// num_buffers_ is the number of buffers maintained by FilePrefetchBuffer to
712733
// prefetch the data at a time.
713734
size_t num_buffers_;
735+
736+
// Direction of prefetch for sequential scans.
737+
PrefetchDirection direction_;
714738
};
715739
} // namespace ROCKSDB_NAMESPACE

table/block_based/block_based_table_iterator.cc

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ void BlockBasedTableIterator::SeekForPrev(const Slice& target) {
269269
}
270270
}
271271

272-
InitDataBlock();
272+
InitDataBlock(PrefetchDirection::kBackward);
273273

274274
block_iter_.SeekForPrev(target);
275275

@@ -297,7 +297,7 @@ void BlockBasedTableIterator::SeekToLast() {
297297
return;
298298
}
299299

300-
InitDataBlock();
300+
InitDataBlock(PrefetchDirection::kBackward);
301301
block_iter_.SeekToLast();
302302
FindKeyBackward();
303303
CheckDataBlockWithinUpperBound();
@@ -362,7 +362,7 @@ void BlockBasedTableIterator::Prev() {
362362
return;
363363
}
364364

365-
InitDataBlock();
365+
InitDataBlock(PrefetchDirection::kBackward);
366366
block_iter_.SeekToLast();
367367
} else {
368368
assert(block_iter_points_to_real_block_);
@@ -372,7 +372,7 @@ void BlockBasedTableIterator::Prev() {
372372
FindKeyBackward();
373373
}
374374

375-
void BlockBasedTableIterator::InitDataBlock() {
375+
void BlockBasedTableIterator::InitDataBlock(PrefetchDirection direction) {
376376
// MultiScan path: load block from ReadSet
377377
if (multi_scan_read_set_) {
378378
BlockHandle data_block_handle = index_iter_->value().handle;
@@ -443,7 +443,7 @@ void BlockBasedTableIterator::InitDataBlock() {
443443

444444
std::function<void(bool, uint64_t&, uint64_t&)> readaheadsize_cb =
445445
nullptr;
446-
if (readahead_cache_lookup_) {
446+
if (readahead_cache_lookup_ && direction == PrefetchDirection::kForward) {
447447
readaheadsize_cb = std::bind(
448448
&BlockBasedTableIterator::BlockCacheLookupForReadAheadSize, this,
449449
std::placeholders::_1, std::placeholders::_2,
@@ -460,7 +460,7 @@ void BlockBasedTableIterator::InitDataBlock() {
460460
rep, data_block_handle, read_options_.readahead_size,
461461
is_for_compaction,
462462
/*no_sequential_checking=*/false, read_options_, readaheadsize_cb,
463-
read_options_.async_io);
463+
read_options_.async_io, direction);
464464

465465
Status s;
466466
table_->NewDataBlockIterator<DataBlockIter>(
@@ -484,7 +484,8 @@ void BlockBasedTableIterator::InitDataBlock() {
484484
}
485485
}
486486

487-
void BlockBasedTableIterator::AsyncInitDataBlock(bool is_first_pass) {
487+
void BlockBasedTableIterator::AsyncInitDataBlock(bool is_first_pass,
488+
PrefetchDirection direction) {
488489
BlockHandle data_block_handle;
489490
bool is_for_compaction =
490491
lookup_context_.caller == TableReaderCaller::kCompaction;
@@ -501,7 +502,7 @@ void BlockBasedTableIterator::AsyncInitDataBlock(bool is_first_pass) {
501502

502503
std::function<void(bool, uint64_t&, uint64_t&)> readaheadsize_cb =
503504
nullptr;
504-
if (readahead_cache_lookup_) {
505+
if (readahead_cache_lookup_ && direction == PrefetchDirection::kForward) {
505506
readaheadsize_cb = std::bind(
506507
&BlockBasedTableIterator::BlockCacheLookupForReadAheadSize, this,
507508
std::placeholders::_1, std::placeholders::_2,
@@ -520,7 +521,7 @@ void BlockBasedTableIterator::AsyncInitDataBlock(bool is_first_pass) {
520521
block_prefetcher_.PrefetchIfNeeded(
521522
rep, data_block_handle, read_options_.readahead_size,
522523
is_for_compaction, /*no_sequential_checking=*/read_options_.async_io,
523-
read_options_, readaheadsize_cb, read_options_.async_io);
524+
read_options_, readaheadsize_cb, read_options_.async_io, direction);
524525

525526
Status s;
526527
table_->NewDataBlockIterator<DataBlockIter>(
@@ -740,7 +741,7 @@ void BlockBasedTableIterator::FindKeyBackward() {
740741
index_iter_->Prev();
741742

742743
if (index_iter_->Valid()) {
743-
InitDataBlock();
744+
InitDataBlock(PrefetchDirection::kBackward);
744745
block_iter_.SeekToLast();
745746
} else {
746747
return;

table/block_based/block_based_table_iterator.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,8 +417,9 @@ class BlockBasedTableIterator : public InternalIteratorBase<Slice> {
417417
// If `target` is null, seek to first.
418418
void SeekImpl(const Slice* target, bool async_prefetch);
419419

420-
void InitDataBlock();
421-
void AsyncInitDataBlock(bool is_first_pass);
420+
void InitDataBlock(PrefetchDirection direction = PrefetchDirection::kForward);
421+
void AsyncInitDataBlock(bool is_first_pass, PrefetchDirection direction =
422+
PrefetchDirection::kForward);
422423
bool MaterializeCurrentBlock();
423424
void FindKeyForward();
424425
void FindBlockForward();

table/block_based/block_prefetcher.cc

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ void BlockPrefetcher::PrefetchIfNeeded(
1717
const size_t readahead_size, bool is_for_compaction,
1818
const bool no_sequential_checking, const ReadOptions& read_options,
1919
const std::function<void(bool, uint64_t&, uint64_t&)>& readaheadsize_cb,
20-
bool is_async_io_prefetch) {
20+
bool is_async_io_prefetch, PrefetchDirection direction) {
2121
if (read_options.read_tier == ReadTier::kBlockCacheTier) {
2222
// Disable prefetching when IO disallowed. (Note that we haven't allocated
2323
// any buffers yet despite the various tracked settings.)
@@ -28,13 +28,15 @@ void BlockPrefetcher::PrefetchIfNeeded(
2828
readahead_params.initial_readahead_size = readahead_size;
2929
readahead_params.max_readahead_size = readahead_size;
3030
readahead_params.num_buffers = is_async_io_prefetch ? 2 : 1;
31+
readahead_params.direction = direction;
3132

3233
const size_t len = BlockBasedTable::BlockSizeWithTrailer(handle);
3334
const size_t offset = handle.offset();
3435
if (is_for_compaction) {
3536
if (!rep->file->use_direct_io() && compaction_readahead_size_ > 0) {
3637
// If FS supports prefetching (readahead_limit_ will be non zero in that
3738
// case) and current block exists in prefetch buffer then return.
39+
// Compaction is always forward.
3840
if (offset + len <= readahead_limit_) {
3941
return;
4042
}
@@ -108,12 +110,29 @@ void BlockPrefetcher::PrefetchIfNeeded(
108110

109111
// If FS supports prefetching (readahead_limit_ will be non zero in that case)
110112
// and current block exists in prefetch buffer then return.
111-
if (offset + len <= readahead_limit_) {
113+
bool in_prefetch_range = false;
114+
if (direction == PrefetchDirection::kForward) {
115+
in_prefetch_range = (offset + len <= readahead_limit_);
116+
} else {
117+
// For backward, prefetch_start_ tracks lower bound of prefetched range.
118+
// If prefetch_start_ is 0 it means not set yet, treat as miss.
119+
in_prefetch_range = (prefetch_start_ != 0 && offset >= prefetch_start_ &&
120+
offset + len <= readahead_limit_);
121+
// Alternative simple check for backward: offset >= prefetch_start_
122+
// We'll use offset >= prefetch_start_ as indicator current block is within
123+
// prefetched window.
124+
if (prefetch_start_ != 0) {
125+
in_prefetch_range = (offset >= prefetch_start_);
126+
} else {
127+
in_prefetch_range = false;
128+
}
129+
}
130+
if (in_prefetch_range) {
112131
UpdateReadPattern(offset, len);
113132
return;
114133
}
115134

116-
if (!IsBlockSequential(offset)) {
135+
if (!IsBlockSequential(offset, len, direction)) {
117136
UpdateReadPattern(offset, len);
118137
ResetValues(rep->table_options.initial_auto_readahead_size);
119138
return;
@@ -148,11 +167,31 @@ void BlockPrefetcher::PrefetchIfNeeded(
148167
}
149168

150169
if (rep->fs_prefetch_support) {
151-
s = rep->file->Prefetch(
152-
opts, handle.offset(),
153-
BlockBasedTable::BlockSizeWithTrailer(handle) + readahead_size_);
170+
uint64_t prefetch_offset;
171+
size_t prefetch_len;
172+
if (direction == PrefetchDirection::kForward) {
173+
prefetch_offset = handle.offset();
174+
prefetch_len =
175+
BlockBasedTable::BlockSizeWithTrailer(handle) + readahead_size_;
176+
} else {
177+
// Backward: prefetch from offset - readahead_size_ to offset + len
178+
uint64_t curr_offset = handle.offset();
179+
uint64_t start =
180+
curr_offset > readahead_size_ ? curr_offset - readahead_size_ : 0;
181+
prefetch_offset = start;
182+
prefetch_len =
183+
curr_offset - start + BlockBasedTable::BlockSizeWithTrailer(handle);
184+
}
185+
s = rep->file->Prefetch(opts, prefetch_offset, prefetch_len);
154186
if (s.ok()) {
155-
readahead_limit_ = offset + len + readahead_size_;
187+
if (direction == PrefetchDirection::kForward) {
188+
readahead_limit_ = offset + len + readahead_size_;
189+
prefetch_start_ =
190+
offset; // not really used for forward but for completeness
191+
} else {
192+
prefetch_start_ = prefetch_offset;
193+
readahead_limit_ = offset + len;
194+
}
156195
// Keep exponentially increasing readahead size until
157196
// max_auto_readahead_size.
158197
readahead_size_ = std::min(max_auto_readahead_size, readahead_size_ * 2);

0 commit comments

Comments
 (0)