Coverage Report

Created: 2026-08-15 01:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/cloud/cloud_internal_service.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 "cloud/cloud_internal_service.h"
19
20
#include <brpc/controller.h>
21
#include <bthread/countdown_event.h>
22
#include <butil/iobuf.h>
23
#include <fmt/format.h>
24
25
#include <algorithm>
26
#include <chrono>
27
#include <limits>
28
#include <list>
29
#include <memory>
30
#include <optional>
31
#include <thread>
32
#include <unordered_map>
33
#include <vector>
34
35
#include "cloud/cloud_storage_engine.h"
36
#include "cloud/cloud_tablet.h"
37
#include "cloud/cloud_tablet_mgr.h"
38
#include "cloud/cloud_warm_up_manager.h"
39
#include "cloud/cloud_warmup_metrics.h"
40
#include "cloud/config.h"
41
#include "io/cache/block_file_cache.h"
42
#include "io/cache/block_file_cache_downloader.h"
43
#include "io/cache/block_file_cache_factory.h"
44
#include "io/fs/path.h"
45
#include "runtime/thread_context.h"
46
#include "runtime/workload_management/io_throttle.h"
47
#include "storage/storage_policy.h"
48
#include "util/async_io.h"
49
#include "util/bvar_windowed_adder.h"
50
#include "util/debug_points.h"
51
52
namespace doris {
53
#include "common/compile_check_avoid_begin.h"
54
55
bvar::Adder<uint64_t> g_file_cache_get_by_peer_num("file_cache_get_by_peer_num");
56
bvar::Adder<uint64_t> g_file_cache_get_by_peer_blocks_num("file_cache_get_by_peer_blocks_num");
57
bvar::Adder<uint64_t> g_file_cache_get_by_peer_success_num("file_cache_get_by_peer_success_num");
58
bvar::Adder<uint64_t> g_file_cache_get_by_peer_failed_num("file_cache_get_by_peer_failed_num");
59
bvar::LatencyRecorder g_file_cache_get_by_peer_server_latency(
60
        "file_cache_get_by_peer_server_latency");
61
bvar::LatencyRecorder g_file_cache_get_by_peer_read_cache_file_latency(
62
        "file_cache_get_by_peer_read_cache_file_latency");
63
bvar::Adder<uint64_t> g_file_cache_get_by_peer_offer_failed_num(
64
        "file_cache_get_by_peer_offer_failed_num");
65
bvar::Adder<uint64_t> g_file_cache_get_by_peer_queue_timeout_num(
66
        "file_cache_get_by_peer_queue_timeout_num");
67
bvar::LatencyRecorder g_file_cache_get_by_peer_queue_wait_latency(
68
        "file_cache_get_by_peer_queue_wait_latency");
69
bvar::LatencyRecorder g_file_cache_get_by_peer_handle_cache_block_req_latency(
70
        "file_cache_get_by_peer_handle_cache_block_req_latency");
71
bvar::LatencyRecorder g_file_cache_get_by_peer_get_cache_latency(
72
        "file_cache_get_by_peer_get_cache_latency");
73
bvar::LatencyRecorder g_file_cache_get_by_peer_get_or_set_latency(
74
        "file_cache_get_by_peer_get_or_set_latency");
75
bvar::Adder<uint64_t> g_file_cache_get_by_peer_get_or_set_calls(
76
        "file_cache_get_by_peer_get_or_set_calls");
77
bvar::Adder<uint64_t> g_file_cache_get_by_peer_get_or_set_blocks_total(
78
        "file_cache_get_by_peer_get_or_set_blocks_total");
79
bvar::Adder<uint64_t> g_file_cache_get_by_peer_request_blocks_total(
80
        "file_cache_get_by_peer_request_blocks_total");
81
bvar::LatencyRecorder g_file_cache_get_by_peer_request_blocks_per_rpc(
82
        "file_cache_get_by_peer_request_blocks_per_rpc");
83
bvar::Adder<uint64_t> g_file_cache_get_by_peer_response_blocks_total(
84
        "file_cache_get_by_peer_response_blocks_total");
85
bvar::Adder<uint64_t> g_file_cache_get_by_peer_response_bytes_total(
86
        "file_cache_get_by_peer_response_bytes_total");
87
bvar::Adder<uint64_t> g_file_cache_get_by_peer_not_downloaded_block_num(
88
        "file_cache_get_by_peer_not_downloaded_block_num");
89
bvar::LatencyRecorder g_file_cache_get_by_peer_read_file_block_total_latency(
90
        "file_cache_get_by_peer_read_file_block_total_latency");
91
bvar::LatencyRecorder g_file_cache_get_by_peer_set_response_data_latency(
92
        "file_cache_get_by_peer_set_response_data_latency");
93
bvar::Adder<uint64_t> g_file_cache_get_by_peer_attachment_response_num(
94
        "file_cache_get_by_peer_attachment_response_num");
95
bvar::Adder<uint64_t> g_file_cache_get_by_peer_pb_response_num(
96
        "file_cache_get_by_peer_pb_response_num");
97
98
bvar::Adder<int64_t> g_peer_server_fill_requested("peer_server_fill_requested");
99
bvar::Adder<int64_t> g_peer_server_fill_success("peer_server_fill_success");
100
bvar::Adder<int64_t> g_peer_server_fill_timeout("peer_server_fill_timeout");
101
bvar::Adder<int64_t> g_peer_server_fill_rejected("peer_server_fill_rejected");
102
bvar::LatencyRecorder g_peer_server_fill_latency("peer_server_fill_latency");
103
bvar::LatencyRecorder g_cloud_internal_service_get_file_cache_meta_by_tablet_id_latency(
104
        "cloud_internal_service_get_file_cache_meta_by_tablet_id_latency");
105
bvar::Adder<int64_t> g_cloud_sync_tablet_meta_requests_total(
106
        "cloud_sync_tablet_meta_requests_total");
107
bvar::Adder<int64_t> g_cloud_sync_tablet_meta_synced_total("cloud_sync_tablet_meta_synced_total");
108
bvar::Adder<int64_t> g_cloud_sync_tablet_meta_skipped_total("cloud_sync_tablet_meta_skipped_total");
109
bvar::Adder<int64_t> g_cloud_sync_tablet_meta_failed_total("cloud_sync_tablet_meta_failed_total");
110
111
namespace {
112
113
void submit_sync_tablet_meta(CloudStorageEngine& engine, FifoThreadPool& work_pool,
114
                             const PSyncTabletMetaRequest* request,
115
2
                             PSyncTabletMetaResponse* response, google::protobuf::Closure* done) {
116
2
    auto start_time = std::chrono::steady_clock::now();
117
2
    bool ret = work_pool.try_offer([engine = &engine, request, response, done, start_time]() {
118
1
        brpc::ClosureGuard closure_guard(done);
119
1
        LOG(INFO) << "begin to sync tablet meta, request=" << request->ShortDebugString();
120
1
        int64_t synced = 0;
121
1
        int64_t skipped = 0;
122
1
        int64_t failed = 0;
123
1
        g_cloud_sync_tablet_meta_requests_total << 1;
124
3
        for (const auto tablet_id : request->tablet_ids()) {
125
3
            auto tablet = engine->tablet_mgr().get_tablet_if_cached(tablet_id);
126
3
            if (!tablet) {
127
1
                ++skipped;
128
1
                continue;
129
1
            }
130
2
            auto st = tablet->sync_meta();
131
2
            if (!st.ok()) {
132
1
                ++failed;
133
1
                LOG(WARNING) << "failed to sync tablet meta from cloud meta service, tablet="
134
1
                             << tablet_id << ", err=" << st;
135
1
                continue;
136
1
            }
137
1
            ++synced;
138
1
        }
139
1
        g_cloud_sync_tablet_meta_synced_total << synced;
140
1
        g_cloud_sync_tablet_meta_skipped_total << skipped;
141
1
        g_cloud_sync_tablet_meta_failed_total << failed;
142
1
        response->set_synced_tablets(synced);
143
1
        response->set_skipped_tablets(skipped);
144
1
        response->set_failed_tablets(failed);
145
1
        Status::OK().to_protobuf(response->mutable_status());
146
1
        auto cost_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
147
1
                               std::chrono::steady_clock::now() - start_time)
148
1
                               .count();
149
1
        LOG(INFO) << "finish to sync tablet meta, request=" << request->ShortDebugString()
150
1
                  << ", response=" << response->ShortDebugString() << ", cost_ms=" << cost_ms;
151
1
    });
152
2
    if (!ret) {
153
1
        brpc::ClosureGuard closure_guard(done);
154
1
        Status::InternalError("failed to offer sync_tablet_meta request to work pool")
155
1
                .to_protobuf(response->mutable_status());
156
1
        auto cost_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
157
1
                               std::chrono::steady_clock::now() - start_time)
158
1
                               .count();
159
1
        LOG(WARNING) << "failed to offer sync_tablet_meta request to work pool, request="
160
1
                     << request->ShortDebugString() << ", response=" << response->ShortDebugString()
161
1
                     << ", cost_ms=" << cost_ms;
162
1
    }
163
2
}
164
165
} // namespace
166
167
// Concurrency guard for server-side S3 pull-through fills.
168
static std::atomic<int32_t> g_active_server_fills {0};
169
bvar::PassiveStatus<int32_t> g_peer_active_fills(
170
        "peer_active_fills",
171
3.46k
        [](void*) { return g_active_server_fills.load(std::memory_order_relaxed); }, nullptr);
172
173
CloudInternalServiceImpl::CloudInternalServiceImpl(CloudStorageEngine& engine, ExecEnv* exec_env)
174
0
        : PInternalService(exec_env), _engine(engine) {}
