Skip to content

[feature](file cache) Add page prefetch and I/O coalescing for cold queries - #66520

Draft
bobhan1 wants to merge 44 commits into
apache:masterfrom
bobhan1:feature/page-prefetch-io-coalescing-phase2
Draft

[feature](file cache) Add page prefetch and I/O coalescing for cold queries#66520
bobhan1 wants to merge 44 commits into
apache:masterfrom
bobhan1:feature/page-prefetch-io-coalescing-phase2

Conversation

@bobhan1

@bobhan1 bobhan1 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: None

Related PR: #65658

Problem Summary:

Cold cloud scans currently discover compressed data-page misses when each FileColumnIterator reaches the page that it needs. Because columns and pages are consumed in order, adjacent remote reads cannot be prepared across columns before consumption, remote latency is exposed on the query thread, and nearby page reads are not coalesced even when one bounded range could satisfy them.

The page-read unit and the file-cache persistence unit are intentionally different. Segment data pages are variable-sized and may be unaligned, while cache persistence accepts complete fixed-size cache blocks. Moving variable-range planning, hole completion, or I/O coalescing into the fixed-block write service would mix query read semantics with cache persistence and would require the service to accept a new task shape.

This PR adds a separate, opt-in query page-prefetch pipeline. It prepares the pages needed by all participating columns, coalesces them under explicit amplification limits, reads exact ranges asynchronously, lets PageIO decode directly from prefetched slices, and optionally completes dense cache blocks for background writeback. The existing fixed-block write service continues to receive one complete fixed-capacity cache-block task at a time.

The feature is disabled by default. Admission pressure, allocation failure, pool rejection, remote-read failure, and prefetched-page decode failure fall back to the existing page-read path. Query cancellation and genuine page metadata or checksum corruption retain their existing error semantics.

Goals

  • Overlap remote data-page I/O across pages and across the physical columns that a SegmentIterator will actually consume.
  • Coalesce nearby page reads while bounding range size, page count, gap size, and read amplification.
  • Preserve exact sparse-rowid reads without adding speculative lookahead pages.
  • Reuse downloaded file-cache blocks and completed inflight buffers without creating cache cells, waiting for downloading blocks, or scheduling writes from the prefetch read itself.
  • Decode prefetched page slices through the same footer, checksum, decompression, pre-decode, and page-cache insertion logic as ordinary page reads.
  • Complete and persist only useful full cache blocks without adding variable-size tasks or range-merging logic to the fixed-block write service.
  • Bound speculative memory and active work independently at query and BE scope, with non-blocking admission and complete rollback on rejection.
  • Keep fallback behavior local so speculative prefetch does not change SQL results.

Non-goals

  • Changing BlockFileCache layout, block size, LRU policy, cache admission semantics, or the fixed-block async write queue contract.
  • Adding cross-query singleflight for page-prefetch ranges. Existing downloaded and inflight cache-block reuse still applies.
  • Fetching sparse holes after a page has already been consumed.
  • Prefetching segment indexes, warm-up or dry-run reads, compaction reads, external Parquet or ORC pages, peer-cache workflows, or non-query readers.
  • Supporting Variant physical-column propagation in this change.
  • Implementing adaptive window sizing. The current selector uses a fixed window.
  • Claiming that every page decode is zero-copy. Compressed pages can consume the prefetched compressed slice directly, while uncompressed pages still take ownership through the existing PageIO representation.

Performance validation

This is still a Draft PR and does not claim a measured performance improvement yet. No cloud regression or end-to-end benchmark has been run for the current branch.

The planned validation will compare the feature disabled and enabled on the same cold cloud dataset and query set after clearing relevant caches. It will record end-to-end query latency, remote request count, remote bytes, requested page bytes, fetched bytes, coalesced gap bytes, completed-block fill bytes, prefetch wait time, fallback rate, and cache writeback behavior. The implementation already enforces the configured read-amplification bound; the benchmark is intended to determine whether the reduction in serialized remote requests outweighs the additional bounded bytes for representative continuous and sparse scans.

Architecture

SegmentIterator performs a prepare pass for every physical column that will be read before it starts the consume pass. A per-column PagePrefetcher owns page state and range references. Pure planning remains separate from asynchronous execution, page decoding, and cache persistence.

flowchart LR
    SI[SegmentIterator two-pass planning]
    CI[Column iterators and nested rowid mapping]
    PF[PagePrefetcher]
    PC[Storage page-cache precheck]
    RP[PageReadPlanner]
    WC[FileCacheWritebackCoordinator]
    IOS[PagePrefetchIOService]
    CR[CachedRemoteFileReader exact NO_WRITE unaligned read]
    LOCAL[Downloaded cache blocks and inflight buffers]
    REMOTE[Remote object storage]
    RANGE[PrefetchRange and variable-size buffer]
    PIO[PageIO slice decode]
    CONSUME[FileColumnIterator consume]
    USEFUL[Consumed-page writeback claim]
    COPY[Background complete-block copy]
    SUBMIT[FixedBlockAsyncWriteSubmitter]
    QUEUE[Existing fixed-block async write queue]
    CACHE[BlockFileCache]

    SI --> CI
    CI --> PF
    PF --> PC
    PC -->|uncached candidates| RP
    RP --> WC
    WC --> IOS
    IOS --> CR
    CR --> LOCAL
    CR --> REMOTE
    CR --> RANGE
    RANGE --> PIO
    PIO --> CONSUME
    CONSUME --> USEFUL
    USEFUL --> COPY
    COPY --> SUBMIT
    SUBMIT --> QUEUE
    QUEUE --> CACHE
Loading

Component responsibilities

Component Responsibility Important guarantee
SegmentIterator Builds the actual column set for normal, predicate, selected-row, and lazy-pruned phases, then calls prepare_page_prefetch for all of them before consuming any column. Remote I/O for multiple participating columns can overlap without changing column-consumption order.
Complex column iterators Propagate logical row requirements to physical child iterators. Struct children reuse parent rowids; array and map iterators read outer offsets first and translate selected rows into item-space rowids. All key/value or item ranges are prepared before their physical children are consumed, while inactive branches are skipped.
PagePrefetcher Owns the fixed window, sparse-page selection, page-cache precheck, page state, range submission, wait-at-consumption, statistics, cancellation, and fallback decisions for one physical FileColumnIterator. A page is decoded from at most one tracked prefetched range, and rejected or unusable ranges fall back locally.
PageReadPlanner Validates immutable page metadata and creates file-offset-ordered ranges under the configured gap, size, page-count, and amplification bounds. Planning is pure and performs no I/O or cache mutation.
FileCacheWritebackCoordinator Computes true EOF-aware cache-block coverage, adds bounded holes only for sufficiently dense blocks, invalidates blocks associated with a bad page, and schedules writeback only after useful consumption. Failure to complete a block removes only the optional block completion; required page reads remain in the plan.
PagePrefetchIOService Applies query and global admission, allocates range buffers, submits reads and writeback copies to the shared segment-prefetch pool, publishes terminal range state, propagates cancellation, supports live budget updates, and drains its own tasks during shutdown. Admission never waits. Every unsuccessful stage releases all acquired range, byte, memory, and outstanding-task ownership.
CachedRemoteFileReader Serves exact NO_WRITE + UNALIGNED ranges from downloaded cache blocks, completed inflight buffers, and remote spans while preserving REMOTE_ONLY_ON_MISS. The prefetch read does not create cache cells, take downloader ownership, wait for DOWNLOADING, append or finalize cache files, or submit a cache write.
PageIO Validates and decodes an already-owned encoded-page slice through the common page implementation. Footer parsing, checksum validation, decompression, pre-decoding, page-cache insertion, and corruption behavior stay aligned with the ordinary read path.
FixedBlockAsyncWriteSubmitter Converts one already-complete cache-block payload into the existing fixed-capacity task after a final cache probe, epoch check, inflight ownership decision, allocation, and rollback-safe queue submission. The write service contract remains fixed-size and unchanged; the physical EOF block uses a short valid prefix in a fixed-capacity task.

Buffer and budget separation

Buffer Shape and lifetime Budget Consumer
PagePrefetchBuffer Variable-sized coalesced range. It remains resident while a range or page slice still references it. Per-query and per-BE resident-byte limits. The active-range slot is released when I/O reaches a terminal state, while the byte reservation remains until the buffer is destroyed. PageIO and the background complete-block copy.
Fixed async write buffer Exactly one cache-block capacity, with a possibly short valid prefix only at physical EOF. Existing per-cache-disk pending-byte admission owned by the fixed-block write service. Cache persistence worker.

The background complete-block copy reserves another cache-block worth of query and BE resident bytes before it runs. This prevents a query thread from copying a large block and accounts for the short interval where both the variable range and the fixed task buffer may coexist.

Important flows

1. Prepare all columns before consuming any column

For continuous scans, the selector covers every page required by the current ordinal range and then looks ahead in scan direction until the fixed target window is reached. It refills when the number of unconsumed planned pages is at or below half of the target. Reverse scans select candidates in reverse consumption order but sort the final I/O candidates by file offset before planning.

For sparse rowids, the selector maps only the supplied rowids to data pages, deduplicates page indexes, excludes already tracked pages, and does not add lookahead. This keeps point and highly selective reads from expanding into a continuous window.

