Skip to content

Commit f74ad7f

Browse files
author
zhongzheyun
committed
perf(parquet): avoid duplicate pre-buffering for mixed row groups
1 parent ded7cfb commit f74ad7f

4 files changed

Lines changed: 265 additions & 25 deletions

File tree

‎src/paimon/format/parquet/file_reader_wrapper.cpp‎

Lines changed: 73 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ std::vector<::arrow::io::ReadRange> MergeOverlappingRanges(
8787

8888
Result<std::unique_ptr<FileReaderWrapper>> FileReaderWrapper::Create(
8989
std::unique_ptr<::parquet::arrow::FileReader>&& file_reader, int64_t batch_size,
90-
std::shared_ptr<::arrow::MemoryPool> pool) {
90+
std::shared_ptr<::arrow::MemoryPool> pool, bool pre_buffer_enabled) {
9191
try {
9292
if (file_reader == nullptr) {
9393
return Status::Invalid("file reader wrapper create failed. file reader is nullptr");
@@ -109,8 +109,9 @@ Result<std::unique_ptr<FileReaderWrapper>> FileReaderWrapper::Create(
109109
}
110110
std::vector<int32_t> columns_indices =
111111
arrow::internal::Iota(file_reader->parquet_reader()->metadata()->num_columns());
112-
auto file_reader_wrapper = std::unique_ptr<FileReaderWrapper>(new FileReaderWrapper(
113-
std::move(file_reader), all_row_group_ranges, num_rows, batch_size, pool));
112+
auto file_reader_wrapper = std::unique_ptr<FileReaderWrapper>(
113+
new FileReaderWrapper(std::move(file_reader), all_row_group_ranges, num_rows,
114+
batch_size, pool, pre_buffer_enabled));
114115
std::vector<TargetRowGroup> all_target_row_groups;
115116
for (int32_t i = 0; i < file_reader_wrapper->GetNumberOfRowGroups(); i++) {
116117
all_target_row_groups.emplace_back(/*rg_index=*/i, /*is_partially_matched=*/false,
@@ -149,11 +150,12 @@ Status FileReaderWrapper::Close() {
149150
FileReaderWrapper::FileReaderWrapper(
150151
std::unique_ptr<::parquet::arrow::FileReader>&& file_reader,
151152
const std::vector<std::pair<uint64_t, uint64_t>>& all_row_group_ranges, uint64_t num_rows,
152-
int64_t batch_size, std::shared_ptr<::arrow::MemoryPool> pool)
153+
int64_t batch_size, std::shared_ptr<::arrow::MemoryPool> pool, bool pre_buffer_enabled)
153154
: file_reader_(std::move(file_reader)),
154155
all_row_group_ranges_(all_row_group_ranges),
155156
pool_(std::move(pool)),
156157
batch_size_(batch_size),
158+
pre_buffer_enabled_(pre_buffer_enabled),
157159
num_rows_(num_rows) {}
158160

159161
void FileReaderWrapper::WaitForPendingPreBuffer() {
@@ -219,12 +221,38 @@ Status FileReaderWrapper::SeekToRow(uint64_t row_number) {
219221

220222
// Rebuild batch_reader_ for non-page-filtered RGs at/after seek position.
221223
std::vector<int32_t> fully_matched_indices;
224+
bool has_partially_matched = false;
222225
for (uint64_t j = i; j < target_row_groups_.size(); j++) {
226+
if (!target_row_groups_[j].IsExcludedByReadRange() &&
227+
target_row_groups_[j].IsPartiallyMatched()) {
228+
has_partially_matched = true;
229+
}
223230
if (!target_row_groups_[j].IsExcludedByReadRange() &&
224231
!target_row_groups_[j].IsPartiallyMatched()) {
225232
fully_matched_indices.push_back(target_row_groups_[j].GetRowGroupIndex());
226233
}
227234
}
235+
if (pre_buffer_enabled_) {
236+
WaitForPendingPreBuffer();
237+
if (!has_partially_matched) {
238+
// Keep Arrow's standard column-chunk range handling for full-only plans.
239+
file_reader_->parquet_reader()->PreBuffer(
240+
fully_matched_indices, target_column_indices_,
241+
file_reader_->properties().io_context(),
242+
file_reader_->properties().cache_options());
243+
} else {
244+
PAIMON_ASSIGN_OR_RAISE(
245+
std::vector<::arrow::io::ReadRange> ranges,
246+
CollectPreBufferRanges(target_column_indices_, /*start_idx=*/i));
247+
Status pre_buffer_status = DispatchPreBuffer(std::move(ranges));
248+
if (!pre_buffer_status.ok() && !fully_matched_indices.empty()) {
249+
::arrow::io::IOContext io_ctx(pool_.get());
250+
file_reader_->parquet_reader()->PreBuffer(
251+
fully_matched_indices, target_column_indices_, io_ctx,
252+
file_reader_->properties().cache_options());
253+
}
254+
}
255+
}
228256
if (!fully_matched_indices.empty()) {
229257
PAIMON_RETURN_NOT_OK_FROM_ARROW(file_reader_->GetRecordBatchReader(
230258
fully_matched_indices, target_column_indices_, &batch_reader_));
@@ -470,15 +498,17 @@ Result<std::vector<std::pair<uint64_t, uint64_t>>> FileReaderWrapper::GetPreBuff
470498
return pre_buffer_ranges;
471499
}
472500

473-
void FileReaderWrapper::DispatchPreBuffer(std::vector<::arrow::io::ReadRange> ranges) {
501+
Status FileReaderWrapper::DispatchPreBuffer(std::vector<::arrow::io::ReadRange> ranges) {
474502
const auto& cache_opts = file_reader_->properties().cache_options();
475503
::arrow::io::IOContext io_ctx(pool_.get());
476504
auto merged_ranges = MergeOverlappingRanges(std::move(ranges));
477505
try {
478506
file_reader_->parquet_reader()->PreBufferRanges(merged_ranges, io_ctx, cache_opts);
479507
prebuffered_ranges_ = std::move(merged_ranges);
480-
} catch (const std::exception&) {
508+
return Status::OK();
509+
} catch (const std::exception& e) {
481510
prebuffered_ranges_.clear();
511+
return Status::IOError("Parquet pre-buffer failed: ", e.what());
482512
}
483513
}
484514

@@ -514,15 +544,40 @@ Status FileReaderWrapper::PrepareForReading(const std::vector<TargetRowGroup>& t
514544
fully_matched_row_groups.push_back(trg.GetRowGroupIndex());
515545
}
516546
}
517-
518-
bool has_partially_matched = fully_matched_row_groups.size() != active_count;
547+
const bool has_partially_matched = fully_matched_row_groups.size() != active_count;
519548

520549
WaitForPendingPreBuffer();
521550

522-
// TODO(Yonghao Fang): Neither Paimon nor Arrow manage the size and lifecycle of prebuffered
523-
// caches. So when a lot of row is needed, there is possibility of OOM due to too much
524-
// prebuffering. Also, DispatchPreBuffer will drop previous prebuffered ranges by
525-
// GetRecordBatchReader, which cause IO wastes.
551+
// TODO(Yonghao Fang): Neither Paimon nor Arrow manage the size and lifecycle of
552+
// pre-buffered caches, so reading many rows may consume too much memory.
553+
// Dispatch one cache for both full and page-filtered row groups before constructing the
554+
// standard reader. Arrow's automatic pre-buffer is disabled when this flag is set, so
555+
// GetRecordBatchReader cannot replace this cache with a full-row-group-only cache.
556+
bool managed_pre_buffer_succeeded = false;
557+
if (pre_buffer_enabled_) {
558+
if (!has_partially_matched) {
559+
// Preserve Arrow's range validation and legacy file handling for full-only plans.
560+
file_reader_->parquet_reader()->PreBuffer(
561+
fully_matched_row_groups, column_indices,
562+
file_reader_->properties().io_context(),
563+
file_reader_->properties().cache_options());
564+
managed_pre_buffer_succeeded = true;
565+
} else {
566+
PAIMON_ASSIGN_OR_RAISE(std::vector<::arrow::io::ReadRange> all_ranges,
567+
CollectPreBufferRanges(column_indices, first_active_idx));
568+
Status pre_buffer_status = DispatchPreBuffer(std::move(all_ranges));
569+
managed_pre_buffer_succeeded = pre_buffer_status.ok();
570+
if (!managed_pre_buffer_succeeded && !fully_matched_row_groups.empty()) {
571+
// Preserve Arrow's original error and fallback behavior if range pre-buffering
572+
// cannot be initialized: full row groups still use the standard cache, while
573+
// page-filtered row groups retain their existing best-effort path below.
574+
::arrow::io::IOContext io_ctx(pool_.get());
575+
file_reader_->parquet_reader()->PreBuffer(
576+
fully_matched_row_groups, column_indices, io_ctx,
577+
file_reader_->properties().cache_options());
578+
}
579+
}
580+
}
526581

527582
// Create standard reader for fully-matched row groups.
528583
if (!fully_matched_row_groups.empty()) {
@@ -532,12 +587,14 @@ Status FileReaderWrapper::PrepareForReading(const std::vector<TargetRowGroup>& t
532587
batch_reader_.reset();
533588
}
534589

535-
// When page-filtered RGs exist, issue a single PreBuffer covering both kinds.
536-
// Otherwise GetRecordBatchReader already issued PreBuffer internally.
537-
if (has_partially_matched) {
590+
// Preserve the legacy page-filtered path for callers that leave Arrow in charge of
591+
// pre-buffering. This includes page-range caching when automatic pre-buffering is off;
592+
// PageFilteredRowGroupReader retains its full-chunk fallback if this best-effort call
593+
// fails.
594+
if ((!pre_buffer_enabled_ || !managed_pre_buffer_succeeded) && has_partially_matched) {
538595
PAIMON_ASSIGN_OR_RAISE(std::vector<::arrow::io::ReadRange> all_ranges,
539596
CollectPreBufferRanges(column_indices, first_active_idx));
540-
DispatchPreBuffer(std::move(all_ranges));
597+
(void)DispatchPreBuffer(std::move(all_ranges));
541598
}
542599

543600
// Reset read state to the first row group that will be read.

‎src/paimon/format/parquet/file_reader_wrapper.h‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,11 @@ class FileReaderWrapper {
5858
public:
5959
~FileReaderWrapper();
6060

61+
// When pre_buffer_enabled is true, the wrapper manages the cache for full and partial
62+
// row groups together; the supplied Arrow reader must have automatic pre-buffering disabled.
6163
static Result<std::unique_ptr<FileReaderWrapper>> Create(
6264
std::unique_ptr<::parquet::arrow::FileReader>&& reader, int64_t batch_size,
63-
std::shared_ptr<arrow::MemoryPool> pool);
65+
std::shared_ptr<arrow::MemoryPool> pool, bool pre_buffer_enabled = false);
6466

6567
/// Seek to the specified row number.
6668
/// @param row_number The row to seek to (must be at a row group boundary).
@@ -162,7 +164,7 @@ class FileReaderWrapper {
162164
FileReaderWrapper(std::unique_ptr<::parquet::arrow::FileReader>&& file_reader,
163165
const std::vector<std::pair<uint64_t, uint64_t>>& all_row_group_ranges,
164166
uint64_t num_rows, int64_t batch_size,
165-
std::shared_ptr<::arrow::MemoryPool> pool);
167+
std::shared_ptr<::arrow::MemoryPool> pool, bool pre_buffer_enabled);
166168

167169
/// Wait for all pending PreBuffer operations to complete.
168170
void WaitForPendingPreBuffer();
@@ -193,7 +195,7 @@ class FileReaderWrapper {
193195
uint64_t start_idx);
194196

195197
/// Dispatch a single PreBufferRanges call with merged ranges.
196-
void DispatchPreBuffer(std::vector<::arrow::io::ReadRange> ranges);
198+
Status DispatchPreBuffer(std::vector<::arrow::io::ReadRange> ranges);
197199

198200
std::unique_ptr<::parquet::arrow::FileReader> file_reader_;
199201
std::unique_ptr<arrow::RecordBatchReader> batch_reader_;
@@ -203,6 +205,7 @@ class FileReaderWrapper {
203205

204206
std::shared_ptr<::arrow::MemoryPool> pool_;
205207
int64_t batch_size_; // 0 means no limit
208+
bool pre_buffer_enabled_;
206209

207210
const uint64_t num_rows_;
208211
uint64_t next_row_to_read_ = std::numeric_limits<uint64_t>::max();

0 commit comments

Comments
 (0)