175
176
0
CloudInternalServiceImpl::~CloudInternalServiceImpl() = default;
177
178
void CloudInternalServiceImpl::sync_tablet_meta(google::protobuf::RpcController* controller,
179
                                                const PSyncTabletMetaRequest* request,
180
                                                PSyncTabletMetaResponse* response,
181
0
                                                google::protobuf::Closure* done) {
182
0
    submit_sync_tablet_meta(_engine, _light_work_pool, request, response, done);
183
0
}
184
185
#ifdef BE_TEST
186
void test_submit_sync_tablet_meta(CloudStorageEngine& engine, FifoThreadPool& work_pool,
187
                                  const PSyncTabletMetaRequest* request,
188
                                  PSyncTabletMetaResponse* response,
189
2
                                  google::protobuf::Closure* done) {
190
2
    submit_sync_tablet_meta(engine, work_pool, request, response, done);
191
2
}
192
#endif
193
194
void CloudInternalServiceImpl::alter_vault_sync(google::protobuf::RpcController* controller,
195
                                                const doris::PAlterVaultSyncRequest* request,
196
                                                PAlterVaultSyncResponse* response,
197
0
                                                google::protobuf::Closure* done) {
198
0
    LOG(INFO) << "alter be to sync vault info from Meta Service";
199
    // If the vaults containing hdfs vault then it would try to create hdfs connection using jni
200
    // which would acuiqre one thread local jniEnv. But bthread context can't guarantee that the brpc
201
    // worker thread wouldn't do bthread switch between worker threads.
202
0
    bool ret = _heavy_work_pool.try_offer([this, done]() {
203
0
        brpc::ClosureGuard closure_guard(done);
204
0
        _engine.sync_storage_vault();
205
0
    });
206
0
    if (!ret) {
207
0
        brpc::ClosureGuard closure_guard(done);
208
0
        LOG(WARNING) << "fail to offer alter_vault_sync request to the work pool, pool="
209
0
                     << _heavy_work_pool.get_info();
210
0
    }
211
0
}
212
213
0
FileCacheType cache_type_to_pb(io::FileCacheType type) {
214
0
    switch (type) {
215
0
    case io::FileCacheType::TTL:
216
0
        return FileCacheType::TTL;
217
0
    case io::FileCacheType::INDEX:
218
0
        return FileCacheType::INDEX;
219
0
    case io::FileCacheType::NORMAL:
220
0
        return FileCacheType::NORMAL;
221
0
    default:
222
0
        DCHECK(false);
223
0
    }
224
0
    return FileCacheType::NORMAL;
225
0
}
226
227
0
static int64_t current_unix_time_us() {
228
0
    return std::chrono::duration_cast<std::chrono::microseconds>(
229
0
                   std::chrono::system_clock::now().time_since_epoch())
230
0
            .count();
231
0
}
232
233
static std::optional<int64_t> warm_up_rowset_cross_host_latency_us(int64_t start_unix_ts_us,
234
0
                                                                   int64_t end_unix_ts_us) {
235
    // The start timestamp is generated by the caller BE. Mixed-version callers may omit it, and
236
    // system clocks across BEs are not guaranteed to be ordered.
237
0
    if (start_unix_ts_us <= 0 || end_unix_ts_us < start_unix_ts_us) {
238
0
        return std::nullopt;
239
0
    }
240
0
    return end_unix_ts_us - start_unix_ts_us;
241
0
}
242
243
static void add_file_cache_block_meta_to_response(
244
        PGetFileCacheMetaResponse* resp, int64_t tablet_id, const std::string& rowset_id,
245
        int32_t segment_id, const std::string& file_name,
246
        const std::tuple<int64_t, int64_t, io::FileCacheType, int64_t>& tuple,
247
0
        const RowsetSharedPtr& rowset, bool is_index) {
248
0
    FileCacheBlockMeta* meta = resp->add_file_cache_block_metas();
249
0
    meta->set_tablet_id(tablet_id);
250
0
    meta->set_rowset_id(rowset_id);
251
0
    meta->set_segment_id(segment_id);
252
0
    meta->set_file_name(file_name);
253
254
0
    if (!is_index) {
255
        // .dat
256
0
        meta->set_file_size(rowset->rowset_meta()->segment_file_size(segment_id));
257
0
        meta->set_file_type(doris::FileType::SEGMENT_FILE);
258
0
    } else {
259
        // .idx
260
0
        const auto& idx_file_info = rowset->rowset_meta()->inverted_index_file_info(segment_id);
261
0
        meta->set_file_size(idx_file_info.has_index_size() ? idx_file_info.index_size() : -1);
262
0
        meta->set_file_type(doris::FileType::INVERTED_INDEX_FILE);
263
0
    }
264
265
0
    meta->set_offset(std::get<0>(tuple));
266
0
    meta->set_size(std::get<1>(tuple));
267
0
    meta->set_cache_type(cache_type_to_pb(std::get<2>(tuple)));
268
0
    meta->set_expiration_time(std::get<3>(tuple));
269
0
}
270
271
static void process_segment_file_cache_meta(PGetFileCacheMetaResponse* resp,
272
                                            const RowsetSharedPtr& rowset, int64_t tablet_id,
273
                                            const std::string& rowset_id, int32_t segment_id,
274
0
                                            bool is_index) {
275
0
    const char* extension = is_index ? ".idx" : ".dat";
276
0
    std::string file_name = fmt::format("{}_{}{}", rowset_id, segment_id, extension);
277
0
    auto cache_key = io::BlockFileCache::hash(file_name);
278
0
    auto* cache = io::FileCacheFactory::instance()->get_by_path(cache_key);
279
0
    if (!cache) return;
280
0
    auto segments_meta = cache->get_hot_blocks_meta(cache_key);
281
0
    for (const auto& tuple : segments_meta) {
282
0
        add_file_cache_block_meta_to_response(resp, tablet_id, rowset_id, segment_id, file_name,
283
0
                                              tuple, rowset, is_index);
284
0
    }
285
0
}
286
287
void CloudInternalServiceImpl::get_file_cache_meta_by_tablet_id(
288
        google::protobuf::RpcController* controller [[maybe_unused]],
289
        const PGetFileCacheMetaRequest* request, PGetFileCacheMetaResponse* response,
290
0
        google::protobuf::Closure* done) {
291
0
    brpc::ClosureGuard closure_guard(done);
292
0
    if (!config::enable_file_cache) {
293
0
        LOG_WARNING("try to access tablet file cache meta, but file cache not enabled");
294
0
        return;
295
0
    }
296
0
    auto begin_ts = std::chrono::duration_cast<std::chrono::microseconds>(
297
0
                            std::chrono::steady_clock::now().time_since_epoch())
298
0
                            .count();
299
0
    std::ostringstream tablet_ids_stream;
300
0
    int count = 0;
301
0
    for (const auto& tablet_id : request->tablet_ids()) {
302
0
        tablet_ids_stream << tablet_id << ", ";
303
0
        count++;
304
0
        if (count >= 10) {
305
0
            break;
306
0
        }
307
0
    }
308
0
    LOG(INFO) << "warm up get meta from this be, tablets num=" << request->tablet_ids().size()
309
0
              << ", first 10 tablet_ids=[ " << tablet_ids_stream.str() << " ]";
310
0
    for (const auto& tablet_id : request->tablet_ids()) {
311
0
        auto res = _engine.tablet_mgr().get_tablet(tablet_id);
312
0
        if (!res.has_value()) {
313
0
            LOG(ERROR) << "failed to get tablet: " << tablet_id
314
0
                       << " err msg: " << res.error().msg();
315
0
            continue;
316
0
        }
317
0
        CloudTabletSPtr tablet = std::move(res.value());
318
0
        auto st = tablet->sync_rowsets();
319
0
        if (!st) {
320
            // just log failed, try it best
321
0
            LOG(WARNING) << "failed to sync rowsets: " << tablet_id
322
0
                         << " err msg: " << st.to_string();
323
0
        }
324
0
        auto rowsets = tablet->get_snapshot_rowset();
325
326
0
        for (const RowsetSharedPtr& rowset : rowsets) {
327
0
            std::string rowset_id = rowset->rowset_id().to_string();
328
0
            for (int32_t segment_id = 0; segment_id < rowset->num_segments(); ++segment_id) {
329
0
                process_segment_file_cache_meta(response, rowset, tablet_id, rowset_id, segment_id,
330
0
                                                false);
331
0
                process_segment_file_cache_meta(response, rowset, tablet_id, rowset_id, segment_id,
332
0
                                                true);
333
0
            }
334
0
        }
335
0
    }
336
0
    auto end_ts = std::chrono::duration_cast<std::chrono::microseconds>(
337
0
                          std::chrono::steady_clock::now().time_since_epoch())
338
0
                          .count();
339
0
    g_cloud_internal_service_get_file_cache_meta_by_tablet_id_latency << (end_ts - begin_ts);
340
0
    LOG(INFO) << "get file cache meta by tablet ids = [ " << tablet_ids_stream.str() << " ] took "
341
0
              << end_ts - begin_ts << " us";
342
0
    VLOG_DEBUG << "get file cache meta by tablet id request=" << request->DebugString()
343
0
               << ", response=" << response->DebugString();
344
0
}
345
346
namespace {
347
// Helper functions for fetch_peer_data
348
36
inline int64_t elapsed_us(std::chrono::steady_clock::time_point start) {
349
36
    return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() -
350
36
                                                                 start)
351
36
            .count();
352
36
}
353
354
1
int64_t get_rowset_meta_tablet_id_from_request(const PFetchPeerDataRequest* request) {
355
1
    return request->has_rowset_meta() && request->rowset_meta().has_tablet_id()
356
1
                   ? request->rowset_meta().tablet_id()
357
1
                   : -1;
358
1
}
359
360
1
std::string get_rowset_meta_resource_id_from_request(const PFetchPeerDataRequest* request) {
361
1
    if (request->has_rowset_meta() && request->rowset_meta().has_resource_id()) {
362
1
        return request->rowset_meta().resource_id();
363
1
    }
364
0
    return "";
365
1
}
366
367
7
std::string get_peer_cache_filename(std::string_view path) {
368
7
    return io::Path(std::string(path)).filename().native();
369
7
}
370
371
std::string format_peer_request_context(const PFetchPeerDataRequest* request,
372
0
                                        const io::UInt128Wrapper& hash, size_t file_size) {
373
0
    const std::string file_size_str =
374
0
            request->has_file_size() ? std::to_string(request->file_size()) : "unknown";
375
0
    return fmt::format(
376
0
            "type={}, path={}, cache_hash={}, request_fill={}, fill_tablet_id={}, "
377
0
            "fill_remote_path={}, fill_resource_id={}, file_size={}, resolved_file_size={}, "
378
0
            "cache_req_count={}, support_attachment={}",
379
0
            request->type(), request->path(), hash.to_string(),
380
0
            request->has_request_cache_fill() && request->request_cache_fill(),
381
0
            get_rowset_meta_tablet_id_from_request(request), request->path(),
382
0
            get_rowset_meta_resource_id_from_request(request), file_size_str,
383
0
            file_size == std::numeric_limits<size_t>::max() ? std::string("unknown")
384
0
                                                            : std::to_string(file_size),
385
0
            request->cache_req_size(),
386
0
            request->has_support_attachment() && request->support_attachment());
387
0
}
388
389
std::string format_peer_cache_block_context(const PFetchPeerDataRequest* request,
390
                                            const CacheBlockReqest& cb_req,
391
                                            const io::FileBlockSPtr& fb,
392
                                            const io::UInt128Wrapper& hash, size_t file_size,
393
0
                                            bool do_fill) {
394
0
    return fmt::format("{}, req_block=[offset={}, size={}], do_fill={}, block={}, cache_file={}",
395
0
                       format_peer_request_context(request, hash, file_size), cb_req.block_offset(),
396
0
                       cb_req.block_size(), do_fill, fb->get_info_for_log(), fb->get_cache_file());
397
0
}
398
399
std::string format_peer_fill_context(const io::FileBlockSPtr& fb, int64_t fill_tablet_id,
400
                                     const std::string& filename, const std::string& resource_id,
401
                                     const std::string& remote_path, int64_t file_size,
402
1
                                     int64_t offset, int64_t size, int32_t timeout_ms) {
403
1
    return fmt::format(
404
1
            "tablet_id={}, filename={}, resource_id={}, remote_path={}, file_size={}, "
405
1
            "request_range=[offset={}, size={}], timeout_ms={}, block={}, cache_file={}",
406
1
            fill_tablet_id, filename, resource_id.empty() ? "<unknown>" : resource_id,
407
1
            remote_path.empty() ? "<unknown>" : remote_path, file_size, offset, size, timeout_ms,
408
1
            fb->get_info_for_log(), fb->get_cache_file());
409
1
}
410
411
1
bool wait_for_file_block_state(const io::FileBlockSPtr& fb, int32_t timeout_ms) {
412
1
    const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout_ms);