Before range planning, each candidate is checked for minimum encoded size, file bounds, monotonically increasing file offsets and page indexes, non-overlapping ordinal ranges, and valid ordinal coverage. A storage page-cache hit is recorded as skipped and does not consume prefetch I/O admission.

sequenceDiagram
    participant S as SegmentIterator
    participant C as Column iterators
    participant P as PagePrefetcher
    participant I as PagePrefetchIOService
    participant R as Remote reader
    participant D as PageIO

    S->>S: determine actual columns and read phase
    loop every participating physical column
        S->>C: prepare_page_prefetch row requirements
        C->>C: map nested rows to child rowids when needed
        C->>P: prepare candidate pages
        P->>I: non-blocking range submissions
        I->>R: exact asynchronous range read
    end
    loop existing column-consumption order
        S->>C: read rows
        C->>P: acquire only the page now being consumed
        P-->>C: ready slice or fallback decision
        C->>D: decode prefetched slice
        D-->>C: parsed page
    end
Loading

2. Bounded range coalescing and block completion

The base planner merges file-ordered page candidates only when all four conditions remain true:

  • The inter-page gap is no larger than query_page_prefetch_max_gap_bytes.
  • The resulting range is no larger than query_page_prefetch_max_range_bytes.
  • The resulting range contains no more than query_page_prefetch_max_pages_per_range pages.
  • fetched_bytes / requested_page_bytes does not exceed query_page_prefetch_max_read_amplification_ratio.

The writeback coordinator starts from that valid page-only plan. It computes cache-block coverage using the real file size, so the final physical block uses its true valid prefix. A block is considered only when covered page bytes meet query_page_prefetch_writeback_min_block_coverage. The coordinator tentatively adds the missing bytes needed to complete that block and keeps the completion only if every range and amplification limit still holds. Otherwise it discards that optional completion and preserves the page-only plan.

Each final range tracks requested page bytes, coalesced gap bytes, optional block-fill bytes, page slice offsets, and complete-block slice offsets. These categories are disjoint and sum to the fetched range size.

3. Exact mixed-source range read

The I/O worker forces CacheWriteMode::NO_WRITE and CacheAlignMode::UNALIGNED for the requested range. CachedRemoteFileReader first reuses complete inflight buffers, then probes existing downloaded cache blocks, and finally issues remote reads for uncovered exact intersections. Adjacent remote intersections are joined, but the request is never expanded merely to align with a cache block.

A DOWNLOADING cache block is treated as remotely readable for this path rather than making the prefetch worker wait. A missing local cache file triggers the existing asynchronous self-heal removal and the affected bytes are read remotely. REMOTE_ONLY_ON_MISS keeps its existing behavior and does not become eligible for block completion or writeback.

This path returns one fully initialized range buffer and source byte statistics, but it never mutates file-cache ownership. Cache population is a separate decision after successful page consumption.

4. Range state, wait, decode, and fallback

PrefetchRange has the following monotonic lifecycle:

CREATED -> QUEUED -> RUNNING -> READY
        -> REJECTED          -> FAILED
                             -> CANCELLED

The range slot is released as soon as the range becomes terminal. The resident-byte reservation remains attached to the reference-counted buffer, allowing PageIO or a background writeback copy to safely outlive the worker task.

The query thread does not wait during planning or admission. It waits only when it reaches a page still covered by an accepted range. At consumption it checks the storage page cache again, waits for the covering range if necessary, obtains the exact page slice, and invokes PageIO::decode_page_from_slice. A ready range avoids another file read.

If the range was rejected, failed, cancelled for a non-query reason, or unavailable, the iterator reads the page through the existing path. If prefetched bytes fail checksum, footer, decompression, or pre-decode validation, associated complete blocks are invalidated and the iterator retries through the existing read path. Only failure of that authoritative path is returned to the query.

5. Useful-only fixed-block writeback

A complete block is not submitted merely because its bytes were fetched. It becomes claimable only when at least one associated source page is successfully decoded and consumed. A decode failure invalidates every complete block associated with that page. Each block can be claimed once.

The writeback copy runs on the shared segment-prefetch pool, not on the query thread. Before and after obtaining the block slice it rechecks service acceptance, both feature gates, query cancellation, REMOTE_ONLY_ON_MISS, block eligibility, and the cache write epoch. It then calls FixedBlockAsyncWriteSubmitter, which performs the final cache-state probe, allocates a fixed-capacity buffer, copies the complete payload, acquires inflight ownership, submits one fixed-block task, and rolls back on every skip or rejection.

A writeback allocation failure, stale epoch, existing cache owner, backpressure rejection, or persistence failure cannot retroactively fail the query. The already-consumed page remains valid and a later read may populate the cache again.

6. Admission, cancellation, and shutdown

Range admission reserves in this order: query range and byte budget, BE range and byte budget, range-buffer allocation, outstanding-task ownership, and shared-pool submission. Writeback-copy admission reserves bytes but not another range slot. All failures unwind acquired ownership without waiting for capacity.

The safe worker I/O context owns copied query ID and cache-admission values and clears query-thread-only pointers. Query contexts keep weak ownership of the runtime query and cancel ranges when the query is cancelled or destroyed.

The service reuses segment_prefetch_thread_pool and does not own or shut down that pool. During ExecEnv shutdown it stops accepting work, waits for registered submitters, cancels live query contexts, waits for only its own outstanding tasks, and is destroyed before the shared pool and file-cache objects that those tasks reference.

Configuration

Configuration Default Purpose
enable_query_page_prefetch false Master gate for the query page-prefetch path.
enable_async_file_cache_write false Existing prerequisite because complete-block persistence uses the fixed-block async write path. If either gate is off, iterators use the legacy page-read path.
query_page_prefetch_window_pages 16 Fixed target page window for continuous forward and reverse scans.
query_page_prefetch_min_window_pages 1 Validated lower bound carried by prefetch options for future adaptive sizing.
query_page_prefetch_max_window_pages 64 Validated upper bound carried by prefetch options for future adaptive sizing.
query_page_prefetch_max_gap_bytes 65536 Maximum gap allowed when coalescing adjacent page components.
query_page_prefetch_max_range_bytes 4194304 Maximum bytes in one submitted range. It must be at least one file-cache block.
query_page_prefetch_max_pages_per_range 32 Maximum requested pages represented by one range.
query_page_prefetch_max_read_amplification_ratio 2.0 Maximum ratio of fetched range bytes to requested page bytes.
query_page_prefetch_max_inflight_ranges_per_query 16 Maximum active prefetch ranges for one query.
query_page_prefetch_max_inflight_ranges 64 Maximum active prefetch ranges for one BE.
query_page_prefetch_max_inflight_bytes_per_query 67108864 (64 MiB) Maximum resident prefetch and writeback-copy bytes for one query.
query_page_prefetch_max_inflight_bytes_per_be 536870912 (512 MiB) Maximum resident prefetch and writeback-copy bytes for one BE.
query_page_prefetch_writeback_min_block_coverage 0.5 Minimum requested-page coverage before a cache block is considered for bounded completion.
enable_query_page_prefetch_adaptive_window false Reserved switch for future adaptive sizing. This change implements fixed-window selection only.

Validators enforce positive sizes and counts, min_window <= window <= max_window, max_gap < max_range, per-query limits no larger than BE limits, amplification at least 1.0, and writeback coverage in (0, 1]. The four query and BE inflight range and byte limits update the service, global budget, and existing query contexts online. Planning options are snapshotted when a physical column creates its PagePrefetcher.

Observability

PagePrefetcher maintains internal accounting for candidate, submitted, consumed, page-cache-skipped, ready-hit, fallback, throttled, and cancelled work, together with wait time, remote I/O time, requested and fetched bytes, coalesced gaps, block-fill bytes, local or inflight bytes, remote bytes, and writeback-eligible blocks. The focused unit tests assert these counters and budget transitions.

Remote and local bytes plus remote I/O time continue to flow through the existing CachedRemoteFileReader and file-cache read statistics. This Draft does not yet add new user-facing RuntimeProfile counters or page-prefetch-specific bvars, so the PR does not claim production profile visibility that is not present in the current implementation.

Compatibility and current scope

  • The path is eligible only in cloud mode for READER_QUERY data-page reads with a live query context, positive tablet ID, CachedRemoteFileReader, file cache, accepting prefetch service, and fixed-block async write service.
  • Storage page-cache hits remain first-class and skip speculative I/O. Index and metadata page behavior is unchanged.
  • Continuous forward and reverse reads, exact sparse rowids, normal reads, predicate reads, selected-row reads, and lazy-pruned reads are wired through the two-pass planner.
  • Scalar and nullable physical columns plus struct, array, and map propagation are implemented. Variant is intentionally left on the existing path.
  • REMOTE_ONLY_ON_MISS, non-query readers, pruned columns, disabled features, unavailable services, and ineligible readers retain the existing behavior.
  • The current implementation has no cross-query page-range singleflight, no after-consumption sparse-hole fetch, and no adaptive window adjustment.

