Skip to content

perf(prefetch): prefetch the late-materialization payload ranges mid-read - #342

Merged
lxy-9602 merged 17 commits into
apache:mainfrom
lucasfang:dev26
Sep 17, 2026
Merged

lxy-9602 merged 17 commits into
apache:mainfrom
lucasfang:dev26

Conversation

@lucasfang

@lucasfang lucasfang commented Sep 14, 2026 •

Copy link
Copy Markdown
Collaborator

Purpose

Linked issue: close #341

The shared read-ahead cache is fed once per read-range generation through PrefetchFileBatchReader::PreBufferRange(), before any read starts. For late materialization the payload pass cannot know which pages hold the matched rows until the probe pass has run, so PreBufferRange() only reported the probe ranges (an explicit TODO covered this) and the payload pass - the one touching the wide columns - was never prefetched: every payload read missed the cache and waited for its own underlying IO. This change lets a reader report byte ranges that only become known after reading has started, and lets the cache register them mid-read.

  • New PrefetchFileBatchReader::PreBufferRangeCallback + SetPreBufferRangeCallback() (default no-op). PrefetchFileBatchReaderImpl installs a callback on each sub-reader; LateMaterializingFileBatchReader reports the payload ranges once the probe pass has refined the inner reader's target pages, and propagates a failure to compute them (they come from the file metadata) rather than swallowing it.
  • ReadAheadCache::AddRanges(std::vector<ByteRange>&&) registers ranges into a live cache and is safe to call repeatedly and concurrently with Read(). It merges into the single offset-ordered, disjoint range list, registering only the parts no registered range covers and dropping the overlap (those bytes are already being fetched), and returns the first newly-registered offset (nullopt when everything was already covered). Warmup(from_offset) then fetches from there instead of from the head.
  • Lifetime is bounded by the impl's cache_warmed_ flag rather than a round counter: CleanUp()/Close() and the reader-reuse entry points (SetReadSchema/RefreshReadRanges) clear it, so a report that outlives its read-range generation registers nothing instead of prefetching bytes nobody reads. The callbacks are detached in Close() and in the destructor after the background thread has been joined.
  • Cache simplification bundled in: ReadAheadCache dropped its pimpl and collapsed the three parallel containers (pending_ranges_/is_cached_/entries_) into one ranges_ vector, where an entry is published exactly when its buffer is set; PreBuffer() takes the write lock only when the prefetch window still holds an unpublished range and otherwise returns under a read-lock fast path; both release paths swap the entries/blocks out under the lock and wait on the in-flight futures outside it; FileBlockCache moved to a std::shared_mutex with a read-lock hit path and a double-checked write-lock miss; AtomicCounterPair::Add was renamed to Record.
  • CacheConfig default tuning: range_size_limit lowered 32 MiB -> 8 MiB (now the single cap for both the up-front and the mid-read ranges; one range is one prefetch IO, so a smaller cap spreads a pass over several concurrent requests instead of one long serial one), and hole_size_limit raised 8 KiB -> 512 KiB (coalesce across the page-sized gaps a filtered read leaves between the pages it keeps, trading bytes for fewer round trips on remote storage).
  • Metrics: the late-registration counters were dropped together with the round concept. The cache still exposes read-ahead-cache.read.count/.bytes/.hits/.hit-bytes/.misses/.miss-bytes, .io.count/.bytes and .block.fetches/.fetch-bytes/.hits/.hit-bytes through BatchReader::GetReaderMetrics().

Risk / known limits: the callback captures the impl raw, which is safe because it owns the sub-readers and ClearPreBufferRangeCallbacks() runs after the background thread is joined. AddRanges() concurrent with Read() is memory-safe (publish happens under the write lock, lookups under the read lock). Prefetch and block fetches issue concurrent ReadAsync on the same underlying stream, so that stream must be thread-safe, as before.

Bundled unrelated to the feature: page_filtered_row_group_reader.cpp changes the up-front value-byte reservation cap from an absolute 16 MiB to 8x the file-bounded compressed chunk size (forged-footer hardening), and retry_waiter_test.cpp is removed (timing-based).

Tests

  • UT read_ahead_cache_test.cpp (TestReadAheadCache): new AddRanges cases TestAddRangesRegistersNewRanges, TestAddRangesClipsIntersectingRange (only the uncovered part is registered, the overlap dropped), TestAddRangesDropsCoveredRange, TestAddRangesInterleavesWithRegisteredRanges (result stays offset-ordered and disjoint), TestAddRangesSplitsLargeRange (cut at range_size_limit into concurrent IOs), TestAddRangesKeepsCachedRanges (published entries are not fetched twice), TestWarmupFromOffset and TestCoalescesSmallHoles (hole_size_limit), alongside the existing no-eviction, metrics, IO-error-propagation, in-flight-racing-reader, pre-buffer-window and block-cache cases.
  • UT late_materializing_file_batch_reader_test.cpp (5 new): PayloadPreBufferRangesReportedToCallback, NoCallbackReportWithoutLateMaterialization, EmptyMatchDoesNotReportPayloadRanges, PayloadPreBufferRangeErrorFailsRead, and PrefetchInnerRegistersPayloadPreBufferRanges (end to end under the prefetch layer: probe ranges registered up front plus payload ranges registered mid-read reach the shared cache and are fetched).
  • IT read_inte_test.cpp::TestReadAheadCacheMetrics: asserts the read/hit/miss/io counters are exposed and hits > 0 exactly when a cache is created, absent otherwise.
  • Test infra: MockFileBatchReader gained per-schema pre-buffer ranges and an injectable PreBufferRange() status; MockInputStream::ReadAsync completes inline so a cache-attached promise is always resolved instead of hanging reads/ReleaseBuffers(). The adaptive-sizer cases in byte_range_combiner_test.cpp were removed together with the adaptive sizer.

Validation run (release build): paimon-common-test 1637 passed / 2 skipped; TestReadAheadCache.* + *LateMaterializing* 48/48; paimon-read-inte-test *ReadAheadCache* 8/8; paimon-parquet-format-test *Cache*:*PreBuffer*:*ReadHints* 10/10; git diff --check clean.

API and Format

New public API surface; no storage format or protocol changes:

  • include/paimon/reader/prefetch_file_batch_reader.h: the PreBufferRangeCallback type alias and virtual void SetPreBufferRangeCallback(PreBufferRangeCallback) (default no-op).
  • include/paimon/utils/prefetch_cache_config.h: no new knobs; two defaults change - range_size_limit 32 MiB -> 8 MiB and hole_size_limit 8 KiB -> 512 KiB (behavior, not signature).

ReadAheadCache::AddRanges / Warmup(uint64_t) and the counter names live in the internal header src/paimon/common/utils/read_ahead_cache.h (ReadAheadCache is internal, not under include/).

Documentation

No new user-facing configuration option; the read-ahead cache stays enabled by default with no option to set, and the two CacheConfig defaults are internal tuning. Prefetch behavior is covered by the existing docs/source/user_guide/prefetch.rst. No documentation changes required.

Generative AI tooling

Generated-by: Qoder

Comment thread include/paimon/utils/prefetch_cache_config.h
Comment thread src/paimon/format/parquet/page_filtered_row_group_reader.cpp Outdated
Comment thread src/paimon/format/parquet/page_filtered_row_group_reader.cpp Outdated
Comment thread include/paimon/reader/prefetch_file_batch_reader.h

@lxy-9602 lxy-9602 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

@lxy-9602
lxy-9602 merged commit b8b8cfe into apache:main Sep 17, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Prefetch the late-materialization payload ranges instead of reading them on demand

2 participants