413
4
    while (true) {
414
4
        const auto state = fb->state();
415
4
        if (state == io::FileBlock::State::DOWNLOADED ||
416
4
            state == io::FileBlock::State::SKIP_CACHE || state == io::FileBlock::State::EMPTY) {
417
1
            return true;
418
1
        }
419
3
        if (std::chrono::steady_clock::now() >= deadline) {
420
0
            return false;
421
0
        }
422
3
        fb->wait();
423
3
    }
424
1
}
425
426
0
Status handle_peer_file_range_request(const std::string& path, PFetchPeerDataResponse* response) {
427
    // Legacy path: PEER_FILE_RANGE still returns payload via protobuf bytes.
428
    // Keep this for compatibility until range path is migrated to attachment mode.
429
    // Read specific range [file_offset, file_offset+file_size) across cached blocks
430
0
    auto datas =
431
0
            io::FileCacheFactory::instance()->get_cache_data_by_path(get_peer_cache_filename(path));
432
0
    for (auto& cb : datas) {
433
0
        *(response->add_datas()) = std::move(cb);
434
0
    }
435
0
    return Status::OK();
436
0
}
437
438
0
void set_error_response(PFetchPeerDataResponse* response, const std::string& error_msg) {
439
0
    response->mutable_status()->add_error_msgs(error_msg);
440
0
    response->mutable_status()->set_status_code(TStatusCode::INTERNAL_ERROR);
441
0
}
442
443
1
void set_too_many_tasks_response(PFetchPeerDataResponse* response, const std::string& error_msg) {
444
1
    response->mutable_status()->add_error_msgs(error_msg);
445
1
    response->mutable_status()->set_status_code(TStatusCode::TOO_MANY_TASKS);
446
1
}
447
448
bool try_reject_if_queue_timed_out(std::chrono::steady_clock::time_point enqueue_ts,
449
2
                                   PFetchPeerDataResponse* response) {
450
2
    auto wait_us = elapsed_us(enqueue_ts);
451
2
    g_file_cache_get_by_peer_queue_wait_latency << wait_us;
452
2
    auto wait_ms = wait_us / 1000;
453
2
    if (wait_ms <= config::peer_fetch_queue_timeout_ms) {
454
1
        return false;
455
1
    }
456
457
1
    const std::string msg = fmt::format("fetch peer data queue timeout, wait_ms={}, timeout_ms={}",
458
1
                                        wait_ms, config::peer_fetch_queue_timeout_ms);
459
1
    g_file_cache_get_by_peer_queue_timeout_num << 1;
460
1
    set_too_many_tasks_response(response, msg);
461
1
    return true;
462
2
}
463
464
Status read_file_block(const std::shared_ptr<io::FileBlock>& file_block, size_t file_size,
465
9
                       doris::CacheBlockPB* output, butil::IOBuf* response_attachment) {
466
9
    auto total_start = std::chrono::steady_clock::now();
467
9
    int64_t set_data_us = 0;
468
9
    Defer report {[&]() {
469
9
        g_file_cache_get_by_peer_read_file_block_total_latency << elapsed_us(total_start);
470
9
        if (set_data_us > 0) {
471
2
            g_file_cache_get_by_peer_set_response_data_latency << set_data_us;
472
2
        }
473
9
    }};
474
    // ATTN: calculate the rightmost boundary value of the block, due to inaccurate current block meta information.
475
    // see CachedRemoteFileReader::read_at_impl for more details.
476
    // Ensure file_size >= file_block->offset() to avoid underflow
477
9
    if (file_size < file_block->offset()) {
478
0
        LOG(WARNING) << "file_size (" << file_size << ") < file_block->offset("
479
0
                     << file_block->offset() << ")";
480
0
        return Status::InternalError<false>("file_size less than block offset");
481
0
    }
482
9
    size_t read_size = std::min(static_cast<size_t>(file_size - file_block->offset()),
483
9
                                file_block->range().size());
484
9
    output->set_block_offset(static_cast<int64_t>(file_block->offset()));
485
9
    output->set_block_size(static_cast<int64_t>(read_size));
486
9
    if (read_size == 0) {
487
0
        return Status::OK();
488
0
    }
489
490
9
    Status read_st = Status::OK();
491
    // Attachment payload mode: protobuf carries metadata only, payload goes to attachment.
492
    // This allows FS cache to use a file-descriptor->IOBuf path directly.
493
9
    if (response_attachment != nullptr) {
494
7
        size_t bytes_read = 0;
495
7
        auto begin_read_file_ts = std::chrono::duration_cast<std::chrono::microseconds>(
496
7
                                          std::chrono::steady_clock::now().time_since_epoch())
497
7
                                          .count();
498
7
        SCOPED_ATTACH_TASK(ExecEnv::GetInstance()->s3_file_buffer_tracker());
499
7
        read_st = file_block->read_to_iobuf(response_attachment, /*read_offset=*/0, read_size,
500
7
                                            &bytes_read);
501
7
        auto end_read_file_ts = std::chrono::duration_cast<std::chrono::microseconds>(
502
7
                                        std::chrono::steady_clock::now().time_since_epoch())
503
7
                                        .count();
504
7
        g_file_cache_get_by_peer_read_cache_file_latency << (end_read_file_ts - begin_read_file_ts);
505
506
7
        if (read_st.ok()) {
507
7
            if (bytes_read != read_size) {
508
0
                return Status::InternalError<false>(
509
0
                        "peer cache read short data, expected={}, actual={}", read_size,
510
0
                        bytes_read);
511
0
            }
512
7
            g_file_cache_get_by_peer_response_bytes_total << bytes_read;
513
7
            return Status::OK();
514
7
        }
515
7
    } else {
516
2
        std::string data;
517
2
        data.resize(read_size);
518
2
        auto begin_read_file_ts = std::chrono::duration_cast<std::chrono::microseconds>(
519
2
                                          std::chrono::steady_clock::now().time_since_epoch())
520
2
                                          .count();
521
2
        SCOPED_ATTACH_TASK(ExecEnv::GetInstance()->s3_file_buffer_tracker());
522
2
        Slice slice(data.data(), data.size());
523
2
        read_st = file_block->read(slice, /*read_offset=*/0);
524
2
        auto end_read_file_ts = std::chrono::duration_cast<std::chrono::microseconds>(
525
2
                                        std::chrono::steady_clock::now().time_since_epoch())
526
2
                                        .count();
527
2
        g_file_cache_get_by_peer_read_cache_file_latency << (end_read_file_ts - begin_read_file_ts);
528
529
2
        if (read_st.ok()) {
530
2
            auto set_data_start = std::chrono::steady_clock::now();
531
2
            output->set_data(std::move(data));
532
2
            set_data_us = elapsed_us(set_data_start);
533
2
            g_file_cache_get_by_peer_response_bytes_total << read_size;
534
2
            return Status::OK();
535
2
        }
536
2
    }
537
538
0
    g_file_cache_get_by_peer_failed_num << 1;
539
0
    LOG(WARNING) << "read cache block failed, file_size=" << file_size
540
0
                 << ", block=" << file_block->get_info_for_log()
541
0
                 << ", cache_file=" << file_block->get_cache_file() << ", err=" << read_st;
542
0
    return read_st;
543
9
}
544
545
// Trigger S3 -> local cache fill for the given file block.
546
// Returns OK when the block is DOWNLOADED after the fill.
547
// Returns TOO_MANY_TASKS when the fill slot is exhausted (server healthy but overloaded):
548
//   client should not rotate or evict, just fall back to S3 and retry same candidate later.
549
// Returns NOT_FOUND for soft misses (tablet not found, fill incomplete, timeout):
550
//   client should rotate the candidate to try a different CG next time.
551
// The peer uses request.path as the full remote path. tablet_id/filename are kept for logging.
552
Status trigger_peer_server_fill(io::FileBlockSPtr& fb, int64_t fill_tablet_id,
553
                                const std::string& filename, const std::string& resource_id,
554
                                const std::string& remote_path, int64_t file_size, int64_t offset,
555
1
                                int64_t size, int32_t timeout_ms) {
556
1
    g_peer_server_fill_requested << 1;
557
558
    // Concurrency guard: atomically reserve a fill slot.
559
    // Excess requests are rejected so the client falls back to its own S3 read.
560
    // Return NOT_FOUND so the client rotates the candidate instead of evicting it.
561
1
    if (g_active_server_fills.fetch_add(1, std::memory_order_relaxed) >=
562
1
        config::max_concurrent_peer_server_fills) {
563
0
        g_active_server_fills.fetch_sub(1, std::memory_order_relaxed);
564
0
        g_peer_server_fill_rejected << 1;
565
0
        VLOG_DEBUG << "trigger_peer_server_fill: rejected (concurrency limit "
566
0
                   << config::max_concurrent_peer_server_fills << "), tablet_id=" << fill_tablet_id;
567
        // TOO_MANY_TASKS: server is healthy but overloaded. Client must not rotate or evict;
568
        // just fall back to S3 for this request and retry the same candidate next time.
569
0
        return Status::Error<ErrorCode::TOO_MANY_TASKS, false>("fill slot exhausted");
570
0
    }
571
    // RAII decrement: runs on every return path below.
572
1
    Defer fill_guard {[]() { g_active_server_fills.fetch_sub(1, std::memory_order_relaxed); }};
573
574
1
    if (remote_path.empty() || resource_id.empty()) {
575
0
        const std::string ctx =
576
0
                format_peer_fill_context(fb, fill_tablet_id, filename, resource_id, remote_path,
577
0
                                         file_size, offset, size, timeout_ms);
578
0
        LOG(WARNING) << "trigger_peer_server_fill: missing remote_path or resource_id, " << ctx;
579
0
        g_peer_server_fill_rejected << 1;
580
0
        return Status::NotFound<false>("fill: missing remote_path or resource_id, {}", ctx);
581
0
    }
582
1
    auto storage_resource = doris::get_storage_resource(resource_id);
583
1
    if (!storage_resource.has_value()) {
584
1
        const std::string ctx =
585
1
                format_peer_fill_context(fb, fill_tablet_id, filename, resource_id, remote_path,
586
1
                                         file_size, offset, size, timeout_ms);
587
1
        LOG(WARNING) << "trigger_peer_server_fill: storage resource not found, " << ctx;
588
1
        g_peer_server_fill_rejected << 1;
589
1
        return Status::NotFound<false>("fill: storage resource not found, {}", ctx);
590
1
    }
591
0
    auto fs = storage_resource->first.fs;
592
593
0
    const auto initial_state = fb->state();
594
0
    if (initial_state == io::FileBlock::State::DOWNLOADING) {
595
        // Another thread already owns the block downloader. Wait up to the request timeout instead
596
        // of the shorter per-wait timeout in FileBlock::wait().
597
0
        [[maybe_unused]] const bool completed = wait_for_file_block_state(fb, timeout_ms);
598
0
        const std::string ctx =
599
0
                format_peer_fill_context(fb, fill_tablet_id, filename, resource_id, remote_path,
600
0
                                         file_size, offset, size, timeout_ms);
601
0
        return fb->state() == io::FileBlock::State::DOWNLOADED
602
0
                       ? Status::OK()
603
0
                       : Status::NotFound<false>("fill: concurrent download incomplete, {}", ctx);
604
0
    }
605
0
    if (initial_state != io::FileBlock::State::EMPTY) {
606
0
        const std::string ctx =
607
0
                format_peer_fill_context(fb, fill_tablet_id, filename, resource_id, remote_path,
608
0
                                         file_size, offset, size, timeout_ms);
609
0
        return initial_state == io::FileBlock::State::DOWNLOADED
610
0
                       ? Status::OK()
611
0
                       : Status::NotFound<false>("fill: unexpected initial block state, {}", ctx);
612
0
    }
613
614
0
    auto fill_start = std::chrono::steady_clock::now();
615
0
    auto fill_done = std::make_shared<bthread::CountdownEvent>(1);
616
0
    auto fill_status = std::make_shared<Status>(Status::OK());
617
0
    io::DownloadFileMeta download_meta {
618
0
            .path = remote_path,
619
0
            .file_size = file_size,
620
0
            .offset = offset,
621
0
            .download_size = size,
622
0
            .file_system = fs,
623
0
            .ctx = {.is_dryrun = config::enable_reader_dryrun_when_download_file_cache,
624
                    // Pull-through fill must go straight to remote storage. If this download
625
                    // re-enters peer race, the original block can remain DOWNLOADING for the
626
                    // duration of nested peer retries and timeouts.
627
0
                    .is_warmup = false,
628
0
                    .bypass_peer_read = true},
629
0
            .download_done =
630
0
                    [fill_done, fill_status](Status st) {
631
0
                        *fill_status = std::move(st);
632
0
                        fill_done->signal();
633
0
                    },
634
0
            .tablet_id = fill_tablet_id,
635
0
    };
636
637
0
    io::DownloadTask task(std::move(download_meta));
638
0
    ExecEnv::GetInstance()
639
0
            ->storage_engine()
640
0
            .to_cloud()
641
0
            .file_cache_block_downloader()
642
0
            .submit_download_task(std::move(task));
643
644
0
    const timespec due_time = butil::milliseconds_from_now(timeout_ms);
645
0
    const bool timed_out = fill_done->timed_wait(due_time) != 0;
646
647
0
    int64_t fill_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
648
0
                              std::chrono::steady_clock::now() - fill_start)