Tests

  • Focused ASAN BE unit tests: 264 passed, 3 existing disabled tests skipped, 0 failed across 267 discovered tests in 13 suites.
    • Exact unaligned cached and remote reads, downloaded and inflight reuse, DOWNLOADING behavior, self-heal, REMOTE_ONLY_ON_MISS, and fixed-block submission rollback.
    • Slice decode checksum, footer, compression, pre-decode, page-cache behavior, and corruption fallback.
    • Candidate validation, forward and reverse fixed windows, exact sparse rowids, coalescing limits, amplification limits, EOF handling, block coverage, and optional completion rollback.
    • Query and BE range and byte admission, live option updates, allocation and pool rejection, cancellation races, state transitions, safe I/O context lifetime, writeback-copy gates, and shutdown draining.
    • FileColumnIterator, scalar and complex-column propagation, two-pass SegmentIterator planning, selected rows, predicate phases, lazy-pruned phases, and fallback.
  • Full BE build completed with the current Release build configuration: ./build.sh --be -j100.
  • Style check passed: build-support/check-format.sh.
  • Patch whitespace check passed: git diff --check b586dd1f429..HEAD.
  • clang-tidy was intentionally not run for this change.

Pending validation before leaving Draft

  • Cloud regression with both gates disabled and enabled, including runtime switching and cache reuse on a second query.
  • End-to-end cold-query performance comparison for continuous, reverse, sparse, selected-row, and complex-column scans.
  • Production-facing profile or bvar integration if the final performance-validation workflow requires page-prefetch-specific visibility.

Release note

Add an opt-in BE query page-prefetch and remote I/O coalescing path for cold cloud scans. The feature prepares data pages across participating columns, reads bounded exact ranges asynchronously, decodes pages from prefetched slices, and can complete useful fixed cache blocks for background persistence. It is controlled by enable_query_page_prefetch, requires enable_async_file_cache_write, and remains disabled by default.

Check List (For Author)

  • Test

    • Regression test
    • Unit Test
    • Manual test (add detailed scripts or steps below)
    • No need to test or manual test. Explain why:
      • This is a refactor/code format and no logic has been changed.
      • Previous test can cover this change.
      • No code files have been changed.
      • Other reason
  • Behavior changed:

    • No.
    • Yes. When both feature gates are explicitly enabled for eligible cloud query reads, data pages can be prefetched and coalesced before consumption and useful complete cache blocks can be persisted in the background. Default behavior is unchanged.
  • Does this need documentation?

    • No. The feature is experimental, disabled by default, and the implementation contract is documented in this Draft PR.
    • Yes.

Check List (For Reviewer who merge this PR)

  • Confirm the release note
  • Confirm test cases
  • Confirm document
  • Add branch pick label

bobhan1 added 30 commits July 29, 2026 11:27
### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary:

On a file-cache miss, CachedRemoteFileReader currently reads the requested data
from remote storage and then appends and finalizes the corresponding local cache
blocks on the query thread. The remote read is required to satisfy the query, but
the subsequent local writes are best-effort cache population. Coupling the two
makes local filesystem latency and backpressure part of foreground scan latency.

Moving only the append call to a background thread is not sufficient. Concurrent
readers may fetch the same missing range, FileBlock downloader ownership has
thread-affine cleanup semantics, and cache clear/remove can invalidate queued
work. Warm-up, prefetch, dry-run, and other explicit cache-population callers also
require synchronous completion semantics.

This change introduces an opt-in asynchronous write path with the following
architecture:

1. BlockFileCache provides a read-only probe API that reports downloaded,
   downloading, empty, and missing ranges without creating cache cells or taking
   downloader ownership.
2. Each cache instance owns an InflightWriteBufferIndex. It publishes remote-read
   buffers with insert-if-absent semantics so later readers can reuse bytes that
   have already been fetched and are awaiting persistence.
3. Each cache disk owns an AsyncCacheWriteService with a bounded MPMC queue,
   tracked-buffer accounting, dynamically resizable workers, task-age protection,
   and an explicit shutdown protocol.
4. The ordinary read path combines inflight buffers and downloaded cache blocks,
   reads the remaining middle range from remote storage once, copies all required
   bytes into the caller buffer, and submits background tasks only for true cache
   misses.
5. Workers revalidate the cache write epoch and current block state before writing.
   Conditional index removal and epoch changes prevent stale callbacks or queued
   tasks from deleting newer entries or recreating data after cache invalidation.

Queue rejection, tracked-buffer pressure, or asynchronous persistence failure does
not fail a remote read that has already produced the requested data. The inflight
entry is rolled back and the operation falls back to best-effort cache behavior.
Explicit cache-population paths continue to use the synchronous implementation.

The feature is disabled by default and can be switched online. Worker counts,
pending-task limits, batch size, and task-age thresholds are validated
through configuration. New bvars and runtime-profile counters expose submissions,
inflight reuse, probe results, rejections, failures, queue depth, buffer memory,
and write latency.

The asynchronous reader implementation is isolated in
cached_remote_file_reader_async_write.cpp. The top-level read function only
orchestrates planning, covered-range materialization, a single remote middle read,
and task submission; the detailed steps are kept in cohesive helper functions.

### Release note

Add an opt-in asynchronous file-cache write path controlled by
`enable_async_file_cache_write`. It is disabled by default.

### Check List (For Author)

- Test:
    - [x] Regression test
        - Docker cloud suite `test_async_file_cache_write`: 1 suite passed
    - [x] Unit Test
        - BE ASAN targeted tests: 26 cases from 4 suites passed
    - [x] Build
        - `./build.sh --be --fe --cloud -j100`
        - `./build.sh --be -j100`
    - [x] Code style
        - `build-support/check-format.sh`
        - clang-tidy was intentionally not run for this change
- Behavior changed:
    - [x] Yes. When explicitly enabled, ordinary file-cache misses return after
      the caller buffer is complete and persist missing cache blocks in background
      workers. The default and explicit cache-population behavior are unchanged.
- Does this need documentation:
    - [x] No. The feature is experimental and disabled by default.
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The phase-one asynchronous file-cache reader built detailed coverage runs, maintained multiple cursors, and materialized individual holes inside a read even though most read_at requests span only one or two cache blocks. This made the query-side orchestration difficult to review and maintain without providing meaningful value for the common case.

Replace that logic with one aligned inflight lookup, one read-only cache probe, and a simple per-block source plan. The reader still gives inflight buffers priority and still distinguishes downloaded, downloading, and missing cache blocks. Downloading blocks outside the remote span retain their wait behavior. When real misses exist, the reader takes the first through last miss as one remote range, intentionally rereads any cache or inflight blocks inside that range, and submits background writes only for the blocks that were actual misses. A cache-side race falls back to one full aligned remote read.

This preserves caller-buffer completeness, inflight deduplication, existing-block reads, cache wait semantics, non-blocking write submission, and backpressure rollback while substantially reducing the amount of control flow in CachedRemoteFileReader::_read_async_write_path and its helpers.

### Release note

None

### Check List (For Author)

- Test:
    - Unit Test: six targeted BlockFileCacheTest cases passed under ASAN, covering inflight reuse, DOWNLOADING wait, cached sides, one remote middle span, real-miss-only submission, backpressure rollback, and per-read mode selection
    - Build: ./build.sh --be -j100 passed
    - Style check: build-support/check-format.sh and git diff --check passed
- Behavior changed: No. This refactor preserves the phase-one asynchronous cache-write behavior while simplifying how the read range is assembled.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The asynchronous cache read planner called BlockFileCache::probe before consulting the inflight write-buffer index. BlockFileCache::probe acquires the cache mutex, so a request already covered entirely by inflight buffers still contended on BlockFileCache even though it needed no cache metadata.

Build the aligned block list and perform the batch inflight lookup first. If every requested block is covered for the current write epoch, return the plan immediately and materialize the caller buffer directly from inflight memory. If any block is not covered, retain the existing mixed-source behavior by issuing one whole-range read-only cache probe and classifying only the non-inflight blocks as downloaded, downloading, or remote misses.

Make the probe result optional in the read plan so ownership matches the conditional probe. Extend the inflight reuse unit test to hold the BlockFileCache mutex during the second read; the read must still complete, directly proving that the full-inflight fast path does not enter BlockFileCache::probe.

### Release note

None

### Check List (For Author)

- Test:
    - Unit Test: six targeted BlockFileCacheTest cases passed under ASAN, including full inflight coverage while the BlockFileCache mutex is held, partial cache coverage, downloading waits, middle-span reads, backpressure rollback, and per-read mode selection
    - Build: ./build.sh --be -j100 passed
    - Style check: build-support/check-format.sh and git diff --check passed
- Behavior changed: Yes. Reads fully covered by current-epoch inflight buffers no longer call BlockFileCache::probe or acquire its cache mutex; partial inflight coverage still probes and combines existing cache blocks.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: AsyncCacheWriteService previously used a follow_global_config flag to switch between fixed test options and direct reads of mutable BE configuration. That made queue admission, batching, and watchdog behavior depend on global state that was not visible in the service interface. It also split online updates across two mechanisms: worker-count changes were forwarded by FileCacheFactory, while the remaining settings were read implicitly from worker and submission paths.

Make configuration ownership explicit. A newly initialized BlockFileCache constructs a complete per-disk options snapshot, and FileCacheFactory registers update callbacks for all five mutable async-write settings. Each callback captures one complete configuration snapshot and forwards it through FileCacheFactory::update_async_write_options to AsyncCacheWriteService::update_options. The service validates the snapshot, applies the requested worker count, and atomically publishes immutable queue, batch, and watchdog settings. Submission and worker paths now consume service-owned snapshots and no longer include or reference common/config.h.

