lucasfang opened a new pull request, #342: URL: https://github.com/apache/paimon-cpp/pull/342
### 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. - `ReadAheadCache::AddRanges(ranges, expected_round)` registers ranges into an already-initialized cache and is safe to call repeatedly and concurrently with `Read()`. It merges the new ranges into the disjoint, offset-ordered pending list, registering only the parts no registered range covers and dropping the overlap (that round is already fetching those bytes), and rebuilds the per-range cached flags so an already-fetched range is not fetched twice. `PreBuffer()` was folded to select, mark, publish and dispatch under one write lock, since `AddRanges()` now rewrites `pending_ranges_`/`is_cached_` from another thread; this also removes the previous unsynchronized read of `pending_ranges_` in `Warmup()`. - The registered part is cut at a new `CacheConfig::late_range_size_limit` (default 8 MiB, smaller than the 32 MiB `range_size_limit`) so a large pass is fetched by several concurrent requests rather than one long one; `Warmup(from_offset)` starts fetching from the first newly-registered range instead of from the head. - A registration round bounds the lifetime: every `Init()` opens a round identified by `RegistrationRound()`, and `AddRanges()` drops everything when `expected_round` is not the open round, so a pass that outlived its generation — cache reset for a new read-range generation, or released by `Close()` — registers nothing instead of prefetching bytes nobody reads. The round counter is monotonic across `Reset()` so a stale round is never mistaken for a new one. - New `PrefetchFileBatchReader::PreBufferSink` + `SetPreBufferSink()` (default no-op): `PrefetchFileBatchReaderImpl` installs a sink on each sub-reader that tags the reported ranges with the current round, calls `AddRanges`, and warms up from the first new range; the sink is detached in `Close()` and in the destructor after the background thread is joined. `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. - New metrics `read-ahead-cache.late.registered` / `.registered-bytes` / `.dropped` / `.dropped-bytes`, counted after coalescing and splitting so `registered-bytes` and `dropped-bytes` together account for every reported byte. Risk / known limits: the sink captures the impl raw, which is safe because it owns the sub-readers and `CleanUp()` joins the background thread before the sinks are cleared; the round tag plus the cache's own round check make a late report that races a reset a no-op rather than a stale registration. ### Tests - UT `read_ahead_cache_test.cpp` (10 new `TestReadAheadCache` cases): `TestAddRangesRegistersNewRanges`, `TestAddRangesClipsIntersectingRange` (only the uncovered part is registered, the overlap dropped), `TestAddRangesDropsCoveredRange`, `TestAddRangesInterleavesWithRegisteredRanges` (result stays offset-ordered and disjoint), `TestAddRangesSplitsLateRanges` (cut at `late_range_size_limit` into concurrent IOs), `TestAddRangesKeepsCachedRanges` (flags rebuilt, no double fetch), `TestAddRangesOnUninitializedCacheIsNoop`, `TestAddRangesOfEndedRoundIsNoop`, `TestAddRangesAfterReleaseBuffersIsNoop`, and `TestWarmupFromOffset`. - UT `late_materializing_file_batch_reader_test.cpp` (5 new cases): `PayloadPreBufferRangesReportedToSink` (reported while the first payload batch is still assembling), `NoSinkReportWithoutLateMaterialization`, `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, asserted via the IO and late-registration counters). - IT `read_inte_test.cpp::TestReadAheadCacheMetrics`: extended to assert the four late counters are exposed and stay at zero for a scan with no late-materialization payload pass. - Test infra: `MockFileBatchReader` gained per-schema pre-buffer ranges and an injectable `PreBufferRange()` status; `MockInputStream::ReadAsync` now completes inline so a cache-attached promise is always resolved instead of hanging `ReleaseBuffers()`/reads. Validation run (Debug build): `paimon-common-test` filtered to `TestReadAheadCache.*:LateMaterializingFileBatchReaderTest.*` 51/51, `paimon-read-inte-test` filtered to `*TestReadAheadCacheMetrics*` 8/8; `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 `PreBufferSink` type alias and `virtual void SetPreBufferSink(PreBufferSink)` (default no-op). - `include/paimon/utils/prefetch_cache_config.h`: `CacheConfig::GetLateRangeSizeLimit()` / `SetLateRangeSizeLimit()` (default 8 MiB). `ReadAheadCache::AddRanges` / `RegistrationRound` / `Warmup(uint64_t)` and the new `ReadAheadCacheMetrics` counter names live in the internal header `src/paimon/common/utils/read_ahead_cache.h`. ### Documentation No new user-facing configuration option; `late_range_size_limit` is a `CacheConfig` tuning knob with a working default and the read-ahead cache stays enabled by default. Prefetch behavior is covered by the existing `docs/source/user_guide/prefetch.rst`. No documentation changes required. ### Generative AI tooling Generated-by: Qoder -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