649
0
                              .count();
650
0
    g_peer_server_fill_latency << fill_ms * 1000; // LatencyRecorder takes microseconds
651
652
0
    if (!timed_out && fill_status->ok() && fb->state() == io::FileBlock::State::DOWNLOADING) {
653
0
        const int32_t settle_timeout_ms =
654
0
                std::max<int32_t>(1, timeout_ms - static_cast<int32_t>(fill_ms));
655
0
        [[maybe_unused]] const bool settled = wait_for_file_block_state(fb, settle_timeout_ms);
656
0
    }
657
658
0
    auto final_state = fb->state();
659
0
    if (final_state == io::FileBlock::State::DOWNLOADED) {
660
0
        g_peer_server_fill_success << 1;
661
0
        return Status::OK();
662
0
    }
663
0
    if (timed_out) {
664
0
        LOG(WARNING) << "trigger_peer_server_fill: fill timeout, elapsed_ms=" << fill_ms << ", "
665
0
                     << format_peer_fill_context(fb, fill_tablet_id, filename, resource_id,
666
0
                                                 remote_path, file_size, offset, size, timeout_ms);
667
0
        g_peer_server_fill_timeout << 1;
668
0
    } else if (!fill_status->ok()) {
669
0
        LOG(WARNING) << "trigger_peer_server_fill: fill failed, elapsed_ms=" << fill_ms
670
0
                     << ", status=" << fill_status->to_string() << ", "
671
0
                     << format_peer_fill_context(fb, fill_tablet_id, filename, resource_id,
672
0
                                                 remote_path, file_size, offset, size, timeout_ms);
673
0
    }
674
    // Any non-DOWNLOADED outcome is a soft failure: the server is otherwise healthy so the
675
    // client should rotate the candidate rather than evict it.
676
0
    return Status::NotFound<false>(
677
0
            "fill: block not downloaded, {}",
678
0
            format_peer_fill_context(fb, fill_tablet_id, filename, resource_id, remote_path,
679
0
                                     file_size, offset, size, timeout_ms));
