Coverage Report

Created: 2026-07-24 22:25

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
6.75k
        [](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
1.43M
static bool use_remote_only_on_cache_miss(const IOContext* io_ctx) {
111
1.43M
    if (io_ctx->file_cache_miss_policy == FileCacheMissPolicy::REMOTE_ONLY_ON_MISS) {
112
6
        return true;
113
6
    }
114
1.43M
    auto* limiter = io_ctx->remote_scan_cache_write_limiter;
115
1.43M
    return limiter != nullptr && limiter->remote_only_on_miss();
116
1.43M
}
117
118
CachedRemoteFileReader::CachedRemoteFileReader(FileReaderSPtr remote_file_reader,
119
                                               const FileReaderOptions& opts)
120
14.3k
        : _is_doris_table(opts.is_doris_table),
121
14.3k
          _tablet_id(opts.tablet_id),
122
14.3k
          _storage_resource_id(opts.storage_resource_id),
123
14.3k
          _remote_file_reader(std::move(remote_file_reader)) {
124
14.3k
    DCHECK(!_is_doris_table || _tablet_id > 0);
125
14.3k
    if (_is_doris_table) {
126
14.3k
        _init_doris_table_cache();
127
14.3k
    } else {
128
13
        _init_external_table_cache(opts);
129
13
    }
130
14.3k
}
131
132
14.3k
void CachedRemoteFileReader::_init_doris_table_cache() {
133
14.3k
    _cache_hash = BlockFileCache::hash(path().filename().native());
134
14.3k
    _cache = FileCacheFactory::instance()->get_by_path(_cache_hash);
135
14.3k
    if (_can_read_cache_file_directly()) {
136
        // this is designed for and test in doris table, external table need extra tests
137
38
        _cache_file_readers = _cache->get_blocks_by_key(_cache_hash);
138
38
    }
139
14.3k
}
140
141
20
void CachedRemoteFileReader::_init_external_table_cache(const FileReaderOptions& opts) {
142
    // Use path and modification time to build cache key.
143
20
    std::string unique_path = fmt::format("{}:{}", path().native(), opts.mtime);
144
20
    _cache_hash = BlockFileCache::hash(unique_path);
145
20
    if (opts.cache_base_path.empty()) {
146
        // If cache path is not specified by session variable, choose randomly.
147
2
        _cache = FileCacheFactory::instance()->get_by_path(_cache_hash);
148
2
        return;
149
2
    }
150
151
    // From query session variable: file_cache_base_path.
152
18
    _cache = FileCacheFactory::instance()->get_by_path(opts.cache_base_path);
153
18
    if (_cache != nullptr) {
154
16
        return;
155
16
    }
156
157
18
    LOG(WARNING) << "Can't get cache from base path: " << opts.cache_base_path
158
2
                 << ", using random instead.";
159
2
    _cache = FileCacheFactory::instance()->get_by_path(_cache_hash);
160
2
}
161
162
2.86M
bool CachedRemoteFileReader::_can_read_cache_file_directly() const {
163
2.86M
    return _is_doris_table && config::enable_read_cache_file_directly;
164
2.86M
}
165
166
10.3k
bool CachedRemoteFileReader::_should_read_from_peer(const IOContext* io_ctx) const {
167
10.3k
    return doris::config::is_cloud_mode() && _is_doris_table && _tablet_id > 0 &&
168
10.3k
           !io_ctx->is_warmup && !io_ctx->bypass_peer_read &&
169
10.3k
           doris::config::enable_cache_read_from_peer;
170
10.3k
}
171
172
1.43M
void CachedRemoteFileReader::_insert_file_reader(FileBlockSPtr file_block) {
173
1.43M
    if (_can_read_cache_file_directly()) {
174
72
        std::lock_guard lock(_mtx);
175
72
        DCHECK(file_block->state() == FileBlock::State::DOWNLOADED);
176
72
        file_block->_owned_by_cached_reader = true;
177
72
        _cache_file_readers.emplace(file_block->offset(), std::move(file_block));
178
72
    }
179
1.43M
}
180
181
14.3k
CachedRemoteFileReader::~CachedRemoteFileReader() {
182
14.3k
    for (auto& it : _cache_file_readers) {
183
114
        it.second->_owned_by_cached_reader = false;
184
114
    }
185
14.3k
    static_cast<void>(close());
186
14.3k
}
187
188
16.4k
Status CachedRemoteFileReader::close() {
189
16.4k
    return _remote_file_reader->close();
190
16.4k
}
191
192
std::pair<size_t, size_t> CachedRemoteFileReader::s_align_size(size_t offset, size_t read_size,
193
1.43M
                                                               size_t length) {
194
1.43M
    size_t left = offset;
195
1.43M
    size_t right = offset + read_size - 1;
196
1.43M
    size_t align_left =
197
1.43M
            (left / config::file_cache_each_block_size) * config::file_cache_each_block_size;
198
1.43M
    size_t align_right =
199
1.43M
            (right / config::file_cache_each_block_size + 1) * config::file_cache_each_block_size;
200
1.43M
    align_right = align_right < length ? align_right : length;
201
1.43M
    size_t align_size = align_right - align_left;
202
1.43M
    if (align_size < config::file_cache_each_block_size && align_left != 0) {
203
10
        align_size += config::file_cache_each_block_size;
204
10
        align_left -= config::file_cache_each_block_size;
205
10
    }
206
1.43M
    return std::make_pair(align_left, align_size);
207
1.43M
}
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
18
bool is_fill_not_found(const Status& st, bool request_fill) {
217
18
    return request_fill && st.is<ErrorCode::NOT_FOUND>();
218
18
}
219
220
96
bool contains_file_block(const PeerFetchedBlockSet& fetched_blocks, const FileBlockSPtr& block) {
221
96
    return fetched_blocks.contains(block.get());
222
96
}
223
224
10.5k
size_t clip_peer_block_size(const FileBlock::Range& range, size_t file_size) {
225
10.5k
    if (range.left >= file_size) {
226
0
        return 0;
227
0
    }
228
10.5k
    return std::min(file_size - range.left, range.size());
229
10.5k
}
230
231
PeerFetchLayout build_peer_fetch_layout(const std::vector<FileBlockSPtr>& blocks,
232
10.3k
                                        size_t file_size) {
233
10.3k
    PeerFetchLayout layout;
234
10.3k
    layout.block_offsets.reserve(blocks.size());
235
10.3k
    layout.block_sizes.reserve(blocks.size());
236
10.5k
    for (const auto& block : blocks) {
237
10.5k
        const size_t block_size = clip_peer_block_size(block->range(), file_size);
238
10.5k
        layout.block_offsets.push_back(layout.total_size);
239
10.5k
        layout.block_sizes.push_back(block_size);
240
10.5k
        layout.total_size += block_size;
241
10.5k
    }
242
10.3k
    return layout;
243
10.3k
}
244
245
Status write_peer_payloads_into_block(const FileBlockSPtr& block,
246
                                      std::vector<const PeerFetchChunk*>& chunks,
247
80
                                      size_t* block_size) {
248
80
    if (block_size == nullptr) {
249
0
        return Status::InvalidArgument("peer block write requires non-null block_size");
250
0
    }
251
80
    *block_size = 0;
252
80
    if (chunks.empty()) {
253
0
        return Status::OK();
254
0
    }
255
80
    std::sort(chunks.begin(), chunks.end(),
256
80
              [](const PeerFetchChunk* lhs, const PeerFetchChunk* rhs) {
257
0
                  return lhs->block_offset < rhs->block_offset;
258
0
              });
259
80
    butil::IOBuf payload;
260
80
    for (const auto* chunk : chunks) {
261
80
        *block_size += chunk->payload.length();
262
80
        payload.append(chunk->payload);
263
80
    }
264
80
    DCHECK(*block_size != 0);
265
80
    return block->append_iobuf(payload);
266
80
}
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
74
                               SourceReadBreakdown& source_read_breakdown) {
271
74
    const size_t payload_size = chunk.payload.length();
272
74
    if (payload_size == 0) {
273
0
        return;
274
0
    }
275
74
    const size_t chunk_left = chunk.block_offset;
276
74
    const size_t chunk_right = chunk_left + payload_size - 1;
277
74
    const size_t copy_left_offset = std::max(offset + already_read, chunk_left);
278
74
    const size_t copy_right_offset = std::min(right_offset, chunk_right);
279
74
    if (copy_left_offset > copy_right_offset) {
280
0
        return;
281
0
    }
282
74
    const size_t copy_offset = copy_left_offset - chunk_left;
283
74
    const size_t copy_size = copy_right_offset - copy_left_offset + 1;
284
74
    char* dst = result.data + (copy_left_offset - offset);
285
74
    chunk.payload.copy_to(dst, copy_size, copy_offset);
286
74
    indirect_read_bytes += copy_size;
287
74
    source_read_breakdown.peer_bytes += copy_size;
288
74
}
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
24
                         const IOContext* io_ctx, const std::string& host, int32_t port) {
295
24
    VLOG_DEBUG << "PeerFileCacheReader read from peer, host=" << host << ", port=" << port
296
0
               << ", file_path=" << file_path;
297
298
24
    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
24
    SCOPED_RAW_TIMER(&stats.peer_read_timer);
306
24
    peer_read_counter << 1;
307
24
    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
24
    auto st = peer_reader.fetch_blocks(empty_blocks, peer_result, file_size, io_ctx,
310
24
                                       /*request_fill=*/false);
311
24
    if (!st.ok()) {
312
8
        LOG_WARNING("PeerFileCacheReader read from peer failed")
313
8
                .tag("host", host)
314
8
                .tag("port", port)
315
8
                .tag("error", st.msg());
316
8
    }
317
24
    stats.from_peer_cache = st.ok();
318
24
    return st;
319
24
}
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
10.3k
                       FileReaderSPtr remote_file_reader) {
325
10.3k
    s3_read_counter << 1;
326
10.3k
    SCOPED_RAW_TIMER(&stats.remote_read_timer);
327
10.3k
    stats.from_peer_cache = false;
328
10.3k
    return remote_file_reader->read_at(empty_start, Slice(buffer.get(), size), &size, io_ctx);
329
10.3k
}
330
331
36
CloudWarmUpManager& get_warm_up_manager() {
332
36
    return ExecEnv::GetInstance()->storage_engine().to_cloud().cloud_warm_up_manager();
333
36
}
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
28
                   std::string resource_id, std::shared_ptr<ResourceContext> parent_resource_ctx) {
361
28
    std::unique_ptr<AttachTask> attach_task;
362
28
    if (parent_resource_ctx != nullptr) {
363
6
        attach_task = std::make_unique<AttachTask>(parent_resource_ctx);
364
6
    }
365
366
28
    bool all_tried = true;
367
28
    MonotonicStopWatch peer_sw;
368
28
    peer_sw.start();
369
370
44
    for (size_t i = 0; i < candidates.size(); ++i) {
371
        // Before issuing the next RPC, check if S3 already won.
372
34
        if (i > 0) {
373
6
            TEST_SYNC_POINT("run_peer_race::between_candidates");
374
6
            std::unique_lock<bthread::Mutex> lk(race->mtx);
375
6
            if (race->winner > 0) {
376
                // S3 already won — stop, but not all candidates were tried.
377
2
                all_tried = false;
378
2
                break;
379
2
            }
380
6
        }
381
382
32
        const auto& cand = candidates[i];
383
32
        peer_read_counter << 1;
384
32
        PeerFileCacheReader peer_reader(file_path, is_doris, cand.host, cand.brpc_port);
385
32
        PeerFetchResult local_peer_res;
386
32
        const bool request_fill =
387
32
                !config::peer_cache_fill_compute_group_id.empty() &&
388
32
                cand.compute_group_id == config::peer_cache_fill_compute_group_id &&
389
32
                !resource_id.empty() && !file_path.empty();
390
32
        MonotonicStopWatch cand_sw;
391
32
        cand_sw.start();
392
32
        auto st = peer_reader.fetch_blocks(empty_blocks, &local_peer_res, file_sz,
393
32
                                           /*ctx=*/nullptr, request_fill, tablet_id, resource_id);
394
32
        if (st.ok()) {
395
14
            manager->update_peer_candidate_on_success(tablet_id, cand.compute_group_id);
396
14
            std::unique_lock<bthread::Mutex> lk(race->mtx);
397
14
            if (race->winner < 0) {
398
14
                race->winner = 0;
399
14
                race->peer_res = std::move(local_peer_res);
400
14
                race->peer_winner_cg_id = cand.compute_group_id;
401
14
                race->peer_winner_host = cand.host;
402
14
                race->peer_elapsed_ns = peer_sw.elapsed_time();
403
14
                race->peer_winner_io_ns = cand_sw.elapsed_time();
404
14
            }
405
14
            race->peer_done = true;
406
14
            race->peer_status = Status::OK();
407
14
            race->cv.notify_all();
408
14
            return;
409
14
        }
410
411
        // Handle per-candidate failure.
412
18
        if (st.template is<ErrorCode::TOO_MANY_TASKS>()) {
413
0
            all_tried = false;
414
0
            break;
415
0
        }
416
18
        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
2
            manager->rotate_peer_candidate_on_cache_miss(tablet_id, cand.host, cand.brpc_port);
421
2
            all_tried = false;
422
2
            break;
423
2
        }
424
16
        if (st.template is<ErrorCode::NOT_FOUND>()) {
425
10
            manager->rotate_peer_candidate_on_cache_miss(tablet_id, cand.host, cand.brpc_port);
426
10
        } else {
427
6
            manager->update_peer_candidate_on_rpc_failure(tablet_id, cand.host, cand.brpc_port);
428
6
        }
429
16
    }
