Skip to content

Commit 40998b0

Browse files
xiaoxmengfacebook-github-bot
authored andcommitted
Use batched local reads for index projector (facebookincubator#845)
Summary: Use the positioned `ReadFile::preadv(...)` batch overload from `DirectDataInput` so local direct-I/O files can take the native io_uring batch path and other files fall back to the default loop over `pread`. Split `TabletReader` data and metadata/index read handles so direct data reads can use direct I/O and io_uring while footer, metadata, and index reads use a buffered handle. Add benchmark plumbing for `--use_io_uring` and update focused unit coverage. Reviewed By: tanjialiang Differential Revision: D107930292
1 parent e049b9e commit 40998b0

7 files changed

Lines changed: 421 additions & 159 deletions

File tree

dwio/nimble/tablet/DataInput.cpp

Lines changed: 35 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@
1616
#include "dwio/nimble/tablet/DataInput.h"
1717

1818
#include <algorithm>
19+
#include <limits>
1920
#include <numeric>
2021

2122
#include "dwio/nimble/common/Exceptions.h"
22-
#include "folly/synchronization/Latch.h"
2323
#include "velox/common/base/BitUtil.h"
2424
#include "velox/common/base/CoalesceIo.h"
2525
#include "velox/common/memory/MemoryAllocator.h"
@@ -30,6 +30,20 @@ namespace facebook::nimble {
3030

3131
// --- DirectDataInput ---
3232

33+
namespace {
34+
35+
velox::ReadFile* checkedFile(velox::ReadFile* file) {
36+
NIMBLE_CHECK_NOT_NULL(file);
37+
return file;
38+
}
39+
40+
uint64_t readAlignment(const velox::ReadFile& file) {
41+
uint64_t alignment{0};
42+
return file.directIo(/*alignment=*/alignment) ? alignment : 1;
43+
}
44+
45+
} // namespace
46+
3347
/*static*/ std::string_view DirectDataInput::stateName(State state) {
3448
switch (state) {
3549
case State::kInit:
@@ -45,22 +59,18 @@ namespace facebook::nimble {
4559
}
4660

4761
DirectDataInput::DirectDataInput(velox::ReadFile* file, const Options& options)
48-
: file_{file},
62+
: file_{checkedFile(file)},
4963
pool_{options.pool},
50-
executor_{options.executor},
5164
ioStats_{options.ioStats},
52-
alignment_{options.alignment},
65+
alignment_{readAlignment(*file_)},
5366
allocAlignment_{std::max(
5467
alignment_,
5568
static_cast<uint64_t>(
5669
velox::memory::MemoryAllocator::kMinAlignment))},
5770
maxCoalesceDistance_{
5871
std::min(options.maxCoalesceDistance, kMaxCoalesceDistance)},
59-
maxCoalesceBytes_{options.maxCoalesceBytes},
60-
minIoGroupsPerTask_{std::max(1, options.minIoGroupsPerTask)} {
61-
NIMBLE_CHECK_NOT_NULL(file_);
72+
maxCoalesceBytes_{options.maxCoalesceBytes} {
6273
NIMBLE_CHECK_NOT_NULL(pool_);
63-
NIMBLE_CHECK_NOT_NULL(executor_);
6474
NIMBLE_CHECK_NOT_NULL(ioStats_);
6575
NIMBLE_CHECK_GT(alignment_, 0, "alignment must be positive");
6676
NIMBLE_CHECK(
@@ -230,54 +240,24 @@ std::pair<char*, DataInput::Handle> DirectDataInput::allocateBuffer(
230240

231241
void DirectDataInput::executeIoGroups(
232242
std::vector<IoGroup>& ioGroups,
233-
char* buffer) {
243+
char* buffer,
244+
uint64_t bufferSize) {
234245
NIMBLE_CHECK(!ioGroups.empty());
235-
const auto numGroups = ioGroups.size();
236-
237-
// Batch IO groups into tasks. Each task reads multiple groups
238-
// sequentially to reduce executor overhead.
239-
const auto batchSize = static_cast<size_t>(minIoGroupsPerTask_);
240-
const auto numTasks = velox::bits::divRoundUp(numGroups, batchSize);
241-
242-
std::exception_ptr readError;
243-
std::mutex errorMutex;
244-
folly::Latch latch(numTasks);
245-
246-
for (size_t t = 0; t < numTasks; ++t) {
247-
const auto start = t * batchSize;
248-
const auto end = std::min(start + batchSize, numGroups);
249-
executor_->add([this,
250-
&ioGroups,
251-
buffer,
252-
start,
253-
end,
254-
&readError,
255-
&errorMutex,
256-
&latch]() {
257-
try {
258-
for (size_t i = start; i < end; ++i) {
259-
velox::common::testutil::TestValue::adjust(
260-
"facebook::nimble::DirectDataInput::executeIoGroups", this);
261-
file_->pread(
262-
ioGroups[i].readOffset,
263-
ioGroups[i].readSize,
264-
buffer + ioGroups[i].bufferOffset);
265-
}
266-
} catch (...) {
267-
std::lock_guard<std::mutex> lock(errorMutex);
268-
if (readError == nullptr) {
269-
readError = std::current_exception();
270-
}
271-
}
272-
latch.count_down();
273-
});
274-
}
275-
276-
latch.wait();
277-
278-
if (readError != nullptr) {
279-
std::rethrow_exception(readError);
246+
std::vector<velox::common::Region> readRegions;
247+
readRegions.reserve(ioGroups.size());
248+
std::vector<folly::Range<char*>> readBuffers;
249+
readBuffers.reserve(ioGroups.size());
250+
for (const auto& ioGroup : ioGroups) {
251+
readRegions.emplace_back(ioGroup.readOffset, ioGroup.readSize);
252+
readBuffers.emplace_back(
253+
buffer + ioGroup.bufferOffset, static_cast<size_t>(ioGroup.readSize));
280254
}
255+
const auto bytesRead = file_->preadv(
256+
folly::Range<const velox::common::Region*>(
257+
readRegions.data(), readRegions.size()),
258+
folly::Range<const folly::Range<char*>*>(
259+
readBuffers.data(), readBuffers.size()));
260+
NIMBLE_CHECK_EQ(bytesRead, bufferSize, "preadv returned a short read");
281261
}
282262

283263
DataInput::Handle DirectDataInput::load() {
@@ -325,7 +305,7 @@ DataInput::Handle DirectDataInput::load() {
325305
uint64_t ioUs{0};
326306
{
327307
velox::MicrosecondTimer ioTimer(&ioUs);
328-
executeIoGroups(ioGroups, buffer);
308+
executeIoGroups(ioGroups, buffer, readBytes);
329309
}
330310

331311
for (const auto& group : ioGroups) {

dwio/nimble/tablet/DataInput.h

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,6 @@
2727
#include "velox/common/io/Options.h"
2828
#include "velox/common/memory/Memory.h"
2929

30-
namespace folly {
31-
class Executor;
32-
} // namespace folly
33-
3430
namespace facebook::nimble {
3531

3632
/// Base class for data I/O with an enqueue + batch load pattern.
@@ -84,12 +80,11 @@ class DataInput {
8480
virtual void clear() = 0;
8581
};
8682

87-
/// Direct data loading with IO coalescing, alignment for direct I/O,
88-
/// and optional parallel reads via executor.
83+
/// Direct data loading with IO coalescing and alignment for direct I/O.
8984
///
9085
/// After coalescing, each I/O group is backed by a non-contiguous
91-
/// Allocation (page-aligned runs). Reads are parallelized across
92-
/// I/O groups via the executor.
86+
/// Allocation (page-aligned runs). Reads are submitted through the file's
87+
/// positioned vector read API.
9388
///
9489
/// NOTE: DirectDataInput is not thread-safe. Each thread must use its
9590
/// own instance.
@@ -100,15 +95,9 @@ class DirectDataInput : public DataInput {
10095

10196
struct Options {
10297
velox::memory::MemoryPool* pool{nullptr};
103-
folly::Executor* executor{nullptr};
10498
std::shared_ptr<velox::io::IoStatistics> ioStats;
105-
uint64_t alignment{1};
10699
int32_t maxCoalesceDistance{kDefaultCoalesceDistance};
107100
int64_t maxCoalesceBytes{velox::io::ReaderOptions::kDefaultCoalesceBytes};
108-
/// Minimum number of IO groups per executor task. Batching multiple
109-
/// pread calls into one task reduces executor overhead (task timing,
110-
/// lambda allocation, scheduling) at the cost of less parallelism.
111-
int32_t minIoGroupsPerTask{4};
112101
};
113102

114103
DirectDataInput(velox::ReadFile* file, const Options& options);
@@ -173,9 +162,11 @@ class DirectDataInput : public DataInput {
173162
// it.
174163
std::pair<char*, Handle> allocateBuffer(uint64_t bytes);
175164

176-
// Executes all physical IO groups, batching executor tasks to reduce
177-
// scheduling overhead.
178-
void executeIoGroups(std::vector<IoGroup>& ioGroups, char* buffer);
165+
// Executes all physical IO groups through ReadFile's positioned vector API.
166+
void executeIoGroups(
167+
std::vector<IoGroup>& ioGroups,
168+
char* buffer,
169+
uint64_t bufferSize);
179170

180171
uint64_t alignDown(uint64_t value) const {
181172
return value & ~(alignment_ - 1);
@@ -189,8 +180,6 @@ class DirectDataInput : public DataInput {
189180
velox::ReadFile* const file_;
190181
// Memory pool for allocating the read buffer.
191182
velox::memory::MemoryPool* const pool_;
192-
// Executor for parallel I/O across coalesced groups.
193-
folly::Executor* const executor_;
194183
// IO statistics for tracking read bytes, latency, and gaps.
195184
const std::shared_ptr<velox::io::IoStatistics> ioStats_;
196185
// I/O alignment for file offsets and read sizes.
@@ -202,8 +191,6 @@ class DirectDataInput : public DataInput {
202191
const int32_t maxCoalesceDistance_;
203192
// Max total bytes per coalesced I/O group.
204193
const int64_t maxCoalesceBytes_;
205-
// Min IO groups per executor task.
206-
const int32_t minIoGroupsPerTask_;
207194

208195
State state_{State::kInit};
209196

dwio/nimble/tablet/TabletReaderCache.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ class TabletReaderCache {
9797

9898
/// Returns a cached or newly created CachedTabletReader for the given file.
9999
/// Uses readFile->getName() as the cache key. On cache miss, creates the
100-
/// TabletReader and deserializes the schema using a sharded system pool
101-
/// (keyed by filename hash) and the provided readFile/options.
100+
/// TabletReader and deserializes the schema using a sharded system pool and
101+
/// the provided readFile/options.
102102
CachedTabletReader get(
103103
const std::shared_ptr<velox::ReadFile>& readFile,
104104
const TabletReader::Options& tabletOptions);

0 commit comments

Comments
 (0)