680
0
}
681
682
Status handle_peer_file_cache_block_request(const PFetchPeerDataRequest* request,
683
                                            PFetchPeerDataResponse* response,
684
7
                                            brpc::Controller* cntl) {
685
7
    auto handle_start = std::chrono::steady_clock::now();
686
7
    const uint64_t request_blocks = request->cache_req_size();
687
7
    uint64_t response_blocks = 0;
688
7
    uint64_t get_or_set_calls = 0;
689
7
    uint64_t get_or_set_blocks = 0;
690
7
    int64_t get_cache_us = 0;
691
7
    Defer report {[&]() {
692
7
        g_file_cache_get_by_peer_handle_cache_block_req_latency << elapsed_us(handle_start);
693
7
        g_file_cache_get_by_peer_get_cache_latency << get_cache_us;
694
7
        g_file_cache_get_by_peer_get_or_set_calls << get_or_set_calls;
695
7
        g_file_cache_get_by_peer_get_or_set_blocks_total << get_or_set_blocks;
696
7
        g_file_cache_get_by_peer_request_blocks_total << request_blocks;
697
7
        g_file_cache_get_by_peer_request_blocks_per_rpc << request_blocks;
698
7
        g_file_cache_get_by_peer_response_blocks_total << response_blocks;
699
7
    }};
700
7
    const auto& path = request->path();
701
7
    const auto cache_key_path = get_peer_cache_filename(path);
702
7
    auto hash = io::BlockFileCache::hash(cache_key_path);
703
7
    auto get_cache_start = std::chrono::steady_clock::now();
704
7
    auto* cache = io::FileCacheFactory::instance()->get_by_path(hash);
705
7
    get_cache_us = elapsed_us(get_cache_start);
706
7
    if (cache == nullptr) {
707
0
        g_file_cache_get_by_peer_failed_num << 1;
708
0
        set_error_response(response, "can't get file cache instance");
709
0
        return Status::InternalError<false>("can't get file cache instance");
710
0
    }
711
712
7
    io::CacheContext ctx {};
713
7
    io::ReadStatistics local_stats;
714
7
    ctx.stats = &local_stats;
715
7
    const size_t file_size =
716
7
            request->has_file_size()
717
7
                    ? static_cast<size_t>(std::max<int64_t>(0, request->file_size()))
718
7
                    : std::numeric_limits<size_t>::max();
719
    // Enable attachment mode only when client advertises support.
720
    // This keeps mixed-version rolling upgrades safe.
721
7
    const bool use_attachment =
722
7
            cntl != nullptr && request->has_support_attachment() && request->support_attachment();
723
7
    response->set_data_in_attachment(use_attachment);
724
7
    if (use_attachment) {
725
4
        g_file_cache_get_by_peer_attachment_response_num << 1;
726
4
    } else {
727
3
        g_file_cache_get_by_peer_pb_response_num << 1;
728
3
    }
729
730
7
    const bool do_fill = request->has_request_cache_fill() && request->request_cache_fill() &&
731
7
                         config::enable_peer_server_cache_fill;
732
733
9
    for (const auto& cb_req : request->cache_req()) {
734
9
        size_t offset = static_cast<size_t>(std::max<int64_t>(0, cb_req.block_offset()));
735
9
        size_t size = static_cast<size_t>(std::max<int64_t>(0, cb_req.block_size()));
736
9
        if (offset >= file_size) {
737
0
            continue;
738
0
        }
739
        // Clip tail requests before get_or_set so peer reads do not synthesize EMPTY blocks past
740
        // EOF and then fail the whole RPC.
741
9
        size = std::min(size, file_size - offset);
742
9
        if (size == 0) {
743
0
            continue;
744
0
        }
745
9
        DBUG_EXECUTE_IF(
746
9
                "CloudInternalServiceImpl::handle_peer_file_cache_block_request_hold_before_get_or_"
747
9
                "set",
748
9
                {
749
9
                    int sleep_ms = dp->param<int>("sleep_ms", 300);
750
9
                    bthread_usleep(sleep_ms * 1000);
751
9
                });
752
9
        auto get_or_set_start = std::chrono::steady_clock::now();
753
9
        auto holder = cache->get_or_set(hash, offset, size, ctx);
754
9
        g_file_cache_get_by_peer_get_or_set_latency << elapsed_us(get_or_set_start);
755
9
        ++get_or_set_calls;
756
9
        get_or_set_blocks += holder.file_blocks.size();
757
758
10
        for (auto& fb : holder.file_blocks) {
759
10
            auto fb_state = fb->state();
760
10
            if (fb_state == io::FileBlock::State::DOWNLOADING) {
761
1
                if (do_fill) {
762
                    // Only peer fill requests should wait longer here. Plain peer-cache reads keep
763
                    // the short wait semantics so they can fail fast and let the client race S3.
764
1
                    [[maybe_unused]] const bool completed = wait_for_file_block_state(
765
1
                            fb, config::peer_server_cache_fill_timeout_ms);
766
1
                    fb_state = fb->state();
767
1
                } else {
768
                    // Wait for in-progress download to complete using the normal short timeout.
769
0
                    fb_state = fb->wait();
770
0
                }
771
1
            }
772
10
            if (fb_state == io::FileBlock::State::EMPTY) {
773
1
                if (!do_fill) {
774
0
                    const std::string msg =
775
0
                            fmt::format("cache block not downloaded, {}",
776
0
                                        format_peer_cache_block_context(request, cb_req, fb, hash,
777
0
                                                                        file_size, do_fill));
778
0
                    g_file_cache_get_by_peer_failed_num << 1;
779
0
                    g_file_cache_get_by_peer_not_downloaded_block_num << 1;
780
0
                    LOG(WARNING) << msg;
781
                    // Use NOT_FOUND so the client can distinguish "block not cached"
782
                    // from an actual RPC/server error.  On NOT_FOUND the client rotates
783
                    // the candidate to the end of its list (trying another CG next time)
784
                    // rather than incrementing the RPC-failure eviction counter.
785
0
                    response->mutable_status()->add_error_msgs(msg);
786
0
                    response->mutable_status()->set_status_code(TStatusCode::NOT_FOUND);
787
0
                    return Status::NotFound<false>(msg);
788
0
                }
789
                // Server-side fill: request.path already carries the full remote path.
790
1
                auto fill_st = trigger_peer_server_fill(
791
1
                        fb, get_rowset_meta_tablet_id_from_request(request), cache_key_path,
792
1
                        get_rowset_meta_resource_id_from_request(request), path,
793
1
                        request->has_file_size() ? request->file_size() : -1,
794
1
                        static_cast<int64_t>(fb->range().left),
795
1
                        static_cast<int64_t>(fb->range().size()),
796
1
                        config::peer_server_cache_fill_timeout_ms);
797
1
                if (!fill_st.ok()) {
798
1
                    g_file_cache_get_by_peer_failed_num << 1;
799
1
                    g_file_cache_get_by_peer_not_downloaded_block_num << 1;
800
1
                    if (fill_st.is<ErrorCode::TOO_MANY_TASKS>()) {
801
                        // Server slot exhausted: healthy but overloaded. Client must not rotate
802
                        // or evict — just fall back to S3 and retry same candidate next time.
803
0
                        response->mutable_status()->add_error_msgs(std::string(fill_st.msg()));
804
0
                        response->mutable_status()->set_status_code(TStatusCode::TOO_MANY_TASKS);
805
1
                    } else if (fill_st.is<ErrorCode::NOT_FOUND>()) {
806
                        // Soft miss (fill incomplete, timeout, unexpected state) — client rotates,
807
                        // not evicts.
808
1
                        response->mutable_status()->add_error_msgs(std::string(fill_st.msg()));
809
1
                        response->mutable_status()->set_status_code(TStatusCode::NOT_FOUND);
810
1
                    } else {
811
0
                        LOG(WARNING) << "cache block fill failed, status=" << fill_st << ", "
812
0
                                     << format_peer_cache_block_context(request, cb_req, fb, hash,
813
0
                                                                        file_size, do_fill);
814
0
                        set_error_response(response, "cache block not ready");
815
0
                    }
816
1
                    return fill_st;
817
1
                }
818
0
                fb_state = io::FileBlock::State::DOWNLOADED;
819
0
            }
820
9
            if (fb_state != io::FileBlock::State::DOWNLOADED) {
821
                // A concurrent download was in progress (DOWNLOADING at request time) but its
822
                // wait() returned a non-DOWNLOADED state (e.g., timed-out while still
823
                // DOWNLOADING, or some other non-EMPTY intermediate state).  The server is
824
                // healthy; the block just isn't available yet.  Return NOT_FOUND so the client
825
                // rotates the candidate instead of evicting it.
826
0
                const std::string msg =
827
0
                        fmt::format("cache block not ready after wait, {}",
828
0
                                    format_peer_cache_block_context(request, cb_req, fb, hash,
829
0
                                                                    file_size, do_fill));
830
0
                g_file_cache_get_by_peer_failed_num << 1;
831
0
                g_file_cache_get_by_peer_not_downloaded_block_num << 1;
832
0
                LOG(WARNING) << msg;
833
0
                response->mutable_status()->add_error_msgs(msg);
834
0
                response->mutable_status()->set_status_code(TStatusCode::NOT_FOUND);
835
0
                return Status::NotFound<false>(msg);
836
0
            }
837
838
9
            g_file_cache_get_by_peer_blocks_num << 1;
839
9
            doris::CacheBlockPB* out = response->add_datas();
840
            // In attachment mode, metadata order must match attachment append order because the
841
            // client consumes attachment payload sequentially using resp.datas() order.
842
9
            Status read_status = read_file_block(
843
9
                    fb, file_size, out, use_attachment ? &cntl->response_attachment() : nullptr);
844
9
            if (!read_status.ok()) {
845
0
                set_error_response(response, "read cache file error");
846
0
                return read_status;
847
0
            }
848
9
            ++response_blocks;
849
9
        }
850
9
    }
851
852
6
    return Status::OK();
853
7
}
854
} // namespace
855
856
#ifdef BE_TEST
857
Status test_handle_peer_file_cache_block_request(const PFetchPeerDataRequest* request,
858
                                                 PFetchPeerDataResponse* response,
859
7
                                                 brpc::Controller* cntl) {
860
7
    return handle_peer_file_cache_block_request(request, response, cntl);
861
7
}
862
863
bool test_try_reject_if_queue_timed_out(std::chrono::steady_clock::time_point enqueue_ts,
864
2
                                        PFetchPeerDataResponse* response) {
865
2
    return try_reject_if_queue_timed_out(enqueue_ts, response);
866
2
}
867
#endif
868
869
void CloudInternalServiceImpl::fetch_peer_data(google::protobuf::RpcController* controller,
870
                                               const PFetchPeerDataRequest* request,
871
                                               PFetchPeerDataResponse* response,
872
0
                                               google::protobuf::Closure* done) {
873
0
    auto enqueue_ts = std::chrono::steady_clock::now();
874
    // Lifetime: cntl is owned by brpc framework and valid until done->Run() is called.
875
    // The ClosureGuard inside the lambda ensures done->Run() happens after all cntl usage,
876
    // so capturing the raw pointer by value is safe.
877
0
    auto* cntl = static_cast<brpc::Controller*>(controller);
878
0
    bool ret = _peer_fetch_pool.try_offer([request, response, done, enqueue_ts, cntl]() {
879
0
        brpc::ClosureGuard closure_guard(done);
880
0
        g_file_cache_get_by_peer_num << 1;
881
0
        if (try_reject_if_queue_timed_out(enqueue_ts, response)) {
882
0
            return;
883
0
        }
884
885
0
        if (!config::enable_file_cache) {
886
0
            LOG_WARNING("try to access file cache data, but file cache not enabled");
887
0
            return;
888
0
        }
889
890
0
        auto begin_ts = std::chrono::duration_cast<std::chrono::microseconds>(
891
0
                                std::chrono::steady_clock::now().time_since_epoch())
892
0
                                .count();
893
894
0
        const auto type = request->type();
895
0
        const auto& path = request->path();
896
0
        response->mutable_status()->set_status_code(TStatusCode::OK);
897
898
0
        Status status = Status::OK();
899
0
        if (type == PFetchPeerDataRequest_Type_PEER_FILE_RANGE) {
900
0
            status = handle_peer_file_range_request(path, response);
901
0
        } else if (type == PFetchPeerDataRequest_Type_PEER_FILE_CACHE_BLOCK) {
902
0
            status = handle_peer_file_cache_block_request(request, response, cntl);
903
0
        }
904
905
0
        if (!status.ok()) {
906
0
            const std::string msg =
907
0
                    "fetch peer data failed: " + status.to_string() + ", " +
908
0
                    format_peer_request_context(
909
0
                            request, io::BlockFileCache::hash(get_peer_cache_filename(path)),
910
0
                            request->has_file_size() ? static_cast<size_t>(std::max<int64_t>(
911
0
                                                               0, request->file_size()))
912
0
                                                     : std::numeric_limits<size_t>::max());
913
0
            if (status.is<ErrorCode::NOT_FOUND>() || status.is<ErrorCode::TOO_MANY_TASKS>()) {
914
0
                VLOG_DEBUG << msg;
915
0
            } else {
916
0
                LOG(WARNING) << msg;
917
0
            }
918
0
            auto* resp_status = response->mutable_status();
919
0
            if (resp_status->status_code() == TStatusCode::OK) {
920
0
                set_error_response(response, status.to_string());
921
0
            } else if (resp_status->error_msgs().empty()) {
922
0
                resp_status->add_error_msgs(status.to_string());
923
0
            }
924
0
        }
925
926
0
        DBUG_EXECUTE_IF("CloudInternalServiceImpl::fetch_peer_data_slower", {
927
0
            int st_us = dp->param<int>("sleep", 1000);
928
0
            LOG_WARNING("CloudInternalServiceImpl::fetch_peer_data_slower").tag("sleep", st_us);
929
0
            bthread_usleep(st_us);
930
0
        });
931
932
0
        auto end_ts = std::chrono::duration_cast<std::chrono::microseconds>(
933
0
                              std::chrono::steady_clock::now().time_since_epoch())
934
0
                              .count();
935
        // Latency covers every completed callback (including failures) so the
936
        // server-side fail-fast paths still show up in the latency histogram.
937
        // success_num must only count actual OK results, otherwise dedup
938
        // TOO_MANY_TASKS / NOT_FOUND / handler errors all fall through here
939
        // and the success rate is meaningless. Use file_cache_get_by_peer_num
940
        // for the total completed-callback count.
941
0
        g_file_cache_get_by_peer_server_latency << (end_ts - begin_ts);
942
0
        if (status.ok()) {
943
0
            g_file_cache_get_by_peer_success_num << 1;
944
0
        }
945
946
0
        VLOG_DEBUG << "fetch cache request=" << request->DebugString()
947
0
                   << ", response=" << response->DebugString();
948
0
    });
949
950
0
    if (!ret) {
951
0
        g_file_cache_get_by_peer_offer_failed_num << 1;
952
0
        brpc::ClosureGuard closure_guard(done);
953
0
        const std::string msg = fmt::format(
954
0
                "fail to offer fetch peer data request to the peer fetch work pool, pool={}",
955
0
                _peer_fetch_pool.get_info());
956
0
        set_too_many_tasks_response(response, msg);
957
0
        LOG(WARNING) << msg;
958
0
    }
959
0
}
960
961
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_submitted_segment_num(
962
        "file_cache_event_driven_warm_up_submitted_segment_num");
