@@ -87,7 +87,7 @@ std::vector<::arrow::io::ReadRange> MergeOverlappingRanges(
8787
8888Result<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() {
149150FileReaderWrapper::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
159161void 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.
0 commit comments