Update unit tests to configure isolated services through the explicit interface, and add coverage proving that config::set_config propagates every mutable setting through the factory into an initialized per-disk service.

### Release note

None

### Check List (For Author)

- Test:
    - Unit Test: `./run-be-ut.sh --run --filter=AsyncCacheWriteServiceTest.*:BlockFileCacheTest.async_write_backpressure_rolls_back_inflight_entry -j 100` passed all 11 tests under ASAN
    - Build: `./build.sh --be -j100` passed
    - Style check: `build-support/check-format.sh` and `git diff --check` passed
- Behavior changed: No. Online mutable settings keep their existing behavior but are propagated through explicit update interfaces.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The phase-one asynchronous file-cache write service used two persistent workers per cache disk. The synchronous path it replaces persisted cache blocks directly on scanner threads, so its effective per-disk write concurrency could scale with the external scanner concurrency, whose default per-context upper bound is 16, and could grow further across concurrent query contexts. A two-worker default therefore serialized writeback far more aggressively than the former path and could fill the bounded pending queue during ordinary scan fan-out.

Increase the default to 16 workers per cache disk. Keep one MPMC queue and let each worker dequeue, revalidate, claim the FileBlock downloader, and write the block in the same thread. Splitting consumption and persistence into separate pools would add a full-task handoff without an independent processing stage, and claiming a downloader before that handoff would violate FileBlock's thread-bound ownership contract. Each worker now uses its own ConsumerToken so concurrent consumers maintain independent producer-stream cursors instead of rescanning streams for every task.

Avoid creating 16 persistent per-disk worker loops while asynchronous writeback is disabled. The cache still constructs the service state and inflight index, but starts workers only when the feature is enabled. A false-to-true online configuration update explicitly starts all initialized services through the factory interface, while mutable service options continue to flow through the explicit factory/service update API. Service readiness is published only after all configured worker loops have been accepted, so query threads reject best-effort submissions instead of enqueueing work to an unready service.

Add deterministic coverage for eight workers consuming distinct tasks concurrently, disabled-service rejection, online enablement, and the existing runtime resize, shutdown, watchdog, inflight cleanup, and reader backpressure rollback behavior.

### Release note

Increase the default asynchronous file-cache write concurrency from 2 to 16 workers per cache disk. Worker threads are created only after asynchronous file-cache writeback is enabled.

### Check List (For Author)

- Test: Unit Test
    - `./build.sh --be -j100`
    - `./run-be-ut.sh --run --filter=AsyncCacheWriteServiceTest.*:BlockFileCacheTest.async_write_backpressure_rolls_back_inflight_entry -j 100` (12 tests passed)
    - `build-support/check-format.sh`
    - `git diff --check`
- Behavior changed: Yes. The default per-disk asynchronous write concurrency is 16, and disabled services no longer keep worker loops resident.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The phase-one async file-cache write implementation had strong happy-path coverage, but several correctness boundaries were not exercised through the complete component interactions. Missing coverage included cache-file disappearance and whole-key self-heal cleanup, wait timeout fallback, direct-read prefix preservation, final concurrent publication deduplication, tracked-buffer allocation failure, external-table cache reuse, worker ownership of existing or deleting cells, remove/write epoch races, runtime worker growth and shrink, and complete propagation of the new profile and synchronous cache-population semantics.

Add compact scenario-oriented BE unit tests that drive the real reader, cache, inflight index, async service, worker, removal, downloader, and index-preload paths. The tests verify both returned data and persistent cache state, including metadata and physical file deletion. Narrow test-only sync points make allocation failures and race windows deterministic without changing normal runtime behavior.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - 51 related ASAN BE unit tests passed with run-be-ut.sh and -j100
    - 7 focused changed-path tests passed with run-be-ut.sh and -j100
    - BE build passed with build.sh --be -j100
    - build-support/check-format.sh passed
- Behavior changed: No; only test coverage and deterministic test injection points are added
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The async cache-write planner queried one block-aligned range, but `BlockFileCache::probe` returned a `FileBlocksHolder` of cache hits plus an independent gap list. The planner then had to scan all hits and gaps for every logical read block even though the read-plan blocks and probe slots use the same block boundaries. This obscured the alignment invariant and introduced unnecessary nested matching logic on the query read path.

Change the probe contract to return one ordered nullable `FileBlock` pointer per aligned input block. A non-null slot is asserted to have the exact corresponding range, except that the final block may end at EOF, while a null slot directly represents a cache miss. The planner now preserves its inflight-first fast path and joins probe results to plan blocks by index; materialization also reads the matching slot directly.

Remove `FileBlocksHolder` and the independent gaps from `FileBlocksProbeResult`. Preserve the existing deferred cleanup semantics for EMPTY and deleting cache blocks through a shared cache-user reference release helper rather than embedding a holder in the probe result. Update focused and end-to-end tests for hit/miss slots, a short final block, retained block states, self-heal cleanup, and an aligned direct-cache prefix followed by an async-written suffix.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - 51 related ASAN BE unit tests passed with `run-be-ut.sh` and `-j100`
    - 2 focused probe/direct-prefix ASAN BE unit tests passed with `run-be-ut.sh` and `-j100`
    - `build-support/check-format.sh` passed
- Behavior changed: No; this simplifies an internal probe/planning contract without changing user-visible cache semantics
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: Existing async file-cache write tests covered a single pending-limit rejection and runtime worker resizing independently, but they did not exercise the complete dynamic backpressure lifecycle. Without that coverage, regressions could allow the MPMC backlog to exceed its bound, lose submissions under producer concurrency, fail to expose sustained rejection at capacity, or leave accepted work stranded after consumers are scaled up.

Add one deterministic service-level BE unit test that stalls the initial worker before cache mutation and drives four producers in controlled waves. The test verifies that the actual queued backlog grows through 4, 8, 12, and 16 tasks, that pending count includes the blocked active task, and that a subsequent 48-task producer burst is rejected without changing the bounded queue or accepted-task count.

After producers stop, the test increases worker concurrency from one to four and batch size from one to four, then releases the artificial write delay. It samples the MPMC backlog independently from pending count, verifies an intermediate lower watermark and an empty queue, and confirms that all accepted tasks finalize with pending count returning to zero. The synchronization point makes both phases deterministic without adding a production-only observation API.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - New dynamic MPMC backpressure ASAN BE unit test passed with run-be-ut.sh and -j100
    - All 14 AsyncCacheWriteServiceTest ASAN BE unit tests passed with run-be-ut.sh and -j100
    - build-support/check-format.sh passed
- Behavior changed: No; this adds deterministic test coverage only
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The async file cache write settings were mixed into the broader block file cache configuration section, while the inflight write buffer index settings did not carry the feature name. This made the feature difficult to locate as one configuration group and made name-based filtering incomplete. Move all async file cache write declarations, definitions, and validators into a dedicated contiguous section. Keep the primary enable_async_file_cache_write switch unchanged, rename only the inflight index enable and shard-count settings with the async_file_cache_write prefix, and update runtime consumers plus BE and regression test configuration. Defaults, mutability, validation, and runtime behavior remain unchanged.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=AsyncCachedRemoteFileReaderTest.*:BlockFileCacheTest.*async_write*:BlockFileCacheTest.cache_write_mode_is_resolved_for_each_read_context:AsyncCacheWriteServiceTest.* -j100 (26 tests passed)
    - build-support/check-format.sh
- Behavior changed: No
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The dynamic async-write backpressure test created four producer threads during queue growth, but each producer submitted only one task per fill wave. That exercised simultaneous entry only weakly and did not model sustained concurrent production before backpressure. Start every producer in a wave through a barrier and let each producer submit four consecutive tasks. The test now observes deterministic queue growth through 16, 32, 48, and 64 queued tasks, verifies a subsequent 128-task concurrent burst is rejected at the pending limit, then confirms the enlarged and accelerated consumer side drains the queue to zero.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=AsyncCachedRemoteFileReaderTest.*:BlockFileCacheTest.*async_write*:BlockFileCacheTest.cache_write_mode_is_resolved_for_each_read_context:AsyncCacheWriteServiceTest.* -j100 (26 tests passed)
    - build-support/check-format.sh
- Behavior changed: No
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: The async file cache write path exposed only an aggregate pending count and a limited set of outcome counters. When throughput degraded, operators could not distinguish producer backpressure, MPMC queue buildup, unavailable workers, inflight-index lock contention, BlockFileCache metadata contention, append/finalize latency, or watchdog and stale-epoch drops.

Add exact atomically maintained queue, active-task, running-worker, configured-capacity, and active-stage gauges. Add reason-specific rejection and watchdog counters, submitted and persisted byte/block throughput, and latency recorders for submission, allocation, queue wait, worker processing, get-or-set, append, finalize, probing, read-plan construction, and write submission. Instrument inflight shard lock wait and hold time, and route probe/get-or-set locking through the existing BlockFileCache lock-wait metric.

Keep queue monitoring passive and exact instead of adding a sampling thread. Remove the unused AsyncCacheWriteService::stats snapshot API and use the service state and metrics directly in tests. Do not expose an inflight index metadata memory estimate because it can be mistaken for total payload memory; async_cache_write_buffer_memory_bytes remains the payload-memory metric.