963
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_finished_segment_num(
964
        "file_cache_event_driven_warm_up_finished_segment_num");
965
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_failed_segment_num(
966
        "file_cache_event_driven_warm_up_failed_segment_num");
967
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_submitted_segment_size(
968
        "file_cache_event_driven_warm_up_submitted_segment_size");
969
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_finished_segment_size(
970
        "file_cache_event_driven_warm_up_finished_segment_size");
971
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_failed_segment_size(
972
        "file_cache_event_driven_warm_up_failed_segment_size");
973
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_submitted_index_num(
974
        "file_cache_event_driven_warm_up_submitted_index_num");
975
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_finished_index_num(
976
        "file_cache_event_driven_warm_up_finished_index_num");
977
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_failed_index_num(
978
        "file_cache_event_driven_warm_up_failed_index_num");
979
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_submitted_index_size(
980
        "file_cache_event_driven_warm_up_submitted_index_size");
981
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_finished_index_size(
982
        "file_cache_event_driven_warm_up_finished_index_size");
983
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_failed_index_size(
984
        "file_cache_event_driven_warm_up_failed_index_size");
985
bvar::Status<int64_t> g_file_cache_warm_up_rowset_last_handle_unix_ts(
986
        "file_cache_warm_up_rowset_last_handle_unix_ts", 0);
987
bvar::Status<int64_t> g_file_cache_warm_up_rowset_last_finish_unix_ts(
988
        "file_cache_warm_up_rowset_last_finish_unix_ts", 0);
989
bvar::LatencyRecorder g_file_cache_warm_up_rowset_latency("file_cache_warm_up_rowset_latency");
990
bvar::LatencyRecorder g_file_cache_warm_up_rowset_request_to_handle_latency(
991
        "file_cache_warm_up_rowset_request_to_handle_latency");
992
bvar::LatencyRecorder g_file_cache_warm_up_rowset_handle_to_finish_latency(
993
        "file_cache_warm_up_rowset_handle_to_finish_latency");
994
bvar::Adder<uint64_t> g_file_cache_warm_up_rowset_slow_count(
995
        "file_cache_warm_up_rowset_slow_count");
996
bvar::Adder<uint64_t> g_file_cache_warm_up_rowset_request_to_handle_slow_count(
997
        "file_cache_warm_up_rowset_request_to_handle_slow_count");
998
bvar::Adder<uint64_t> g_file_cache_warm_up_rowset_handle_to_finish_slow_count(
999
        "file_cache_warm_up_rowset_handle_to_finish_slow_count");
1000
bvar::Adder<uint64_t> g_file_cache_warm_up_rowset_wait_for_compaction_num(
1001
        "file_cache_warm_up_rowset_wait_for_compaction_num");
1002
bvar::Adder<uint64_t> g_file_cache_warm_up_rowset_wait_for_compaction_timeout_num(
1003
        "file_cache_warm_up_rowset_wait_for_compaction_timeout_num");
1004
1005
// Per-job windowed metrics for target BE
1006
// bvar::Window enforces MAX_SECONDS_LIMIT = 3600, so the longest window is 1h.
1007
static constexpr int WINDOW_5M = 300;
1008
static constexpr int WINDOW_30M = 1800;
1009
static constexpr int WINDOW_1H = 3600;
1010
1011
MBvarWindowedAdder g_warmup_ed_finish_segment_num("warmup_ed_finish_segment_num", {"job_id"},
1012
                                                  {WINDOW_5M, WINDOW_30M, WINDOW_1H}, false);
1013
MBvarWindowedAdder g_warmup_ed_finish_segment_size("warmup_ed_finish_segment_size", {"job_id"},
1014
                                                   {WINDOW_5M, WINDOW_30M, WINDOW_1H}, false);
1015
MBvarWindowedAdder g_warmup_ed_finish_index_num("warmup_ed_finish_index_num", {"job_id"},
1016
                                                {WINDOW_5M, WINDOW_30M, WINDOW_1H}, false);
1017
MBvarWindowedAdder g_warmup_ed_finish_index_size("warmup_ed_finish_index_size", {"job_id"},
1018
                                                 {WINDOW_5M, WINDOW_30M, WINDOW_1H}, false);
1019
MBvarWindowedAdder g_warmup_ed_fail_segment_num("warmup_ed_fail_segment_num", {"job_id"},
1020
                                                {WINDOW_5M, WINDOW_30M, WINDOW_1H}, false);
1021
MBvarWindowedAdder g_warmup_ed_fail_segment_size("warmup_ed_fail_segment_size", {"job_id"},
1022
                                                 {WINDOW_5M, WINDOW_30M, WINDOW_1H}, false);
1023
MBvarWindowedAdder g_warmup_ed_fail_index_num("warmup_ed_fail_index_num", {"job_id"},
1024
                                              {WINDOW_5M, WINDOW_30M, WINDOW_1H}, false);
1025
MBvarWindowedAdder g_warmup_ed_fail_index_size("warmup_ed_fail_index_size", {"job_id"},
1026
                                               {WINDOW_5M, WINDOW_30M, WINDOW_1H}, false);
