github-actions[bot] commented on code in PR #66504:
URL: https://github.com/apache/doris/pull/66504#discussion_r3746335429


##########
be/src/io/cache/cached_remote_file_reader.cpp:
##########
@@ -1050,6 +1111,98 @@ Status 
CachedRemoteFileReader::_read_remaining_blocks_from_cache(
     return Status::OK();
 }
 
+Status CachedRemoteFileReader::read_at_from_cache(size_t offset, Slice result, 
size_t* bytes_read,
+                                                  bool* cache_hit, const 
IOContext* io_ctx) {
+    IOContext default_io_ctx;
+    if (io_ctx == nullptr) {
+        io_ctx = &default_io_ctx;
+    }
+    const auto read_type =
+            io_ctx->is_inverted_index
+                    ? FileCacheReadType::INVERTED_INDEX
+                    : (io_ctx->is_index_data ? 
FileCacheReadType::SEGMENT_FOOTER_INDEX
+                                             : FileCacheReadType::DATA);
+    auto publish_stats = [&](const ReadStatistics& stats,
+                             const SourceReadBreakdown& source_read_breakdown) 
{
+        if (io_ctx->is_dryrun) {
+            return;
+        }
+        if (io_ctx->file_cache_stats != nullptr) {
+            _update_stats(stats, source_read_breakdown, 
io_ctx->file_cache_stats, read_type);
+        }
+        if (!io_ctx->is_warmup) {
+            FileCacheStatistics increment;
+            _update_stats(stats, source_read_breakdown, &increment, read_type);
+            FileCacheMetrics::instance().update(&increment);
+        }
+    };
+    *bytes_read = 0;
+    *cache_hit = false;
+    const size_t bytes_req = std::min(result.size, size() - std::min(offset, 
size()));
+    if (bytes_req == 0) {
+        *cache_hit = true;
+        return Status::OK();
+    }
+    if (_read_from_memory_block_cache(offset, Slice(result.data, bytes_req))) {
+        *bytes_read = bytes_req;
+        *cache_hit = true;
+        ReadStatistics stats;
+        stats.bytes_read = cast_set<int64_t>(bytes_req);
+        SourceReadBreakdown source_read_breakdown;
+        source_read_breakdown.local_bytes = cast_set<int64_t>(bytes_req);
+        publish_stats(stats, source_read_breakdown);
+        return Status::OK();
+    }
+
+    ReadStatistics stats;
+    SourceReadBreakdown source_read_breakdown;
+    stats.bytes_read = cast_set<int64_t>(bytes_req);
+    const size_t block_size = 
cast_set<size_t>(config::file_cache_each_block_size);
+    const size_t align_left = offset / block_size * block_size;
+    const size_t request_end = offset + bytes_req;
+    const size_t align_end =
+            std::min((request_end + block_size - 1) / block_size * block_size, 
size());
+    const size_t align_size = align_end - align_left;
+    CacheContext cache_context(io_ctx);
+    cache_context.stats = &stats;
+    MonotonicStopWatch sw;
+    sw.start();
+    FileBlocksHolder holder =
+            _cache->get_or_set(_cache_hash, align_left, align_size, 
cache_context);
+    stats.cache_get_or_set_timer += sw.elapsed_time();
+    if (std::ranges::any_of(holder.file_blocks, [](const auto& block) {

Review Comment:
   [P2] Avoid repeating a mutating cache lookup on every miss
   
   This `get_or_set()` is not a read-only coverage probe: it can create/touch 
cache cells and updates BlockFileCache lookup/hit metrics. Returning here drops 
its query-side timers, after which MergeRange falls back through 
`CachedRemoteFileReader::read_at()` and performs the same `get_or_set()` again. 
Partial coverage and eviction-after-copy take the same split-accounting path. 
Please use a non-mutating exact-coverage lookup or carry this holder/result 
into fallback so one logical read performs one cache lookup and publishes its 
costs once.



##########
be/src/io/fs/buffered_reader.cpp:
##########
@@ -45,6 +45,38 @@ namespace doris {
 namespace io {
 struct IOContext;
 
+Status MergeRangeFileReader::add_random_access_ranges(const 
std::vector<PrefetchRange>& ranges,
+                                                      uint32_t stage) {
+    for (const auto& range : ranges) {
+        if (range.start_offset >= range.end_offset) {
+            return Status::InvalidArgument("Invalid merge-read range [{}, 
{})", range.start_offset,
+                                           range.end_offset);
+        }
+        auto it = std::lower_bound(_random_access_ranges.begin(), 
_random_access_ranges.end(),
+                                   range.start_offset,
+                                   [](const PrefetchRange& lhs, size_t 
start_offset) {
+                                       return lhs.start_offset < start_offset;
+                                   });
+        const size_t index = static_cast<size_t>(it - 
_random_access_ranges.begin());
+        if (it != _random_access_ranges.end() && it->start_offset == 
range.start_offset) {
+            if (it->end_offset != range.end_offset) {
+                return Status::InvalidArgument("Overlapping merge-read 
ranges");
+            }
+            _range_stages[index] = std::min(_range_stages[index], stage);
+            continue;
+        }
+        if ((it != _random_access_ranges.begin() &&

Review Comment:
   [P1] Accept compatible PARQUET-816 padded overlaps
   
   These ranges come from `compute_column_chunk_range(..., 
parquet_816_padding=true)`, which deliberately extends every affected pre-1.2.9 
parquet-mr chunk by up to 100 bytes. Adjacent chunks can therefore have valid 
overlapping extents. On a remote/exact-cache scan with two predicate leaves, 
activating the second range reaches this branch and returns `InvalidArgument`, 
aborting a file that the native reader otherwise supports. Please 
normalize/coalesce these compatibility ranges (while preserving their 
per-column cursor contract) instead of rejecting the overlap, and cover an 
old-writer adjacent-chunk case.



##########
be/benchmark/parquet/benchmark_parquet_decoder.hpp:
##########
@@ -626,7 +625,7 @@ inline void run_decoder(benchmark::State& state, 
DecoderScenario scenario, int s
 
 inline bool register_decoder_benchmarks() {
     for (const auto& scenario : decoder_scenarios()) {
-        for (const int selectivity : {0, 1, 10, 50, 90, 100}) {
+        for (const int selectivity : {0, 1, 5, 10, 50, 90, 100}) {

Review Comment:
   [P2] Keep the required benchmark guide in sync
   
   This seventh selectivity changes the decoder registration count from 228 to 
266, as the updated invariant test confirms. However 
`be/benchmark/parquet/AGENTS.md` still tells maintainers to expect 228 and 
still describes only 0/1/10/50/90/100 at lines 51, 137-138, and 353. Please 
update those required build/list/coverage instructions together with this loop.



##########
be/src/exec/scan/file_scanner_v2.cpp:
##########
@@ -1095,7 +1103,10 @@ void FileScannerV2::update_realtime_counters() {
     
_state->get_query_ctx()->resource_ctx()->io_context()->update_scan_bytes_from_remote_storage(
             deltas.scan_bytes_from_remote_storage);
 
-    COUNTER_SET(_file_read_bytes_counter, bytes_read);
+    // Scanner instances share the profile counter, so publishing an absolute 
value would erase
+    // bytes already reported by sibling scanners.
+    COUNTER_UPDATE(_file_read_bytes_counter,
+                   _cumulative_profile_delta(bytes_read, 
&_reported_file_read_bytes));
     COUNTER_SET(_file_read_calls_counter, 
cast_set<int64_t>(_file_reader_stats->read_calls));

Review Comment:
   [P2] Accumulate calls and time across sibling scanners too
   
   The comment above applies to all three counters: every FileScannerV2 created 
by one FileScanLocalState registers against the same scanner profile, and 
`add_counter()` returns the existing object. If scanner A publishes 100 
calls/10 ms and scanner B then publishes 40 calls/4 ms, these SETs leave 40/4 
rather than 140/14 (and close repeats the overwrite). Track per-scanner 
reported calls/time and UPDATE their deltas in both publication paths, as this 
patch now does for bytes.



##########
be/src/io/cache/cached_remote_file_reader.cpp:
##########
@@ -800,6 +800,67 @@ bool 
CachedRemoteFileReader::_try_read_from_cached_files_directly(
     return false;
 }
 
+bool CachedRemoteFileReader::_read_from_memory_block_cache(size_t offset, 
Slice result) {
+    std::lock_guard lock(_memory_block_cache_mutex);
+    auto it = _memory_block_cache.upper_bound(offset);
+    if (it == _memory_block_cache.begin()) {
+        return false;
+    }
+    --it;
+    const size_t relative_offset = offset - it->first;
+    if (relative_offset > it->second->size() ||
+        result.size > it->second->size() - relative_offset) {
+        return false;
+    }
+    memcpy(result.data, it->second->data() + relative_offset, result.size);
+    return true;
+}
+
+Status CachedRemoteFileReader::_read_local_block(const FileBlockSPtr& block, 
size_t file_offset,
+                                                 size_t absolute_offset, Slice 
result) {
+    if (_read_from_memory_block_cache(absolute_offset, result)) {
+        return Status::OK();
+    }
+    if (_is_doris_table) {
+        return block->read(result, file_offset);
+    }
+
+    bool promote = false;
+    {
+        std::lock_guard lock(_memory_block_cache_mutex);
+        auto& accesses = _memory_block_access_counts[block->offset()];
+        accesses = std::min<uint8_t>(accesses + 1, 
MEMORY_BLOCK_PROMOTION_ACCESSES);
+        promote = accesses == MEMORY_BLOCK_PROMOTION_ACCESSES;
+        if (_memory_block_access_counts.size() > 128) {
+            
_memory_block_access_counts.erase(_memory_block_access_counts.begin());
+        }
+    }
+    if (!promote || block->range().size() > MAX_MEMORY_BLOCK_CACHE_BYTES) {
+        return block->read(result, file_offset);
+    }
+
+    auto block_data = 
std::make_shared<std::vector<char>>(block->range().size());

Review Comment:
   [P1] Budget and account the promoted block copies globally
   
   A third small page access now reads and retains the entire FileCache block, 
but both callers attribute only the requested slice. The retained map is capped 
at 16 MiB per reader, not per query/backend; the default 16 file scanners in 
one local state can therefore hold another 256 MiB of duplicated FileCache 
data, with no pressure-driven cache eviction or retained/promotion profile. 
Please put promotion under an aggregate cache/query budget (or reuse a shared 
bounded cache), expose the full-block read/retained bytes, and add multi-reader 
pressure coverage.



##########
be/src/format_v2/parquet/parquet_scan.cpp:
##########
@@ -2310,6 +2368,9 @@ Status ParquetScanScheduler::read_filter_columns(int64_t 
batch_rows,
             const size_t idx = 
_predicate_indices_by_position_scratch.at(position);
             const auto& col = request.predicate_columns[idx];
             const auto fid = col.column_id();
+            if (_current_merge_range_reader != nullptr) {

Review Comment:
   [P1] Expose ranges before skipping later predicate readers
   
   This activates a column only when it is materialized. If an earlier staged 
predicate reduces the batch to zero, the early-exit path instead calls 
`skip_unmaterialized_predicate_columns()` for every later reader without 
exposing those ranges. `NativeColumnReader::skip()` still drives 
`read_with_filter(..., filter_all=true)` over selected rows, so the empty 
MergeRange plan delegates the page reads directly and loses 
coalescing/exact-cache handling on the most selective remote path. Activate the 
skipped columns as a stage before that loop (or provide a genuinely I/O-free 
skip contract), and add a scheduler-level all-filtered test.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to