Repository navigation
[opt](file cache) Add PageIO read-ahead, I/O coalescing, and cache block hole filling - #67292
Conversation
|
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
7048235 to
a5d11e4
Compare
a5d11e4 to
aeb3178
Compare
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: Read-ahead and background hole filling both need to combine sparse logical ranges into bounded physical reads. Add a pure one-pass coalescer whose contract requires validated options and sorted, disjoint, non-empty inputs. Each merge checks the maximum gap, resulting span, and cumulative read amplification. The range-size limit controls merging; an indivisible input larger than the limit remains one read. ### Release note None ### Check List (For Author) - Test: Unit Test - ./run-be-ut.sh -j100 --run '--filter=FileRangeCoalescerTest.*' - Behavior changed: No; this commit adds an internal planning primitive. - Does this need documentation: No
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: Read-ahead needs to coalesce sorted page ranges and opportunistically complete highly covered File Cache blocks without repeatedly rebuilding the whole plan. Add a planner that considers only the two boundary blocks of each base range, orders candidates by additional bytes, updates affected interval components, and produces compact input-to-buffer mappings. Its complexity is O(n log n), and an oversized atomic input remains standalone without preventing unrelated blocks from being completed. ### Release note None ### Check List (For Author) - Test: Unit Test - ./run-be-ut.sh -j100 --run '--filter=FileRangePlannerTest.*' - Behavior changed: No; this commit adds an internal range planner. - Does this need documentation: No
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: Scanner batches expose too little future IO for efficient remote reads. Add a per-physical-column byte window that always includes current-batch pages, advances through the final scan rowids in either direction, replenishes at configurable watermarks, and retires consumed, skipped, or fallback pages without coupling the component to cache lookup or IO execution. Validate watermarks through the option type and use checked rowid conversion when querying Roaring ranks. ### Release note None ### Check List (For Author) - Test: Unit Test - ./run-be-ut.sh -j100 --run '--filter=ColumnReadAheadTest.*' - Behavior changed: No; this commit adds an internal read-ahead window. - Does this need documentation: No
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: Column byte windows need physical ordinal-index ranges and a safe page-decoding lifecycle. Build compressed page candidates from each FileColumnIterator ordinal index, expose prepared physical-column plans, consume planned pages only after complete decoding succeeds, and retry tracked read-ahead or page-cache decode failures through the original reader while preserving existing corruption recovery. ### Release note None ### Check List (For Author) - Test: Unit Test - ./run-be-ut.sh -j100 --run '--filter=ColumnReaderTest.FileColumnIteratorPreparesCompressedPageByteWindow:ColumnReaderTest.FileColumnIteratorConsumesSubmittedReadAheadPage:ColumnReaderTest.FileColumnIteratorFallsBackAfterReadAheadChecksumFailure:SegmentReadAheadTest.*:ColumnReadAheadTest.*' - Behavior changed: No; this commit connects internal components before production query integration. - Does this need documentation: No
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: Partial query ranges can cover unordered and overlapping fragments inside one File Cache block. Add a block-local planner that unions existing coverage, takes its complement to produce sorted holes, and applies the dedicated three-parameter coalescing policy without crossing the block boundary. The coalescing size limit controls combining holes; one mandatory hole may be larger and remains one remote read. ### Release note None ### Check List (For Author) - Test: Unit Test - ./run-be-ut.sh -j100 --run '--filter=HoleFillPlannerTest.*' - Behavior changed: No; this commit adds an internal hole-fill planner. - Does this need documentation: No
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: Query read-ahead can produce partially covered File Cache blocks that need bounded asynchronous completion before entering the existing cache write path. Add a BE-level manager that merges queued fragments, deduplicates active blocks, evicts the oldest queued block under pressure, waits for Phase 1 spare capacity, fills block-local holes through a dedicated remote-read pool, and hands completed buffers to Phase 1 without another copy. The hole-fill range limit governs coalescing and may be smaller than one cache block because a mandatory single hole is still readable. ### Release note None ### Check List (For Author) - Test: Unit Test - ./run-be-ut.sh -j100 --run '--filter=PartialBlockWritebackManagerTest.*:PartialBlockWritebackOptionsTest.*:AsyncCacheWriteManagerTest.ReportsOnlyUnusedPendingCapacity' - Behavior changed: No; this commit adds an unwired internal component. - Does this need documentation: No
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: Background partial-block writeback needs production defaults and a BE-owned lifetime that outlives query read-ahead work while remaining inside the File Cache lifetime. Add validated hole-fill configuration, create the manager after File Cache initialization, and stop it before File Cache teardown. Keep option invariants with PartialBlockWritebackOptions::validate(), including independent block size and hole-coalescing range limits. ### Release note None ### Check List (For Author) - Test: Unit Test - ./run-be-ut.sh -j100 --run '--filter=PartialBlockWritebackOptionsTest.*' - Behavior changed: No; this commit initializes an internal component that is connected to query writeback later. - Does this need documentation: No
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: Segment read-ahead must establish the range planner contract while preserving shared physical pages and cache invalidation state. Sort cache-miss pages at the segment boundary, collapse exact duplicate ranges while retaining all column owners, submit compact FileRange plans, and capture a per-submission consumer immediately before asynchronous IO. Only accepted and consumed ranges retain and invoke that consumer. ### Release note None ### Check List (For Author) - Test: Unit Test - ./run-be-ut.sh -j100 --run '--filter=SegmentReadAheadTest.*:ColumnReaderTest.FileColumnIteratorConsumesSubmittedReadAheadPage:ColumnReaderTest.FileColumnIteratorFallsBackAfterReadAheadChecksumFailure' - Behavior changed: No; production writeback is connected by a later commit. - Does this need documentation: No
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: Scanner batches expose too little future IO for efficient remote reads. Add a per-physical-column byte window that always includes current-batch pages, advances through the final scan rowids in either direction, replenishes at configurable watermarks, and retires consumed, skipped, or fallback pages without coupling the component to cache lookup or IO execution. Validate watermarks through the option type and use checked rowid conversion when querying Roaring ranks. ### Release note None ### Check List (For Author) - Test: Unit Test - ./run-be-ut.sh -j100 --run '--filter=ColumnReadAheadTest.*' - Behavior changed: No; this commit adds an internal read-ahead window. - Does this need documentation: No
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: Column byte windows need physical ordinal-index ranges and a safe page-decoding lifecycle. Build compressed page candidates from each FileColumnIterator ordinal index, expose prepared physical-column plans, consume planned pages only after complete decoding succeeds, and retry tracked read-ahead or page-cache decode failures through the original reader while preserving existing corruption recovery. ### Release note None ### Check List (For Author) - Test: Unit Test - ./run-be-ut.sh -j100 --run '--filter=ColumnReaderTest.FileColumnIteratorPreparesCompressedPageByteWindow:ColumnReaderTest.FileColumnIteratorConsumesSubmittedReadAheadPage:ColumnReaderTest.FileColumnIteratorFallsBackAfterReadAheadChecksumFailure:SegmentReadAheadTest.*:ColumnReadAheadTest.*' - Behavior changed: No; this commit connects internal components before production query integration. - Does this need documentation: No
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: Partial query ranges can cover unordered and overlapping fragments inside one File Cache block. Add a block-local planner that unions existing coverage, takes its complement to produce sorted holes, and applies the dedicated three-parameter coalescing policy without crossing the block boundary. The coalescing size limit controls combining holes; one mandatory hole may be larger and remains one remote read. ### Release note None ### Check List (For Author) - Test: Unit Test - ./run-be-ut.sh -j100 --run '--filter=HoleFillPlannerTest.*' - Behavior changed: No; this commit adds an internal hole-fill planner. - Does this need documentation: No
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: Query read-ahead can produce partially covered File Cache blocks that need bounded asynchronous completion before entering the existing cache write path. Add a BE-level manager that merges queued fragments, deduplicates active blocks, evicts the oldest queued block under pressure, waits for Phase 1 spare capacity, fills block-local holes through a dedicated remote-read pool, and hands completed buffers to Phase 1 without another copy. The hole-fill range limit governs coalescing and may be smaller than one cache block because a mandatory single hole is still readable. ### Release note None ### Check List (For Author) - Test: Unit Test - ./run-be-ut.sh -j100 --run '--filter=PartialBlockWritebackManagerTest.*:PartialBlockWritebackOptionsTest.*:AsyncCacheWriteManagerTest.ReportsOnlyUnusedPendingCapacity' - Behavior changed: No; this commit adds an unwired internal component. - Does this need documentation: No
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: Background partial-block writeback needs production defaults and a BE-owned lifetime that outlives query read-ahead work while remaining inside the File Cache lifetime. Add validated hole-fill configuration, create the manager after File Cache initialization, and stop it before File Cache teardown. Keep option invariants with PartialBlockWritebackOptions::validate(), including independent block size and hole-coalescing range limits. ### Release note None ### Check List (For Author) - Test: Unit Test - ./run-be-ut.sh -j100 --run '--filter=PartialBlockWritebackOptionsTest.*' - Behavior changed: No; this commit initializes an internal component that is connected to query writeback later. - Does this need documentation: No
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: Segment read-ahead must establish the range planner contract while preserving shared physical pages and cache invalidation state. Sort cache-miss pages at the segment boundary, collapse exact duplicate ranges while retaining all column owners, submit compact FileRange plans, and capture a per-submission consumer immediately before asynchronous IO. Only accepted and consumed ranges retain and invoke that consumer. ### Release note None ### Check List (For Author) - Test: Unit Test - ./run-be-ut.sh -j100 --run '--filter=SegmentReadAheadTest.*:ColumnReaderTest.FileColumnIteratorConsumesSubmittedReadAheadPage:ColumnReaderTest.FileColumnIteratorFallsBackAfterReadAheadChecksumFailure' - Behavior changed: No; production writeback is connected by a later commit. - Does this need documentation: No
55ff2ba to
64fe07a
Compare
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: Asynchronous read-ahead ranges must preserve their coalesced byte boundaries and avoid populating File Cache before consumption is known. Route per-read NO_WRITE requests through CachedRemoteFileReader's existing remote-only-on-cache-miss path: a fully cached range is served locally, while incomplete coverage issues one exact remote read for the entire planned range without creating cache cells, waiting for downloading blocks, reading from peers, or submitting cache writes. Keep cache-write mode resolution in the main reader dispatch because it selects among all read paths rather than belonging to the asynchronous cache-write implementation. ### Release note None ### Check List (For Author) - Test: Unit Test and BE build - ./run-be-ut.sh -j100 --run '--filter=AsyncCachedRemoteFileReaderTest.exact_no_write_*:BlockFileCacheTest.cached_remote_file_reader_remote_only_on_miss:BlockFileCacheTest.get_downloaded_blocks_if_fully_covered_is_read_only:BlockFileCacheTest.cache_write_mode_is_resolved_for_each_read_context:AsyncFileRangeReaderTest.*:PartialBlockWritebackManagerTest.*' (29 passed) - ./build.sh --be -j100 - Behavior changed: Yes; no-write asynchronous ranges reuse full cache hits and otherwise issue one exact remote read for the planned range. - Does this need documentation: Yes; covered by the Phase 2 design document.
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: Scanner batches expose too little future IO for efficient remote reads. Add a per-physical-column byte window that always includes current-batch pages, advances through the final scan rowids in either direction, replenishes at configurable watermarks, and retires consumed, skipped, or fallback pages without coupling the component to cache lookup or IO execution. Validate watermarks through the option type and use checked rowid conversion when querying Roaring ranks. ### Release note None ### Check List (For Author) - Test: Unit Test - ./run-be-ut.sh -j100 --run '--filter=ColumnReadAheadTest.*' - Behavior changed: No; this commit adds an internal read-ahead window. - Does this need documentation: No
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: Column byte windows need physical ordinal-index ranges and a safe page-decoding lifecycle. Build compressed page candidates from each FileColumnIterator ordinal index, expose prepared physical-column plans, consume planned pages only after complete decoding succeeds, and retry tracked read-ahead or page-cache decode failures through the original reader while preserving existing corruption recovery. ### Release note None ### Check List (For Author) - Test: Unit Test - ./run-be-ut.sh -j100 --run '--filter=ColumnReaderTest.FileColumnIteratorPreparesCompressedPageByteWindow:ColumnReaderTest.FileColumnIteratorConsumesSubmittedReadAheadPage:ColumnReaderTest.FileColumnIteratorFallsBackAfterReadAheadChecksumFailure:SegmentReadAheadTest.*:ColumnReadAheadTest.*' - Behavior changed: No; this commit connects internal components before production query integration. - Does this need documentation: No
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: Partial query ranges can cover unordered and overlapping fragments inside one File Cache block. Add a block-local planner that unions existing coverage, takes its complement to produce sorted holes, and applies the dedicated three-parameter coalescing policy without crossing the block boundary. The coalescing size limit controls combining holes; one mandatory hole may be larger and remains one remote read. ### Release note None ### Check List (For Author) - Test: Unit Test - ./run-be-ut.sh -j100 --run '--filter=HoleFillPlannerTest.*' - Behavior changed: No; this commit adds an internal hole-fill planner. - Does this need documentation: No
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: Query read-ahead can produce partially covered File Cache blocks that need bounded asynchronous completion before entering the existing cache write path. Add a BE-level manager that merges queued fragments, deduplicates active blocks, evicts the oldest queued block under pressure, waits for Phase 1 spare capacity, fills block-local holes through a dedicated remote-read pool, and hands completed buffers to Phase 1 without another copy. The hole-fill range limit governs coalescing and may be smaller than one cache block because a mandatory single hole is still readable. ### Release note None ### Check List (For Author) - Test: Unit Test - ./run-be-ut.sh -j100 --run '--filter=PartialBlockWritebackManagerTest.*:PartialBlockWritebackOptionsTest.*:AsyncCacheWriteManagerTest.ReportsOnlyUnusedPendingCapacity' - Behavior changed: No; this commit adds an unwired internal component. - Does this need documentation: No
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: Background partial-block writeback needs production defaults and a BE-owned lifetime that outlives query read-ahead work while remaining inside the File Cache lifetime. Add validated hole-fill configuration, create the manager after File Cache initialization, and stop it before File Cache teardown. Keep option invariants with PartialBlockWritebackOptions::validate(), including independent block size and hole-coalescing range limits. ### Release note None ### Check List (For Author) - Test: Unit Test - ./run-be-ut.sh -j100 --run '--filter=PartialBlockWritebackOptionsTest.*' - Behavior changed: No; this commit initializes an internal component that is connected to query writeback later. - Does this need documentation: No
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: Segment read-ahead must establish the range planner contract while preserving shared physical pages and cache invalidation state. Sort cache-miss pages at the segment boundary, collapse exact duplicate ranges while retaining all column owners, submit compact FileRange plans, and capture a per-submission consumer immediately before asynchronous IO. Only accepted and consumed ranges retain and invoke that consumer. ### Release note None ### Check List (For Author) - Test: Unit Test - ./run-be-ut.sh -j100 --run '--filter=SegmentReadAheadTest.*:ColumnReaderTest.FileColumnIteratorConsumesSubmittedReadAheadPage:ColumnReaderTest.FileColumnIteratorFallsBackAfterReadAheadChecksumFailure' - Behavior changed: No; production writeback is connected by a later commit. - Does this need documentation: No
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: The initial query read-ahead implementation spread execution, page lifecycle, and submission state across several adapters and state machines. Rename the BE-global component to FileRangeReadScheduler, flatten its ownership and synchronization while executing work on SegmentPrefetchThreadPool, reduce column windows to pending-byte accounting, and make SegmentReadAhead the single cache-probe, coalescing, and submission boundary. SegmentReadAheadFileReader now retires pages at successful buffer reads, and background hole fill reuses its already-owned IO context. ### Release note None ### Check List (For Author) - Test: Unit Test - 61 focused read-ahead and writeback tests with ./run-be-ut.sh -j100 - 15 CachedRemoteFileReader and file-cache tests with ./run-be-ut.sh -j100 - ./build.sh --be -j100 (Release) - build-support/check-format.sh - build-support/check-build-hygiene.sh - Behavior changed: Yes; successful prefetched bytes are consumed at the exact PageIO read, while later page decode failures retry the original reader. - Does this need documentation: No
29d8551 to
6954c33
Compare
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: Read-ahead and background hole filling both need to combine sparse logical ranges into bounded physical reads. Add a pure one-pass coalescer whose contract requires validated options and sorted, disjoint, non-empty inputs. Each merge checks the maximum gap, resulting span, and cumulative read amplification. The range-size limit controls merging; an indivisible input larger than the limit remains one read. ### Release note None ### Check List (For Author) - Test: Unit Test - ./run-be-ut.sh -j100 --run '--filter=FileRangeCoalescerTest.*' - Behavior changed: No; this commit adds an internal planning primitive. - Does this need documentation: No
### What problem does this PR solve? Issue Number: Part of apache#67611 Related PR: apache#67579, apache#67292 Problem Summary: Prepare eligible top-level scalar pages once after pruning and advance scan windows at the data-page read entry before Page Cache lookup. Scalar point reads retain exact-row preparation. Report initialization and page-advancement time, then let ColumnReadAheadPlan accumulate its maintenance sub-timers. Preserve existing page decoding, cache hits and synchronous fallback. ARRAY/MAP/STRUCT, including offsets, null maps and nested children, keep the upstream reader implementation. Their children may have independent predicate/lazy ordinals, so this component adds no dependent-item preparation or cursor-equality assertion. Cover the nested STRUCT/MAP lazy-read crash and complex-column preparation boundary. ### Release note Connect top-level scalar data-page reads to byte-window read-ahead. ### Check List (For Author) - Test: Unit Test — 134 tests from 4 suites passed on the integrated apache#67579 tree with ./run-be-ut.sh -j100 --run (ASAN/PCH, no clean). Repository clang-format 16, header hygiene and git diff --check passed. No cluster or clang-tidy run. This intermediate component commit was not built separately. - Behavior changed: Yes — introduces opt-in scalar read-ahead while preserving complex-column behavior. - Does this need documentation: Yes — scope and synchronization are recorded in apache#67611 and the related PR descriptions.
### What problem does this PR solve? Issue Number: Part of apache#67611 Related PR: apache#67579, apache#67292 Problem Summary: Initialize eager and lazy top-level scalar windows together after segment pruning instead of rebuilding rowid plans every scanner batch. Later physical page reads advance each window. Select the reader per column: eligible scalars use the read-ahead wrapper; complex and extracted variant columns retain the original reader and segment prefetchers. Keep reverse-scan behavior and configure compressed-byte windows, coalescing limits and optional foreground block completion. Test mixed scalar/complex planning and legacy prefetch initialization with the new context enabled or absent. ### Release note Opt-in regular segment scans use page-driven read-ahead across scanner batches for top-level scalar columns. ### Check List (For Author) - Test: Unit Test — 134 tests from 4 suites passed on the integrated apache#67579 tree with ./run-be-ut.sh -j100 --run (ASAN/PCH, no clean). Repository clang-format 16, header hygiene and git diff --check passed. No cluster or clang-tidy run. This intermediate component commit was not built separately. - Behavior changed: Yes — introduces opt-in scalar scan initialization with a 4 MiB eager window and a 1 MiB lazy window; complex columns retain their existing read and prefetch path. - Does this need documentation: Yes — scope and synchronization are recorded in apache#67611 and the related PR descriptions.
### What problem does this PR solve? Issue Number: apache#67611 (tracking) Related PR: apache#67292, apache#67579, apache#67612 Problem Summary: Expose complete-block submission, tracked owned-buffer handoff, and low-priority spare-capacity admission together as one cache-writer component. Reuse existing epoch checks and asynchronous disk writes. Background hole filling can pass a completed block without another data copy while normal foreground cache writes keep priority. ### Release note Internal complete-block and zero-copy handoff for best-effort cache population. ### Check List (For Author) - Test: Unit Test — component coverage is included with this change. The integrated source snapshot d5c427f passed 198 ASAN tests from 11 suites via run-be-ut.sh -j100. This individual intermediate commit was not built separately; target-branch validation is recorded on the PR. Repository clang-format v16, build hygiene and diff checks passed on the integrated tree. No cluster or clang-tidy run. - Behavior changed: Yes — introduces the opt-in component; this synchronization preserves existing defaults and IO/writeback semantics. - Does this need documentation: Yes — component scope and current behavior are tracked in apache#67611 and the related PR descriptions.
### What problem does this PR solve?
Issue Number: None
Related PR: None
Problem Summary: Query read-ahead produces variable file ranges, while the existing asynchronous File Cache writer accepts complete fixed-size blocks. Add a synchronous ownership-transfer boundary that splits an already consumed range at cache-block boundaries, routes complete blocks and partial fragments to separate consumers, and handles the short physical EOF block as complete. The dispatcher reports generated and accepted fragments without retaining the query range buffer.
### Release note
None
### Check List (For Author)
- Test: Unit Test
- ./run-be-ut.sh -j100 --run '--filter=RangeWritebackDispatcherTest.*'
- Behavior changed: No
- Does this need documentation: No
### What problem does this PR solve? Issue Number: None Related PR: apache#67292 Problem Summary: Partial query ranges can cover unordered and overlapping fragments inside one File Cache block. Add a block-local planner that unions existing coverage, takes its complement to produce sorted holes, and applies the dedicated three-parameter coalescing policy without crossing the block boundary. The coalescing size limit controls combining holes; one mandatory hole may be larger and remains one remote read. ### Release note None ### Check List (For Author) - Test: Unit Test - ./run-be-ut.sh -j100 --run '--filter=HoleFillPlannerTest.*' - Behavior changed: No; this commit adds an internal hole-fill planner. - Does this need documentation: No
### What problem does this PR solve? Issue Number: Part of apache#67611 Related PR: apache#67292, apache#67579 Problem Summary: Partial cache blocks need bounded background completion without stalling query reads. Keep queued fragments mergeable under a configurable aggregation delay and a BE byte budget. One HoleFillDispatch thread owns admission-time-ordered deadline and writer-capacity checks, stops at the first future deadline, bypasses busy writers for other due tasks, and hands off at most the configured block-worker slots. Workers execute ready tasks with reusable remote-read tokens: one hole range is read inline and multiple ranges use the shared remote pool. Retain cache/inflight reuse, epoch checks, eviction, dynamic worker/pool/delay updates, and complete-block handoff. Trace fragment lifecycles and submission/queue timings with serialization outside the queue mutex. Cancel queued and undelivered work at shutdown and drain executing reads before destroying the remote pool. ### Release note Add opt-in background cache-block hole filling with queued-fragment aggregation, dedicated dispatch, bounded worker handoff and parallel hole reads. ### Check List (For Author) - Test: Unit Test - Component tests are included with the implementation; rebase and synchronization validation is recorded in the related PRs and tracking issue. - Repository clang-format 16, header hygiene and git diff --check passed before folding. - No cluster, external-object-store benchmark or clang-tidy run during synchronization. - Behavior changed: Yes, includes final component behavior and applicable diagnostic instrumentation; default enable switches remain off. - Does this need documentation: Yes, tools/read_io_trace/README.md and related PR/tracking descriptions.
### What problem does this PR solve? Issue Number: apache#67611 (tracking) Related PR: apache#67292 Problem Summary: Background partial-block writeback needs production defaults and a BE-owned lifetime bounded by the File Cache lifecycle. Add validated hole-fill configuration, create the manager after File Cache initialization, and stop it before File Cache teardown. Configure a 4 GiB BE-wide queued/active buffer budget and a 1.0 hole-read amplification limit. Use hole_fill_workers_per_be for block workers (default 32, range 1 through 128) and hole_fill_remote_read_threads_per_be for the shared multi-range remote-read pool (default 64, positive values). Both settings resize their pools at runtime without rebuilding the manager. Single-range reads execute on block workers outside the remote-read pool limit. Add mutable hole_fill_merge_delay_ms with a 2000 ms default and nonnegative validation. Positive values delay activation from the first admission timestamp; dynamic updates apply to already queued tasks without resetting that timestamp. ### Release note Support runtime resizing of BE-wide block workers and the shared multi-range remote-read pool. Configure minimum queued-fragment aggregation time through hole_fill_merge_delay_ms; its 2000 ms default keeps queued fragments mergeable before dispatch, and setting it to 0 disables deliberate waiting. Limit queued/active partial-block buffers to 4 GiB per BE and use a 1.0 background read-amplification limit. ### Check List (For Author) - Test: Unit Test - Existing PartialBlockWritebackOptionsTest coverage via run-be-ut.sh -j100 with ASAN/PCH; no new tests for the default-value update. - Behavior changed: Yes; configure background hole filling, resize its pools at runtime, and adjust queued partial-block aggregation delay. - Does this need documentation: Yes; configuration and execution contracts are tracked in apache#67611 and apache#67292.
### What problem does this PR solve? Issue Number: Part of apache#67611 Related PR: apache#67292, apache#67579 Problem Summary: Consumed read-ahead buffers can span complete and partial cache blocks. Split each range, submit complete blocks to the existing asynchronous cache writer, and enqueue partial fragments for background completion under the captured invalidation epoch. Preserve unified cache/inflight reuse and mixed outcomes. Carry the originating read identity into partial tasks and interval traces, and separate complete/partial foreground dispatch time in Profile counters. Read and persistence outcomes remain best effort. ### Release note None ### Check List (For Author) - Test: Unit Test - Component tests are included with the implementation; rebase and synchronization validation is recorded in the related PRs and tracking issue. - Repository clang-format 16, header hygiene and git diff --check passed before folding. - No cluster, external-object-store benchmark or clang-tidy run during synchronization. - Behavior changed: Yes, includes final component behavior and applicable diagnostic instrumentation; default enable switches remain off. - Does this need documentation: Yes, tools/read_io_trace/README.md and related PR/tracking descriptions.
### What problem does this PR solve? Issue Number: Part of apache#67611 Related PR: apache#67292, apache#67579 Problem Summary: Centralize query read-ahead construction so regular scans and exact-row callers share range planning, byte windows, cache probes, scheduler ownership and writeback setup. Merge per-read IO statistics into caller accounting and publish each consumed range once. Submit writeback only for consumed ranges that read remote bytes, preserving no-write policy and avoiding redundant hole filling for File Cache or inflight hits. Carry the read identity through the consumed-range callback so offline traces can attribute background work to its foreground source. Require both query read-ahead and asynchronous cache writes so disabling the writer preserves synchronous cache filling. Handle a switch changing during scan initialization through the original reader. Keep delayed hole-fill sources open with an aliasing shared_ptr that retains the cached wrapper; regression tests release both the foreground reader and writeback context before completing the queued fill. ### Release note None ### Check List (For Author) - Test: Unit Test - Component tests are included with the implementation; rebase and synchronization validation is recorded in the related PRs and tracking issue. - Repository clang-format 16, header hygiene and git diff --check passed before folding. - No cluster, external-object-store benchmark or clang-tidy run during synchronization. - Behavior changed: Yes, includes final component behavior and applicable diagnostic instrumentation; default enable switches remain off. - Does this need documentation: Yes, tools/read_io_trace/README.md and related PR/tracking descriptions.
### What problem does this PR solve? Issue Number: None Related PR: apache#67579, apache#67292 Problem Summary: Short-circuit point queries read row-store data or missing column-store values through exact row IDs. Create one request-scoped range-read context, initialize the scalar row-store or missing top-level scalar iterators against the shared read-ahead reader, and submit the required pages before decoding. Complex columns and extracted variant paths retain Segment's original iterator/reader resolution. Preserve row-cache behavior, result padding, request-scoped constants and synchronous fallback. Append read-ahead Profile statistics to point-query diagnostics; BE cumulative counters retain unsampled totals. Include exact-row helpers and tests in this component. TopN second-stage integration remains identical to the master base and is outside this PR series. Handle a dynamic switch closing after a point request creates its context: if create_for_query returns no read-ahead object, read row-store data synchronously through the already loaded segment. The deterministic row-store test closes either enable switch after segment resolution, verifies that no segment-loader call is made, and compares the result with the original reader. Rebase compatibility: reuse the segment returned by primary-key lookup in both the read-ahead and synchronous row-store paths, preserving master's segment and acquired-rowset lifetime. ### Release note Use query range read-ahead for scalar point-query data pages when enable_query_read_ahead and enable_async_file_cache_write are enabled. ### Check List (For Author) - Test: Unit Test — pre-rebase integrated ASAN/PCH validation covered 225 tests from 10 suites through ./run-be-ut.sh --run -j100. Includes partial budget admission, original page offsets, cancellation, allocation rollback, executor rejection, consumed-range callbacks and row-store switch fallback. Individual intermediate commits are not built separately; target-branch validation is recorded on the PR. Clang-format 16, header hygiene and diff checks pass. No cluster or clang-tidy run. - Behavior changed: Yes — adds scalar point-query prefetch without changing complex-column decoding, scheduling limits or query results. - Does this need documentation: Yes — scope and synchronization are recorded in the related active PR descriptions.
### What problem does this PR solve? Issue Number: None Related PR: apache#67292, apache#67579 Problem Summary: File Cache initialization rejects an S3 upload buffer size that is smaller than a cache block or not a multiple of the block size. The buffer size is mutable, but runtime updates did not register the same validation, so a later update could violate the layout assumed by cached reads and hole-fill deduplication. Register the existing alignment constraint as a config validator and cover accepted updates, rejected updates with value rollback, invalid block sizes, and File Cache disabled behavior. Cache probing, read-ahead, and hole-fill algorithms remain unchanged. ### Release note Reject S3 upload buffer configuration updates that violate File Cache block alignment while File Cache is enabled. ### Check List (For Author) - Test: Unit Test — 66 tests from 4 suites pass with ./run-be-ut.sh --run -j100 (ASAN/PCH), including configuration validation and partial-block/async cache writeback. Direct Release/PCH BE build passes with ./build.sh --be -j100. Clang-format 16, build hygiene, and diff checks pass. No cluster or clang-tidy run. - Behavior changed: Yes — invalid runtime S3 upload buffer updates return INVALID_ARGUMENT and retain the previous value; defaults and IO mechanisms are unchanged. - Does this need documentation: No — applies the existing File Cache startup constraint to runtime configuration updates.
### What problem does this PR solve? Issue Number: None Related PR: apache#67292, apache#67579 Problem Summary: Older binaries could persist non-aligned File Cache blocks after a runtime S3 upload-buffer update. These intervals survive cache reopening even after the buffer setting is corrected. Partial-block writeback deduplication used a grid-only cache probe, so encountering a restored irregular interval could abort the BE. Check coverage using the actual cached intervals, accepting contiguous DOWNLOADED or DOWNLOADING blocks without allocating cache cells or changing LRU state. Keep the inflight shortcut and the existing grid probe unchanged. ### Release note Avoid a BE abort during partial-block writeback when File Cache restores historical non-aligned blocks. ### Check List (For Author) - Test: The restored-cache regression test reproduced SIGABRT before the fix. All 92 related tests in 5 suites passed with run-be-ut.sh --run -j100 (ASAN/PCH), including restored irregular cache gaps and mixed downloaded/downloading coverage. A direct Release/PCH BE build passed with build.sh --be -j100. Clang-format 16, build hygiene and git diff --check passed. No cluster or clang-tidy run. - Behavior changed: Yes. Optional writeback deduplication supports actual cached block boundaries, including restored irregular blocks. - Does this need documentation: No. The PR descriptions record this compatibility fix; public configuration semantics are unchanged.
|
/review |
|
Codex automated review failed and did not complete. Error: Selected model is at capacity. Please try a different model. Please inspect the workflow logs and rerun the review after the underlying issue is resolved. |
|
/review |
|
Codex automated review failed and did not complete. Error: Selected model is at capacity. Please try a different model. Please inspect the workflow logs and rerun the review after the underlying issue is resolved. |
|
/review |
There was a problem hiding this comment.
Reviewed PR head b237ebc against the supplied authoritative diff under the static-only contract. The complete changed-file sweep and two-round risk-focused recheck converged with three new P2 findings: stale active partial-writeback tasks can absorb post-invalidation fragments; the new range-coverage helper treats deletion-marked blocks as usable coverage; and deferred hole-fill reads bypass workload-group remote-read throttling and accounting. Existing P1 4344901080 remains applicable and is carried in existing_blocking_comment_ids without duplication. Existing P1s 4278847462, 4278847468, and 4278847472 were revalidated as fixed at this head; existing P2/P3 threads were deduplicated. No builds, tests, or runtime failure injection were run; changed tests were inspected only. No additional user review focus was supplied. Review converged after round 2 with no unresolved candidates.
Review converged after 2 round(s).
Existing P0/P1 findings confirmed for this head: #67292 (comment)
| trace.fragment_lock_wait_ns += locked_at - lock_start; | ||
| Defer hold_time {[&] { trace.fragment_lock_hold_ns += trace.now() - locked_at; }}; | ||
| trace.task_id = io_context.io_context.read_trace_id; | ||
| if (is_active()) { |
There was a problem hiding this comment.
[P2] Do not deduplicate new-epoch fragments into a stale active task
try_merge() returns ACTIVE_DEDUPLICATED for any active task before checking whether its write epoch is still current, and _enqueue_or_get_existing() makes the same active-task assumption. Cache invalidation advances the key epoch while the old task remains in _tasks; _process_task() then drops that stale task. A read after invalidation can therefore be merged into the stale owner and its fragment is never queued for the new epoch, leaving the block uncached until another read. Treat an active task from an old epoch as non-deduplicating (or defer a new-generation task) and add an invalidate-then-read case.
| return ranges; | ||
| } | ||
|
|
||
| bool BlockFileCache::is_range_downloaded_or_downloading(const UInt128Wrapper& hash, size_t offset, |
There was a problem hiding this comment.
[P2] Exclude deleting blocks from writeback coverage
is_range_downloaded_or_downloading() treats a DOWNLOADED block as coverage without checking is_deleting(). Eviction and cache-clear mark the block deleting while it remains visible, so should_skip_block_writeback() can discard the only writeback task; after the block is recycled, those consumed bytes are not cached. Exclude deleting blocks under the canonical cache lock order so a replacement writeback can proceed, and cover an eviction race.
| read_ahead_bvars().hole_fill_remote_requests << 1; | ||
| { | ||
| SCOPED_RAW_TIMER(&read_ns); | ||
| status = source_reader->read_at( |
There was a problem hiding this comment.
[P2] Preserve workload-group accounting for deferred hole fills
_read_hole() performs remote reads on manager and remote-token threads after copying IOContext, but those callbacks never attach the originating ResourceContext. S3FileReader gates LIMIT_REMOTE_SCAN_IO and its workload-group accounting on an attached task, so deferred hole-fill GETs bypass the query's remote-read limit and are absent from group IO accounting. Carry a safe resource-group owner into each callback and attach it before source IO, or route these reads through an explicitly throttled policy, with coverage for single- and multi-hole blocks.
|
/review Please re-evaluate the existing blocking P1 finding: #67292 (comment). This issue has been fixed in the current head,
The fix includes The latest review targeted this fixed head but still carried the original P1 in This focus is additional to the normal review and does not ask to ignore other findings. |
There was a problem hiding this comment.
Static exact-head review of PR 67292 at b237ebc against the supplied authoritative diff and base 2a4191c. I read the required AGENTS.md files, all changed paths, existing threads, user focus, and the shared ledger. Two new P2 findings are posted inline: a partial writeback fragment can merge into a task evicted during the unlocked lookup window, and read-ahead fallback counters include ordinary nested/complex data-page reads that never attempted the wrapper. Existing scheduler callback, trace buffering, workload-group, stale-epoch, deletion-state, worker startup/resize, metadata, dictionary, and cache-budget findings were independently revalidated and left in their existing threads without duplicates. The user-focus P1 is fixed for its reported read-ahead partial-writeback path: should_skip_block_writeback now uses interval-aware coverage and the irregular-cache tests cover the restored-gap case, so existing_blocking_comment_ids is empty. An unchanged direct ASYNC_WRITE reader path still contains a pre-existing grid-only probe, but it is outside the fixed read-ahead writeback call chain and is not reposted here. This was a static-only review; no builds, tests, or runtime injection were run, and changed tests were inspected only. Review converged after 2 rounds with no unresolved candidates.
Review converged after 2 round(s).
| return trace.finish(PartialBlockSubmitResult::REJECTED); | ||
| } | ||
| } | ||
| if (existing != nullptr) { |
There was a problem hiding this comment.
[P2] Do not merge fragments into an evicted partial-block task. try_submit() finds an existing queued task under _mutex, releases that lock, and then calls Task::try_merge() at this line. While the lock is released, another submission can fill _max_pending_tasks and evict this inactive task from both _tasks and _queue. The shared pointer keeps the orphan alive, so its still-current epoch passes and try_merge() copies the fragment and returns MERGED, but no worker can dispatch that task; the caller reports success while the consumed bytes are lost. The same window exists after the EXISTING merge at lines 448-455. Revalidate that the task is still the current queued owner under the manager lock, or retry enqueueing the candidate before accepting the merge.
| #include "runtime/runtime_profile.h" | ||
| #include "storage/cache/page_cache.h" | ||
| #include "storage/olap_common.h" | ||
| #include "storage/segment/encoding_info.h" |
There was a problem hiding this comment.
[P2] Count only actual read-ahead fallbacks. read_ahead_stats is shared by the whole segment, but non-scalar/nested iterators are deliberately left on their original FileReader. When such a DATA_PAGE reaches this branch, the dynamic cast at line 178 returns null, yet the code still increments fallback_pages, fallback_bytes, and fallback_time before the ordinary read. ReadAheadFallback* profile counters therefore report nested reads as failed or rejected read-ahead fallbacks. Gate this accounting on a successful SegmentReadAheadFileReader attempt and add nested-column coverage.
|
run buildall |
TPC-H: Total hot run time: 28261 ms |
TPC-DS: Total hot run time: 151236 ms |
ClickBench: Total hot run time: 24.25 s |
BE UT Coverage ReportIncrement line coverage Increment coverage report
|
BE Regression && UT Coverage ReportIncrement line coverage Increment coverage report
|
What problem does this PR solve?
Issue Number: None
Related PR: #67579 (validation and follow-ups)
Problem Summary:
Doris data pages are commonly much smaller than a 1 MiB File Cache block. On a cache miss, the existing query path expands a page read to the block boundary. This behaves like a fixed 1 MiB read-ahead for continuous scans, but the size and shape cannot follow the actual scan. Sparse point-query data-page reads can therefore download a full block for a small page, while sequential scans still expose one remote page dependency at a time through
FileColumnIterator.This PR adds an opt-in cloud query path that prepares each eligible top-level scalar scan column's compressed data pages once after pruning, advances its window on physical data-page reads, merges nearby pages into bounded file ranges, and serves the original exact page slices to the existing PageIO and decoder. Only a range that PageIO actually consumes becomes eligible for File Cache writeback. Complete cache blocks use the existing asynchronous cache writer; partial blocks enter a bounded background pipeline that fills their holes and then hands a complete block to the same writer.
The implementation covers top-level scalar columns in regular segment scans and short-circuit point queries, including scalar row-store reads. ARRAY/MAP/STRUCT and their offsets, null maps and children retain the original readers and legacy segment prefetchers; variant paths remain on the original path. TopN/global delayed-materialization second-stage read-ahead is outside the current PRs; that path stays identical to master. The feature is disabled by default and requires both
enable_query_read_ahead=trueandenable_async_file_cache_write=true. Planning, admission, asynchronous read, checksum, decode, and best-effort writeback failures preserve the original query result path.Entry paths and component boundaries
flowchart TB SCAN["Regular SegmentIterator scan"] POINT["PointQueryExecutor<br/>row store or missing columns"] SCAN --> COLUMNS["After pruning: initialize scalar column windows once"] POINT --> LOOKUP["Collect exact row IDs and required scalar columns"] COLUMNS --> SEGMENT["SegmentReadAhead"] LOOKUP --> SEGMENT SCAN --> COMPLEX["Complex columns: original reader and prefetch"] POINT --> COMPLEX SEGMENT --> PLAN["FileRangePlanner"] PLAN --> SCHED["FileRangeReadScheduler"] SCHED --> POOL["Existing SegmentPrefetchThreadPool"] POOL --> READER["SegmentReadAheadFileReader"] READER --> PAGEIO["Existing PageIO and decoder"] READER --> WRITEBACK["Consumed-range writeback"]ColumnReadAhead/ColumnReadAheadPlanSegmentReadAheadFileRangePlannerFileRangeReadSchedulerSegmentReadAheadFileReaderRangeWritebackDispatcher/RangeCacheWritebackPartialBlockWritebackManagerRead-ahead window
Columns are classified by dependency: eager columns have no earlier filtering dependency; lazy columns depend on an earlier filter or expression result. After segment pruning, initialize all currently resolvable eager and lazy windows together. Build each column's candidate-page indexes once from the pruned selection. Later data-page reads advance the window using the already resolved physical page index, before Page Cache lookup. Small scanner batches no longer rebuild rowid plans for every column.
Each window selects eligible pages until their compressed bytes reach the configured size; its final page can cross that size. The second selected page triggers the next window, or the only page for a one-page window. First access and a jump beyond planned coverage start at the current page. Reverse scans retain predictions while reading an ascending batch and retire them when the next lower batch begins.
Scalar point reads keep exact-row preparation, grouping sorted row IDs by distinct pages. Complex columns keep the original dependent-child reading path. Page completion updates ownership/accounting; the page-read position drives scan-window extension. Initial planning remains on the scanner thread; no separate planning pool is introduced.
read_ahead_eager_window_bytesread_ahead_lazy_window_bytesflowchart TD Init["After pruning: select candidate pages once<br/>initialize eager and lazy windows together"] --> Read Read["Column reader resolves a physical data page"] --> Advance["Retire passed predictions<br/>include the current page"] Advance --> Outside{"At or beyond planned coverage?"} Outside -- Yes --> Start["Start a byte window at this page<br/>mark its second selected page as the trigger"] Outside -- No --> Trigger Start --> Trigger{"Current page reaches the trigger?"} Trigger -- Yes --> Append["Append the next byte window<br/>move the trigger into it"] Trigger -- No --> Apply Append --> Apply["Submit newly planned pages<br/>reuse existing buffered pages"] Apply --> PageIO["Existing Page Cache / PageIO / decoder"] PageIO --> ReadThe window is a planning unit, not a resident-memory limit; query and BE budgets bound retained range buffers.
ReadAheadColumnInitTimecovers initialization,ReadAheadPageAdvanceTimecovers page-driven maintenance, andReadAheadCurrentBatchPlanTimecovers the retained exact-row path. Window discard and extension remain sub-timers underReadAheadColumnPlanTime.Range planning and concurrent I/O
The range merge uses a left-to-right algorithm inspired by Velox. Here, “merge” means combining nearby requested page intervals into one physical object-store GET.
SegmentReadAheadsupplies sorted, disjoint, non-empty page intervals; one linear pass adds the next interval only when every configured limit remains satisfied.read_ahead_max_gap_bytesread_ahead_max_range_bytesread_ahead_max_read_amplification_ratioAfter the base ranges are formed,
FileRangePlannerexamines their boundary File Cache blocks.read_ahead_block_fill_min_coverage=1.0requires full requested-page coverage, so the default adds no foreground bytes to complete a partial block. Lowering this threshold retains the existing optional completion algorithm within the 2 MiB range limit.flowchart LR PAGES["Sorted unique data-page intervals"] --> MERGE["Linear three-limit merge"] MERGE --> BASE["Base ranges"] BASE --> COVER{"Boundary block coverage<br/>at least 50 percent?"} COVER -->|"Yes and final range at most 2 MiB"| COMPLETE["Extend to block boundary"] COVER -->|No| KEEP["Keep base range"] COMPLETE --> BATCH["One atomic submission batch"] KEEP --> BATCH BATCH -->|Accepted| WORKERS["Concurrent range reads"] BATCH -->|Rejected| FALLBACK["Original synchronous path"]FileRangeReadSchedulerwalks the planned ranges once under both query and BE budget locks, admitting each range that fits and continuing past skipped ranges so later smaller ranges can still proceed. Returned handles preserve the original range indices, with empty slots for budget skips; only admitted ranges receive buffers and tasks. It adds no range-count or concurrency quota. Accepted tasks run on the existing segment-prefetch thread pool, whose configured thread count controls physical read concurrency; the scheduler does not own or stop that shared pool. A byte reservation follows the query-owned buffer lifetime and is released with the last reference.Scheduled reads suppress direct cache writes. A ready range is indexed back to every exact page it contains, so PageIO still receives the original offset and size and keeps the existing checksum and decoder behavior.
sequenceDiagram participant Scan as Scanner participant Column as ColumnReadAhead participant Segment as SegmentReadAhead participant Scheduler as FileRangeReadScheduler participant PageIO Scan->>Column: initialize candidates and first windows after pruning Column->>Segment: eager and lazy initial plans Segment->>Scheduler: page-cache probe, merge, complete blocks, submit ranges loop Physical data-page reads Scan->>Column: resolved page index Column->>Segment: new and released pages when the window changes Segment->>Scheduler: submit newly planned ranges Scan->>PageIO: exact page offset and size PageIO->>Segment: read buffered page Segment-->>PageIO: ready slice or original-reader fallback endPoint queries create one request-scoped range-read context and plan either the row-store column or all missing column-store values before decoding; existing row-cache and result-padding behavior remains unchanged. The point-query component owns the shared exact-row page helpers and their tests.
Consumed-range writeback
A ready range that read remote bytes becomes eligible for writeback when PageIO successfully reads its first registered page, and it is published at most once. Pure File Cache/inflight hits skip writeback. A prefetched range whose pages are all passed without use is released without a cache write.
flowchart TD CONSUMED["Consumed ready range"] --> ORIGIN{"Range read remote bytes?"} ORIGIN -- No --> SKIP["Skip duplicate writeback"] ORIGIN -- Yes --> SPLIT["Split at 1 MiB File Cache boundaries"] SPLIT --> FULL{"Fragment covers the complete valid block?"} FULL -->|Yes| ASYNC["Existing AsyncCacheWriteManager"] FULL -->|No| PARTIAL["PartialBlockWritebackManager queue"] PARTIAL --> DISPATCH["HoleFillDispatch: ordered deadline and capacity checks"] DISPATCH --> ELIGIBLE{"Aggregation deadline elapsed<br/>and cache writer has capacity?"} ELIGIBLE -- No --> WAIT["Dispatcher waits on condition variable<br/>task remains queued and mergeable"] WAIT --> ELIGIBLE ELIGIBLE -- Yes --> READY["Bounded worker handoff"] READY --> WORKER["Block worker: recheck cache and plan block-local holes"] WORKER --> COUNT{"Number of merged hole ranges?"} COUNT -- Zero --> OWNED["Complete owned block buffer"] COUNT -- One --> INLINE["Read directly on the block worker"] COUNT -- Multiple --> REMOTE["HoleFillRemoteReadPool: one task per range"] REMOTE --> JOIN["Block worker joins all accepted reads"] INLINE --> RESULT{"All reads succeeded?"} JOIN --> RESULT RESULT -- Yes --> OWNED RESULT -- No --> DROP["Drop best-effort writeback"] OWNED --> ASYNC ASYNC --> CACHE["Existing File Cache write path"]Complete 1 MiB fragments, including a shorter physical EOF block, enter the existing asynchronous cache writer directly. Partial fragments are copied into a BE-level queue. Fragments for the same queued block merge under that task's fragment lock; an already active fill is deduplicated. A combined inflight-buffer and File Cache probe skips cached/downloading blocks before admission and once when a worker starts the task, outside the queue mutex. The task then executes its planned GETs and retains the final cache-writer checks. Its source pointer reads directly from the inner remote reader while retaining the cached wrapper as the shared owner, keeping the source open through delayed hole filling.
Queued partial blocks retain a mutable minimum aggregation interval,
hole_fill_merge_delay_ms=2000(milliseconds), to keep queued fragments mergeable for at least two seconds before dispatch. Timing starts at first admission; merging later fragments preserves the timestamp. The queue remains admission-time ordered, with stale replacements moving to the tail. OneHoleFillDispatchthread checks due entries and stops at the first future deadline. It can bypass a due task whose writer lacks capacity, then waits until the earliest deadline or capacity retry. Runtime delay updates wake this dispatcher; tail arrivals that cannot advance its wait leave the timer alone. Waiting releases the mutex and preserves fragment merging. Complete blocks still enter the asynchronous cache writer directly.The queue accounts one full cache-block buffer per queued or active entry and is bounded by
hole_fill_max_pending_bytes_per_be=4 GiB. When full, the oldest queued task is evicted. If every retained task is already active, new cache-only work is rejected. The query read result is unaffected in either case.Block workers, dynamically resized by
hole_fill_workers_per_be=32, consume ready tasks. The total of dispatched and executing tasks is bounded by that worker count, so excess entries stay in the mergeable queue. Dispatch moves existing list nodes into the handoff queue. Shrinking lets already-dispatched work drain before admitting more. Writer-capacity accounting includes the handoff, and final write submission still uses spare-capacity-only admission. Active-task metrics include both dispatched and executing work.Hole-fill algorithm
For one partial cache block,
HoleFillPlannerunions the covered fragments, takes their complement inside that block, and applies the same three-limit merge algorithm to the missing intervals. Hole ranges never cross a 1 MiB block boundary. A single merged range is read directly on the block worker, avoiding task submission and wakeup overhead. Multiple ranges are submitted concurrently to the BE-wideHoleFillRemoteReadPool, controlled by mutablehole_fill_remote_read_threads_per_be=64. This pool limit applies only to multi-range reads; single-range reads also run on the block workers.Each block worker creates and reuses one concurrent token for its lifetime. Before writeback or failure cleanup it joins all accepted GETs, including when a submission or read fails. Shutdown stops admission, joins the dispatcher and discards queued/undelivered work without its GETs, then joins executing block workers and releases their tokens before shutting down the remote-read pool. Block-worker and asynchronous cache-writer pools start with their configured worker counts; the remote-read pool retains at least one thread.
The hole-fill limits are tighter because crossing already covered bytes consumes background bandwidth without reducing the foreground query latency.
hole_fill_max_gap_byteshole_fill_max_range_byteshole_fill_max_read_amplification_ratioExample: all offsets below are KiB within one 1 MiB block.
[0, 256),[384, 416),[544, 1024)[256, 384), hole2[416, 544)[256, 384)and[416, 544): 256 KiB total, with no covered bytes reread.flowchart LR C1["covered 0 to 256 KiB"] --> H1["hole1 256 to 384 KiB"] H1 --> GAP["covered gap 384 to 416 KiB"] GAP --> H2["hole2 416 to 544 KiB"] H2 --> C2["covered 544 to 1024 KiB"] H1 --> GET1["GET1: 256 to 384 KiB"] H2 --> GET2["GET2: 416 to 544 KiB"] GET1 --> JOIN["Join both GETs"] GET2 --> JOIN JOIN --> BLOCK["complete block buffer"] BLOCK --> WRITER["existing async cache writer"]Failure and compatibility boundaries
enable_query_read_ahead=trueandenable_async_file_cache_write=true; runtime updates affect newly initialized reads. Disabling asynchronous cache writes retains the original query reader and cache-filling path.Configuration defaults
enable_query_read_aheadfalseenable_async_file_cache_write=true.read_ahead_max_bytes_per_query256 MiBread_ahead_max_bytes_per_be1 GiBread_ahead_eager_window_bytes4 MiBread_ahead_lazy_window_bytes1 MiBread_ahead_max_gap_bytes64 KiBread_ahead_max_range_bytes2 MiBread_ahead_max_read_amplification_ratio1.0read_ahead_block_fill_min_coverage1.0hole_fill_max_gap_bytes32 KiBhole_fill_max_range_bytes1 MiBhole_fill_max_read_amplification_ratio1.0hole_fill_max_pending_bytes_per_be4 GiBhole_fill_merge_delay_ms2000 ms0disables it. Merges keep the original timestamp.hole_fill_workers_per_be32hole_fill_remote_read_threads_per_be64Read IO diagnostics
enable_read_io_trace=falsedynamically enables actual S3 GET intervals/outcomes, range/task provenance, read-only cache/inflight coverage snapshots and typed timing events. Records are batched in memory and flushed by a dedicated writer to standalone JSONL files; they do not go tobe.INFOand have no cumulative event cap. Normal and fast shutdown drain submitted records.enable_read_io_traceread_io_trace_dirread_io_trace/subdirectory, unless explicitly setread_io_trace_flush_bytesread_io_trace_flush_interval_msdoris_read_io_trace_record_time_nsexposes diagnostic overhead. The implementation records consumed-range writeback, queued/active fragments and dispatcher scan timing. Profile separates complete/partial foreground submission even with tracing disabled. The offline analyzer undertools/read_io_traceis no longer included.S3 upload-buffer configuration updates now enforce the existing File Cache startup alignment constraint: when File Cache is enabled, the buffer must cover at least one cache block and be an exact multiple of its size. Invalid updates return
INVALID_ARGUMENTand retain the previous value. Cache probing, read-ahead and hole-fill mechanisms are preserved.Writeback deduplication also handles historical non-aligned cache blocks restored from disk. It checks actual contiguous
DOWNLOADED/DOWNLOADINGintervals, retaining the inflight shortcut; the existing grid-onlyprobe(), read-ahead windows and hole-fill scheduling are unchanged.Commit structure
The branch is based on upstream master
fc1b82283cband contains 21 commits: 18 components, one unity-build fix, one S3 configuration validation fix, and one restored-cache coverage fix. Shared BE diagnostics enter once as component 6; commit 7 scopes filesystem error-code imports for no-PCH builds. The RLE reverse-reach baseline increases only by the feature's six translation-unit users (828 to 834; measured reach: 910 to 916 TUs); existing global expression/reader header dependencies are retained. Hole-fill and consumed-range follow-ups remain in components 15, 17 and 18.Performance benchmarks
Time is FE Profile Total; S3 bytes include background I/O through drain.
Hardware
ecs.g9i.xlargeecs.g9i.8xlargeTest settings
Baselineis a cold read with asynchronous cache writes and page read-ahead disabled.Enabledis a cold read with asynchronous cache writes and page read-ahead/hole filling enabled.Local File Cachereads from warmed disk File Cache with both optimizations disabled and the OS page cache cleared.enable_async_file_cache_writefalsetruefalseenable_query_read_aheadfalsetruefalseread_ahead_eager_window_bytes/read_ahead_lazy_window_bytesread_ahead_max_gap_bytes/read_ahead_max_range_bytesread_ahead_max_read_amplification_ratio/read_ahead_block_fill_min_coverageread_ahead_max_bytes_per_query/read_ahead_max_bytes_per_behole_fill_merge_delay_mshole_fill_max_pending_bytes_per_behole_fill_max_gap_bytes/hole_fill_max_range_byteshole_fill_max_read_amplification_ratiohole_fill_workers_per_be/hole_fill_remote_read_threads_per_beasync_file_cache_write_workers_per_disk/async_file_cache_write_max_pending_bytessegment_prefetch_thread_pool_thread_num_min/segment_prefetch_thread_pool_thread_num_maxfile_cache_each_block_sizedisable_storage_page_cache/storage_page_cache_limitfalse/ 20%enable_query_segment_file_cache_prefetch/enable_compaction_segment_file_cache_prefetchfalse/falseenable_read_io_tracefalseenable_sql_cache/enable_query_cachefalse/falseparallel_pipeline_task_numSummary
BE CPU and memory during S3 cold reads
The table compares Baseline (both optimizations off) with Enabled (Async Write + Read-Ahead, 256 MiB per query and 1 GiB per BE). Both groups started each query with an empty File Cache after a BE restart. Average BE CPU is in logical cores. CPU time is estimated from one-second BE process samples during the FE query window, using query pairs with continuous samples and at least 3 s in both runs. The RSS column reports the median of per-query sampled BE process peaks, including short queries.
The SSB Flat groups used different builds. On the same build and cluster, SSB q4.1 went from 236.000 s to 99.000 s, with estimated BE CPU time rising from 513.43 core-seconds to 590.46 core-seconds and peak RSS rising from 25.382 GiB to 28.041 GiB.
BE network and File Cache disk I/O
Values are Baseline → Enabled under the S3 cold-read settings above. We sampled BE eth0 and the four File Cache devices once per second during the FE query window. Host RX includes all interface traffic. The S3 totals above include background I/O through drain.
Disk columns use pairs with complete samples on all four devices. For each query, iostat w_await is weighted by write operations before taking the median across queries. The write totals cover the query window, with the rest of the background drain outside that window.
The disk comparison omits SSB q2.2 and TPC-H q9 because one device sample was missing for each query. TPC-H q18 peaked at 2464.3 MiB/s RX with the optimization enabled.
The SSB Local File Cache group read 417.627 GiB from the four cache disks after clearing the OS page cache, with 0 B of S3 reads.
Per-query results
The dataset details below include CPU and RSS for every reported group. A dash in a CPU cell marks an FE query time under 3 s. RSS still shows the observed peak for those queries.
SSB Flat SF1000: per-query results
BE CPU (average / sampled peak / estimated CPU time)
BE memory (sampled peak RSS)
TPCH SF1000: per-query results
BE CPU (average / sampled peak / estimated CPU time)
BE memory (sampled peak RSS)
SSB SF1000: per-query results
BE CPU (average / sampled peak / estimated CPU time)
BE memory (sampled peak RSS)
Release note
Add an opt-in range read-ahead and partial File Cache block hole-fill pipeline for cloud query data pages. The feature is disabled by default.
Check List (For Author)
run-be-ut.sh --run -j100(ASAN/PCH), covering range planning/scheduling, page-position windows, scalar/legacy complex-column reads, count-emission fast paths, point queries, cache/inflight reuse, writeback, hole filling, tracing and configuration validation. A direct Release/PCH BE build passed withbuild.sh --be -j100. Full-checkout clang-format 16, build hygiene, gate self-tests and diff/range-diff checks passed; [fix](be) Fix Phase 2 no-PCH compilation (compile only) #67579 has the same final source tree. No cluster or clang-tidy run.Check List (For Reviewer who merge this PR)