1027
bvar::MultiDimension<bvar::Status<int64_t>> g_warmup_ed_last_finish_ts({"job_id"});
1028
1029
0
void update_warmup_ed_last_finish_ts(const std::string& job_id_str) {
1030
0
    auto* finish_ts = g_warmup_ed_last_finish_ts.get_stats(std::list<std::string> {job_id_str});
1031
0
    if (finish_ts) {
1032
0
        finish_ts->set_value(std::chrono::duration_cast<std::chrono::milliseconds>(
1033
0
                                     std::chrono::system_clock::now().time_since_epoch())
1034
0
                                     .count());
1035
0
    }
1036
0
}
1037
1038
0
void record_warmup_ed_finish_segment(const std::string& job_id_str, int64_t segment_size) {
1039
0
    g_warmup_ed_finish_segment_num.put({job_id_str}, 1);
1040
0
    g_warmup_ed_finish_segment_size.put({job_id_str}, segment_size);
1041
0
    update_warmup_ed_last_finish_ts(job_id_str);
1042
0
}
1043
1044
0
void record_warmup_ed_finish_index(const std::string& job_id_str, int64_t idx_size) {
1045
0
    g_warmup_ed_finish_index_num.put({job_id_str}, 1);
1046
0
    g_warmup_ed_finish_index_size.put({job_id_str}, idx_size);
1047
0
    update_warmup_ed_last_finish_ts(job_id_str);
1048
0
}
1049
1050
0
void record_warmup_ed_fail_segment(const std::string& job_id_str, int64_t segment_size) {
1051
0
    g_warmup_ed_fail_segment_num.put({job_id_str}, 1);
1052
0
    g_warmup_ed_fail_segment_size.put({job_id_str}, segment_size);
1053
0
}
1054
1055
0
void record_warmup_ed_fail_index(const std::string& job_id_str, int64_t idx_size) {
1056
0
    g_warmup_ed_fail_index_num.put({job_id_str}, 1);
1057
0
    g_warmup_ed_fail_index_size.put({job_id_str}, idx_size);
1058
0
}
1059
1060
void record_warmup_ed_skipped_rowset_as_finished(RowsetMeta& rs_meta,
1061
0
                                                 const std::string& job_id_str) {
1062
0
    auto schema_ptr = rs_meta.tablet_schema();
1063
0
    bool has_inverted_index = schema_ptr->has_inverted_index() || schema_ptr->has_ann_index();
1064
0
    auto idx_version = schema_ptr->get_inverted_index_storage_format();
1065
0
    for (int64_t segment_id = 0; segment_id < rs_meta.num_segments(); segment_id++) {
1066
0
        record_warmup_ed_finish_segment(job_id_str, rs_meta.segment_file_size(segment_id));
1067
1068
0
        if (!has_inverted_index) {
1069
0
            continue;
1070
0
        }
1071
0
        auto&& inverted_index_info = rs_meta.inverted_index_file_info(segment_id);
1072
0
        if (idx_version == InvertedIndexStorageFormatPB::V1) {
1073
0
            std::unordered_map<int64_t, int64_t> index_size_map;
1074
0
            for (const auto& info : inverted_index_info.index_info()) {
1075
0
                if (info.index_file_size() != -1) {
1076
0
                    index_size_map[info.index_id()] = info.index_file_size();
1077
0
                } else {
1078
0
                    VLOG_DEBUG << "Invalid index_file_size for segment_id " << segment_id
1079
0
                               << ", index_id " << info.index_id();
1080
0
                }
1081
0
            }
1082
0
            for (const auto& index : schema_ptr->inverted_indexes()) {
1083
0
                record_warmup_ed_finish_index(job_id_str, index_size_map[index->index_id()]);
1084
0
            }
1085
0
        } else { // InvertedIndexStorageFormatPB::V2
1086
0
            int64_t idx_size = 0;
1087
0
            if (inverted_index_info.has_index_size()) {
1088
0
                idx_size = inverted_index_info.index_size();
1089
0
            } else {
1090
0
                VLOG_DEBUG << "index_size is not set for segment " << segment_id;
1091
0
            }
1092
0
            record_warmup_ed_finish_index(job_id_str, idx_size);
1093
0
        }
1094
0
    }
1095
0
}
1096
1097
void handle_segment_download_done(Status st, int64_t tablet_id, const RowsetId& rowset_id,
1098
                                  int64_t segment_id, std::shared_ptr<CloudTablet> tablet,
1099
                                  std::shared_ptr<bthread::CountdownEvent> wait, Version version,
1100
                                  int64_t segment_size, int64_t request_ts, int64_t handle_ts,
1101
0
                                  std::string job_id_str, int64_t upstream_trigger_ts_ms) {
1102
0
    DBUG_EXECUTE_IF("CloudInternalServiceImpl::warm_up_rowset.download_segment", {
1103
0
        auto sleep_time = dp->param<int>("sleep", 3);
1104
0
        LOG_INFO("[verbose] block download for rowset={}, version={}, sleep={}",
1105
0
                 rowset_id.to_string(), version.to_string(), sleep_time);
1106
0
        std::this_thread::sleep_for(std::chrono::seconds(sleep_time));
1107
0
    });
1108
0
    DBUG_EXECUTE_IF(
1109
0
            "CloudInternalServiceImpl::warm_up_rowset.download_segment.inject_"
1110
0
            "error",
1111
0
            {
1112
0
                st = Status::InternalError("injected error");
1113
0
                LOG_INFO("[verbose] inject error, tablet={}, rowset={}, st={}", tablet_id,
1114
0
                         rowset_id.to_string(), st.to_string());
1115
0
            });
1116
0
    if (st.ok()) {
1117
0
        g_file_cache_event_driven_warm_up_finished_segment_num << 1;
1118
0
        g_file_cache_event_driven_warm_up_finished_segment_size << segment_size;
1119
0
        record_warmup_ed_finish_segment(job_id_str, segment_size);
1120
0
        int64_t now_ts = current_unix_time_us();
1121
0
        g_file_cache_warm_up_rowset_last_finish_unix_ts.set_value(now_ts);
1122
0
        auto rowset_latency_us = warm_up_rowset_cross_host_latency_us(request_ts, now_ts);
1123
0
        if (rowset_latency_us.has_value()) {
1124
0
            g_file_cache_warm_up_rowset_latency << *rowset_latency_us;
1125
0
        }
1126
0
        g_file_cache_warm_up_rowset_handle_to_finish_latency << (now_ts - handle_ts);
1127
0
        if (rowset_latency_us.has_value() &&
1128
0
            *rowset_latency_us > config::warm_up_rowset_slow_log_ms * 1000) {
1129
0
            g_file_cache_warm_up_rowset_slow_count << 1;
1130
0
            LOG(INFO) << "warm up rowset took " << *rowset_latency_us
1131
0
                      << " us, tablet_id: " << tablet_id << ", rowset_id: " << rowset_id.to_string()
1132
0
                      << ", segment_id: " << segment_id;
1133
0
        }
1134
0
        if (now_ts - handle_ts > config::warm_up_rowset_slow_log_ms * 1000) {
1135
0
            g_file_cache_warm_up_rowset_handle_to_finish_slow_count << 1;
1136
0
            LOG(INFO) << "warm up rowset (handle to finish) took " << now_ts - handle_ts
1137
0
                      << " us, tablet_id: " << tablet_id << ", rowset_id: " << rowset_id.to_string()
1138
0
                      << ", segment_id: " << segment_id;
1139
0
        }
1140
0
    } else {
1141
0
        g_file_cache_event_driven_warm_up_failed_segment_num << 1;
1142
0
        g_file_cache_event_driven_warm_up_failed_segment_size << segment_size;
1143
0
        record_warmup_ed_fail_segment(job_id_str, segment_size);
1144
0
        LOG(WARNING) << "download segment failed, tablet_id: " << tablet_id
1145
0
                     << " rowset_id: " << rowset_id.to_string() << ", error: " << st;
1146
0
    }
1147
0
    if (tablet->complete_rowset_segment_warmup(WarmUpTriggerSource::EVENT_DRIVEN, rowset_id, st, 1,
1148
0
                                               0)
1149
0
                .trigger_source == WarmUpTriggerSource::EVENT_DRIVEN) {
1150
0
        VLOG_DEBUG << "warmup rowset " << version.to_string() << "(" << rowset_id.to_string()
1151
0
                   << ") completed";
1152
0
    }
1153
0
    g_warmup_ed_downstream_progress_tracker.record_task_done(job_id_str, upstream_trigger_ts_ms);
1154
0
    if (wait) {
1155
0
        wait->signal();
1156
0
    }
1157
0
}
1158
1159
void handle_inverted_index_download_done(Status st, int64_t tablet_id, const RowsetId& rowset_id,
1160
                                         int64_t segment_id, std::string index_path,
1161
                                         std::shared_ptr<CloudTablet> tablet,
1162
                                         std::shared_ptr<bthread::CountdownEvent> wait,
1163
                                         Version version, uint64_t idx_size, int64_t request_ts,
1164
                                         int64_t handle_ts, std::string job_id_str,
1165
0
                                         int64_t upstream_trigger_ts_ms) {
1166
0
    DBUG_EXECUTE_IF("CloudInternalServiceImpl::warm_up_rowset.download_inverted_idx", {
1167
0
        auto sleep_time = dp->param<int>("sleep", 3);
1168
0
        LOG_INFO(
1169
0
                "[verbose] block download for rowset={}, inverted index "
1170
0
                "file={}, sleep={}",
1171
0
                rowset_id.to_string(), index_path, sleep_time);
1172
0
        std::this_thread::sleep_for(std::chrono::seconds(sleep_time));
1173
0
    });
1174
0
    if (st.ok()) {
1175
0
        g_file_cache_event_driven_warm_up_finished_index_num << 1;
1176
0
        g_file_cache_event_driven_warm_up_finished_index_size << idx_size;
1177
0
        record_warmup_ed_finish_index(job_id_str, static_cast<int64_t>(idx_size));
1178
0
        int64_t now_ts = current_unix_time_us();
1179
0
        g_file_cache_warm_up_rowset_last_finish_unix_ts.set_value(now_ts);
1180
0
        auto rowset_latency_us = warm_up_rowset_cross_host_latency_us(request_ts, now_ts);
1181
0
        if (rowset_latency_us.has_value()) {
1182
0
            g_file_cache_warm_up_rowset_latency << *rowset_latency_us;
1183
0
        }
1184
0
        g_file_cache_warm_up_rowset_handle_to_finish_latency << (now_ts - handle_ts);
1185
0
        if (rowset_latency_us.has_value() &&
1186
0
            *rowset_latency_us > config::warm_up_rowset_slow_log_ms * 1000) {
1187
0
            g_file_cache_warm_up_rowset_slow_count << 1;
1188
0
            LOG(INFO) << "warm up rowset took " << *rowset_latency_us
1189
0
                      << " us, tablet_id: " << tablet_id << ", rowset_id: " << rowset_id.to_string()
1190
0
                      << ", segment_id: " << segment_id;
1191
0
        }
1192
0
        if (now_ts - handle_ts > config::warm_up_rowset_slow_log_ms * 1000) {
1193
0
            g_file_cache_warm_up_rowset_handle_to_finish_slow_count << 1;
1194
0
            LOG(INFO) << "warm up rowset (handle to finish) took " << now_ts - handle_ts
1195
0
                      << " us, tablet_id: " << tablet_id << ", rowset_id: " << rowset_id.to_string()
1196
0
                      << ", segment_id: " << segment_id;
1197
0
        }
1198
0
    } else {
1199
0
        g_file_cache_event_driven_warm_up_failed_index_num << 1;
1200
0
        g_file_cache_event_driven_warm_up_failed_index_size << idx_size;
1201
0
        record_warmup_ed_fail_index(job_id_str, static_cast<int64_t>(idx_size));
1202
0
        LOG(WARNING) << "download inverted index failed, tablet_id: " << tablet_id
1203
0
                     << " rowset_id: " << rowset_id << ", error: " << st;
1204
0
    }
1205
0
    if (tablet->complete_rowset_segment_warmup(WarmUpTriggerSource::EVENT_DRIVEN, rowset_id, st, 0,
1206
0
                                               1)
1207
0
                .trigger_source == WarmUpTriggerSource::EVENT_DRIVEN) {
1208
0
        VLOG_DEBUG << "warmup rowset " << version.to_string() << "(" << rowset_id.to_string()
1209
0
                   << ") completed";
1210
0
    }
1211
0
    g_warmup_ed_downstream_progress_tracker.record_task_done(job_id_str, upstream_trigger_ts_ms);
1212
0
    if (wait) {
1213
0
        wait->signal();
1214
0
    }
1215
0
}
1216
1217
void CloudInternalServiceImpl::warm_up_rowset(google::protobuf::RpcController* controller
1218
                                              [[maybe_unused]],
1219
                                              const PWarmUpRowsetRequest* request,
1220
                                              PWarmUpRowsetResponse* response,
1221
0
                                              google::protobuf::Closure* done) {
1222
0
    brpc::ClosureGuard closure_guard(done);
1223
0
    std::shared_ptr<bthread::CountdownEvent> wait = nullptr;
1224
0
    timespec due_time;
1225
0
    if (request->has_sync_wait_timeout_ms() && request->sync_wait_timeout_ms() > 0) {
1226
0
        g_file_cache_warm_up_rowset_wait_for_compaction_num << 1;
1227
0
        wait = std::make_shared<bthread::CountdownEvent>(0);
1228
0
        VLOG_DEBUG << "sync_wait_timeout: " << request->sync_wait_timeout_ms() << " ms";
1229
0
        due_time = butil::milliseconds_from_now(request->sync_wait_timeout_ms());
1230
0
    }
1231
1232
    // Extract job_id from request (0 if not set, for backward compatibility)
1233
0
    std::string job_id_str = std::to_string(request->has_job_id() ? request->job_id() : 0);
1234
0
    int64_t upstream_trigger_ts_ms =
1235
0
            request->has_upstream_trigger_ts_ms() ? request->upstream_trigger_ts_ms() : 0;
1236
1237
0
    for (auto& rs_meta_pb : request->rowset_metas()) {
1238
0
        RowsetMeta rs_meta;
1239
0
        rs_meta.init_from_pb(rs_meta_pb);
1240
0
        auto storage_resource = rs_meta.remote_storage_resource();
1241
0
        if (!storage_resource) {
1242
0
            LOG(WARNING) << storage_resource.error();
1243
0
            continue;
1244
0
        }
1245
0
        int64_t tablet_id = rs_meta.tablet_id();
1246
0
        auto rowset_id = rs_meta.rowset_id();
1247
0
        bool local_only = !(request->has_skip_existence_check() && request->skip_existence_check());
1248
0
        auto res = _engine.tablet_mgr().get_tablet(tablet_id, /* warmup_data = */ false,
1249
0
                                                   /* sync_delete_bitmap = */ true,
1250
0
                                                   /* sync_stats = */ nullptr,
1251
0
                                                   /* local_only = */ local_only);
1252
0
        if (!res.has_value()) {
1253
0
            LOG_WARNING("Warm up error ").tag("tablet_id", tablet_id).error(res.error());
1254
0
            if (res.error().msg().find("local_only=true") != std::string::npos ||
1255
0
                res.error().msg().find("force_use_only_cached=true") != std::string::npos) {
1256
0
                res.error().set_code(ErrorCode::TABLE_NOT_FOUND);
1257
0
            }
1258
0
            res.error().to_protobuf(response->mutable_status());
1259
0
            continue;
1260
0
        }
1261
0
        auto tablet = res.value();
1262
0
        auto tablet_meta = tablet->tablet_meta();
1263
1264
0
        int64_t handle_ts = current_unix_time_us();
1265
0
        g_file_cache_warm_up_rowset_last_handle_unix_ts.set_value(handle_ts);
1266
0
        int64_t request_ts = request->has_unix_ts_us() ? request->unix_ts_us() : 0;
1267
0
        auto request_to_handle_latency_us =
1268
0
                warm_up_rowset_cross_host_latency_us(request_ts, handle_ts);
1269
0
        if (request_to_handle_latency_us.has_value()) {
1270
0
            g_file_cache_warm_up_rowset_request_to_handle_latency << *request_to_handle_latency_us;
1271
0
        }
1272
0
        if (request_to_handle_latency_us.has_value() &&
1273
0
            *request_to_handle_latency_us > config::warm_up_rowset_slow_log_ms * 1000) {
1274
0
            g_file_cache_warm_up_rowset_request_to_handle_slow_count << 1;
1275
0
            LOG(INFO) << "warm up rowset (request to handle) took " << *request_to_handle_latency_us
1276
0
                      << " us, tablet_id: " << rs_meta.tablet_id()
1277
0
                      << ", rowset_id: " << rowset_id.to_string();
1278
0
        }
1279
0
        int64_t expiration_time = tablet_meta->ttl_seconds();
1280
1281
0
        if (!tablet->add_rowset_warmup_state(rs_meta, WarmUpTriggerSource::EVENT_DRIVEN)) {
1282
0
            LOG(INFO) << "found duplicate warmup task for rowset " << rowset_id.to_string()
1283
0
                      << ", skip it";
1284
0
            g_warmup_ed_downstream_progress_tracker.record_task_done(job_id_str,
1285
0
                                                                     upstream_trigger_ts_ms);
1286
0
            record_warmup_ed_skipped_rowset_as_finished(rs_meta, job_id_str);
1287
0
            continue;
1288
0
        }
1289
0
        if (rs_meta.num_segments() == 0) {
1290
0
            g_warmup_ed_downstream_progress_tracker.record_task_done(job_id_str,
1291
0
                                                                     upstream_trigger_ts_ms);
1292
0
        }
1293
1294
0
        for (int64_t segment_id = 0; segment_id < rs_meta.num_segments(); segment_id++) {
1295
0
            if (!config::file_cache_enable_only_warm_up_idx) {
1296
0
                auto segment_size = rs_meta.segment_file_size(segment_id);
1297
1298
                // Use rs_meta.fs() instead of storage_resource.value()->fs to support packed files.
1299
                // PackedFileSystem wrapper in rs_meta.fs() handles the index_map lookup and
1300
                // reads from the correct packed file.
1301
0
                io::DownloadFileMeta download_meta {
1302
0
                        .path = storage_resource.value()->remote_segment_path(rs_meta, segment_id),
1303
0
                        .file_size = segment_size,
1304
0
                        .offset = 0,
1305
0
                        .download_size = segment_size,
1306
0
                        .file_system = rs_meta.fs(),
1307
0
                        .ctx = {.is_index_data = false,
1308
0
                                .expiration_time = expiration_time,
1309
0
                                .is_dryrun = config::enable_reader_dryrun_when_download_file_cache,
1310
0
                                .is_warmup = true},
1311
0
                        .download_done =
1312
0
                                [=, version = rs_meta.version()](Status st) {
1313
0
                                    handle_segment_download_done(
1314
0
                                            st, tablet_id, rowset_id, segment_id, tablet, wait,
1315
0
                                            version, segment_size, request_ts, handle_ts,
1316
0
                                            job_id_str, upstream_trigger_ts_ms);
1317
0
                                },
1318
0
                        .tablet_id = tablet_id};
1319
1320
0
                g_file_cache_event_driven_warm_up_submitted_segment_num << 1;
1321
0
                g_file_cache_event_driven_warm_up_submitted_segment_size << segment_size;
1322
0
                if (wait) {
1323
0
                    wait->add_count();
1324
0
                }
1325
0
                g_warmup_ed_downstream_progress_tracker.record_task_submit(job_id_str,
1326
0
                                                                           upstream_trigger_ts_ms);
1327
1328
0
                _engine.file_cache_block_downloader().submit_download_task(download_meta);
1329
0
            }
1330
1331
            // Use rs_meta.fs() to support packed files for inverted index download.
1332
0
            auto download_inverted_index = [&, tablet, job_id_str](std::string index_path,
1333
0
                                                                   uint64_t idx_size) {
1334
0
                io::DownloadFileMeta download_meta {
1335
0
                        .path = io::Path(index_path),
1336
0
                        .file_size = static_cast<int64_t>(idx_size),
1337
0
                        .file_system = rs_meta.fs(),
1338
0
                        .ctx = {.is_index_data = false, // DORIS-20877
1339
0
                                .expiration_time = expiration_time,
1340
0
                                .is_dryrun = config::enable_reader_dryrun_when_download_file_cache,
1341
0
                                .is_warmup = true},
1342
0
                        .download_done =
1343
0
                                [=, version = rs_meta.version()](Status st) {
1344
0
                                    handle_inverted_index_download_done(
1345
0
                                            st, tablet_id, rowset_id, segment_id, index_path,
1346
0
                                            tablet, wait, version, idx_size, request_ts, handle_ts,
1347
0
                                            job_id_str, upstream_trigger_ts_ms);
1348
0
                                },
1349
0
                        .tablet_id = tablet_id,
1350
0
                };
1351
0
                g_file_cache_event_driven_warm_up_submitted_index_num << 1;
1352
0
                g_file_cache_event_driven_warm_up_submitted_index_size << idx_size;
1353
0
                tablet->update_rowset_warmup_state_inverted_idx_num(
1354
0
                        WarmUpTriggerSource::EVENT_DRIVEN, rowset_id, 1);
1355
0
                if (wait) {
1356
0
                    wait->add_count();
1357
0
                }
1358
0
                g_warmup_ed_downstream_progress_tracker.record_task_submit(job_id_str,
1359
0
                                                                           upstream_trigger_ts_ms);
1360
0
                _engine.file_cache_block_downloader().submit_download_task(download_meta);
1361
0
            };
1362
1363
            // inverted index
1364
0
            auto schema_ptr = rs_meta.tablet_schema();
1365
0
            auto idx_version = schema_ptr->get_inverted_index_storage_format();
1366
1367
0
            if (schema_ptr->has_inverted_index() || schema_ptr->has_ann_index()) {
1368
0
                if (idx_version == InvertedIndexStorageFormatPB::V1) {
1369
0
                    auto&& inverted_index_info = rs_meta.inverted_index_file_info(segment_id);
1370
0
                    std::unordered_map<int64_t, int64_t> index_size_map;
1371
0
                    for (const auto& info : inverted_index_info.index_info()) {
1372
0
                        if (info.index_file_size() != -1) {
1373
0
                            index_size_map[info.index_id()] = info.index_file_size();
1374
0
                        } else {
1375
0
                            VLOG_DEBUG << "Invalid index_file_size for segment_id " << segment_id
1376
0
                                       << ", index_id " << info.index_id();
1377
0
                        }
1378
0
                    }
1379
0
                    for (const auto& index : schema_ptr->inverted_indexes()) {
1380
0
                        auto idx_path = storage_resource.value()->remote_idx_v1_path(
1381
0
                                rs_meta, segment_id, index->index_id(), index->get_index_suffix());
1382
0
                        download_inverted_index(idx_path, index_size_map[index->index_id()]);
1383
0
                    }
1384
0
                } else { // InvertedIndexStorageFormatPB::V2
1385
0
                    auto&& inverted_index_info = rs_meta.inverted_index_file_info(segment_id);
1386
0
                    int64_t idx_size = 0;
1387
0
                    if (inverted_index_info.has_index_size()) {
1388
0
                        idx_size = inverted_index_info.index_size();
1389
0
                    } else {
1390
0
                        VLOG_DEBUG << "index_size is not set for segment " << segment_id;
1391
0
                    }
1392
0
                    auto idx_path =
1393
0
                            storage_resource.value()->remote_idx_v2_path(rs_meta, segment_id);
1394
0
                    download_inverted_index(idx_path, idx_size);
1395
0
                }
1396
0
            }
1397
0
        }
1398
0
    }
1399
0
    if (wait && wait->timed_wait(due_time)) {
1400
0
        g_file_cache_warm_up_rowset_wait_for_compaction_timeout_num << 1;
1401
0
        LOG_WARNING("the time spent warming up {} rowsets exceeded {} ms",
1402
0
                    request->rowset_metas().size(), request->sync_wait_timeout_ms());
1403
0
    }
1404
0
}
1405
1406
bvar::Adder<uint64_t> g_file_cache_recycle_cache_finished_segment_num(
1407
        "file_cache_recycle_cache_finished_segment_num");