430
431
14
    if (all_tried) {
432
10
        manager->record_peer_all_miss(tablet_id);
433
10
    }
434
14
    std::unique_lock<bthread::Mutex> lk(race->mtx);
435
14
    race->peer_done = true;
436
14
    race->peer_status = Status::InternalError<false>("peer: all candidates failed");
437
14
    if (race->winner < 0 && race->s3_done) {
438
0
        race->winner = race->s3_status.ok() ? 1 : -2;
439
0
    }
440
14
    race->cv.notify_all();
441
14
}
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
28
                    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
28
    auto do_s3_read = [race, empty_start, span_size, remote_reader, owner]() {
456
16
        (void)owner;
457
16
        auto s3_buf = std::make_unique<char[]>(span_size);
458
16
        size_t read_size = span_size;
459
16
        s3_read_counter << 1;
460
16
        TEST_SYNC_POINT("CachedRemoteFileReader::_execute_winner_race::s3_before_read");
461
16
        auto st = remote_reader->read_at(empty_start, Slice(s3_buf.get(), span_size), &read_size,
462
16
                                         nullptr);
463
16
        std::unique_lock<bthread::Mutex> lk(race->mtx);
464
16
        race->s3_done = true;
465
16
        race->s3_status = st;
466
16
        if (st.ok() && race->winner < 0) {
467
14
            race->winner = 1;
468
14
            race->s3_buf = std::move(s3_buf);
469
14
        }
470
16
        race->cv.notify_all();
471
16
    };
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
28
    bool peer_already_won = false;