Extend the BE unit tests to verify live queue and stage gauges, metric counters and latency samples, lock instrumentation, and the concurrent MPMC growth, rejection, scale-up, and drain flow.

### Release note

Add monitoring metrics for async file cache write queue pressure, worker activity, stage latency, throughput, rejection causes, and lock contention.

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh -j100 --run --filter=AsyncCacheWriteServiceTest.*:InflightWriteBufferIndexTest.*:BlockFileCacheTest.Probe*:AsyncCachedRemoteFileReaderTest.* (29 tests passed under ASAN_UT)
- Behavior changed: Yes. Adds observability only; async cache read and write semantics are unchanged.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The existing file-cache microbenchmark either includes object-store and network latency through CachedRemoteFileReader or stops at BlockFileCache::get_or_set. It cannot isolate phase-1 asynchronous writeback, distinguish caller return time from background drain, or expose queue saturation and inflight-index contention.

Add a standalone Release microbenchmark beside the existing tool. It combines a deterministic in-memory remote reader with a real filesystem-backed BlockFileCache and covers three layers: synchronous versus asynchronous cold-miss reader latency with complete-range persistence verification; producer admission, bounded MPMC queue behavior, worker scaling, backpressure, and real get_or_set/append/finalize persistence; and sharded miss/hit versus single-hot-key InflightWriteBufferIndex contention.

Each case emits machine-readable latency percentiles, throughput, accepted/rejected/persisted counts, and pending/queued/inflight high-water marks. Benchmark data defaults to output/ so it remains untracked and uses the larger workspace disk.

### Release note

None

### Check List (For Author)

- Test: Manual test
    - ./build.sh --be --file-cache-microbench -j100
    - Existing file_cache_get_or_set benchmark with 1 and 32 threads
    - Full async benchmark in all mode with 16 producers and worker counts 1,4,16
    - build-support/check-format.sh
    - git diff --check
- Behavior changed: No (benchmark tooling only)
- Does this need documentation: No (tool README updated)
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The asynchronous file-cache write microbenchmark previously emitted only one sample per case and did not establish the storage baseline of the cache filesystem. These short concurrent cases are sensitive to scheduler activity, page-cache state, filesystem metadata, and background writeback, so a single number can hide material variance and make worker-scaling conclusions unreliable.

Run every selected reader, service, and inflight-index case five times by default and add the one-based repetition to each machine-readable RESULT line. Add an installed runner that can measure direct 1 MiB sequential QD1 and random QD16 writes on the same filesystem before starting the benchmark.

The fio behavior is explicit and does not make fio a mandatory dependency:

| RUN_FIO | fio available | Behavior |
| --- | --- | --- |
| auto (default) | Yes | Run both disk baselines, then run the cache benchmark |
| auto (default) | No | Print DISK_BASELINE skipped and continue directly with the cache benchmark |
| 1 | No | Fail because the caller explicitly required fio |
| 0 | Any | Skip fio and run the cache benchmark |

The runner uses a unique sibling directory under the selected cache path, unlinks fio data, and keeps direct I/O out of the page cache. The benchmark rejects non-empty cache paths instead of recursively clearing them, and suppresses INFO logging so merged stdout and stderr cannot corrupt RESULT records.

Expand the tool README with the component flow, coverage and non-goals of each group, default workload, field semantics, fio controls, cache-path ownership, repetition methodology, and interpretation guidance. Median is the primary value and the observed minimum and maximum are retained.

Release experiment configuration: /dev/nvme11n1 ext4, 1 MiB blocks, 64 KiB caller reads, 16 producers, 128 reader operations, 256 service attempts, and five repetitions.

fio direct-I/O baseline:

| Workload | Bandwidth | p95 completion latency |
| --- | ---: | ---: |
| 1 MiB sequential write, QD1 | 2513 MiB/s | 161 us |
| 1 MiB random write, QD16 | 3106 MiB/s | 10.552 ms |

CachedRemoteFileReader foreground results:

| Write mode | Median ops/s | Minimum ops/s | Maximum ops/s | Median average latency |
| --- | ---: | ---: | ---: | ---: |
| Synchronous | 5645 | 5124 | 7649 | 1459 us |
| Asynchronous | 6739 | 4739 | 8298 | 912 us |

The asynchronous median was 19.4% higher in throughput and 37.5% lower in average latency. The overlapping ranges are retained because they show why a single run is insufficient.

AsyncCacheWriteService verified completion results:

| Workers | Median MiB/s | Minimum MiB/s | Maximum MiB/s | Median drain time |
| ---: | ---: | ---: | ---: | ---: |
| 1 | 798 | 730 | 968 | 0.300 s |
| 4 | 1562 | 1260 | 1751 | 0.153 s |
| 16 | 7825 | 5255 | 13203 | 0.014 s |

These values measure buffered append and finalize completion without fsync. They are not durable-media throughput and are not directly comparable with the direct-I/O fio baseline.

Bounded backpressure results:

| Metric | Median | Minimum | Maximum |
| --- | ---: | ---: | ---: |
| Accepted tasks | 76 | 64 | 101 |
| Rejected tasks | 180 | 155 | 192 |
| Peak pending | 64 | 64 | 64 |
| Peak queued | 48 | 48 | 48 |
| Peak inflight | 65 | 65 | 67 |

Every accepted task was verified as persisted, and peak pending stayed at the configured limit.

InflightWriteBufferIndex lookup results:

| Workload | Median ops/s | Minimum ops/s | Maximum ops/s | Median average latency |
| --- | ---: | ---: | ---: | ---: |
| Sharded miss | 5.531M | 4.028M | 6.621M | 2.688 us |
| Sharded hit | 4.198M | 3.270M | 6.036M | 3.171 us |
| Hot-key hit | 1.104M | 1.090M | 1.268M | 13.701 us |

All 45 RESULT records were complete and parseable. All reader ranges and all accepted service tasks passed final BlockFileCache coverage verification.

### Release note

None

### Check List (For Author)

- Test: Manual test
    - ./build.sh --be --file-cache-microbench -j100 (Release)
    - ./output/be/bin/run-async-file-cache-write-microbench.sh --benchmark_mode=all --cache_path=./output/async_file_cache_write_microbench_repeat_5_clean --producer_threads=16 --reader_workers=16 --worker_counts=1,4,16 --repetitions=5
    - Non-empty cache-path rejection with sentinel preservation
    - build-support/clang-format.sh
    - build-support/check-format.sh
    - bash -n and shellcheck for the runner
    - git diff --check
- Behavior changed: No (benchmark tooling only)
- Does this need documentation: No (tool README updated)
### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: File writers allocate cache cells at the normal full block size before the final upload buffer size is known. The final partial block is shrunk to its actual byte count only during FileBlock::finalize(). An async cache read racing with that interval builds a logical tail block ending at file EOF, but BlockFileCache::probe required the cached right boundary to match it exactly. The mismatch aborted the BE in probe; the async read plan and materialization path also carried the same exact-range assumption.

Allow the final short probe slot to be covered by a larger preallocated cache block while preserving exact-size assertions for complete slots. Keep the async reader stricter by allowing the larger boundary only for the logical block that ends at the real file EOF. Add a focused probe unit test that reproduced the original fatal assertion and an async CachedRemoteFileReader test that exercises the complete EOF read flow. The reproducer aborted before the fix and both tests pass after it.

### Release note

Fix a BE crash when an async file-cache read races with preallocation of a partial final file block.

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=BlockFileCacheTest.ProbeAcceptsPreallocatedBlockCoveringFileTail:AsyncCachedRemoteFileReaderTest.preallocated_cache_block_can_cover_the_short_file_tail -j100 (ASAN, 2 tests passed)
- Behavior changed: Yes. Async cache reads now accept a full-size preallocated cache block that covers the short EOF block.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: A systematic review after the file-tail crash found that FileBlocksProbeResult reused FileBlocksHolder cleanup semantics. Destroying a read-only probe result on the same thread as an independently owned downloader therefore called complete_unlocked(), reset a valid DOWNLOADING block to EMPTY, and cleared its downloader. A focused BEUT reproduced that state transition before the fix.

Give holder and probe references explicit cleanup roles: holders still complete downloader ownership acquired through get_or_set(), while probes only retain blocks and perform the existing deferred EMPTY/deleting-cell cleanup. Also stop re-reading the mutable FileBlock range after probe() has validated slot coverage under the cache mutex; a concurrent file writer may shrink a preallocated EOF block during finalize(), so the async reader now consistently uses its immutable logical plan range for cache offsets and diagnostics.

The review added concise end-to-end coverage for a DOWNLOADING preallocated tail that finalizes while a reader waits, mixed existing-cache and inflight coverage, and operation with the optional inflight index disabled. The fixture now resets the process-wide FD cache together with FileCacheFactory because its key omits the per-test cache path; without that isolation, newly added cases exposed stale descriptors from earlier cases.

### Release note

Fix async file-cache read races involving read-only probe lifetime and concurrent finalization of a preallocated file-tail block.

### Check List (For Author)