1408
bvar::Adder<uint64_t> g_file_cache_recycle_cache_finished_index_num(
1409
        "file_cache_recycle_cache_finished_index_num");
1410
1411
void CloudInternalServiceImpl::recycle_cache(google::protobuf::RpcController* controller
1412
                                             [[maybe_unused]],
1413
                                             const PRecycleCacheRequest* request,
1414
                                             PRecycleCacheResponse* response,
1415
0
                                             google::protobuf::Closure* done) {
1416
0
    brpc::ClosureGuard closure_guard(done);
1417
1418
0
    if (!config::enable_file_cache) {
1419
0
        return;
1420
0
    }
1421
0
    for (const auto& meta : request->cache_metas()) {
1422
0
        for (int64_t segment_id = 0; segment_id < meta.num_segments(); segment_id++) {
1423
0
            auto file_key = Segment::file_cache_key(meta.rowset_id(), segment_id);
1424
0
            auto* file_cache = io::FileCacheFactory::instance()->get_by_path(file_key);
1425
0
            file_cache->remove_if_cached_async(file_key);
1426
0
            g_file_cache_recycle_cache_finished_segment_num << 1;
1427
0
        }
1428
1429
        // inverted index
1430
0
        for (const auto& file_name : meta.index_file_names()) {
1431
0
            auto file_key = io::BlockFileCache::hash(file_name);
1432
0
            auto* file_cache = io::FileCacheFactory::instance()->get_by_path(file_key);
1433
0
            file_cache->remove_if_cached_async(file_key);
1434
0
            g_file_cache_recycle_cache_finished_index_num << 1;
1435
0
        }
1436
0
    }
1437
0
}
1438
1439
#include "common/compile_check_avoid_end.h"
1440
} // namespace doris