478
28
    if (config::peer_race_hedge_delay_ms > 0) {
479
26
        std::unique_lock<bthread::Mutex> lk(race->mtx);
480
26
        if (!race->peer_done) {
481
26
            race->cv.wait_for(lk, static_cast<long>(config::peer_race_hedge_delay_ms) * 1000);
482
26
        }
483
26
        peer_already_won = (race->winner == 0);
484
26
        if (peer_already_won) {
485
12
            race->s3_done = true;
486
12
            race->s3_status = Status::InternalError<false>("skipped: peer won during hedge delay");
487
12
        }
488
26
    }
489
490
28
    if (!peer_already_won) {
491
16
        auto s3_fn = [do_s3_read, parent_resource_ctx]() mutable {
492
14
            std::unique_ptr<AttachTask> attach_task;
493
14
            if (parent_resource_ctx != nullptr) {
494
2
                attach_task = std::make_unique<AttachTask>(parent_resource_ctx);
495
2
            }
496
14
            do_s3_read();
497
14
        };
498
16
        auto* s3_pool = ExecEnv::GetInstance()->peer_race_s3_thread_pool();
499
16
        if (s3_pool == nullptr || !s3_pool->submit_func(s3_fn).ok()) {
500
2
            do_s3_read();
501
2
        }
502
16
    }
503
28
}
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
28
                           ReadStatistics& stats, const IOContext* io_ctx) {
509
28
    {
510
28
        std::unique_lock<bthread::Mutex> lk(race->mtx);
511
42
        while (race->winner < 0 && !(race->peer_done && race->s3_done)) {
512
14
            race->cv.wait(lk);
513
14
        }
514
28
    }
515
28
    g_active_peer_races.fetch_sub(1, std::memory_order_relaxed);
516
517
28
    const std::string self_cg_id =
518
28
            static_cast<CloudClusterInfo*>(ExecEnv::GetInstance()->cluster_info())
519
28
                    ->cloud_compute_group_id();
520
521
28
    if (race->winner == 0) {
522
        // Peer won.
523
14
        if (peer_result != nullptr) {
524
14
            *peer_result = std::move(race->peer_res);
525
14
        }
526
14
        stats.from_peer_cache = true;
527
14
        stats.peer_read_timer += race->peer_elapsed_ns;
528
14
        g_peer_race_peer_win << 1;
529
14
        const bool is_cross_cg =
530
14
                !race->peer_winner_cg_id.empty() && race->peer_winner_cg_id != self_cg_id;
531
14
        if (is_cross_cg) {
532
10
            g_peer_cross_compute_group_read << 1;
533
10
        } else {
534
4
            g_peer_same_compute_group_read << 1;
535
4
        }
536
14
        if (io_ctx != nullptr && io_ctx->file_cache_stats != nullptr) {
537
14
            io_ctx->file_cache_stats->num_peer_race_peer_win++;
538
14
            io_ctx->file_cache_stats->peer_hosts.insert(race->peer_winner_host);
539
14
            if (is_cross_cg) {
540
10
                io_ctx->file_cache_stats->num_cross_cg_peer_io_total++;
541
10
                io_ctx->file_cache_stats->bytes_read_from_cross_cg_peer += span_size;
542
10
                io_ctx->file_cache_stats->cross_cg_peer_io_timer += race->peer_winner_io_ns;
543
10
            } else {
544
4
                io_ctx->file_cache_stats->num_same_cg_peer_io_total++;
545
4
                io_ctx->file_cache_stats->bytes_read_from_same_cg_peer += span_size;
546
4
                io_ctx->file_cache_stats->same_cg_peer_io_timer += race->peer_winner_io_ns;
547
4
            }
548
14
        }
549
14
        return Status::OK();
550
14
    } else if (race->winner == 1) {
551
        // S3 won.
552
14
        buffer = std::move(race->s3_buf);
553
14
        stats.from_peer_cache = false;
554
14
        g_peer_race_s3_win << 1;
555
14
        if (io_ctx != nullptr && io_ctx->file_cache_stats != nullptr) {
556
14
            io_ctx->file_cache_stats->num_peer_race_s3_win++;
557
14
        }
558
14
        return Status::OK();
559
14
    }
560
0
    g_peer_race_both_fail << 1;
561
0
    return Status::InternalError<false>("peer race: both peer and s3 failed");
562
28
}
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
10.3k
                                                    const IOContext* io_ctx) {
571
10.3k
    if (peer_result != nullptr) {
572
10.3k
        peer_result->clear();
573
10.3k
    }
574
10.3k
    buffer.reset(new char[span_size]);
575
10.3k
    size_t read_size = span_size;
576
10.3k
    return execute_s3_read(empty_start, read_size, buffer, stats, io_ctx, _remote_file_reader);
577
10.3k
}
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
2
        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
2
    if (candidates.empty()) {
587
0
        return _execute_s3_fallback(empty_start, span_size, buffer, peer_result, stats, io_ctx);
588
0
    }
589
590
2
    auto& manager = get_warm_up_manager();
591
2
    PeerFetchResult serial_res;
592
2
    const int64_t timer_before = stats.peer_read_timer;
593
2
    auto st = execute_peer_read(empty_blocks, &serial_res, path().native(), this->size(),
594
2
                                _is_doris_table, stats, io_ctx, candidates[0].host,
595
2
                                candidates[0].brpc_port);
