Coverage Report

Created: 2026-07-15 13:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/io/cache/cached_remote_file_reader.cpp
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#include "io/cache/cached_remote_file_reader.h"
19
20
#include <brpc/controller.h>
21
#include <bthread/bthread.h>
22
#include <bthread/condition_variable.h>
23
#include <bthread/mutex.h>
24
#include <fmt/format.h>
25
#include <gen_cpp/Types_types.h>
26
#include <gen_cpp/internal_service.pb.h>
27
#include <glog/logging.h>
28
29
#include <algorithm>
30
#include <atomic>
31
#include <condition_variable>
32
#include <cstring>
33
#include <functional>
34
#include <list>
35
#include <memory>
36
#include <mutex>
37
#include <thread>
38
#include <vector>
39
40
#include "cloud/cloud_cluster_info.h"
41
#include "cloud/cloud_warm_up_manager.h"
42
#include "cloud/config.h"
43
#include "common/compiler_util.h" // IWYU pragma: keep
44
#include "common/config.h"
45
#include "common/metrics/doris_metrics.h"
46
#include "cpp/sync_point.h"
47
#include "io/cache/block_file_cache.h"
48
#include "io/cache/block_file_cache_factory.h"
49
#include "io/cache/block_file_cache_profile.h"
50
#include "io/cache/file_block.h"
51
#include "io/cache/peer_file_cache_reader.h"
52
#include "io/cache/remote_scan_cache_write_limiter.h"
53
#include "io/fs/file_reader.h"
54
#include "io/fs/local_file_system.h"
55
#include "io/io_common.h"
56
#include "runtime/exec_env.h"
57
#include "runtime/runtime_profile.h"
58
#include "runtime/thread_context.h"
59
#include "runtime/workload_management/io_throttle.h"
60
#include "service/backend_options.h"
61
#include "util/bit_util.h"
62
#include "util/brpc_client_cache.h" // BrpcClientCache
63
#include "util/bthread_utils.h"
64
#include "util/client_cache.h"
65
#include "util/concurrency_stats.h"
66
#include "util/debug_points.h"
67
#include "util/defer_op.h"
68
69
namespace doris::io {
70
71
bvar::Adder<uint64_t> s3_read_counter("cached_remote_reader_s3_read");
72
bvar::Adder<uint64_t> peer_read_counter("cached_remote_reader_peer_read");
73
bvar::LatencyRecorder g_skip_cache_num("cached_remote_reader_skip_cache_num");
74
bvar::Adder<uint64_t> g_skip_cache_sum("cached_remote_reader_skip_cache_sum");
75
bvar::Adder<uint64_t> g_skip_local_cache_io_sum_bytes(
76
        "cached_remote_reader_skip_local_cache_io_sum_bytes");
77
bvar::Adder<uint64_t> g_read_cache_direct_whole_num("cached_remote_reader_cache_direct_whole_num");
78
bvar::Adder<uint64_t> g_read_cache_direct_partial_num(
79
        "cached_remote_reader_cache_direct_partial_num");
80
bvar::Adder<uint64_t> g_read_cache_indirect_num("cached_remote_reader_cache_indirect_num");
81
bvar::Adder<uint64_t> g_read_cache_direct_whole_bytes(
82
        "cached_remote_reader_cache_direct_whole_bytes");
83
bvar::Adder<uint64_t> g_read_cache_direct_partial_bytes(
84
        "cached_remote_reader_cache_direct_partial_bytes");
85
bvar::Adder<uint64_t> g_read_cache_indirect_bytes("cached_remote_reader_cache_indirect_bytes");
86
bvar::Adder<uint64_t> g_read_cache_indirect_total_bytes(
87
        "cached_remote_reader_cache_indirect_total_bytes");
88
bvar::Adder<uint64_t> g_read_cache_self_heal_on_not_found(
89
        "cached_remote_reader_self_heal_on_not_found");
90
bvar::Window<bvar::Adder<uint64_t>> g_read_cache_indirect_bytes_1min_window(
91
        "cached_remote_reader_indirect_bytes_1min_window", &g_read_cache_indirect_bytes, 60);
92
bvar::Window<bvar::Adder<uint64_t>> g_read_cache_indirect_total_bytes_1min_window(
93
        "cached_remote_reader_indirect_total_bytes_1min_window", &g_read_cache_indirect_total_bytes,
94
        60);
95
bvar::Adder<uint64_t> g_failed_get_peer_addr_counter(
96
        "cached_remote_reader_failed_get_peer_addr_counter");
97
98
static std::atomic<int> g_active_peer_races {0};
99
bvar::PassiveStatus<int> g_active_peer_races_bvar(
100
        "peer_race_active_count",
101
3.29k
        [](void*) { return g_active_peer_races.load(std::memory_order_relaxed); }, nullptr);
102
// Cross-CG peer read race statistics
103
bvar::Adder<uint64_t> g_peer_race_peer_win("peer_race_peer_win");
104
bvar::Adder<uint64_t> g_peer_race_s3_win("peer_race_s3_win");
105
bvar::Adder<uint64_t> g_peer_race_both_fail("peer_race_both_fail");
106
bvar::Adder<uint64_t> g_peer_cross_compute_group_read("peer_cross_compute_group_read");
107
bvar::Adder<uint64_t> g_peer_same_compute_group_read("peer_same_compute_group_read");
108
bvar::Adder<uint64_t> g_peer_lazy_fetch_triggered("peer_lazy_fetch_triggered");
109
110
718k
static bool use_remote_only_on_cache_miss(const IOContext* io_ctx) {
111
718k
    if (io_ctx->file_cache_miss_policy == FileCacheMissPolicy::REMOTE_ONLY_ON_MISS) {
112
3
        return true;
113
3
    }
114
718k
    auto* limiter = io_ctx->remote_scan_cache_write_limiter;
115
718k
    return limiter != nullptr && limiter->remote_only_on_miss();
116
718k
}
117
118
CachedRemoteFileReader::CachedRemoteFileReader(FileReaderSPtr remote_file_reader,
119
                                               const FileReaderOptions& opts)
120
7.18k
        : _is_doris_table(opts.is_doris_table),
121
7.18k
          _tablet_id(opts.tablet_id),
122
7.18k
          _storage_resource_id(opts.storage_resource_id),
123
7.18k
          _remote_file_reader(std::move(remote_file_reader)) {
124
7.18k
    DCHECK(!_is_doris_table || _tablet_id > 0);
125
7.18k
    if (_is_doris_table) {
126
7.17k
        _init_doris_table_cache();
127
7.17k
    } else {
128
9
        _init_external_table_cache(opts);
129
9
    }
130
7.18k
}
131
132
7.17k
void CachedRemoteFileReader::_init_doris_table_cache() {
133
7.17k
    _cache_hash = BlockFileCache::hash(path().filename().native());
134
7.17k
    _cache = FileCacheFactory::instance()->get_by_path(_cache_hash);
135
7.17k
    if (_can_read_cache_file_directly()) {
136
        // this is designed for and test in doris table, external table need extra tests
137
19
        _cache_file_readers = _cache->get_blocks_by_key(_cache_hash);
138
19
    }
139
7.17k
}
140
141
10
void CachedRemoteFileReader::_init_external_table_cache(const FileReaderOptions& opts) {
142
    // Use path and modification time to build cache key.
143
10
    std::string unique_path = fmt::format("{}:{}", path().native(), opts.mtime);
144
10
    _cache_hash = BlockFileCache::hash(unique_path);
145
10
    if (opts.cache_base_path.empty()) {
146
        // If cache path is not specified by session variable, choose randomly.
147
1
        _cache = FileCacheFactory::instance()->get_by_path(_cache_hash);
148
1
        return;
149
1
    }
150
151
    // From query session variable: file_cache_base_path.
152
9
    _cache = FileCacheFactory::instance()->get_by_path(opts.cache_base_path);
153
9
    if (_cache != nullptr) {
154
8
        return;
155
8
    }
156
157
9
    LOG(WARNING) << "Can't get cache from base path: " << opts.cache_base_path
158
1
                 << ", using random instead.";
159
1
    _cache = FileCacheFactory::instance()->get_by_path(_cache_hash);
160
1
}
161
162
1.43M
bool CachedRemoteFileReader::_can_read_cache_file_directly() const {
163
1.43M
    return _is_doris_table && config::enable_read_cache_file_directly;
164
1.43M
}
165
166
5.18k
bool CachedRemoteFileReader::_should_read_from_peer(const IOContext* io_ctx) const {
167
5.18k
    return doris::config::is_cloud_mode() && _is_doris_table && _tablet_id > 0 &&
168
5.18k
           !io_ctx->is_warmup && !io_ctx->bypass_peer_read &&
169
5.18k
           doris::config::enable_cache_read_from_peer;
170
5.18k
}
171
172
717k
void CachedRemoteFileReader::_insert_file_reader(FileBlockSPtr file_block) {
173
717k
    if (_can_read_cache_file_directly()) {
174
36
        std::lock_guard lock(_mtx);
175
36
        DCHECK(file_block->state() == FileBlock::State::DOWNLOADED);
176
36
        file_block->_owned_by_cached_reader = true;
177
36
        _cache_file_readers.emplace(file_block->offset(), std::move(file_block));
178
36
    }
179
717k
}
180
181
7.18k
CachedRemoteFileReader::~CachedRemoteFileReader() {
182
7.18k
    for (auto& it : _cache_file_readers) {
183
57
        it.second->_owned_by_cached_reader = false;
184
57
    }
185
7.18k
    static_cast<void>(close());
186
7.18k
}
187
188
8.20k
Status CachedRemoteFileReader::close() {
189
8.20k
    return _remote_file_reader->close();
190
8.20k
}
191
192
std::pair<size_t, size_t> CachedRemoteFileReader::s_align_size(size_t offset, size_t read_size,
193
718k
                                                               size_t length) {
194
718k
    size_t left = offset;
195
718k
    size_t right = offset + read_size - 1;
196
718k
    size_t align_left =
197
718k
            (left / config::file_cache_each_block_size) * config::file_cache_each_block_size;
198
718k
    size_t align_right =
199
718k
            (right / config::file_cache_each_block_size + 1) * config::file_cache_each_block_size;
200
718k
    align_right = align_right < length ? align_right : length;
201
718k
    size_t align_size = align_right - align_left;
202
718k
    if (align_size < config::file_cache_each_block_size && align_left != 0) {
203
4
        align_size += config::file_cache_each_block_size;
204
4
        align_left -= config::file_cache_each_block_size;
205
4
    }
206
718k
    return std::make_pair(align_left, align_size);
207
718k
}
208
209
namespace {
210
struct PeerFetchLayout {
211
    std::vector<size_t> block_offsets;
212
    std::vector<size_t> block_sizes;
213
    size_t total_size = 0;
214
};
215
216
9
bool is_fill_not_found(const Status& st, bool request_fill) {
217
9
    return request_fill && st.is<ErrorCode::NOT_FOUND>();
218
9
}
219
220
48
bool contains_file_block(const PeerFetchedBlockSet& fetched_blocks, const FileBlockSPtr& block) {
221
48
    return fetched_blocks.contains(block.get());
222
48
}
223
224
5.25k
size_t clip_peer_block_size(const FileBlock::Range& range, size_t file_size) {
225
5.25k
    if (range.left >= file_size) {
226
0
        return 0;
227
0
    }
228
5.25k
    return std::min(file_size - range.left, range.size());
229
5.25k
}
230
231
PeerFetchLayout build_peer_fetch_layout(const std::vector<FileBlockSPtr>& blocks,
232
5.18k
                                        size_t file_size) {
233
5.18k
    PeerFetchLayout layout;
234
5.18k
    layout.block_offsets.reserve(blocks.size());
235
5.18k
    layout.block_sizes.reserve(blocks.size());
236
5.25k
    for (const auto& block : blocks) {
237
5.25k
        const size_t block_size = clip_peer_block_size(block->range(), file_size);
238
5.25k
        layout.block_offsets.push_back(layout.total_size);
239
5.25k
        layout.block_sizes.push_back(block_size);
240
5.25k
        layout.total_size += block_size;
241
5.25k
    }
242
5.18k
    return layout;
243
5.18k
}
244
245
Status write_peer_payloads_into_block(const FileBlockSPtr& block,
246
                                      std::vector<const PeerFetchChunk*>& chunks,
247
40
                                      size_t* block_size) {
248
40
    if (block_size == nullptr) {
249
0
        return Status::InvalidArgument("peer block write requires non-null block_size");
250
0
    }
251
40
    *block_size = 0;
252
40
    if (chunks.empty()) {
253
0
        return Status::OK();
254
0
    }
255
40
    std::sort(chunks.begin(), chunks.end(),
256
40
              [](const PeerFetchChunk* lhs, const PeerFetchChunk* rhs) {
257
0
                  return lhs->block_offset < rhs->block_offset;
258
0
              });
259
40
    butil::IOBuf payload;
260
40
    for (const auto* chunk : chunks) {
261
40
        *block_size += chunk->payload.length();
262
40
        payload.append(chunk->payload);
263
40
    }
264
40
    DCHECK(*block_size != 0);
265
40
    return block->append_iobuf(payload);
266
40
}
267
268
void copy_peer_chunk_to_result(const PeerFetchChunk& chunk, size_t offset, size_t right_offset,
269
                               size_t already_read, Slice result, size_t& indirect_read_bytes,
270
37
                               SourceReadBreakdown& source_read_breakdown) {
271
37
    const size_t payload_size = chunk.payload.length();
272
37
    if (payload_size == 0) {
273
0
        return;
274
0
    }
275
37
    const size_t chunk_left = chunk.block_offset;
276
37
    const size_t chunk_right = chunk_left + payload_size - 1;
277
37
    const size_t copy_left_offset = std::max(offset + already_read, chunk_left);
278
37
    const size_t copy_right_offset = std::min(right_offset, chunk_right);
279
37
    if (copy_left_offset > copy_right_offset) {
280
0
        return;
281
0
    }
282
37
    const size_t copy_offset = copy_left_offset - chunk_left;
283
37
    const size_t copy_size = copy_right_offset - copy_left_offset + 1;
284
37
    char* dst = result.data + (copy_left_offset - offset);
285
37
    chunk.payload.copy_to(dst, copy_size, copy_offset);
286
37
    indirect_read_bytes += copy_size;
287
37
    source_read_breakdown.peer_bytes += copy_size;
288
37
}
289
290
// Execute peer read targeting a specific host:port.
291
Status execute_peer_read(const std::vector<FileBlockSPtr>& empty_blocks,
292
                         PeerFetchResult* peer_result, const std::string& file_path,
293
                         size_t file_size, bool is_doris_table, ReadStatistics& stats,
294
12
                         const IOContext* io_ctx, const std::string& host, int32_t port) {
295
12
    VLOG_DEBUG << "PeerFileCacheReader read from peer, host=" << host << ", port=" << port
296
0
               << ", file_path=" << file_path;
297
298
12
    if (host.empty() || port == 0) {
299
0
        g_failed_get_peer_addr_counter << 1;
300
0
        LOG_EVERY_N(WARNING, 100) << "PeerFileCacheReader host or port is empty"
301
0
                                  << ", host=" << host << ", port=" << port
302
0
                                  << ", file_path=" << file_path;
303
0
        return Status::InternalError<false>("host or port is empty");
304
0
    }
305
12
    SCOPED_RAW_TIMER(&stats.peer_read_timer);
306
12
    peer_read_counter << 1;
307
12
    PeerFileCacheReader peer_reader(file_path, is_doris_table, host, port);
308
    // Serial peer read: source BE has the data from rebalance warm-up; no fill needed.
309
12
    auto st = peer_reader.fetch_blocks(empty_blocks, peer_result, file_size, io_ctx,
310
12
                                       /*request_fill=*/false);
311
12
    if (!st.ok()) {
312
4
        LOG_WARNING("PeerFileCacheReader read from peer failed")
313
4
                .tag("host", host)
314
4
                .tag("port", port)
315
4
                .tag("error", st.msg());
316
4
    }
317
12
    stats.from_peer_cache = st.ok();
318
12
    return st;
319
12
}
320
321
// Execute S3 read
322
Status execute_s3_read(size_t empty_start, size_t& size, std::unique_ptr<char[]>& buffer,
323
                       ReadStatistics& stats, const IOContext* io_ctx,
324
5.16k
                       FileReaderSPtr remote_file_reader) {
325
5.16k
    s3_read_counter << 1;
326
5.16k
    SCOPED_RAW_TIMER(&stats.remote_read_timer);
327
5.16k
    stats.from_peer_cache = false;
328
5.16k
    return remote_file_reader->read_at(empty_start, Slice(buffer.get(), size), &size, io_ctx);
329
5.16k
}
330
331
18
CloudWarmUpManager& get_warm_up_manager() {
332
18
    return ExecEnv::GetInstance()->storage_engine().to_cloud().cloud_warm_up_manager();
333
18
}
334
335
// Shared state for peer-vs-S3 winner race.
336
// Uses bthread primitives — never std::mutex/condition_variable in bthread context.
337
struct RaceState {
338
    bthread::Mutex mtx;
339
    bthread::ConditionVariable cv;
340
    int winner = -1; // 0=peer won, 1=s3 won, -1=undecided, -2=both failed
341
    bool peer_done = false;
342
    bool s3_done = false;
343
    Status peer_status;
344
    Status s3_status;
345
    std::unique_ptr<char[]> s3_buf;
346
    PeerFetchResult peer_res;
347
    std::string peer_winner_cg_id; // compute_group_id of the winning peer candidate
348
    std::string peer_winner_host;  // host of the winning peer candidate
349
    int64_t peer_elapsed_ns = 0;   // wall-clock time of the entire peer path (including retries)
350
    int64_t peer_winner_io_ns = 0; // I/O time of the winning candidate only
351
};
352
353
// Peer race logic: try candidates sequentially until one succeeds or all fail.
354
// NOTE: Do NOT capture io_ctx here — it points into the caller's stack which may be destroyed
355
// when S3 wins the race and the caller returns before this bthread finishes.
356
void run_peer_race(std::shared_ptr<RaceState> race, std::vector<FileBlockSPtr> empty_blocks,
357
                   const std::string& file_path, size_t file_sz, bool is_doris,
358
                   std::shared_ptr<CloudWarmUpManager> manager,
359
                   std::vector<doris::PeerCandidate> candidates, int64_t tablet_id,
360
14
                   std::string resource_id, std::shared_ptr<ResourceContext> parent_resource_ctx) {
361
14
    std::unique_ptr<AttachTask> attach_task;
362
14
    if (parent_resource_ctx != nullptr) {
363
3
        attach_task = std::make_unique<AttachTask>(parent_resource_ctx);
364
3
    }
365
366
14
    bool all_tried = true;
367
14
    MonotonicStopWatch peer_sw;
368
14
    peer_sw.start();
369
370
22
    for (size_t i = 0; i < candidates.size(); ++i) {
371
        // Before issuing the next RPC, check if S3 already won.
372
17
        if (i > 0) {
373
3
            TEST_SYNC_POINT("run_peer_race::between_candidates");
374
3
            std::unique_lock<bthread::Mutex> lk(race->mtx);
375
3
            if (race->winner > 0) {
376
                // S3 already won — stop, but not all candidates were tried.
377
1
                all_tried = false;
378
1
                break;
379
1
            }
380
3
        }
381
382
16
        const auto& cand = candidates[i];
383
16
        peer_read_counter << 1;
384
16
        PeerFileCacheReader peer_reader(file_path, is_doris, cand.host, cand.brpc_port);
385
16
        PeerFetchResult local_peer_res;
386
16
        const bool request_fill =
387
16
                !config::peer_cache_fill_compute_group_id.empty() &&
388
16
                cand.compute_group_id == config::peer_cache_fill_compute_group_id &&
389
16
                !resource_id.empty() && !file_path.empty();
390
16
        MonotonicStopWatch cand_sw;
391
16
        cand_sw.start();
392
16
        auto st = peer_reader.fetch_blocks(empty_blocks, &local_peer_res, file_sz,
393
16
                                           /*ctx=*/nullptr, request_fill, tablet_id, resource_id);
394
16
        if (st.ok()) {
395
7
            manager->update_peer_candidate_on_success(tablet_id, cand.compute_group_id);
396
7
            std::unique_lock<bthread::Mutex> lk(race->mtx);
397
7
            if (race->winner < 0) {
398
7
                race->winner = 0;
399
7
                race->peer_res = std::move(local_peer_res);
400
7
                race->peer_winner_cg_id = cand.compute_group_id;
401
7
                race->peer_winner_host = cand.host;
402
7
                race->peer_elapsed_ns = peer_sw.elapsed_time();
403
7
                race->peer_winner_io_ns = cand_sw.elapsed_time();
404
7
            }
405
7
            race->peer_done = true;
406
7
            race->peer_status = Status::OK();
407
7
            race->cv.notify_all();
408
7
            return;
409
7
        }
410
411
        // Handle per-candidate failure.
412
9
        if (st.template is<ErrorCode::TOO_MANY_TASKS>()) {
413
0
            all_tried = false;
414
0
            break;
415
0
        }
416
9
        if (is_fill_not_found(st, request_fill)) {
417
            // Pull-through fill already told us this designated fill CG could not serve the block
418
            // in time. Do not serially retry additional candidates in the same race; let S3 win
419
            // instead of paying more peer RPC latency.
420
1
            manager->rotate_peer_candidate_on_cache_miss(tablet_id, cand.host, cand.brpc_port);
421
1
            all_tried = false;
422
1
            break;
423
1
        }
424
8
        if (st.template is<ErrorCode::NOT_FOUND>()) {
425
5
            manager->rotate_peer_candidate_on_cache_miss(tablet_id, cand.host, cand.brpc_port);
426
5
        } else {
427
3
            manager->update_peer_candidate_on_rpc_failure(tablet_id, cand.host, cand.brpc_port);
428
3
        }
429
8
    }
430
431
7
    if (all_tried) {
432
5
        manager->record_peer_all_miss(tablet_id);
433
5
    }
434
7
    std::unique_lock<bthread::Mutex> lk(race->mtx);
435
7
    race->peer_done = true;
436
7
    race->peer_status = Status::InternalError<false>("peer: all candidates failed");
437
7
    if (race->winner < 0 && race->s3_done) {
438
0
        race->winner = race->s3_status.ok() ? 1 : -2;
439
0
    }
440
7
    race->cv.notify_all();
441
7
}
442
443
// Apply hedge delay, then submit S3 read to the thread pool (or run inline).
444
void launch_s3_race(std::shared_ptr<RaceState> race, size_t empty_start, size_t span_size,
445
                    const IOContext* io_ctx, FileReaderSPtr remote_reader,
446
                    std::shared_ptr<ResourceContext> parent_resource_ctx,
447
14
                    std::shared_ptr<CachedRemoteFileReader> owner) {
448
    // Raw S3 read body.
449
    // `owner` keeps the CachedRemoteFileReader alive until the S3 task finishes,
450
    // preventing close() from being called on remote_reader while we are still reading.
451
    // Do NOT capture io_ctx: it points into the caller's stack/iterator which may be
452
    // destroyed when the query is cancelled before this background task runs. The S3
453
    // leg of the race is a best-effort background task whose result is discarded if the
454
    // peer wins; passing nullptr is safe because S3FileReader::read_at_impl ignores it.
455
14
    auto do_s3_read = [race, empty_start, span_size, remote_reader, owner]() {
456
8
        (void)owner;
457
8
        auto s3_buf = std::make_unique<char[]>(span_size);
458
8
        size_t read_size = span_size;
459
8
        s3_read_counter << 1;
460
8
        TEST_SYNC_POINT("CachedRemoteFileReader::_execute_winner_race::s3_before_read");
461
8
        auto st = remote_reader->read_at(empty_start, Slice(s3_buf.get(), span_size), &read_size,
462
8
                                         nullptr);
463
8
        std::unique_lock<bthread::Mutex> lk(race->mtx);
464
8
        race->s3_done = true;
465
8
        race->s3_status = st;
466
8
        if (st.ok() && race->winner < 0) {
467
7
            race->winner = 1;
468
7
            race->s3_buf = std::move(s3_buf);
469
7
        }
470
8
        race->cv.notify_all();
471
8
    };
472
473
    // Hedge delay: give peer a head start, but wake up early if peer finishes.
474
    // Uses cv.wait_for() instead of bthread_usleep() so the calling thread is
475
    // unblocked as soon as the peer bthread signals completion, avoiding the
476
    // unconditional 20ms sleep that dominated latency on cache-miss-heavy queries.
477
14
    bool peer_already_won = false;
478
14
    if (config::peer_race_hedge_delay_ms > 0) {
479
13
        std::unique_lock<bthread::Mutex> lk(race->mtx);
480
13
        if (!race->peer_done) {
481
13
            race->cv.wait_for(lk, static_cast<long>(config::peer_race_hedge_delay_ms) * 1000);
482
13
        }
483
13
        peer_already_won = (race->winner == 0);
484
13
        if (peer_already_won) {
485
6
            race->s3_done = true;
486
6
            race->s3_status = Status::InternalError<false>("skipped: peer won during hedge delay");
487
6
        }
488
13
    }
489
490
14
    if (!peer_already_won) {
491
8
        auto s3_fn = [do_s3_read, parent_resource_ctx]() mutable {
492
7
            std::unique_ptr<AttachTask> attach_task;
493
7
            if (parent_resource_ctx != nullptr) {
494
1
                attach_task = std::make_unique<AttachTask>(parent_resource_ctx);
495
1
            }
496
7
            do_s3_read();
497
7
        };
498
8
        auto* s3_pool = ExecEnv::GetInstance()->peer_race_s3_thread_pool();
499
8
        if (s3_pool == nullptr || !s3_pool->submit_func(s3_fn).ok()) {
500
1
            do_s3_read();
501
1
        }
502
8
    }
503
14
}
504
505
// Wait for the race to finish and populate the output accordingly.
506
Status collect_race_result(std::shared_ptr<RaceState> race, size_t span_size,
507
                           std::unique_ptr<char[]>& buffer, PeerFetchResult* peer_result,
508
14
                           ReadStatistics& stats, const IOContext* io_ctx) {
509
14
    {
510
14
        std::unique_lock<bthread::Mutex> lk(race->mtx);
511
21
        while (race->winner < 0 && !(race->peer_done && race->s3_done)) {
512
7
            race->cv.wait(lk);
513
7
        }
514
14
    }
515
14
    g_active_peer_races.fetch_sub(1, std::memory_order_relaxed);
516
517
14
    const std::string self_cg_id =
518
14
            static_cast<CloudClusterInfo*>(ExecEnv::GetInstance()->cluster_info())
519
14
                    ->cloud_compute_group_id();
520
521
14
    if (race->winner == 0) {
522
        // Peer won.
523
7
        if (peer_result != nullptr) {
524
7
            *peer_result = std::move(race->peer_res);
525
7
        }
526
7
        stats.from_peer_cache = true;
527
7
        stats.peer_read_timer += race->peer_elapsed_ns;
528
7
        g_peer_race_peer_win << 1;
529
7
        const bool is_cross_cg =
530
7
                !race->peer_winner_cg_id.empty() && race->peer_winner_cg_id != self_cg_id;
531
7
        if (is_cross_cg) {
532
5
            g_peer_cross_compute_group_read << 1;
533
5
        } else {
534
2
            g_peer_same_compute_group_read << 1;
535
2
        }
536
7
        if (io_ctx != nullptr && io_ctx->file_cache_stats != nullptr) {
537
7
            io_ctx->file_cache_stats->num_peer_race_peer_win++;
538
7
            io_ctx->file_cache_stats->peer_hosts.insert(race->peer_winner_host);
539
7
            if (is_cross_cg) {
540
5
                io_ctx->file_cache_stats->num_cross_cg_peer_io_total++;
541
5
                io_ctx->file_cache_stats->bytes_read_from_cross_cg_peer += span_size;
542
5
                io_ctx->file_cache_stats->cross_cg_peer_io_timer += race->peer_winner_io_ns;
543
5
            } else {
544
2
                io_ctx->file_cache_stats->num_same_cg_peer_io_total++;
545
2
                io_ctx->file_cache_stats->bytes_read_from_same_cg_peer += span_size;
546
2
                io_ctx->file_cache_stats->same_cg_peer_io_timer += race->peer_winner_io_ns;
547
2
            }
548
7
        }
549
7
        return Status::OK();
550
7
    } else if (race->winner == 1) {
551
        // S3 won.
552
7
        buffer = std::move(race->s3_buf);
553
7
        stats.from_peer_cache = false;
554
7
        g_peer_race_s3_win << 1;
555
7
        if (io_ctx != nullptr && io_ctx->file_cache_stats != nullptr) {
556
7
            io_ctx->file_cache_stats->num_peer_race_s3_win++;
557
7
        }
558
7
        return Status::OK();
559
7
    }
560
0
    g_peer_race_both_fail << 1;
561
0
    return Status::InternalError<false>("peer race: both peer and s3 failed");
562
14
}
563
564
} // anonymous namespace
565
566
Status CachedRemoteFileReader::_execute_s3_fallback(size_t empty_start, size_t span_size,
567
                                                    std::unique_ptr<char[]>& buffer,
568
                                                    PeerFetchResult* peer_result,
569
                                                    ReadStatistics& stats,
570
5.16k
                                                    const IOContext* io_ctx) {
571
5.16k
    if (peer_result != nullptr) {
572
5.16k
        peer_result->clear();
573
5.16k
    }
574
5.16k
    buffer.reset(new char[span_size]);
575
5.16k
    size_t read_size = span_size;
576
5.16k
    return execute_s3_read(empty_start, read_size, buffer, stats, io_ctx, _remote_file_reader);
577
5.16k
}
578
579
Status CachedRemoteFileReader::_execute_sequential_peer_read(
580
        const std::vector<FileBlockSPtr>& empty_blocks, size_t empty_start, size_t span_size,
581
        std::unique_ptr<char[]>& buffer, PeerFetchResult* peer_result, ReadStatistics& stats,
582
        const IOContext* io_ctx, const std::vector<doris::PeerCandidate>& candidates,
583
1
        int64_t tablet_id) {
584
    // candidates[0] already reflects last_successful_compute_group_id affinity:
585
    // get_peer_candidates() applies stable_partition before returning.
586
1
    if (candidates.empty()) {
587
0
        return _execute_s3_fallback(empty_start, span_size, buffer, peer_result, stats, io_ctx);
588
0
    }
589
590
1
    auto& manager = get_warm_up_manager();
591
1
    PeerFetchResult serial_res;
592
1
    const int64_t timer_before = stats.peer_read_timer;
593
1
    auto st = execute_peer_read(empty_blocks, &serial_res, path().native(), this->size(),
594
1
                                _is_doris_table, stats, io_ctx, candidates[0].host,
595
1
                                candidates[0].brpc_port);
596
1
    if (st.ok()) {
597
1
        manager.update_peer_candidate_on_success(tablet_id, candidates[0].compute_group_id);
598
1
        if (peer_result != nullptr) {
599
1
            *peer_result = std::move(serial_res);
600
1
        }
601
        // Update profile counters for cross/same CG stats.
602
1
        const std::string self_cg_id =
603
1
                static_cast<CloudClusterInfo*>(ExecEnv::GetInstance()->cluster_info())
604
1
                        ->cloud_compute_group_id();
605
1
        const bool is_cross_cg = !candidates[0].compute_group_id.empty() &&
606
1
                                 candidates[0].compute_group_id != self_cg_id;
607
1
        if (is_cross_cg) {
608
1
            g_peer_cross_compute_group_read << 1;
609
1
        } else {
610
0
            g_peer_same_compute_group_read << 1;
611
0
        }
612
1
        if (io_ctx != nullptr && io_ctx->file_cache_stats != nullptr) {
613
1
            io_ctx->file_cache_stats->peer_hosts.insert(candidates[0].host);
614
1
            if (is_cross_cg) {
615
1
                io_ctx->file_cache_stats->num_cross_cg_peer_io_total++;
616
1
                io_ctx->file_cache_stats->bytes_read_from_cross_cg_peer += span_size;
617
1
                io_ctx->file_cache_stats->cross_cg_peer_io_timer +=
618
1
                        stats.peer_read_timer - timer_before;
619
1
            } else {
620
0
                io_ctx->file_cache_stats->num_same_cg_peer_io_total++;
621
0
                io_ctx->file_cache_stats->bytes_read_from_same_cg_peer += span_size;
622
0
                io_ctx->file_cache_stats->same_cg_peer_io_timer +=
623
0
                        stats.peer_read_timer - timer_before;
624
0
            }
625
1
        }
626
1
        return st;
627
1
    }
628
    // Track failure so affinity / eviction logic stays consistent with the race path.
629
0
    if (st.is<ErrorCode::TOO_MANY_TASKS>()) {
630
        // Server healthy but overloaded — don't penalize candidate.
631
0
    } else if (st.is<ErrorCode::NOT_FOUND>()) {
632
0
        manager.rotate_peer_candidate_on_cache_miss(tablet_id, candidates[0].host,
633
0
                                                    candidates[0].brpc_port);
634
0
    } else {
635
0
        manager.update_peer_candidate_on_rpc_failure(tablet_id, candidates[0].host,
636
0
                                                     candidates[0].brpc_port);
637
0
    }
638
0
    return _execute_s3_fallback(empty_start, span_size, buffer, peer_result, stats, io_ctx);
639
1
}
640
641
Status CachedRemoteFileReader::_execute_remote_read(const std::vector<FileBlockSPtr>& empty_blocks,
642
                                                    size_t empty_start, size_t span_size,
643
                                                    std::unique_ptr<char[]>& buffer,
644
                                                    PeerFetchResult* peer_result,
645
                                                    ReadStatistics& stats,
646
5.18k
                                                    const IOContext* io_ctx) {
647
    // --- Non-peer path: direct S3 ---
648
5.18k
    if (!_should_read_from_peer(io_ctx)) {
649
5.15k
        return _execute_s3_fallback(empty_start, span_size, buffer, peer_result, stats, io_ctx);
650
5.15k
    }
651
652
    // --- UT debug point: injected peer address ---
653
28
    DBUG_EXECUTE_IF("PeerFileCacheReader::_fetch_from_peer_cache_blocks", {
654
28
        std::string dp_host = dp->param<std::string>("host", "127.0.0.1");
655
28
        int32_t dp_port = dp->param("port", 9060);
656
28
        buffer.reset();
657
28
        DCHECK(peer_result != nullptr);
658
28
        peer_result->clear();
659
28
        auto st = execute_peer_read(empty_blocks, peer_result, path().native(), this->size(),
660
28
                                    _is_doris_table, stats, io_ctx, dp_host, dp_port);
661
28
        if (st.ok()) return st;
662
28
        return _execute_s3_fallback(empty_start, span_size, buffer, peer_result, stats, io_ctx);
663
28
    });
664
665
    // --- Resolve tablet and obtain peer candidates ---
666
17
    int64_t tablet_id = _tablet_id;
667
17
    auto& manager = get_warm_up_manager();
668
17
    auto candidates = manager.get_peer_candidates(tablet_id);
669
17
    if (candidates.empty()) {
670
2
        if (!manager.is_peer_cooldown(tablet_id)) {
671
            // Cold miss: trigger background FE fetch and fall back to S3.
672
1
            g_peer_lazy_fetch_triggered << 1;
673
1
            auto manager_ptr =
674
1
                    ExecEnv::GetInstance()->storage_engine().to_cloud().cloud_warm_up_manager_ptr();
675
1
            start_bthread([manager_ptr = std::move(manager_ptr), tablet_id]() {
676
1
                manager_ptr->fetch_candidates_from_fe(tablet_id);
677
1
            });
678
1
        }
679
2
        return _execute_s3_fallback(empty_start, span_size, buffer, peer_result, stats, io_ctx);
680
2
    }
681
682
    // --- Dispatch: concurrent race or sequential fallback ---
683
    // Candidates are already sorted by last_successful_compute_group_id affinity
684
    // (stable_partition in get_peer_candidates), so the winner race peer bthread
685
    // naturally tries the most promising candidate first — whether same-CG or cross-CG.
686
15
    if (config::enable_peer_s3_race) {
687
14
        return _execute_winner_race(empty_blocks, empty_start, span_size, buffer, peer_result,
688
14
                                    stats, io_ctx, candidates, tablet_id);
689
14
    }
690
1
    return _execute_sequential_peer_read(empty_blocks, empty_start, span_size, buffer, peer_result,
691
1
                                         stats, io_ctx, candidates, tablet_id);
692
15
}
693
694
Status CachedRemoteFileReader::_execute_winner_race(
695
        const std::vector<FileBlockSPtr>& empty_blocks, size_t empty_start, size_t span_size,
696
        std::unique_ptr<char[]>& buffer, PeerFetchResult* peer_result, ReadStatistics& stats,
697
        const IOContext* io_ctx, const std::vector<doris::PeerCandidate>& candidates,
698
14
        int64_t tablet_id) {
699
    // Reserve a race slot; degrade to sequential if at limit.
700
14
    if (g_active_peer_races.fetch_add(1, std::memory_order_relaxed) >=
701
14
        config::max_concurrent_peer_races) {
702
0
        g_active_peer_races.fetch_sub(1, std::memory_order_relaxed);
703
0
        return _execute_sequential_peer_read(empty_blocks, empty_start, span_size, buffer,
704
0
                                             peer_result, stats, io_ctx, candidates, tablet_id);
705
0
    }
706
707
14
    auto race = std::make_shared<RaceState>();
708
14
    auto manager = ExecEnv::GetInstance()->storage_engine().to_cloud().cloud_warm_up_manager_ptr();
709
710
    // Capture context for child threads.
711
14
    const std::string file_path = path().native();
712
14
    const size_t file_sz = this->size();
713
14
    const bool is_doris = _is_doris_table;
714
14
    auto remote_reader = _remote_file_reader;
715
14
    std::shared_ptr<ResourceContext> parent_resource_ctx;
716
14
    auto* parent_thread_context = thread_context();
717
14
    if (parent_thread_context != nullptr && parent_thread_context->is_attach_task()) {
718
3
        parent_resource_ctx = parent_thread_context->resource_ctx();
719
3
    }
720
721
    // Launch peer bthread.
722
14
    start_bthread(
723
14
            [race, empty_blocks = std::move(empty_blocks), file_path, file_sz, is_doris,
724
14
             manager = std::move(manager), candidates = std::move(candidates), tablet_id,
725
14
             resource_id = _storage_resource_id, parent_resource_ctx]() mutable {
726
14
                run_peer_race(race, std::move(empty_blocks), file_path, file_sz, is_doris,
727
14
                              std::move(manager), std::move(candidates), tablet_id,
728
14
                              std::move(resource_id), parent_resource_ctx);
729
14
            },
730
14
            /*init_thread_ctx=*/true);
731
732
    // Launch S3 (with optional hedge delay).
733
    // Pass shared_from_this() so the background S3 task holds a reference to this
734
    // reader, preventing destruction (and close()) until the S3 task completes.
735
14
    launch_s3_race(race, empty_start, span_size, io_ctx, remote_reader, parent_resource_ctx,
736
14
                   shared_from_this());
737
738
    // Collect race result.
739
14
    return collect_race_result(race, span_size, buffer, peer_result, stats, io_ctx);
740
14
}
741
742
bool CachedRemoteFileReader::_try_read_from_cached_files_directly(
743
        size_t offset, Slice result, size_t bytes_req, bool is_dryrun, ReadStatistics& stats,
744
718k
        SourceReadBreakdown& source_read_breakdown, size_t& already_read, size_t* bytes_read) {
745
718k
    if (!_can_read_cache_file_directly()) {
746
717k
        return false;
747
717k
    }
748
749
1.03k
    SCOPED_RAW_TIMER(&stats.read_cache_file_directly_timer);
750
1.03k
    size_t need_read_size = bytes_req;
751
1.03k
    std::shared_lock lock(_mtx);
752
1.03k
    if (_cache_file_readers.empty()) {
753
16
        return false;
754
16
    }
755
756
1.02k
    auto iter = _cache_file_readers.upper_bound(offset);
757
1.02k
    if (iter != _cache_file_readers.begin()) {
758
1.01k
        --iter;
759
1.01k
    }
760
761
1.02k
    size_t current_offset = offset;
762
2.44k
    while (need_read_size != 0 && iter != _cache_file_readers.end()) {
763
1.42k
        if (iter->second->offset() > current_offset ||
764
1.42k
            iter->second->range().right < current_offset) {
765
7
            break;
766
7
        }
767
768
1.41k
        size_t file_offset = current_offset - iter->second->offset();
769
1.41k
        size_t reserve_bytes = std::min(need_read_size, iter->second->range().size() - file_offset);
770
1.41k
        if (is_dryrun) [[unlikely]] {
771
1
            g_skip_local_cache_io_sum_bytes << reserve_bytes;
772
1.41k
        } else {
773
1.41k
            SCOPED_RAW_TIMER(&stats.local_read_timer);
774
1.41k
            if (!iter->second
775
1.41k
                         ->read(Slice(result.data + (current_offset - offset), reserve_bytes),
776
1.41k
                                file_offset)
777
1.41k
                         .ok()) { // TODO: maybe read failed because block evict, should handle error
778
0
                break;
779
0
            }
780
1.41k
            source_read_breakdown.local_bytes += reserve_bytes;
781
1.41k
        }
782
783
1.41k
        _cache->add_need_update_lru_block(iter->second);
784
1.41k
        need_read_size -= reserve_bytes;
785
1.41k
        current_offset += reserve_bytes;
786
1.41k
        already_read += reserve_bytes;
787
1.41k
        ++iter;
788
1.41k
    }
789
790
1.02k
    if (need_read_size == 0) {
791
1.00k
        *bytes_read = bytes_req;
792
1.00k
        stats.hit_cache = true;
793
1.00k
        g_read_cache_direct_whole_num << 1;
794
1.00k
        g_read_cache_direct_whole_bytes << bytes_req;
795
1.00k
        return true;
796
1.00k
    }
797
798
14
    g_read_cache_direct_partial_num << 1;
799
14
    g_read_cache_direct_partial_bytes << already_read;
800
14
    return false;
801
1.02k
}
802
803
std::vector<FileBlockSPtr> CachedRemoteFileReader::_collect_remote_read_blocks(
804
717k
        const FileBlocksHolder& holder, ReadStatistics& stats) {
805
717k
    std::vector<FileBlockSPtr> empty_blocks;
806
717k
    for (auto& block : holder.file_blocks) {
807
717k
        switch (block->state()) {
808
5.24k
        case FileBlock::State::EMPTY:
809
5.24k
            VLOG_DEBUG << fmt::format("Block EMPTY path={} hash={}:{}:{} offset={} cache_path={}",
810
0
                                      path().native(), _cache_hash.to_string(), _cache_hash.high(),
811
0
                                      _cache_hash.low(), block->offset(), block->get_cache_file());
812
5.24k
            block->get_or_set_downloader();
813
5.24k
            if (block->is_downloader()) {
814
5.24k
                empty_blocks.push_back(block);
815
5.24k
                TEST_SYNC_POINT_CALLBACK("CachedRemoteFileReader::EMPTY");
816
5.24k
            }
817
5.24k
            stats.hit_cache = false;
818
5.24k
            break;
819
4
        case FileBlock::State::SKIP_CACHE:
820
4
            VLOG_DEBUG << fmt::format(
821
0
                    "Block SKIP_CACHE path={} hash={}:{}:{} offset={} cache_path={}",
822
0
                    path().native(), _cache_hash.to_string(), _cache_hash.high(), _cache_hash.low(),
823
0
                    block->offset(), block->get_cache_file());
824
4
            empty_blocks.push_back(block);
825
4
            stats.hit_cache = false;
826
4
            stats.skip_cache = true;
827
4
            break;
828
2
        case FileBlock::State::DOWNLOADING:
829
2
            stats.hit_cache = false;
830
2
            break;
831
712k
        case FileBlock::State::DOWNLOADED:
832
712k
            _insert_file_reader(block);
833
712k
            break;
834
717k
        }
835
717k
    }
836
717k
    return empty_blocks;
837
717k
}
838
839
Status CachedRemoteFileReader::_read_remote_blocks_into_cache(
840
        const std::vector<FileBlockSPtr>& empty_blocks, size_t offset, size_t bytes_req,
841
        size_t already_read, Slice result, bool is_dryrun, ReadStatistics& stats,
842
        SourceReadBreakdown& source_read_breakdown, const IOContext* io_ctx,
843
        size_t& indirect_read_bytes, size_t& empty_start, size_t& empty_end,
844
717k
        PeerFetchedBlockSet& peer_fetched_blocks) {
845
717k
    empty_start = 0;
846
717k
    empty_end = 0;
847
717k
    peer_fetched_blocks.clear();
848
717k
    if (empty_blocks.empty()) {
849
712k
        return Status::OK();
850
712k
    }
851
852
5.18k
    empty_start = empty_blocks.front()->range().left;
853
5.18k
    empty_end = empty_blocks.back()->range().right;
854
5.18k
    const size_t span_read_size = empty_end - empty_start + 1;
855
5.18k
    const auto peer_fetch_layout = build_peer_fetch_layout(empty_blocks, size());
856
5.18k
    std::unique_ptr<char[]> buffer;
857
5.18k
    PeerFetchResult peer_result;
858
859
5.18k
    RETURN_IF_ERROR(_execute_remote_read(empty_blocks, empty_start, span_read_size, buffer,
860
5.18k
                                         &peer_result, stats, io_ctx));
861
862
5.18k
    std::vector<std::vector<const PeerFetchChunk*>> peer_chunks_by_block;
863
5.18k
    if (stats.from_peer_cache) {
864
        // Peer returns sparse payloads; remember the exact sparse blocks that were filled.
865
15
        peer_fetched_blocks.reserve(empty_blocks.size());
866
40
        for (const auto& block : empty_blocks) {
867
40
            peer_fetched_blocks.insert(block.get());
868
40
        }
869
15
        peer_chunks_by_block.resize(empty_blocks.size());
870
40
        for (const auto& chunk : peer_result.chunks) {
871
40
            DCHECK_LT(chunk.block_index, empty_blocks.size());
872
40
            peer_chunks_by_block[chunk.block_index].push_back(&chunk);
873
40
        }
874
15
    }
875
876
5.18k
    SCOPED_CONCURRENCY_COUNT(ConcurrencyStatsManager::instance().cached_remote_reader_write_back);
877
10.4k
    for (size_t idx = 0; idx < empty_blocks.size(); ++idx) {
878
5.25k
        auto& block = empty_blocks[idx];
879
5.25k
        if (block->state() == FileBlock::State::SKIP_CACHE) {
880
4
            continue;
881
4
        }
882
883
5.24k
        SCOPED_RAW_TIMER(&stats.local_write_timer);
884
5.24k
        size_t block_size = block->range().size();
885
5.24k
        Status st;
886
5.24k
        if (stats.from_peer_cache) {
887
40
            block_size = peer_fetch_layout.block_sizes[idx];
888
40
            if (block_size == 0) {
889
0
                continue;
890
0
            }
891
40
            st = write_peer_payloads_into_block(block, peer_chunks_by_block[idx], &block_size);
892
5.20k
        } else {
893
5.20k
            char* current_ptr = buffer.get() + block->range().left - empty_start;
894
5.20k
            st = block->append(Slice(current_ptr, block_size));
895
5.20k
        }
896
5.24k
        if (st.ok()) {
897
5.24k
            st = block->finalize();
898
5.24k
        }
899
5.24k
        if (!st.ok()) {
900
2
            LOG(WARNING) << "write data to file cache failed, source="
901
2
                         << (stats.from_peer_cache ? "peer" : "remote")
902
2
                         << ", path=" << path().native() << ", tablet_id=" << _tablet_id
903
2
                         << ", file_size=" << size() << ", cache_hash=" << _cache_hash.to_string()
904
2
                         << ", write_block_size=" << block_size
905
2
                         << ", block=" << block->get_info_for_log()
906
2
                         << ", cache_file=" << block->get_cache_file() << ", err=" << st;
907
5.24k
        } else {
908
5.24k
            _insert_file_reader(block);
909
5.24k
            stats.bytes_write_into_file_cache += block_size;
910
5.24k
        }
911
5.24k
    }
912
913
5.18k
    const size_t right_offset = offset + bytes_req - 1;
914
5.18k
    if (stats.from_peer_cache) {
915
15
        if (is_dryrun) {
916
1
            return Status::OK();
917
1
        }
918
37
        for (const auto& chunk : peer_result.chunks) {
919
37
            copy_peer_chunk_to_result(chunk, offset, right_offset, already_read, result,
920
37
                                      indirect_read_bytes, source_read_breakdown);
921
37
        }
922
14
        return Status::OK();
923
15
    }
924
925
5.17k
    if (empty_start <= right_offset && empty_end >= offset + already_read && !is_dryrun) {
926
5.16k
        size_t copy_left_offset = std::max(offset + already_read, empty_start);
927
5.16k
        size_t copy_right_offset = std::min(right_offset, empty_end);
928
5.16k
        char* dst = result.data + (copy_left_offset - offset);
929
5.16k
        char* src = buffer.get() + (copy_left_offset - empty_start);
930
5.16k
        size_t copy_size = copy_right_offset - copy_left_offset + 1;
931
5.16k
        memcpy(dst, src, copy_size);
932
5.16k
        indirect_read_bytes += copy_size;
933
5.16k
        source_read_breakdown.remote_bytes += copy_size;
934
5.16k
    }
935
5.17k
    return Status::OK();
936
5.18k
}
937
938
Status CachedRemoteFileReader::_read_remaining_blocks_from_cache(
939
        const FileBlocksHolder& holder, size_t offset, size_t bytes_req, Slice result,
940
        bool is_dryrun, size_t empty_start, size_t empty_end,
941
        const PeerFetchedBlockSet& peer_fetched_blocks, ReadStatistics& stats,
942
        SourceReadBreakdown& source_read_breakdown, size_t& indirect_read_bytes, size_t* bytes_read,
943
717k
        const IOContext* io_ctx) {
944
717k
    size_t current_offset = offset + *bytes_read;
945
717k
    size_t end_offset = offset + bytes_req - 1;
946
717k
    bool need_self_heal = false;
947
717k
    for (auto& block : holder.file_blocks) {
948
717k
        if (current_offset > end_offset) {
949
0
            break;
950
0
        }
951
952
717k
        size_t left = block->range().left;
953
717k
        size_t right = block->range().right;
954
717k
        if (right < offset) {
955
1
            continue;
956
1
        }
957
958
717k
        size_t read_size =
959
717k
                end_offset > right ? right - current_offset + 1 : end_offset - current_offset + 1;
960
717k
        if (!peer_fetched_blocks.empty() && contains_file_block(peer_fetched_blocks, block)) {
961
            // For sparse peer reads, skip only blocks fetched from peer. Other blocks inside the
962
            // enclosing span may still come from local cache.
963
41
            *bytes_read += read_size;
964
41
            current_offset = right + 1;
965
41
            continue;
966
41
        }
967
717k
        if (peer_fetched_blocks.empty() && empty_start <= left && right <= empty_end) {
968
5.21k
            *bytes_read += read_size;
969
5.21k
            current_offset = right + 1;
970
5.21k
            continue;
971
5.21k
        }
972
973
712k
        FileBlock::State block_state = block->state();
974
712k
        int64_t wait_time = 0;
975
712k
        static int64_t max_wait_time = 10;
976
712k
        TEST_SYNC_POINT_CALLBACK("CachedRemoteFileReader::max_wait_time", &max_wait_time);
977
712k
        if (block_state != FileBlock::State::DOWNLOADED) {
978
3
            SCOPED_CONCURRENCY_COUNT(
979
3
                    ConcurrencyStatsManager::instance().cached_remote_reader_blocking);
980
4
            do {
981
4
                SCOPED_RAW_TIMER(&stats.remote_wait_timer);
982
4
                TEST_SYNC_POINT_CALLBACK("CachedRemoteFileReader::DOWNLOADING");
983
4
                block_state = block->wait();
984
4
                if (block_state != FileBlock::State::DOWNLOADING) {
985
2
                    break;
986
2
                }
987
4
            } while (++wait_time < max_wait_time);
988
3
        }
989
712k
        if (wait_time == max_wait_time) [[unlikely]] {
990
1
            LOG_WARNING("Waiting too long for the download to complete");
991
1
        }
992
993
712k
        Status st;
994
        /*
995
         * If block_state == EMPTY, the thread reads the data from remote.
996
         * If block_state == DOWNLOADED, when the cache file is deleted by the other process,
997
         * the thread reads the data from remote too.
998
         */
999
712k
        if (block_state == FileBlock::State::DOWNLOADED) {
1000
712k
            if (is_dryrun) [[unlikely]] {
1001
1
                g_skip_local_cache_io_sum_bytes << read_size;
1002
712k
            } else {
1003
712k
                size_t file_offset = current_offset - left;
1004
712k
                SCOPED_RAW_TIMER(&stats.local_read_timer);
1005
712k
                SCOPED_CONCURRENCY_COUNT(
1006
712k
                        ConcurrencyStatsManager::instance().cached_remote_reader_local_read);
1007
712k
                st = block->read(Slice(result.data + (current_offset - offset), read_size),
1008
712k
                                 file_offset);
1009
712k
                indirect_read_bytes += read_size;
1010
712k
                if (st.ok()) {
1011
712k
                    source_read_breakdown.local_bytes += read_size;
1012
712k
                }
1013
712k
            }
1014
712k
            if (block_state == FileBlock::State::DOWNLOADED && st.is<ErrorCode::NOT_FOUND>()) {
1015
1
                need_self_heal = true;
1016
1
                g_read_cache_self_heal_on_not_found << 1;
1017
1
                LOG_EVERY_N(WARNING, 100)
1018
1
                        << "Cache block file is missing, will self-heal by clearing cache hash. "
1019
1
                        << "path=" << path().native() << ", hash=" << _cache_hash.to_string()
1020
1
                        << ", offset=" << left << ", err=" << st.msg();
1021
1
            }
1022
712k
        }
1023
712k
        if (!st || block_state != FileBlock::State::DOWNLOADED) {
1024
4
            if (is_dryrun) [[unlikely]] {
1025
0
                *bytes_read += read_size;
1026
0
                current_offset = right + 1;
1027
0
                continue;
1028
0
            }
1029
4
            LOG(WARNING) << "Read data failed from file cache downloaded by others. err="
1030
4
                         << st.msg() << ", block state=" << block_state;
1031
4
            size_t remote_bytes_read {0};
1032
4
            stats.hit_cache = false;
1033
4
            stats.from_peer_cache = false;
1034
4
            s3_read_counter << 1;
1035
4
            SCOPED_RAW_TIMER(&stats.remote_read_timer);
1036
4
            RETURN_IF_ERROR(_remote_file_reader->read_at(
1037
4
                    current_offset, Slice(result.data + (current_offset - offset), read_size),
1038
4
                    &remote_bytes_read, io_ctx));
1039
3
            indirect_read_bytes += read_size;
1040
3
            source_read_breakdown.remote_bytes += remote_bytes_read;
1041
3
            DCHECK(remote_bytes_read == read_size);
1042
3
        }
1043
1044
712k
        *bytes_read += read_size;
1045
712k
        current_offset = right + 1;
1046
712k
    }
1047
717k
    if (need_self_heal && _cache != nullptr) {
1048
1
        _cache->remove_if_cached_async(_cache_hash);
1049
1
    }
1050
717k
    return Status::OK();
1051
717k
}
1052
1053
Status CachedRemoteFileReader::_read_from_indirect_cache(size_t offset, Slice result,
1054
                                                         size_t bytes_req, size_t already_read,
1055
                                                         bool is_dryrun, size_t* bytes_read,
1056
                                                         ReadStatistics& stats,
1057
                                                         SourceReadBreakdown& source_read_breakdown,
1058
717k
                                                         const IOContext* io_ctx) {
1059
717k
    g_read_cache_indirect_num << 1;
1060
717k
    size_t indirect_read_bytes = 0;
1061
717k
    auto [align_left, align_size] =
1062
717k
            s_align_size(offset + already_read, bytes_req - already_read, size());
1063
717k
    CacheContext cache_context(io_ctx);
1064
717k
    cache_context.stats = &stats;
1065
717k
    MonotonicStopWatch sw;
1066
717k
    sw.start();
1067
717k
    ConcurrencyStatsManager::instance().cached_remote_reader_get_or_set->increment();
1068
717k
    FileBlocksHolder holder =
1069
717k
            _cache->get_or_set(_cache_hash, align_left, align_size, cache_context);
1070
717k
    ConcurrencyStatsManager::instance().cached_remote_reader_get_or_set->decrement();
1071
717k
    stats.cache_get_or_set_timer += sw.elapsed_time();
1072
1073
717k
    auto empty_blocks = _collect_remote_read_blocks(holder, stats);
1074
717k
    size_t empty_start = 0;
1075
717k
    size_t empty_end = 0;
1076
717k
    PeerFetchedBlockSet peer_fetched_blocks;
1077
717k
    RETURN_IF_ERROR(_read_remote_blocks_into_cache(empty_blocks, offset, bytes_req, already_read,
1078
717k
                                                   result, is_dryrun, stats, source_read_breakdown,
1079
717k
                                                   io_ctx, indirect_read_bytes, empty_start,
1080
717k
                                                   empty_end, peer_fetched_blocks));
1081
717k
    *bytes_read = already_read;
1082
717k
    RETURN_IF_ERROR(_read_remaining_blocks_from_cache(holder, offset, bytes_req, result, is_dryrun,
1083
717k
                                                      empty_start, empty_end, peer_fetched_blocks,
1084
717k
                                                      stats, source_read_breakdown,
1085
717k
                                                      indirect_read_bytes, bytes_read, io_ctx));
1086
717k
    g_read_cache_indirect_bytes << indirect_read_bytes;
1087
717k
    g_read_cache_indirect_total_bytes << *bytes_read;
1088
717k
    DCHECK(*bytes_read == bytes_req);
1089
717k
    return Status::OK();
1090
717k
}
1091
1092
Status CachedRemoteFileReader::_read_remote_only_on_cache_miss(
1093
        size_t offset, Slice result, size_t bytes_req, bool is_dryrun, size_t* bytes_read,
1094
        ReadStatistics& stats, SourceReadBreakdown& source_read_breakdown,
1095
5
        const IOContext* io_ctx) {
1096
5
    auto read_remote = [&]() -> Status {
1097
3
        stats.hit_cache = false;
1098
3
        stats.from_peer_cache = false;
1099
3
        stats.skip_cache = true;
1100
3
        s3_read_counter << 1;
1101
3
        if (is_dryrun) [[unlikely]] {
1102
0
            *bytes_read = bytes_req;
1103
0
            g_read_cache_indirect_bytes << 0;
1104
0
            g_read_cache_indirect_total_bytes << bytes_req;
1105
0
            return Status::OK();
1106
0
        }
1107
1108
3
        size_t remote_bytes_read = bytes_req;
1109
3
        SCOPED_RAW_TIMER(&stats.remote_read_timer);
1110
3
        RETURN_IF_ERROR(_remote_file_reader->read_at(offset, Slice(result.data, bytes_req),
1111
3
                                                     &remote_bytes_read, io_ctx));
1112
3
        *bytes_read = remote_bytes_read;
1113
3
        DCHECK_EQ(*bytes_read, bytes_req);
1114
3
        source_read_breakdown.remote_bytes += remote_bytes_read;
1115
3
        g_read_cache_indirect_bytes << remote_bytes_read;
1116
3
        g_read_cache_indirect_total_bytes << remote_bytes_read;
1117
3
        return Status::OK();
1118
3
    };
1119
1120
5
    g_read_cache_indirect_num << 1;
1121
5
    CacheContext cache_context(io_ctx);
1122
5
    cache_context.stats = &stats;
1123
5
    cache_context.tablet_id = _tablet_id;
1124
5
    FileBlocks file_blocks;
1125
5
    bool fully_covered = false;
1126
5
    {
1127
5
        SCOPED_RAW_TIMER(&stats.get_timer);
1128
5
        RETURN_IF_ERROR(_cache->get_downloaded_blocks_if_fully_covered(
1129
5
                _cache_hash, offset, bytes_req, cache_context, &file_blocks, &fully_covered));
1130
5
    }
1131
5
    if (!fully_covered) {
1132
3
        return read_remote();
1133
3
    }
1134
1135
2
    size_t local_read_bytes = 0;
1136
2
    size_t current_offset = offset;
1137
2
    size_t end_offset = offset + bytes_req - 1;
1138
2
    for (auto& block : file_blocks) {
1139
2
        if (current_offset > end_offset) {
1140
0
            break;
1141
0
        }
1142
2
        const auto& block_range = block->range();
1143
2
        if (block_range.right < current_offset) {
1144
0
            continue;
1145
0
        }
1146
1147
2
        size_t read_left = std::max(current_offset, block_range.left);
1148
2
        size_t read_right = std::min(end_offset, block_range.right);
1149
2
        size_t read_size = read_right - read_left + 1;
1150
2
        if (is_dryrun) [[unlikely]] {
1151
0
            g_skip_local_cache_io_sum_bytes << read_size;
1152
2
        } else {
1153
2
            SCOPED_RAW_TIMER(&stats.local_read_timer);
1154
2
            Status st = block->read(Slice(result.data + (read_left - offset), read_size),
1155
2
                                    read_left - block_range.left);
1156
2
            if (!st.ok()) {
1157
0
                if (st.is<ErrorCode::NOT_FOUND>()) {
1158
0
                    _cache->remove_if_cached_async(_cache_hash);
1159
0
                }
1160
0
                LOG_EVERY_N(WARNING, 100)
1161
0
                        << "Read data failed from file cache in remote-only-on-miss path. "
1162
0
                        << "Fallback to remote. err=" << st.msg()
1163
0
                        << ", block state=" << block->state();
1164
0
                return read_remote();
1165
0
            }
1166
2
            source_read_breakdown.local_bytes += read_size;
1167
2
            local_read_bytes += read_size;
1168
2
        }
1169
2
        current_offset = read_right + 1;
1170
2
    }
1171
1172
2
    *bytes_read = bytes_req;
1173
2
    stats.hit_cache = true;
1174
2
    g_read_cache_indirect_bytes << local_read_bytes;
1175
2
    g_read_cache_indirect_total_bytes << bytes_req;
1176
2
    return Status::OK();
1177
2
}
1178
1179
Status CachedRemoteFileReader::read_at_impl(size_t offset, Slice result, size_t* bytes_read,
1180
718k
                                            const IOContext* io_ctx) {
1181
718k
    SCOPED_CONCURRENCY_COUNT(ConcurrencyStatsManager::instance().cached_remote_reader_read_at);
1182
718k
    IOContext default_io_ctx;
1183
718k
    if (io_ctx == nullptr) {
1184
1
        io_ctx = &default_io_ctx;
1185
1
    }
1186
718k
    DCHECK(io_ctx);
1187
718k
    DCHECK(!closed());
1188
1189
718k
    const bool is_dryrun = io_ctx->is_dryrun;
1190
718k
    if (offset > size()) {
1191
1
        return Status::InvalidArgument(
1192
1
                fmt::format("offset exceeds file size(offset: {}, file size: {}, path: {})", offset,
1193
1
                            size(), path().native()));
1194
1
    }
1195
1196
718k
    size_t bytes_req = std::min(result.size, size() - offset);
1197
718k
    if (UNLIKELY(bytes_req == 0)) {
1198
1
        *bytes_read = 0;
1199
1
        return Status::OK();
1200
1
    }
1201
1202
718k
    ReadStatistics stats;
1203
718k
    SourceReadBreakdown source_read_breakdown;
1204
718k
    Status read_st = Status::OK();
1205
718k
    MonotonicStopWatch read_at_sw;
1206
718k
    read_at_sw.start();
1207
718k
    stats.bytes_read += bytes_req;
1208
718k
    Defer defer {[&]() {
1209
718k
        if (config::print_stack_when_cache_miss) {
1210
0
            if (io_ctx->file_cache_stats == nullptr && !stats.hit_cache && !io_ctx->is_warmup) {
1211
0
                LOG_INFO("[verbose] {}", Status::InternalError<true>("not hit cache"));
1212
0
            }
1213
0
        }
1214
718k
        if (!stats.hit_cache && config::read_cluster_cache_opt_verbose_log) {
1215
0
            LOG_INFO(
1216
0
                    "[verbose] not hit cache, path: {}, offset: {}, size: {}, cost: {} ms, warmup: "
1217
0
                    "{}",
1218
0
                    path().native(), offset, bytes_req, read_at_sw.elapsed_time_milliseconds(),
1219
0
                    io_ctx->is_warmup);
1220
0
        }
1221
718k
        if (read_st.ok() && !is_dryrun) {
1222
            // Only successful reads contribute to query profile and file-cache metrics.
1223
718k
            const auto file_cache_read_type =
1224
718k
                    io_ctx->is_inverted_index
1225
718k
                            ? FileCacheReadType::INVERTED_INDEX
1226
718k
                            : (io_ctx->is_index_data ? FileCacheReadType::SEGMENT_FOOTER_INDEX
1227
718k
                                                     : FileCacheReadType::DATA);
1228
718k
            if (io_ctx->file_cache_stats) {
1229
709k
                _update_stats(stats, source_read_breakdown, io_ctx->file_cache_stats,
1230
709k
                              file_cache_read_type);
1231
709k
                auto* limiter = io_ctx->remote_scan_cache_write_limiter;
1232
709k
                if (limiter != nullptr) {
1233
5
                    io_ctx->file_cache_stats->remote_only_on_miss_triggered =
1234
5
                            io_ctx->file_cache_stats->remote_only_on_miss_triggered ||
1235
5
                            limiter->remote_only_on_miss();
1236
5
                    io_ctx->file_cache_stats->remote_only_on_miss_threshold_bytes =
1237
5
                            limiter->threshold_bytes();
1238
5
                }
1239
709k
            }
1240
718k
            if (!io_ctx->is_warmup) {
1241
718k
                FileCacheStatistics fcache_stats_increment;
1242
718k
                _update_stats(stats, source_read_breakdown, &fcache_stats_increment,
1243
718k
                              file_cache_read_type);
1244
718k
                io::FileCacheMetrics::instance().update(&fcache_stats_increment);
1245
718k
            }
1246
718k
        }
1247
718k
    }};
1248
1249
718k
    if (use_remote_only_on_cache_miss(io_ctx)) {
1250
5
        read_st = _read_remote_only_on_cache_miss(offset, result, bytes_req, is_dryrun, bytes_read,
1251
5
                                                  stats, source_read_breakdown, io_ctx);
1252
5
        return read_st;
1253
5
    }
1254
1255
718k
    size_t already_read = 0;
1256
718k
    if (_try_read_from_cached_files_directly(offset, result, bytes_req, is_dryrun, stats,
1257
718k
                                             source_read_breakdown, already_read, bytes_read)) {
1258
1.00k
        return Status::OK();
1259
1.00k
    }
1260
1261
717k
    read_st = _read_from_indirect_cache(offset, result, bytes_req, already_read, is_dryrun,
1262
717k
                                        bytes_read, stats, source_read_breakdown, io_ctx);
1263
717k
    return read_st;
1264
718k
}
1265
1266
0
void CachedRemoteFileReader::prefetch_range(size_t offset, size_t size, const IOContext* io_ctx) {
1267
0
    if (offset >= this->size() || size == 0) {
1268
0
        return;
1269
0
    }
1270
1271
0
    size = std::min(size, this->size() - offset);
1272
1273
0
    ThreadPool* pool = ExecEnv::GetInstance()->segment_prefetch_thread_pool();
1274
0
    if (pool == nullptr) {
1275
0
        return;
1276
0
    }
1277
1278
0
    IOContext dryrun_ctx;
1279
0
    if (io_ctx != nullptr) {
1280
0
        dryrun_ctx = *io_ctx;
1281
0
    }
1282
0
    dryrun_ctx.is_dryrun = true;
1283
0
    dryrun_ctx.query_id = nullptr;
1284
0
    dryrun_ctx.file_cache_stats = nullptr;
1285
0
    dryrun_ctx.file_reader_stats = nullptr;
1286
0
    dryrun_ctx.remote_scan_cache_write_limiter = nullptr;
1287
1288
0
    LOG_IF(INFO, config::enable_segment_prefetch_verbose_log)
1289
0
            << fmt::format("[verbose] Submitting prefetch task for offset={} size={}, file={}",
1290
0
                           offset, size, path().filename().native());
1291
0
    std::weak_ptr<CachedRemoteFileReader> weak_this = shared_from_this();
1292
0
    auto st = pool->submit_func([weak_this, offset, size, dryrun_ctx]() {
1293
0
        auto self = weak_this.lock();
1294
0
        if (self == nullptr) {
1295
0
            return;
1296
0
        }
1297
0
        size_t bytes_read = 0;
1298
0
        Slice dummy_buffer((char*)nullptr, size);
1299
0
        (void)self->read_at_impl(offset, dummy_buffer, &bytes_read, &dryrun_ctx);
1300
0
        LOG_IF(INFO, config::enable_segment_prefetch_verbose_log)
1301
0
                << fmt::format("[verbose] Prefetch task completed for offset={} size={}, file={}",
1302
0
                               offset, size, self->path().filename().native());
1303
0
    });
1304
1305
0
    if (!st.ok()) {
1306
0
        VLOG_DEBUG << "Failed to submit prefetch task for offset=" << offset << " size=" << size
1307
0
                   << " error=" << st.to_string();
1308
0
    }
1309
0
}
1310
1311
void CachedRemoteFileReader::_update_stats(const ReadStatistics& read_stats,
1312
                                           const SourceReadBreakdown& source_read_breakdown,
1313
                                           FileCacheStatistics* statis,
1314
1.42M
                                           FileCacheReadType read_type) const {
1315
1.42M
    if (statis == nullptr) {
1316
0
        return;
1317
0
    }
1318
1.42M
    const bool has_source_bytes = source_read_breakdown.local_bytes != 0 ||
1319
1.42M
                                  source_read_breakdown.remote_bytes != 0 ||
1320
1.42M
                                  source_read_breakdown.peer_bytes != 0;
1321
1.42M
    if (has_source_bytes) {
1322
1.42M
        if (source_read_breakdown.local_bytes != 0) {
1323
1.41M
            statis->num_local_io_total++;
1324
1.41M
            statis->bytes_read_from_local += source_read_breakdown.local_bytes;
1325
1.41M
        }
1326
1.42M
        if (source_read_breakdown.peer_bytes != 0 || read_stats.from_peer_cache) {
1327
            // Count peer IO whenever peer was used, even if its fetched blocks were entirely
1328
            // outside the copy range (e.g., backward-aligned prefetch block before
1329
            // offset+already_read).  In that case peer_bytes==0 but the peer RPC did happen
1330
            // and wrote data into the local file cache.
1331
28
            statis->num_peer_io_total++;
1332
28
            statis->bytes_read_from_peer += source_read_breakdown.peer_bytes;
1333
28
            statis->peer_io_timer += read_stats.peer_read_timer;
1334
28
        }
1335
1.42M
        if (source_read_breakdown.remote_bytes != 0) {
1336
10.3k
            statis->num_remote_io_total++;
1337
10.3k
            statis->bytes_read_from_remote += source_read_breakdown.remote_bytes;
1338
10.3k
            statis->remote_io_timer += read_stats.remote_read_timer;
1339
10.3k
        }
1340
18.4E
    } else if (read_stats.hit_cache) {
1341
0
        statis->num_local_io_total++;
1342
0
        statis->bytes_read_from_local += read_stats.bytes_read;
1343
18.4E
    } else if (read_stats.from_peer_cache) {
1344
0
        statis->num_peer_io_total++;
1345
0
        statis->bytes_read_from_peer += read_stats.bytes_read;
1346
0
        statis->peer_io_timer += read_stats.peer_read_timer;
1347
18.4E
    } else {
1348
18.4E
        statis->num_remote_io_total++;
1349
18.4E
        statis->bytes_read_from_remote += read_stats.bytes_read;
1350
18.4E
        statis->remote_io_timer += read_stats.remote_read_timer;
1351
18.4E
    }
1352
1.42M
    statis->remote_wait_timer += read_stats.remote_wait_timer;
1353
1.42M
    statis->local_io_timer += read_stats.local_read_timer;
1354
1.42M
    statis->num_skip_cache_io_total += read_stats.skip_cache;
1355
1.42M
    statis->bytes_write_into_cache += read_stats.bytes_write_into_file_cache;
1356
1.42M
    statis->write_cache_io_timer += read_stats.local_write_timer;
1357
1358
1.42M
    statis->read_cache_file_directly_timer += read_stats.read_cache_file_directly_timer;
1359
1.42M
    statis->cache_get_or_set_timer += read_stats.cache_get_or_set_timer;
1360
1.42M
    statis->lock_wait_timer += read_stats.lock_wait_timer;
1361
1.42M
    statis->get_timer += read_stats.get_timer;
1362
1.42M
    statis->set_timer += read_stats.set_timer;
1363
1364
1.42M
    auto update_index_stats = [&](int64_t& num_local_io_total, int64_t& num_remote_io_total,
1365
1.42M
                                  int64_t& num_peer_io_total, int64_t& bytes_read_from_local,
1366
1.42M
                                  int64_t& bytes_read_from_remote, int64_t& bytes_read_from_peer,
1367
1.42M
                                  int64_t& local_io_timer, int64_t& remote_io_timer,
1368
1.42M
                                  int64_t& peer_io_timer, int64_t& write_cache_io_timer,
1369
1.42M
                                  int64_t& bytes_write_into_cache) {
1370
6
        if (has_source_bytes) {
1371
6
            if (source_read_breakdown.local_bytes != 0) {
1372
0
                num_local_io_total++;
1373
0
                bytes_read_from_local += source_read_breakdown.local_bytes;
1374
0
            }
1375
6
            if (source_read_breakdown.peer_bytes != 0 || read_stats.from_peer_cache) {
1376
0
                num_peer_io_total++;
1377
0
                bytes_read_from_peer += source_read_breakdown.peer_bytes;
1378
0
                peer_io_timer += read_stats.peer_read_timer;
1379
0
            }
1380
6
            if (source_read_breakdown.remote_bytes != 0) {
1381
6
                num_remote_io_total++;
1382
6
                bytes_read_from_remote += source_read_breakdown.remote_bytes;
1383
6
                remote_io_timer += read_stats.remote_read_timer;
1384
6
            }
1385
6
        } else if (read_stats.hit_cache) {
1386
0
            num_local_io_total++;
1387
0
            bytes_read_from_local += read_stats.bytes_read;
1388
0
        } else if (read_stats.from_peer_cache) {
1389
0
            num_peer_io_total++;
1390
0
            bytes_read_from_peer += read_stats.bytes_read;
1391
0
            peer_io_timer += read_stats.peer_read_timer;
1392
0
        } else {
1393
0
            num_remote_io_total++;
1394
0
            bytes_read_from_remote += read_stats.bytes_read;
1395
0
            remote_io_timer += read_stats.remote_read_timer;
1396
0
        }
1397
6
        local_io_timer += read_stats.local_read_timer;
1398
6
        write_cache_io_timer += read_stats.local_write_timer;
1399
6
        bytes_write_into_cache += read_stats.bytes_write_into_file_cache;
1400
6
    };
1401
1402
1.42M
    switch (read_type) {
1403
1.42M
    case FileCacheReadType::DATA:
1404
1.42M
        break;
1405
2
    case FileCacheReadType::INVERTED_INDEX:
1406
2
        update_index_stats(
1407
2
                statis->inverted_index_num_local_io_total,
1408
2
                statis->inverted_index_num_remote_io_total,
1409
2
                statis->inverted_index_num_peer_io_total,
1410
2
                statis->inverted_index_bytes_read_from_local,
1411
2
                statis->inverted_index_bytes_read_from_remote,
1412
2
                statis->inverted_index_bytes_read_from_peer, statis->inverted_index_local_io_timer,
1413
2
                statis->inverted_index_remote_io_timer, statis->inverted_index_peer_io_timer,
1414
2
                statis->inverted_index_write_cache_io_timer,
1415
2
                statis->inverted_index_bytes_write_into_cache);
1416
2
        break;
1417
4
    case FileCacheReadType::SEGMENT_FOOTER_INDEX:
1418
4
        update_index_stats(statis->segment_footer_index_num_local_io_total,
1419
4
                           statis->segment_footer_index_num_remote_io_total,
1420
4
                           statis->segment_footer_index_num_peer_io_total,
1421
4
                           statis->segment_footer_index_bytes_read_from_local,
1422
4
                           statis->segment_footer_index_bytes_read_from_remote,
1423
4
                           statis->segment_footer_index_bytes_read_from_peer,
1424
4
                           statis->segment_footer_index_local_io_timer,
1425
4
                           statis->segment_footer_index_remote_io_timer,
1426
4
                           statis->segment_footer_index_peer_io_timer,
1427
4
                           statis->segment_footer_index_write_cache_io_timer,
1428
4
                           statis->segment_footer_index_bytes_write_into_cache);
1429
4
        break;
1430
1.42M
    }
1431
1432
1.42M
    g_skip_cache_sum << read_stats.skip_cache;
1433
1.42M
}
1434
1435
} // namespace doris::io