Skip to content

Commit 80da747

Browse files
committed
GH-39808: [C++][Parquet] Fix cross-row-group test cache limits
The cross-row-group eviction test set range_size_limit == hole_size_limit, which trips the range_size_limit > hole_size_limit check in debug/CI builds (the release build compiles it out). Make range one byte larger so the limits stay valid while still coalescing every chunk into one entry. Also trim the remaining added comments to bare invariants per review feedback.
1 parent de90ce7 commit 80da747

6 files changed

Lines changed: 20 additions & 41 deletions

File tree

cpp/src/arrow/io/caching.cc

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -265,9 +265,8 @@ struct ReadRangeCache::Impl {
265265
return AllComplete(futures);
266266
}
267267

268-
// `entries` is sorted by offset, so we stop once an entry starts at/after
269-
// `end_offset`. An entry that starts before but extends past `end_offset`
270-
// (coalesced with a range a later consumer still needs) is left in place.
268+
// `entries` is sorted by offset; an entry that extends past `end_offset`
269+
// (coalesced with a range a later consumer still needs) is kept.
271270
int64_t EvictEntriesBefore(int64_t end_offset) {
272271
int64_t n_evicted = 0;
273272
std::unique_lock<std::mutex> guard(entry_mutex);

cpp/src/arrow/io/caching.h

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -142,16 +142,10 @@ class ARROW_EXPORT ReadRangeCache {
142142
/// \brief Wait until all given ranges have been cached.
143143
Future<> WaitFor(std::vector<ReadRange> ranges);
144144

145-
/// \brief Evict cache entries that end at or before `end_offset`.
146-
///
147-
/// Releases the memory of entries whose byte range lies entirely before
148-
/// `end_offset`. An entry straddling `end_offset` is retained, so this is safe
149-
/// even when I/O coalescing merged several requested ranges into one entry.
150-
/// Buffers already returned by Read() stay valid through shared ownership.
151-
///
152-
/// \param[in] end_offset Exclusive byte bound; entries ending at or before it
153-
/// are evicted.
154-
/// \return Number of cache entries evicted.
145+
/// \brief Evict cache entries ending at or before `end_offset`, returning the
146+
/// count. An entry straddling `end_offset` is retained (safe when coalescing
147+
/// merged ranges). Buffers already returned by Read() stay valid through
148+
/// shared ownership.
155149
int64_t EvictEntriesBefore(int64_t end_offset);
156150

157151
protected:

cpp/src/parquet/arrow/arrow_reader_writer_test.cc

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2673,10 +2673,8 @@ TEST(TestArrowReadWrite, GetRecordBatchReaderNoColumns) {
26732673
ASSERT_EQ(actual_batch->num_rows(), num_rows);
26742674
}
26752675

2676-
// GH-39808: bytes cached by PreBuffer() for an already-decoded row group
2677-
// should be released when the caller explicitly evicts them, otherwise
2678-
// Dataset.to_batches accumulates memory across the lifetime of an open
2679-
// FileReader.
2676+
// GH-39808: bytes cached by PreBuffer() for a decoded row group must be
2677+
// releasable, else Dataset.to_batches accumulates memory over the reader's life.
26802678
TEST(TestArrowReadWrite, EvictPreBufferedDataBefore) {
26812679
ArrowReaderProperties properties = default_arrow_reader_properties();
26822680
properties.set_pre_buffer(true);
@@ -2758,7 +2756,7 @@ TEST(TestArrowReadWrite, EvictPreBufferedDataBeforeReleasesCrossRowGroupEntry) {
27582756
// spanning all row-group boundaries.
27592757
::arrow::io::CacheOptions options = ::arrow::io::CacheOptions::LazyDefaults();
27602758
options.hole_size_limit = static_cast<int64_t>(buffer->size());
2761-
options.range_size_limit = static_cast<int64_t>(buffer->size());
2759+
options.range_size_limit = static_cast<int64_t>(buffer->size()) + 1;
27622760
reader->parquet_reader()->PreBuffer(row_groups, column_indices,
27632761
::arrow::io::IOContext(), options);
27642762
ASSERT_OK(reader->parquet_reader()->WhenBuffered(row_groups, column_indices).status());

cpp/src/parquet/arrow/reader.cc

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1239,10 +1239,9 @@ FileReaderImpl::GetRecordBatchGenerator(std::shared_ptr<FileReader> reader,
12391239
reader_properties_.cache_options());
12401240
END_PARQUET_CATCH_EXCEPTIONS
12411241
}
1242-
// GH-39808: when pre-buffering, release each row group's cached bytes once the
1243-
// contiguous prefix of decoded row groups no longer needs them, keeping the
1244-
// memory footprint bounded while iterating. Confined to this read-once path,
1245-
// so PreBuffer()'s contract for other callers is unchanged.
1242+
// GH-39808: evict each row group's bytes as the decoded prefix advances, so
1243+
// memory stays bounded. Only this read-once path evicts, so PreBuffer()'s
1244+
// contract is unchanged for other callers.
12461245
std::shared_ptr<ReadCacheEvictionState> eviction_state;
12471246
if (reader_properties_.pre_buffer() && !column_indices.empty() &&
12481247
!row_group_indices.empty()) {

cpp/src/parquet/arrow/reader_internal.h

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,9 @@ Status TransferColumnData(::parquet::internal::RecordReader* reader,
138138
// ----------------------------------------------------------------------
139139
// Pre-buffer eviction
140140

141-
// GH-39808: releases pre-buffered cache entries as row groups are decoded.
142-
// RowGroupDecoded() is called (possibly out of order, from readahead
143-
// continuations) once a row group's batches are produced. It advances a
144-
// watermark over the contiguous prefix of completed row groups and evicts every
145-
// cache entry ending before the lowest byte any not-yet-completed row group
146-
// still needs. A coalesced entry spanning a row-group boundary is freed once
147-
// the watermark passes its end.
141+
// GH-39808: as row groups finish decoding (possibly out of order under
142+
// readahead), advance a watermark over the leading run of completed ones and
143+
// evict cache entries ending before the lowest byte any remaining one needs.
148144
class ReadCacheEvictionState {
149145
public:
150146
// evict_before_offsets[i] = lowest byte offset row groups i..n-1 (in
@@ -159,11 +155,8 @@ class ReadCacheEvictionState {
159155
}
160156
}
161157

162-
// Marks row_group_index decoded and advances the contiguous completed prefix.
163-
// Returns the offset to evict before when the prefix advanced, or std::nullopt
164-
// when nothing newly became evictable (index out of range, already completed,
165-
// or the prefix did not grow). Separated from RowGroupDecoded so the
166-
// out-of-order watermark logic is unit-testable without a reader.
158+
// Reader-free half of RowGroupDecoded (so the out-of-order advance is testable):
159+
// the evict-before offset when the prefix grows, else nullopt.
167160
std::optional<int64_t> MarkDecodedAndGetEvictOffset(size_t row_group_index) {
168161
std::lock_guard<std::mutex> lock(mutex_);
169162
if (row_group_index >= completed_.size() || completed_[row_group_index]) {

cpp/src/parquet/file_reader.h

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -201,13 +201,9 @@ class PARQUET_EXPORT ParquetFileReader {
201201
const ::arrow::io::IOContext& ctx,
202202
const ::arrow::io::CacheOptions& options);
203203

204-
/// \brief Release cached bytes that end at or before `end_offset`.
205-
///
206-
/// Only affects data cached by PreBuffer(). Call once the row groups needing
207-
/// those bytes are fully decoded; later reads of evicted ranges may fail.
208-
/// Buffers already handed to readers stay valid through shared ownership. A
209-
/// no-op (returns 0) if PreBuffer() was not called. Returns the number of
210-
/// cache entries evicted.
204+
/// \brief Release cached bytes (from PreBuffer()) ending at or before
205+
/// `end_offset`. Call once those row groups are decoded; later reads of evicted
206+
/// ranges may fail. No-op (returns 0) if PreBuffer() was not called.
211207
int64_t EvictPreBufferedDataBefore(int64_t end_offset);
212208

213209
/// Retrieve the list of byte ranges that would need to be read to retrieve

0 commit comments

Comments
 (0)