596
2
    if (st.ok()) {
597
2
        manager.update_peer_candidate_on_success(tablet_id, candidates[0].compute_group_id);
598
2
        if (peer_result != nullptr) {
599
2
            *peer_result = std::move(serial_res);
600
2
        }
601
        // Update profile counters for cross/same CG stats.
602
2
        const std::string self_cg_id =
603
2
                static_cast<CloudClusterInfo*>(ExecEnv::GetInstance()->cluster_info())
604
2
                        ->cloud_compute_group_id();
605
2
        const bool is_cross_cg = !candidates[0].compute_group_id.empty() &&
606
2
                                 candidates[0].compute_group_id != self_cg_id;
607
2
        if (is_cross_cg) {
608
2
            g_peer_cross_compute_group_read << 1;
609
2
        } else {
610
0
            g_peer_same_compute_group_read << 1;
611
0
        }
612
2
        if (io_ctx != nullptr && io_ctx->file_cache_stats != nullptr) {
613
2
            io_ctx->file_cache_stats->peer_hosts.insert(candidates[0].host);
614
2
            if (is_cross_cg) {
615
2
                io_ctx->file_cache_stats->num_cross_cg_peer_io_total++;
616
2
                io_ctx->file_cache_stats->bytes_read_from_cross_cg_peer += span_size;
617
2
                io_ctx->file_cache_stats->cross_cg_peer_io_timer +=
618
2
                        stats.peer_read_timer - timer_before;
619
2
            } 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
2
        }
626
2
        return st;
627
2
    }
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
2
}
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
10.3k
                                                    const IOContext* io_ctx) {
647
    // --- Non-peer path: direct S3 ---
648
10.3k
    if (!_should_read_from_peer(io_ctx)) {
649
10.3k
        return _execute_s3_fallback(empty_start, span_size, buffer, peer_result, stats, io_ctx);
650
10.3k
    }
651
652
    // --- UT debug point: injected peer address ---
653
56
    DBUG_EXECUTE_IF("PeerFileCacheReader::_fetch_from_peer_cache_blocks", {
654
56
        std::string dp_host = dp->param<std::string>("host", "127.0.0.1");
655
56
        int32_t dp_port = dp->param("port", 9060);
656
56
        buffer.reset();
657
56
        DCHECK(peer_result != nullptr);
658
56
        peer_result->clear();
659
56
        auto st = execute_peer_read(empty_blocks, peer_result, path().native(), this->size(),
660
56
                                    _is_doris_table, stats, io_ctx, dp_host, dp_port);
661
56
        if (st.ok()) return st;
662
56
        return _execute_s3_fallback(empty_start, span_size, buffer, peer_result, stats, io_ctx);
663
56
    });
664
665
    // --- Resolve tablet and obtain peer candidates ---
666
34
    int64_t tablet_id = _tablet_id;
667
34
    auto& manager = get_warm_up_manager();
668
34
    auto candidates = manager.get_peer_candidates(tablet_id);
669
34
    if (candidates.empty()) {
670
4
        if (!manager.is_peer_cooldown(tablet_id)) {
671
            // Cold miss: trigger background FE fetch and fall back to S3.
672
2
            g_peer_lazy_fetch_triggered << 1;
673
2
            auto manager_ptr =
674
2
                    ExecEnv::GetInstance()->storage_engine().to_cloud().cloud_warm_up_manager_ptr();
675
2
            start_bthread([manager_ptr = std::move(manager_ptr), tablet_id]() {
676
2
                manager_ptr->fetch_candidates_from_fe(tablet_id);
677
2
            });
678
2
        }
679
4
        return _execute_s3_fallback(empty_start, span_size, buffer, peer_result, stats, io_ctx);
680
4
    }
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
30
    if (config::enable_peer_s3_race) {
687
28
        return _execute_winner_race(empty_blocks, empty_start, span_size, buffer, peer_result,
688
28
                                    stats, io_ctx, candidates, tablet_id);
689
28
    }
690
2
    return _execute_sequential_peer_read(empty_blocks, empty_start, span_size, buffer, peer_result,
691
2
                                         stats, io_ctx, candidates, tablet_id);
692
30
}
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
28
        int64_t tablet_id) {
699
    // Reserve a race slot; degrade to sequential if at limit.
700
28
    if (g_active_peer_races.fetch_add(1, std::memory_order_relaxed) >=
701
28
        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
28
    auto race = std::make_shared<RaceState>();
708
28
    auto manager = ExecEnv::GetInstance()->storage_engine().to_cloud().cloud_warm_up_manager_ptr();
709
710
    // Capture context for child threads.
711
28
    const std::string file_path = path().native();
712
28
    const size_t file_sz = this->size();
713
28
    const bool is_doris = _is_doris_table;
714
28
    auto remote_reader = _remote_file_reader;
715
28
    std::shared_ptr<ResourceContext> parent_resource_ctx;
716
28
    auto* parent_thread_context = thread_context();
717
28
    if (parent_thread_context != nullptr && parent_thread_context->is_attach_task()) {
718
6
        parent_resource_ctx = parent_thread_context->resource_ctx();
719
6
    }
720
721
    // Launch peer bthread.
722
28
    start_bthread(
723
28
            [race, empty_blocks = std::move(empty_blocks), file_path, file_sz, is_doris,
724
28
             manager = std::move(manager), candidates = std::move(candidates), tablet_id,
725
28
             resource_id = _storage_resource_id, parent_resource_ctx]() mutable {
726
28
                run_peer_race(race, std::move(empty_blocks), file_path, file_sz, is_doris,
727
28
                              std::move(manager), std::move(candidates), tablet_id,
728
28
                              std::move(resource_id), parent_resource_ctx);
729
28
            },
730
28
            /*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
28
    launch_s3_race(race, empty_start, span_size, io_ctx, remote_reader, parent_resource_ctx,
736
28
                   shared_from_this());
737
738
    // Collect race result.
739
28
    return collect_race_result(race, span_size, buffer, peer_result, stats, io_ctx);
740
28
}
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
1.43M
        SourceReadBreakdown& source_read_breakdown, size_t& already_read, size_t* bytes_read) {
745
1.43M
    if (!_can_read_cache_file_directly()) {
746
1.43M
        return false;
747
1.43M
    }
748
749
2.08k
    SCOPED_RAW_TIMER(&stats.read_cache_file_directly_timer);
750
2.08k
    size_t need_read_size = bytes_req;
751
2.08k
    std::shared_lock lock(_mtx);
752
2.08k
    if (_cache_file_readers.empty()) {
753
32
        return false;
754
32
    }
755
756
2.04k
    auto iter = _cache_file_readers.upper_bound(offset);
757
2.04k
    if (iter != _cache_file_readers.begin()) {
758
2.03k
        --iter;
759
2.03k
    }
760
761
2.04k
    size_t current_offset = offset;
762
4.91k
    while (need_read_size != 0 && iter != _cache_file_readers.end()) {
763
2.88k
        if (iter->second->offset() > current_offset ||
764
2.88k
            iter->second->range().right < current_offset) {
765
14
            break;
766
14
        }
767
768
2.87k
        size_t file_offset = current_offset - iter->second->offset();
769
2.87k
        size_t reserve_bytes = std::min(need_read_size, iter->second->range().size() - file_offset);
770
2.87k
        if (is_dryrun) [[unlikely]] {
771
2
            g_skip_local_cache_io_sum_bytes << reserve_bytes;
772
2.86k
        } else {
773
2.86k
            SCOPED_RAW_TIMER(&stats.local_read_timer);
774
2.86k
            if (!iter->second
775
2.86k
                         ->read(Slice(result.data + (current_offset - offset), reserve_bytes),
776
2.86k
                                file_offset)
777
2.86k
                         .ok()) { // TODO: maybe read failed because block evict, should handle error
778
0
                break;
779
0
            }
780
2.86k
            source_read_breakdown.local_bytes += reserve_bytes;
781
2.86k
        }
782
783
2.87k
        _cache->add_need_update_lru_block(iter->second);
784
2.87k
        need_read_size -= reserve_bytes;
785
2.87k
        current_offset += reserve_bytes;
786
2.87k
        already_read += reserve_bytes;
787
2.87k
        ++iter;
788
2.87k
    }
789
790
2.04k
    if (need_read_size == 0) {
791
2.01k
        *bytes_read = bytes_req;
792
2.01k
        stats.hit_cache = true;
793
2.01k
        g_read_cache_direct_whole_num << 1;
794
2.01k
        g_read_cache_direct_whole_bytes << bytes_req;
795
2.01k
        return true;
796
2.01k
    }
797
798
34
    g_read_cache_direct_partial_num << 1;
799
34
    g_read_cache_direct_partial_bytes << already_read;
800
34
    return false;
801
2.04k
}
802
803
std::vector<FileBlockSPtr> CachedRemoteFileReader::_collect_remote_read_blocks(
804
1.43M
        const FileBlocksHolder& holder, ReadStatistics& stats) {
805
1.43M
    std::vector<FileBlockSPtr> empty_blocks;
806
1.43M
    for (auto& block : holder.file_blocks) {
807
1.43M
        switch (block->state()) {
808
10.4k
        case FileBlock::State::EMPTY:
809
10.4k
            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
10.4k
            block->get_or_set_downloader();
813
10.4k
            if (block->is_downloader()) {
814
10.4k
                empty_blocks.push_back(block);
815
10.4k
                TEST_SYNC_POINT_CALLBACK("CachedRemoteFileReader::EMPTY");
816
10.4k
            }
817
10.4k
            stats.hit_cache = false;
818
10.4k
            break;
819
8
        case FileBlock::State::SKIP_CACHE:
820
8
            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
8
            empty_blocks.push_back(block);
825
8
            stats.hit_cache = false;
826
8
            stats.skip_cache = true;
827
8
            break;
828
4
        case FileBlock::State::DOWNLOADING:
829
4
            stats.hit_cache = false;
830
4
            break;
831
1.42M
        case FileBlock::State::DOWNLOADED:
832
1.42M
            _insert_file_reader(block);
833
1.42M
            break;
834
1.43M
        }
835
1.43M
    }
836
1.43M
    return empty_blocks;
837
1.43M
}
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
1.43M
        PeerFetchedBlockSet& peer_fetched_blocks) {
845
1.43M
    empty_start = 0;
846
1.43M
    empty_end = 0;
847
1.43M
    peer_fetched_blocks.clear();
848
1.43M
    if (empty_blocks.empty()) {
849
1.42M
        return Status::OK();
850
1.42M
    }
851
852
10.3k
    empty_start = empty_blocks.front()->range().left;
853
10.3k
    empty_end = empty_blocks.back()->range().right;
854
10.3k
    const size_t span_read_size = empty_end - empty_start + 1;
855
10.3k
    const auto peer_fetch_layout = build_peer_fetch_layout(empty_blocks, size());
856
10.3k
    std::unique_ptr<char[]> buffer;
857
10.3k
    PeerFetchResult peer_result;
858
859
10.3k
    RETURN_IF_ERROR(_execute_remote_read(empty_blocks, empty_start, span_read_size, buffer,
860
10.3k
                                         &peer_result, stats, io_ctx));
861
862
10.3k
    std::vector<std::vector<const PeerFetchChunk*>> peer_chunks_by_block;
863
10.3k
    if (stats.from_peer_cache) {
864
        // Peer returns sparse payloads; remember the exact sparse blocks that were filled.
865
30
        peer_fetched_blocks.reserve(empty_blocks.size());
866
80
        for (const auto& block : empty_blocks) {
867
80
            peer_fetched_blocks.insert(block.get());
868
80
        }
869
30
        peer_chunks_by_block.resize(empty_blocks.size());
870
80
        for (const auto& chunk : peer_result.chunks) {
871
80
            DCHECK_LT(chunk.block_index, empty_blocks.size());
872
80
            peer_chunks_by_block[chunk.block_index].push_back(&chunk);
873
80
        }
874
30
    }
875
876
10.3k
    SCOPED_CONCURRENCY_COUNT(ConcurrencyStatsManager::instance().cached_remote_reader_write_back);
877
20.8k
    for (size_t idx = 0; idx < empty_blocks.size(); ++idx) {
878
10.5k
        auto& block = empty_blocks[idx];
879
10.5k
        if (block->state() == FileBlock::State::SKIP_CACHE) {
880
8
            continue;
881
8
        }
882
883
10.4k
        SCOPED_RAW_TIMER(&stats.local_write_timer);
884
10.4k
        size_t block_size = block->range().size();
885
10.4k
        Status st;
886
10.4k
        if (stats.from_peer_cache) {
887
80
            block_size = peer_fetch_layout.block_sizes[idx];
888
80
            if (block_size == 0) {
889
0
                continue;
890
0
            }
891
80
            st = write_peer_payloads_into_block(block, peer_chunks_by_block[idx], &block_size);
892
10.4k
        } else {
893
10.4k
            char* current_ptr = buffer.get() + block->range().left - empty_start;
894
10.4k
            st = block->append(Slice(current_ptr, block_size));
895
10.4k
        }
896
10.4k
        if (st.ok()) {
897
10.4k
            st = block->finalize();
898
10.4k
        }
899
10.4k
        if (!st.ok()) {
900
4
            LOG(WARNING) << "write data to file cache failed, source="
901
4
                         << (stats.from_peer_cache ? "peer" : "remote")
902
4
                         << ", path=" << path().native() << ", tablet_id=" << _tablet_id
903
4
                         << ", file_size=" << size() << ", cache_hash=" << _cache_hash.to_string()
904
4
                         << ", write_block_size=" << block_size
905
4
                         << ", block=" << block->get_info_for_log()
906
4
                         << ", cache_file=" << block->get_cache_file() << ", err=" << st;
907
10.4k
        } else {
908
10.4k
            _insert_file_reader(block);
909
10.4k
            stats.bytes_write_into_file_cache += block_size;
910
10.4k
        }
911
10.4k
    }
912
913
10.3k
    const size_t right_offset = offset + bytes_req - 1;
914
10.3k
    if (stats.from_peer_cache) {
915
30
        if (is_dryrun) {
916
2
            return Status::OK();
917
2
        }
918
74
        for (const auto& chunk : peer_result.chunks) {
919
74
            copy_peer_chunk_to_result(chunk, offset, right_offset, already_read, result,
920
74
                                      indirect_read_bytes, source_read_breakdown);
921
74
        }
922
28
        return Status::OK();
923
30
    }
924
925
10.3k
    if (empty_start <= right_offset && empty_end >= offset + already_read && !is_dryrun) {
926
10.3k
        size_t copy_left_offset = std::max(offset + already_read, empty_start);
927
10.3k
        size_t copy_right_offset = std::min(right_offset, empty_end);
928
10.3k
        char* dst = result.data + (copy_left_offset - offset);
929
10.3k
        char* src = buffer.get() + (copy_left_offset - empty_start);
930
10.3k
        size_t copy_size = copy_right_offset - copy_left_offset + 1;
931
10.3k
        memcpy(dst, src, copy_size);
932
10.3k
        indirect_read_bytes += copy_size;
933
10.3k
        source_read_breakdown.remote_bytes += copy_size;
934
10.3k
    }
935
10.3k
    return Status::OK();
936
10.3k
}
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
1.43M
        const IOContext* io_ctx) {
944
1.43M
    size_t current_offset = offset + *bytes_read;
945
1.43M
    size_t end_offset = offset + bytes_req - 1;
946
1.43M
    bool need_self_heal = false;
947
1.43M
    for (auto& block : holder.file_blocks) {
948
1.43M
        if (current_offset > end_offset) {
949
0
            break;
950
0
        }
951
952
1.43M
        size_t left = block->range().left;
953
1.43M
        size_t right = block->range().right;
954
1.43M
        if (right < offset) {
955
2
            continue;
956
2
        }
957
958
1.43M
        size_t read_size =
959
1.43M
                end_offset > right ? right - current_offset + 1 : end_offset - current_offset + 1;
960
1.43M
        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
82
            *bytes_read += read_size;
964
82
            current_offset = right + 1;
965
82
            continue;
966
82
        }
967
1.43M
        if (peer_fetched_blocks.empty() && empty_start <= left && right <= empty_end) {
968
10.4k
            *bytes_read += read_size;
969
10.4k
            current_offset = right + 1;
970
10.4k
            continue;
971
10.4k
        }
972
973
1.42M
        FileBlock::State block_state = block->state();
974
1.42M
        int64_t wait_time = 0;
975
1.42M
        static int64_t max_wait_time = 10;
976
1.42M
        TEST_SYNC_POINT_CALLBACK("CachedRemoteFileReader::max_wait_time", &max_wait_time);
977
1.42M
        if (block_state != FileBlock::State::DOWNLOADED) {
978
6
            SCOPED_CONCURRENCY_COUNT(
979
6
                    ConcurrencyStatsManager::instance().cached_remote_reader_blocking);
980
8
            do {
981
8
                SCOPED_RAW_TIMER(&stats.remote_wait_timer);
982
8
                TEST_SYNC_POINT_CALLBACK("CachedRemoteFileReader::DOWNLOADING");
983
8
                block_state = block->wait();
984
8
                if (block_state != FileBlock::State::DOWNLOADING) {
985
4
                    break;
986
4
                }
987
8
            } while (++wait_time < max_wait_time);
988
6
        }
989
1.42M
        if (wait_time == max_wait_time) [[unlikely]] {
990
2
            LOG_WARNING("Waiting too long for the download to complete");
991
2
        }
992
993
1.42M
        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
1.42M
        if (block_state == FileBlock::State::DOWNLOADED) {
1000
1.42M
            if (is_dryrun) [[unlikely]] {
1001
2
                g_skip_local_cache_io_sum_bytes << read_size;
1002
1.42M
            } else {
1003
1.42M
                size_t file_offset = current_offset - left;
1004
1.42M
                SCOPED_RAW_TIMER(&stats.local_read_timer);
1005
1.42M
                SCOPED_CONCURRENCY_COUNT(
1006
1.42M
                        ConcurrencyStatsManager::instance().cached_remote_reader_local_read);
1007
1.42M
                st = block->read(Slice(result.data + (current_offset - offset), read_size),
1008
1.42M
                                 file_offset);
1009
1.42M
                indirect_read_bytes += read_size;
1010
1.42M
                if (st.ok()) {
1011
1.42M
                    source_read_breakdown.local_bytes += read_size;
1012
1.42M
                }
1013
1.42M
            }
1014
1.42M
            if (block_state == FileBlock::State::DOWNLOADED && st.is<ErrorCode::NOT_FOUND>()) {
1015
2
                need_self_heal = true;
1016
2
                g_read_cache_self_heal_on_not_found << 1;
1017
2
                LOG_EVERY_N(WARNING, 100)
1018
2
                        << "Cache block file is missing, will self-heal by clearing cache hash. "
1019
2
                        << "path=" << path().native() << ", hash=" << _cache_hash.to_string()
1020
2
                        << ", offset=" << left << ", err=" << st.msg();
1021
2
            }
1022
1.42M
        }
1023
1.42M
        if (!st || block_state != FileBlock::State::DOWNLOADED) {
1024
8
            if (is_dryrun) [[unlikely]] {
1025
0
                *bytes_read += read_size;
1026
0
                current_offset = right + 1;
1027
0
                continue;
1028
0
            }
1029
8
            LOG(WARNING) << "Read data failed from file cache downloaded by others. err="
1030
8
                         << st.msg() << ", block state=" << block_state;
1031
8
            size_t remote_bytes_read {0};
1032
8
            stats.hit_cache = false;
1033
8
            stats.from_peer_cache = false;
1034
8
            s3_read_counter << 1;
1035
8
            SCOPED_RAW_TIMER(&stats.remote_read_timer);
1036
8
            RETURN_IF_ERROR(_remote_file_reader->read_at(
1037
8
                    current_offset, Slice(result.data + (current_offset - offset), read_size),
1038
8
                    &remote_bytes_read, io_ctx));
1039
6
            indirect_read_bytes += read_size;
1040
6
            source_read_breakdown.remote_bytes += remote_bytes_read;
1041
6
            DCHECK(remote_bytes_read == read_size);
1042
6
        }
1043
1044
1.42M
        *bytes_read += read_size;
1045
1.42M
        current_offset = right + 1;
1046
1.42M
    }
1047
1.43M
    if (need_self_heal && _cache != nullptr) {
1048
2
        _cache->remove_if_cached_async(_cache_hash);
1049
2
    }
1050
1.43M
    return Status::OK();
1051
1.43M
}
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
1.43M
                                                         const IOContext* io_ctx) {
1059
1.43M
    g_read_cache_indirect_num << 1;
1060
1.43M
    size_t indirect_read_bytes = 0;
1061
1.43M
    auto [align_left, align_size] =
1062
1.43M
            s_align_size(offset + already_read, bytes_req - already_read, size());
1063
1.43M
    CacheContext cache_context(io_ctx);
1064
1.43M
    cache_context.stats = &stats;
1065
1.43M
    MonotonicStopWatch sw;
1066
1.43M
    sw.start();
1067
1.43M
    ConcurrencyStatsManager::instance().cached_remote_reader_get_or_set->increment();
1068
1.43M
    FileBlocksHolder holder =
1069
1.43M
            _cache->get_or_set(_cache_hash, align_left, align_size, cache_context);
1070
1.43M
    ConcurrencyStatsManager::instance().cached_remote_reader_get_or_set->decrement();
1071
1.43M
    stats.cache_get_or_set_timer += sw.elapsed_time();
1072
1073
1.43M
    auto empty_blocks = _collect_remote_read_blocks(holder, stats);
1074
1.43M
    size_t empty_start = 0;
1075
1.43M
    size_t empty_end = 0;
1076
1.43M
    PeerFetchedBlockSet peer_fetched_blocks;
1077
1.43M
    RETURN_IF_ERROR(_read_remote_blocks_into_cache(empty_blocks, offset, bytes_req, already_read,
1078
1.43M
                                                   result, is_dryrun, stats, source_read_breakdown,
1079
1.43M
                                                   io_ctx, indirect_read_bytes, empty_start,
1080
1.43M
                                                   empty_end, peer_fetched_blocks));
1081
1.43M
    *bytes_read = already_read;
1082
1.43M
    RETURN_IF_ERROR(_read_remaining_blocks_from_cache(holder, offset, bytes_req, result, is_dryrun,
1083
1.43M
                                                      empty_start, empty_end, peer_fetched_blocks,
1084
1.43M
                                                      stats, source_read_breakdown,
1085
1.43M
                                                      indirect_read_bytes, bytes_read, io_ctx));
1086
1.43M
    g_read_cache_indirect_bytes << indirect_read_bytes;
1087
1.43M
    g_read_cache_indirect_total_bytes << *bytes_read;
1088
1.43M
    DCHECK(*bytes_read == bytes_req);
1089
1.43M
    return Status::OK();
1090
1.43M
}
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
10
        const IOContext* io_ctx) {
1096
10
    auto read_remote = [&]() -> Status {
1097
6
        stats.hit_cache = false;
1098
6
        stats.from_peer_cache = false;
1099
6
        stats.skip_cache = true;
1100
6
        s3_read_counter << 1;
1101
6
        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
6
        size_t remote_bytes_read = bytes_req;
1109
6
        SCOPED_RAW_TIMER(&stats.remote_read_timer);
1110
6
        RETURN_IF_ERROR(_remote_file_reader->read_at(offset, Slice(result.data, bytes_req),
1111
6
                                                     &remote_bytes_read, io_ctx));
1112
6
        *bytes_read = remote_bytes_read;
1113
6
        DCHECK_EQ(*bytes_read, bytes_req);
1114
6
        source_read_breakdown.remote_bytes += remote_bytes_read;
1115
6
        g_read_cache_indirect_bytes << remote_bytes_read;
1116
6
        g_read_cache_indirect_total_bytes << remote_bytes_read;
1117
6
        return Status::OK();
1118
6
    };
1119
1120
10
    g_read_cache_indirect_num << 1;
1121
10
    CacheContext cache_context(io_ctx);
1122
10
    cache_context.stats = &stats;
1123
10
    cache_context.tablet_id = _tablet_id;
1124
10
    FileBlocks file_blocks;
1125
10
    bool fully_covered = false;
1126
10
    {
1127
10
        SCOPED_RAW_TIMER(&stats.get_timer);
1128
10
        RETURN_IF_ERROR(_cache->get_downloaded_blocks_if_fully_covered(
1129
10
                _cache_hash, offset, bytes_req, cache_context, &file_blocks, &fully_covered));
1130
10
    }
1131
10
    if (!fully_covered) {
1132
6
        return read_remote();
1133
6
    }
1134
1135
4
    size_t local_read_bytes = 0;
1136
4
    size_t current_offset = offset;
1137
4
    size_t end_offset = offset + bytes_req - 1;
1138
4
    for (auto& block : file_blocks) {
1139
4
        if (current_offset > end_offset) {
1140
0
            break;
1141
0
        }
1142
4
        const auto& block_range = block->range();
1143
4
        if (block_range.right < current_offset) {
1144
0
            continue;
1145
0
        }
1146
1147
4
        size_t read_left = std::max(current_offset, block_range.left);
1148
4
        size_t read_right = std::min(end_offset, block_range.right);
1149
4
        size_t read_size = read_right - read_left + 1;
1150
4
        if (is_dryrun) [[unlikely]] {
1151
0
            g_skip_local_cache_io_sum_bytes << read_size;
1152
4
        } else {
1153
4
            SCOPED_RAW_TIMER(&stats.local_read_timer);
1154
4
            Status st = block->read(Slice(result.data + (read_left - offset), read_size),
1155
4
                                    read_left - block_range.left);
1156
4
            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
4
            source_read_breakdown.local_bytes += read_size;
1167
4
            local_read_bytes += read_size;
1168
4
        }
1169
4
        current_offset = read_right + 1;
1170
4
    }
1171
1172
4
    *bytes_read = bytes_req;
1173
4
    stats.hit_cache = true;
1174
4
    g_read_cache_indirect_bytes << local_read_bytes;
1175
4
    g_read_cache_indirect_total_bytes << bytes_req;
1176
4
    return Status::OK();
1177
4
}
1178
1179
Status CachedRemoteFileReader::read_at_impl(size_t offset, Slice result, size_t* bytes_read,
1180
1.43M
                                            const IOContext* io_ctx) {
1181
1.43M
    SCOPED_CONCURRENCY_COUNT(ConcurrencyStatsManager::instance().cached_remote_reader_read_at);
1182
1.43M
    IOContext default_io_ctx;
1183
1.43M
    if (io_ctx == nullptr) {
1184
2
        io_ctx = &default_io_ctx;
1185
2
    }
1186
1.43M
    DCHECK(io_ctx);
1187
1.43M
    DCHECK(!closed());
1188
1189
1.43M
    const bool is_dryrun = io_ctx->is_dryrun;
1190
1.43M
    if (offset > size()) {
1191
2
        return Status::InvalidArgument(
1192
2
                fmt::format("offset exceeds file size(offset: {}, file size: {}, path: {})", offset,
1193
2
                            size(), path().native()));
1194
2
    }
1195
1196
1.43M
    size_t bytes_req = std::min(result.size, size() - offset);
1197
1.43M
    if (UNLIKELY(bytes_req == 0)) {
1198
2
        *bytes_read = 0;
1199
2
        return Status::OK();
1200
2
    }
1201
1202
1.43M
    ReadStatistics stats;
1203
1.43M
    SourceReadBreakdown source_read_breakdown;
1204
1.43M
    Status read_st = Status::OK();
1205
1.43M
    MonotonicStopWatch read_at_sw;
1206
1.43M
    read_at_sw.start();
1207
1.43M
    stats.bytes_read += bytes_req;
1208
1.43M
    Defer defer {[&]() {
1209
1.43M
        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
1.43M
        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
1.43M
        if (read_st.ok() && !is_dryrun) {
1222
            // Only successful reads contribute to query profile and file-cache metrics.
1223
1.43M
            const auto file_cache_read_type =
1224
1.43M
                    io_ctx->is_inverted_index
1225
1.43M
                            ? FileCacheReadType::INVERTED_INDEX
1226
1.43M
                            : (io_ctx->is_index_data ? FileCacheReadType::SEGMENT_FOOTER_INDEX
1227
1.43M
                                                     : FileCacheReadType::DATA);
1228
1.43M
            if (io_ctx->file_cache_stats) {
1229
1.41M
                _update_stats(stats, source_read_breakdown, io_ctx->file_cache_stats,
1230
1.41M
                              file_cache_read_type);
1231
1.41M
                auto* limiter = io_ctx->remote_scan_cache_write_limiter;
1232
1.41M
                if (limiter != nullptr) {
1233
10
                    io_ctx->file_cache_stats->remote_only_on_miss_triggered =
1234
10
                            io_ctx->file_cache_stats->remote_only_on_miss_triggered ||
1235
10
                            limiter->remote_only_on_miss();
1236
10
                    io_ctx->file_cache_stats->remote_only_on_miss_threshold_bytes =
1237
10
                            limiter->threshold_bytes();
1238
10
                }
1239
1.41M
            }
1240
1.43M
            if (!io_ctx->is_warmup) {
1241
1.43M
                FileCacheStatistics fcache_stats_increment;
1242
1.43M
                _update_stats(stats, source_read_breakdown, &fcache_stats_increment,
1243
1.43M
                              file_cache_read_type);
1244
1.43M
                io::FileCacheMetrics::instance().update(&fcache_stats_increment);
1245
1.43M
            }
1246
1.43M
        }
1247
1.43M
    }};
1248
1249
1.43M
    if (use_remote_only_on_cache_miss(io_ctx)) {
1250
10
        read_st = _read_remote_only_on_cache_miss(offset, result, bytes_req, is_dryrun, bytes_read,
1251
10
                                                  stats, source_read_breakdown, io_ctx);
1252
10
        return read_st;
1253
10
    }
1254
1255
1.43M
    size_t already_read = 0;
1256
1.43M
    if (_try_read_from_cached_files_directly(offset, result, bytes_req, is_dryrun, stats,
1257
1.43M
                                             source_read_breakdown, already_read, bytes_read)) {
1258
2.01k
        return Status::OK();
1259
2.01k
    }
1260
1261
1.43M
    read_st = _read_from_indirect_cache(offset, result, bytes_req, already_read, is_dryrun,
1262
1.43M
                                        bytes_read, stats, source_read_breakdown, io_ctx);
1263
1.43M
    return read_st;
1264
1.43M
}
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
2.85M
                                           FileCacheReadType read_type) const {
1315
2.85M
    if (statis == nullptr) {
1316
0
        return;
1317
0
    }
1318
2.85M
    const bool has_source_bytes = source_read_breakdown.local_bytes != 0 ||
1319
2.85M
                                  source_read_breakdown.remote_bytes != 0 ||
1320
2.85M
                                  source_read_breakdown.peer_bytes != 0;
1321
2.85M
    if (has_source_bytes) {
1322
2.85M
        if (source_read_breakdown.local_bytes != 0) {
1323
2.83M
            statis->num_local_io_total++;
1324
2.83M
            statis->bytes_read_from_local += source_read_breakdown.local_bytes;
1325
2.83M
        }
1326
2.85M
        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
56
            statis->num_peer_io_total++;
1332
56
            statis->bytes_read_from_peer += source_read_breakdown.peer_bytes;
1333
56
            statis->peer_io_timer += read_stats.peer_read_timer;
1334
56
        }
1335
2.85M
        if (source_read_breakdown.remote_bytes != 0) {
1336
20.6k
            statis->num_remote_io_total++;
1337
20.6k
            statis->bytes_read_from_remote += source_read_breakdown.remote_bytes;
1338
20.6k
            statis->remote_io_timer += read_stats.remote_read_timer;
1339
20.6k
        }
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
2.85M
    statis->remote_wait_timer += read_stats.remote_wait_timer;
1353
2.85M
    statis->local_io_timer += read_stats.local_read_timer;
1354
2.85M
    statis->num_skip_cache_io_total += read_stats.skip_cache;
1355
2.85M
    statis->bytes_write_into_cache += read_stats.bytes_write_into_file_cache;
1356
2.85M
    statis->write_cache_io_timer += read_stats.local_write_timer;
1357
1358
2.85M
    statis->read_cache_file_directly_timer += read_stats.read_cache_file_directly_timer;
1359
2.85M
    statis->cache_get_or_set_timer += read_stats.cache_get_or_set_timer;
1360
2.85M
    statis->lock_wait_timer += read_stats.lock_wait_timer;
1361
2.85M
    statis->get_timer += read_stats.get_timer;
1362
2.85M
    statis->set_timer += read_stats.set_timer;
1363
1364
2.85M
    auto update_index_stats = [&](int64_t& num_local_io_total, int64_t& num_remote_io_total,
1365
2.85M
                                  int64_t& num_peer_io_total, int64_t& bytes_read_from_local,
1366
2.85M
                                  int64_t& bytes_read_from_remote, int64_t& bytes_read_from_peer,
1367
2.85M
                                  int64_t& local_io_timer, int64_t& remote_io_timer,
1368
2.85M
                                  int64_t& peer_io_timer, int64_t& write_cache_io_timer,
1369
2.85M
                                  int64_t& bytes_write_into_cache) {
1370
12
        if (has_source_bytes) {
1371
12
            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
12
            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
12
            if (source_read_breakdown.remote_bytes != 0) {
1381
12
                num_remote_io_total++;
1382
12
                bytes_read_from_remote += source_read_breakdown.remote_bytes;
1383
12
                remote_io_timer += read_stats.remote_read_timer;
1384
12
            }
1385
12
        } 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
12
        local_io_timer += read_stats.local_read_timer;
1398
12
        write_cache_io_timer += read_stats.local_write_timer;
1399
12
        bytes_write_into_cache += read_stats.bytes_write_into_file_cache;
1400
12
    };
1401
1402
2.85M
    switch (read_type) {
1403
2.85M
    case FileCacheReadType::DATA:
1404
2.85M
        break;
1405
4
    case FileCacheReadType::INVERTED_INDEX:
1406
4
        update_index_stats(
1407
4
                statis->inverted_index_num_local_io_total,
1408
4
                statis->inverted_index_num_remote_io_total,
1409
4
                statis->inverted_index_num_peer_io_total,
1410
4
                statis->inverted_index_bytes_read_from_local,
1411
4
                statis->inverted_index_bytes_read_from_remote,
1412
4
                statis->inverted_index_bytes_read_from_peer, statis->inverted_index_local_io_timer,
1413
4
                statis->inverted_index_remote_io_timer, statis->inverted_index_peer_io_timer,
1414
4
                statis->inverted_index_write_cache_io_timer,
1415
4
                statis->inverted_index_bytes_write_into_cache);
1416
4
        break;
1417
8
    case FileCacheReadType::SEGMENT_FOOTER_INDEX:
1418
8
        update_index_stats(statis->segment_footer_index_num_local_io_total,
1419
8
                           statis->segment_footer_index_num_remote_io_total,
1420
8
                           statis->segment_footer_index_num_peer_io_total,
1421
8
                           statis->segment_footer_index_bytes_read_from_local,
1422
8
                           statis->segment_footer_index_bytes_read_from_remote,
1423
8
                           statis->segment_footer_index_bytes_read_from_peer,
1424
8
                           statis->segment_footer_index_local_io_timer,
1425
8
                           statis->segment_footer_index_remote_io_timer,
1426
8
                           statis->segment_footer_index_peer_io_timer,
1427
8
                           statis->segment_footer_index_write_cache_io_timer,
1428
8
                           statis->segment_footer_index_bytes_write_into_cache);
1429
8
        break;
1430
2.85M
    }
1431
1432
2.85M
    g_skip_cache_sum << read_stats.skip_cache;
1433
2.85M
}
1434
1435
} // namespace doris::io