- Test: Unit Test
    - Pre-fix reproduction: BlockFileCacheTest.ProbeResultDoesNotCompleteDownloaderOwnedByCaller failed because the block became EMPTY and its downloader was cleared
    - Targeted ASAN BEUT: 6 focused probe/EOF/cache-inflight/external-table cases passed with -j100
    - Relevant ASAN BEUT sweep: 58 of 60 passed and exposed two cross-case FDCache isolation failures; after the isolation fix, the complete affected AsyncCachedRemoteFileReaderTest suite passed 9 of 9 with -j100, while the other 51 relevant tests had already passed in the sweep
    - build-support/clang-format.sh, build-support/check-format.sh, and git diff --check passed
- Behavior changed: Yes. Read-only probes no longer complete downloader ownership, and async reads remain valid while a preallocated EOF block is finalized and shrunk.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: Enabling asynchronous file-cache writes could abort BE during cloud compaction or page reads when BlockFileCache::probe encountered an existing cache cell whose range did not match the logical async-read slot. The probe treated exact range alignment as an invariant, but generic get_or_set callers such as segment index-cache writers can legitimately create cells at arbitrary offsets and sizes. A cell beginning inside a slot triggered the left-boundary fatal check, while a cell beginning at the slot but ending early could trigger the adjacent right-boundary check.

Change the read-only probe to look up each logical slot by its exact start offset. Return only an exact slot-sized block, while retaining support for a full-size preallocated block covering the final short file tail. Treat all other valid cache layouts as misses so the async reader falls back to one remote read without crashing, and continue probing later aligned slots independently.

Add a low-level probe test covering both incompatible boundary shapes and preservation of a later aligned hit. Add an async CachedRemoteFileReader test proving an unaligned cached fragment falls back to the remote aligned range, returns correct data, and submits persistence without aborting.

### Release note

Fix a BE crash when asynchronous file-cache reads encounter valid cache blocks whose ranges do not align with async probe slots.

### Check List (For Author)

- Test: Unit Test
    - Pre-fix BEUT reproduced the fatal left-boundary check.
    - 23 related probe and asynchronous reader tests passed with -j100.
    - 2 final focused boundary and reader tests passed with -j100 after extending adjacent coverage.
    - build-support/check-format.sh passed.
- Behavior changed: Yes. Incompatible existing cache cells are treated as probe misses and read from remote instead of aborting BE.
- Does this need documentation: No. This is an internal correctness fix.
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: SegmentWriter::finalize closes an S3 segment asynchronously and classifies its index bytes through FileCacheAllocatorBuilder. The index range starts at the actual index offset, which is generally not aligned to the file-cache block size. While that holder is alive, a concurrent asynchronous cache reader can probe the same file using canonical block slots. The writer-created cell then starts inside the first probe slot and triggers the strict BlockFileCache::probe left-boundary check. The end-to-end reproduction produced an EMPTY INDEX cell at [113, 757] and aborted at the same file_block range assertion reported by cloud_p0.

The previous fix made probe treat incompatible existing cells as misses. Revert that behavior and restore the strict probe invariant. Instead, expand every FileCacheAllocatorBuilder request outward to the owning BlockFileCache block boundaries before get_or_set. This keeps metadata-only SegmentWriter allocations, S3 data-buffer allocations, and read-only probe slots on one canonical partition. File writers can still shrink the final downloaded block to the real EOF during finalize.

Add an end-to-end BEUT that constructs a real SegmentWriter, appends a block, executes SegmentWriter::finalize, pauses the asynchronous cache upload, and probes while the index holder is still alive. It checks the unaligned index input, aligned EMPTY INDEX block, successful strict probe, and the final aligned DOWNLOADED block after upload completion.

### Release note

Fix a BE crash when asynchronous file-cache reads race with unaligned SegmentWriter index-cache allocation.

### Check List (For Author)

- Test: Unit Test
    - Before the fix, the new SegmentWriter BEUT reproduced the exact left-boundary fatal check with cache range [113, 757].
    - The new end-to-end SegmentWriter file-cache alignment BEUT passed with -j100.
    - 18 related BlockFileCache probe, asynchronous CachedRemoteFileReader, and cloud file-cache tests passed with -j100.
    - build-support/check-format.sh passed.
- Behavior changed: Yes. FileCacheAllocatorBuilder now expands writer allocations to canonical cache block boundaries, and BlockFileCache::probe retains its strict aligned-slot contract.
- Does this need documentation: No. This is an internal correctness fix.
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65905

Problem Summary: SegmentWriter::finalize allocated a cache holder over the segment index range and changed every intersecting block to INDEX. The range can start inside a file-cache block and conflict with canonical async-read probe ranges or cross S3 multipart write boundaries. The previous workaround aligned all FileCacheAllocatorBuilder allocations and added a SegmentWriter alignment test, but that changes the behavior of every writer-side allocation to compensate for a holder whose only purpose is cache-type reclassification.

Follow the narrower solution from apache#65905: remove the post-finalize holder allocation and change_cache_type call. Segment cache blocks now retain the type selected by the file writer. Remove the allocator-alignment workaround, its accessor and comments, and the temporary synchronization hook and end-to-end alignment test. Keep the strict probe contract and the independent file-tail and downloader-ownership fixes.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=CloudFileCacheWriteIndexOnlyTest.* -j100 (3 tests passed)
    - build-support/check-format.sh
- Behavior changed: Yes. Segment cache blocks are no longer reclassified to INDEX after SegmentWriter finalization.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: The bounded async file-cache writer rejected newly downloaded blocks whenever every pending slot was occupied. After the caller rolled back the new inflight entry, adjacent reads of the same block could issue duplicate remote reads. Replace the per-producer MPMC queue with a mutex-protected global FIFO, add an opt-in drop_oldest policy that replaces only the oldest queued task, and finalize victims outside the queue lock while preserving pending, active, and inflight ownership. Keep reject_new as the default and add runtime validation, metrics, and benchmark coverage.

### Release note

Adds `async_file_cache_write_queue_full_policy` with `reject_new` (default) and `drop_oldest`.

### Check List (For Author)

- Test: Unit Test
    - ASAN BE unit tests for async cache write service, inflight index, block file cache, and cached remote reader
    - ASAN BE and async file cache write microbenchmark build
    - Async file cache write microbenchmark functional smoke test for both policies
    - BE clang-format and check-format
- Behavior changed: Yes. Operators can opt into replacing the oldest queued async cache write when the bounded queue is full; the default remains reject-new.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: The locked FIFO async file-cache writer supports both reject_new and drop_oldest, but the initial default retained reject_new. Make drop_oldest the production and service-option default so newly downloaded blocks remain available to adjacent reads under queue saturation, while keeping reject_new available through the dynamic configuration for rollback and A/B comparison.

### Release note

`async_file_cache_write_queue_full_policy` now defaults to `drop_oldest`. Set it to `reject_new` to restore the previous admission behavior.

### Check List (For Author)

- Test: No need to test (default-selection-only change; both policy implementations and runtime switching are covered by existing unit tests)
    - BE clang-format and check-format
- Behavior changed: Yes. Full async write queues now replace the oldest queued task by default.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: Async file-cache write admission exposed both reject_new and drop_oldest even though retaining the newest queued block is now the intended behavior. Remove the selectable policy and its dynamic configuration so a full queue always replaces the oldest queued task. Preserve bounded admission by rejecting only when every pending task is already active or a runtime limit reduction leaves pending above the new limit. Move the standalone async-write benchmark out of the HTTP microbenchmark directory.

### Release note

Remove `async_file_cache_write_queue_full_policy`. A full async file-cache write queue now always replaces its oldest queued task; submissions are rejected only when no queued victim exists or pending remains above a reduced runtime limit.

### Check List (For Author)

- Test: Unit Test
    - `./run-be-ut.sh --run --filter='AsyncCacheWriteServiceTest.*:AsyncCachedRemoteFileReaderTest.drop_oldest_keeps_new_block_for_adjacent_page_and_drops_old_victim' -j50` (22 tests passed, Release)
    - `./build.sh --be -j100` (Release)
    - `./build-support/clang-format.sh`
    - `./build-support/check-format.sh`
- Behavior changed: Yes. Full async write queues always preserve the newest accepted task by replacing the oldest queued task.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The async file-cache writer bounded pending work by task count, which did not express its actual memory ownership, while its queue-age watchdog could discard accepted writes. Bound queued plus active work by fixed cache-block buffer capacity, preserve FIFO drop-oldest admission for queued tasks, remove watchdog expiry, and use write_size as the valid prefix for the short physical EOF block. Add explicit read-plan contracts and invariants so aligned block partitioning, source classification, remote slicing, inflight publication, and task creation retain the same index and range semantics. Keep the microbenchmark wrapper in its source directory without changing build.sh or installing the wrapper.

### Release note

The async file-cache writer now uses async_file_cache_write_max_pending_bytes_per_disk, defaulting to 256 MiB per cache disk, instead of a pending-task count. Accepted writes are no longer discarded based on queue age.

### Check List (For Author)

- Test: Unit Test
    - ./build.sh --be -j100
    - ./run-be-ut.sh --run --filter=AsyncCacheWriteServiceTest.*:AsyncCachedRemoteFileReaderTest.*:BlockFileCacheTest.async_write_* -j 100 (37 tests passed)
