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]

Reply via email to