- Behavior changed: Yes. Pending async writes are bounded by buffer-capacity bytes; a full queue evicts its oldest queued task; watchdog age drops are removed.
- Does this need documentation: Yes. The existing PR description and companion async-write design and observability documents need the new config, metric, and EOF-tail contracts.
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: Async write admission modeled replacement and each backpressure reason as separate submit results. In particular, lowering the mutable pending-memory limit below the current pending bytes rejected new submissions even when an oldest queued task could be replaced, which diverged from the FIFO drop-oldest policy and made try_submit harder to follow. Collapse admission outcomes to accepted, backpressure, and enqueue failure. Both a full service and a temporarily over-limit service now replace the oldest queued fixed-size task without increasing pending bytes, and return backpressure only when no queued victim exists or one task exceeds the limit.

### Release note

After the async file-cache write pending-memory limit is lowered at runtime, new writes continue by replacing the oldest queued task while queued work exists. Active tasks are never evicted.

### Check List (For Author)

- Test: Unit Test
    - ./build.sh --be -j100
    - ./run-be-ut.sh --run --filter=AsyncCacheWriteServiceTest.* -j 100 (22 tests passed)
- Behavior changed: Yes. Runtime pending-memory limit decreases use the same FIFO drop-oldest admission path as a full service.
- Does this need documentation: Yes. The async-write design and observability documentation should describe the runtime limit-decrease behavior and aggregate backpressure metrics.
### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: Async file-cache write testing needs query-side cache population to remain enabled while load and compaction outputs do not pre-populate file cache. Add a default-enabled BE switch that can disable cache population from normal S3 file uploads and packed small-file writes without changing object storage writes.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=S3FileWriterTest.DisableFileCacheWriteFromS3FileWriter:PackedFileManagerTest.DisableFileCacheWriteFromS3FileWriter -j100
- Behavior changed: No. The new switch defaults to enabled, preserving existing behavior.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The async file-cache write branch retained unrelated changes to build.sh and the existing file-cache microbenchmark README. Restore both files to the PR merge-base content so the branch remains scoped to async file-cache write behavior.

### Release note

None

### Check List (For Author)

- Test: No need to test. Both files are restored exactly to the PR merge-base and have no final PR diff.
- Behavior changed: No
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: Async cache write admission retained an artificial enqueue-failure path, duplicate queued-task state, and always-on invariant checks in hot paths. The batch-size option did not batch queue transfers or writes; it only delayed worker resize and shutdown checks. Simplify admission to direct locked FIFO insertion, derive queue size from the deque, retain conservation checks as debug assertions, and remove the ineffective batch-size configuration while preserving one active task per worker and FIFO drop-oldest behavior.

### Release note

Remove the ineffective `async_file_cache_write_batch_size` backend configuration. Async cache write workers continue to process one task at a time.

### Check List (For Author)

- Test: Unit Test
    - `./build.sh --be -j100`
    - `./run-be-ut.sh --run --filter='AsyncCacheWriteServiceTest.*:InflightWriteBufferIndexTest.*:AsyncCachedRemoteFileReaderTest.*' -j100` (36 tests passed)
    - `build-support/check-format.sh`
- Behavior changed: Yes. Remove the ineffective async cache write batch-size option; FIFO admission and drop-oldest semantics are unchanged.
- Does this need documentation: Yes. Async-write design and observability documentation should no longer describe a batch-size setting.
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: Async cache write workers used numeric worker IDs both as identity and resize control state. Each loop compared its ID with a global target count, while start, resize, and shutdown separately maintained a scheduled-bit vector and condition variable. Encapsulate submission, resize stop requests, and completion waiting in a Worker object. The service now resizes an owned worker collection directly, while task processing, one-task-at-a-time execution, FIFO admission, and drop-oldest behavior remain unchanged.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - `./build.sh --be -j100`
    - `./run-be-ut.sh --run --filter=AsyncCacheWriteServiceTest.* -j100` (21 tests passed)
    - `build-support/check-format.sh`
- Behavior changed: No. Worker lifecycle management is encapsulated without changing queue admission, task concurrency, resize, or shutdown behavior.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The async file cache write change moved assignment of a new runtime configuration value ahead of validation in the generic UPDATE_FIELD macro. This changes shared configuration behavior outside the feature scope. Restore the existing macro ordering so the async write implementation does not modify the generic runtime configuration framework.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - ./build.sh --be -j100
    - ./run-be-ut.sh --run --filter=AsyncCacheWriteServiceTest.MutableConfigUpdatesServicesExplicitly -j100
    - ./run-be-ut.sh --run --filter=ConfigTest.UpdateConfigs:ConfigOnUpdateTest.* -j100
    - build-support/check-format.sh
- Behavior changed: No. This restores the pre-existing generic runtime configuration update behavior.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The pending, queued, and active byte fields are maintained independently from their task-count counterparts, but their ownership relationship was not documented next to the state. Clarify that pending covers queued plus active ownership and that byte state remains the authoritative directly maintained input for memory admission, including the fixed-capacity EOF buffer contract.

### Release note

None

### Check List (For Author)

- Test: No need to test. Comment-only change.
    - clang-format --dry-run --Werror be/src/io/cache/async_cache_write_service.h
    - git diff --check
- Behavior changed: No
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The inflight write-buffer index exposed an ambiguously named size gauge but did not expose the buffer capacity retained by its entries. Rename the entry gauge to count, maintain a separate byte gauge across insertion, epoch replacement, stale removal, and conditional removal, and remove redundant queue invariant and post-shutdown terminal checks.

### Release note

Rename `inflight_write_buffer_index_size` to `inflight_write_buffer_index_count` and add `inflight_write_buffer_index_buffer_bytes`.

### Check List (For Author)

- Test: Unit Test / Manual test
    - `./build.sh --be --file-cache-microbench -j100`
    - Focused async cache write, inflight index, and cached reader BE unit tests with `./run-be-ut.sh ... -j100`
    - Async file cache write microbenchmark all-mode smoke run
    - Targeted `clang-format --dry-run --Werror` and `git diff --check`
- Behavior changed: Yes. The inflight index size metric is renamed and a retained-buffer byte metric is added; async write lifecycle behavior is unchanged.
- Does this need documentation: No
bobhan1 added 14 commits August 3, 2026 16:21
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The async File Cache write queue used a fixed 256 MiB pending-memory limit per cache disk, so its default did not scale on large-memory BEs. Raise the default absolute per-disk limit to 512 MiB and support -1 as an automatic mode that selects max(512 MiB, 1% of the BE memory limit). Positive values remain exact per-disk byte limits, and cache creation and runtime updates share the same resolver.

### Release note

async_file_cache_write_max_pending_bytes_per_disk now defaults to 512 MiB. Set it to -1 to use max(512 MiB, 1% of the BE memory limit) for each cache disk.

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=AsyncCacheWriteConfigTest.ResolveMaxPendingBytesPerDisk:AsyncCacheWriteServiceTest.MutableConfigUpdatesServicesExplicitly -j100
- Behavior changed: Yes. The default per-disk limit is 512 MiB, and -1 enables automatic sizing.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: Page-level prefetch needs an exact-range reader that can reuse already available File Cache and inflight bytes without creating cache cells or coupling range planning into AsyncCacheWriteService. Add per-call alignment and write-mode overrides plus a NO_WRITE and UNALIGNED path that selects INFLIGHT, DOWNLOADED, or exact remote intersections, skips DOWNLOADING blocks without waiting, preserves REMOTE_ONLY_ON_MISS semantics, and self-heals missing cache files.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=AsyncCachedRemoteFileReaderTest.no_write_unaligned* -j100 (9 tests passed)
    - ./run-be-ut.sh --run --filter=AsyncCachedRemoteFileReaderTest.* -j100 (19 tests passed)
- Behavior changed: No. The new path is reachable only through an explicit per-call override; existing aligned readers retain Phase 1 behavior.
- Does this need documentation: Yes. The Phase 2 design documents are maintained in the doris-io documentation repository.
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: Page-range prefetch will already own complete on-disk page bytes, but PageIO previously coupled checksum, footer parsing, decompression, pre-decoding, and page-cache insertion to a synchronous FileReader read. Extract the common decode path, add a slice entry that never borrows the range buffer after returning, preserve the old owned-buffer path, validate footer bounds, and expose a statistics-neutral page-cache precheck for future prefetch planning.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=PageIOSliceDecodeTest.* -j100 (6 tests passed)
    - ./run-be-ut.sh --run --filter=PageIOSliceDecodeTest.*:ColumnReaderTest.NullMapOnlyReadBySparseRowidsAcrossPages:OrdinalPageIndexTest.*:ColumnZoneMapTest.NormalTestIntPage -j100 (10 tests passed)
- Behavior changed: No. Existing PageIO callers retain the file-read path; the new slice entry is not wired into query execution yet.
- Does this need documentation: Yes. The Phase 2 design documents are maintained in the doris-io documentation repository.
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: Page-range prefetch needs independently bounded buffers and a repeatable completion primitive without adding range or hole semantics to AsyncCacheWriteService. Add a monotonic PrefetchRange state machine, prompt waiter cancellation with worker-owned terminal completion, move-only query/global reservation tokens, separate active-range and resident-byte lifetimes, tracked PagePrefetchBuffer allocation, and weak range registration for query cancellation.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=PagePrefetchAdmissionTest.*:PagePrefetchRangeTest.* -j100 (10 tests passed)
- Behavior changed: No. The new admission and range primitives are not wired into query execution yet.
- Does this need documentation: Yes. The Phase 2 design documents are maintained in the doris-io documentation repository.
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: Phase 2 page prefetch needs to execute admitted exact-range reads on the existing segment prefetch pool without carrying query-thread raw pointers or coupling range semantics into AsyncCacheWriteService. Add a value-owned safe IO context, weak runtime-query cancellation, a query-context registry, non-owning shared-pool submission, exact NO_WRITE plus UNALIGNED workers, outstanding-task shutdown guards, and complete rollback for allocation, shutdown, and pool rejection paths.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=PagePrefetchAdmissionTest.*:PagePrefetchRangeTest.*:PagePrefetchSafeIOContextTest.*:PagePrefetchIOServiceTest.* -j100 (16 tests passed)
    - build-support/check-format.sh
- Behavior changed: No. The new service is not wired into query execution yet.
- Does this need documentation: Yes. The Phase 2 design documents are maintained in the doris-io documentation repository.
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: Page prefetch admission previously had no BE configuration source or ExecEnv-owned lifecycle. Add validated dynamic Phase 2 configuration, propagate live admission-budget updates through immutable snapshots, create the non-owning page prefetch service after the shared segment prefetch pool, and shut it down before the pool and file cache. Online limit reductions preserve existing reservations while rejecting new work until usage falls below the new limits.

### Release note

Add disabled-by-default BE configuration for query page prefetch and IO coalescing.

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=PagePrefetchAdmissionTest.*:PagePrefetchRangeTest.*:PagePrefetchSafeIOContextTest.*:PagePrefetchIOServiceTest.* -j100
    - build-support/check-format.sh
- Behavior changed: No. The feature remains disabled by default.
- Does this need documentation: Yes. The Phase 2 design documents are maintained in the companion doris-io documentation repository.
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: Phase 2 needs deterministic page candidate planning before any asynchronous IO is submitted. Add corruption-checked page metadata validation, a fixed forward/reverse ordinal window and exact rowid selector, and a pure PageReadPlanner that coalesces required page intervals only within configured gap, range-size, page-count, and read-amplification limits. The planner produces stable page-to-range slice mappings and contains no IO, cache-hole completion, or writeback logic.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=PageReadPlannerTest.*:FixedPagePrefetchWindowTest.* -j100
    - build-support/check-format.sh
- Behavior changed: No. The planner is not connected to query reads yet.
- Does this need documentation: Yes. The Phase 2 design documents are maintained in the companion doris-io documentation repository.
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: Phase 1 and Phase 2 need one fixed-block producer without coupling page-range or cache-hole logic into AsyncCacheWriteService. Extract final cache probing, fixed-capacity tracked-buffer allocation, EOF valid-prefix copying, epoch revalidation, inflight ownership, queue submission, and rejection rollback into FixedBlockAsyncWriteSubmitter. Migrate the existing CachedRemoteFileReader path to the helper while preserving its fixed-block FIFO service and caller statistics.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=FixedBlockAsyncWriteSubmitterTest.*:AsyncCachedRemoteFileReaderTest.* -j100
    - ./run-be-ut.sh --run --filter=FixedBlockAsyncWriteSubmitterTest.*:AsyncCachedRemoteFileReaderTest.concurrent_cold_reads_publish_only_one_async_write_task -j100
    - build-support/check-format.sh
- Behavior changed: No. Phase 1 retains fixed-block asynchronous cache writes and Phase 2 is not connected yet.
- Does this need documentation: Yes. The Phase 2 design documents are maintained in the companion doris-io documentation repository.
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: Phase 2 needs to complete sufficiently dense file-cache blocks without exposing hole intervals or variable-size ranges to AsyncCacheWriteService. Add a pure FileCacheWritebackCoordinator planner that computes per-block page coverage, handles physical EOF blocks, tentatively fills eligible holes, and rebuilds final ranges under gap, range-size, page-count, and read-amplification limits. Rejected block completion removes only the optional holes and preserves all required page reads, while output byte accounting keeps requested pages, block fill, and coalesced gaps disjoint.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=FileCacheWritebackCoordinatorTest.*:PageReadPlannerTest.*:FixedPagePrefetchWindowTest.* -j100
    - build-support/check-format.sh
- Behavior changed: No. The coordinator is not connected to query reads or background writeback yet.
- Does this need documentation: Yes. The Phase 2 design documents are maintained in the companion doris-io documentation repository.
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: Phase 2 range reads can contain complete file-cache blocks, but writing them back on the query thread would add a fixed-block copy to the foreground path and coupling range semantics into AsyncCacheWriteService would violate its fixed-block contract. Capture immutable writeback identity and epoch with each range, track per-block usefulness and corruption state, reserve temporary Phase 2 capacity nonblockingly, and schedule only complete blocks associated with successfully consumed pages on the shared prefetch pool. The worker rechecks cancellation, runtime gates, invalidation, and epoch before passing the complete slice to FixedBlockAsyncWriteSubmitter; rejected work is terminally skipped and never affects query results.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=PagePrefetchIOServiceTest.*:PagePrefetchRangeTest.*:PagePrefetchAdmissionTest.*:FileCacheWritebackCoordinatorTest.*:FixedBlockAsyncWriteSubmitterTest.* -j100
    - build-support/check-format.sh
- Behavior changed: No. Writeback scheduling is not connected to ColumnReader consumption yet.
- Does this need documentation: Yes. The Phase 2 design documents are maintained in the companion doris-io documentation repository.
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: Phase 2 had pure page-window and range planners but no per-column owner that could connect speculative ranges to later page consumption without blocking preparation. Add a PagePrefetcher state machine that snapshots one physical column metadata set, filters already tracked or page-cache-resident pages, submits range reads nonblockingly, waits only when a requested page is consumed, and records terminal fallback, consumption, skipped lookahead, cancellation, and one-time range statistics. Successful slice consumption and decode failure are forwarded to the isolated file-cache writeback coordinator.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=PagePrefetchIOServiceTest.*:PageReadPlannerTest.*:FixedPagePrefetchWindowTest.* -j100
    - build-support/check-format.sh
- Behavior changed: No. The state machine is not connected to FileColumnIterator yet.
- Does this need documentation: Yes. The Phase 2 design documents are maintained in the companion doris-io documentation repository.
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: Connect FileColumnIterator to the Phase 2 page prefetch pipeline. Build page candidates lazily from the ordinal index, submit exact read windows through the independent prefetch service, decode owned range slices through PageIO, and fall back to the normal page read path on admission, I/O, or decode failure. Preserve the existing SegmentPrefetcher path when Phase 2 is ineligible and keep AsyncCacheWriteService fixed-block-only.

### Release note

Improve cloud query cold reads by allowing column iterators to consume asynchronously prefetched page ranges.

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=PagePrefetch*:PageReadPlannerTest.*:FixedPagePrefetchWindowTest.* -j100
    - build-support/check-format.sh
- Behavior changed: Yes. Eligible cloud query column iterators can consume Phase 2 prefetched page slices and fall back to normal reads on failure.
- Does this need documentation: Yes. The Phase 2 design documents are maintained in the companion doris-io documentation repository.
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: Preserve physical row spaces when page prefetch crosses struct, array, and map iterators. Route outer row requests only to physical metadata columns, derive exact item-space ranges after decoding array and map offsets, and prepare every key/value range before child consumption. Skip inactive predicate or lazy branches and keep AsyncCacheWriteService fixed-block-only.

### Release note

Improve cloud query cold reads for struct, array, and map columns through correctly scoped page prefetch requests.

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run '--filter=ColumnReaderTest.PagePrefetchPreparationPreservesComplexColumnRowidSpaces:ColumnReaderTest.ArrayPreparesDecodedItemRangeBeforeConsumption:ColumnReaderTest.MapPreparesAllDecodedItemRangesBeforeConsumption' -j100
    - ./run-be-ut.sh --run '--filter=ColumnReaderTest.*' -j100
    - build-support/check-format.sh

- Behavior changed: Yes. Eligible complex column reads now prepare physical metadata and decoded child-item ranges before consuming pages.

- Does this need documentation: Yes. The Phase 2 design documents are maintained in the companion doris-io documentation repository.
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: Connect eligible query column iterators to the independent Phase 2 IO service and add two-pass SegmentIterator planning. Filter actual predicate, selected-rowid, and lazy work first, submit every column prefetch request without waiting, and only then consume columns in the original order. Preserve exact physical rowids, use direction-aware ordinal ranges for forward and reverse scans, and leave non-query, pruned, Variant, and legacy paths unchanged.

### Release note

Improve cloud query cold-read concurrency by preparing page ranges across columns before decoding the first column.

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run '--filter=SegmentIteratorLazyPrunedTest.*' -j100
    - ./run-be-ut.sh --run '--filter=PagePrefetch*:PageReadPlannerTest.*:FixedPagePrefetchWindowTest.*:ColumnReaderTest.*:SegmentIteratorLazyPrunedTest.*' -j100
    - ./run-be-ut.sh --run '--filter=SegmentIterator*' -j100
    - build-support/check-format.sh

- Behavior changed: Yes. Eligible cloud query batches now fan out page prefetch work across actual columns before ordered consumption.

- Does this need documentation: Yes. The Phase 2 design documents are maintained in the companion doris-io documentation repository.
@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

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.

2 participants