Coverage Report

Created: 2026-08-14 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/load/channel/load_channel_mgr.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 "load/channel/load_channel_mgr.h"
19
20
#include <fmt/format.h>
21
#include <gen_cpp/internal_service.pb.h>
22
23
#include <algorithm>
24
// IWYU pragma: no_include <bits/chrono.h>
25
#include <chrono> // IWYU pragma: keep
26
#include <ctime>
27
#include <memory>
28
#include <ostream>
29
#include <string>
30
#include <vector>
31
32
#include "common/config.h"
33
#include "common/logging.h"
34
#include "common/metrics/doris_metrics.h"
35
#include "common/metrics/metrics.h"
36
#include "load/channel/load_channel.h"
37
#include "runtime/exec_env.h"
38
#include "util/debug_points.h"
39
#include "util/defer_op.h"
40
#include "util/thread.h"
41
42
namespace doris {
43
44
#ifndef BE_TEST
45
constexpr uint32_t START_BG_INTERVAL = 60;
46
#else
47
constexpr uint32_t START_BG_INTERVAL = 1;
48
#endif
49
50
DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(load_channel_count, MetricUnit::NOUNIT);
51
DEFINE_GAUGE_METRIC_PROTOTYPE_5ARG(load_channel_mem_consumption, MetricUnit::BYTES, "",
52
                                   mem_consumption, Labels({{"type", "load"}}));
53
54
32.3k
static int64_t calc_channel_timeout_s(int64_t timeout_in_req_s) {
55
32.3k
    int64_t load_channel_timeout_s = config::streaming_load_rpc_max_alive_time_sec;
56
32.3k
    if (timeout_in_req_s > 0) {
57
32.3k
        load_channel_timeout_s = std::max<int64_t>(load_channel_timeout_s, timeout_in_req_s);
58
32.3k
    }
59
32.3k
    return load_channel_timeout_s;
60
32.3k
}
61
62
13
LoadChannelMgr::LoadChannelMgr() : _stop_background_threads_latch(1) {
63
13
    REGISTER_HOOK_METRIC(load_channel_count, [this]() {
64
        // std::lock_guard<std::mutex> l(_lock);
65
13
        return _load_channels.size();
66
13
    });
67
13
}
68
69
9
void LoadChannelMgr::stop() {
70
9
    DEREGISTER_HOOK_METRIC(load_channel_count);
71
9
    DEREGISTER_HOOK_METRIC(load_channel_mem_consumption);
72
9
    _stop_background_threads_latch.count_down();
73
9
    if (_load_channels_clean_thread) {
74
3
        _load_channels_clean_thread->join();
75
3
    }
76
9
}
77
78
7
Status LoadChannelMgr::init(int64_t process_mem_limit) {
79
7
    _load_state_channels = std::make_unique<LoadStateChannelCache>(1024);
80
7
    _final_tablet_result_cache = std::make_unique<FinalTabletResultCache>();
81
7
    RETURN_IF_ERROR(_start_bg_worker());
82
7
    return Status::OK();
83
7
}
84
85
48.9k
Status LoadChannelMgr::open(const PTabletWriterOpenRequest& params) {
86
48.9k
    UniqueId load_id(params.id());
87
48.9k
    std::shared_ptr<LoadChannel> channel;
88
48.9k
    {
89
48.9k
        std::lock_guard<std::mutex> l(_lock);
90
48.9k
        auto it = _load_channels.find(load_id);
91
48.9k
        if (it != _load_channels.end()) {
92
16.5k
            channel = it->second;
93
32.3k
        } else {
94
            // create a new load channel
95
32.3k
            int64_t timeout_in_req_s =
96
18.4E
                    params.has_load_channel_timeout_s() ? params.load_channel_timeout_s() : -1;
97
32.3k
            int64_t channel_timeout_s = calc_channel_timeout_s(timeout_in_req_s);
98
32.3k
            bool is_high_priority = (params.has_is_high_priority() && params.is_high_priority());
99
100
32.3k
            int64_t wg_id = -1;
101
32.3k
            if (params.has_workload_group_id()) {
102
32.3k
                wg_id = params.workload_group_id();
103
32.3k
            }
104
32.3k
            channel.reset(new LoadChannel(load_id, channel_timeout_s, is_high_priority,
105
32.3k
                                          params.sender_ip(), params.backend_id(),
106
32.3k
                                          params.enable_profile(), wg_id));
107
32.3k
            _load_channels.insert({load_id, channel});
108
32.3k
        }
109
48.9k
    }
110
111
48.9k
    RETURN_IF_ERROR(channel->open(params));
112
113
48.9k
    return Status::OK();
114
48.9k
}
115
116
Status LoadChannelMgr::_copy_cached_final_tablet_result(const UniqueId& load_id,
117
                                                        const PTabletWriterAddBlockRequest& request,
118
                                                        Cache::Handle* result_handle,
119
6
                                                        PTabletWriterAddBlockResult* response) {
120
6
    DORIS_CHECK(result_handle != nullptr);
121
6
    Defer release_result_handle(
122
6
            [this, result_handle] { _final_tablet_result_cache->release(result_handle); });
123
6
    const auto* result_cache_value = reinterpret_cast<FinalTabletResultCache::CacheValue*>(
124
6
            _final_tablet_result_cache->value(result_handle));
125
6
    const auto result = result_cache_value->_results.find(request.index_id());
126
6
    if (result == result_cache_value->_results.end()) {
127
1
        LOG(WARNING) << "Final tablet result is unavailable for retried load " << load_id
128
1
                     << ", index_id=" << request.index_id()
129
1
                     << ", sender_id=" << request.sender_id();
130
1
        return Status::InternalError<false>(
131
1
                "Final tablet result is unavailable for retried load {}, index_id={}, sender_id={}",
132
1
                load_id.to_string(), request.index_id(), request.sender_id());
133
1
    }
134
5
    DBUG_EXECUTE_IF("LoadChannelMgr.get.before_copy", DBUG_RUN_CALLBACK());
135
5
    response->CopyFrom(result->second.result);
136
5
    response->set_final_tablet_result_fanout(request.sender_id() != result->second.owner_sender_id);
137
5
    return Status::OK();
138
6
}
139
140
Status LoadChannelMgr::_get_load_channel(std::shared_ptr<LoadChannel>& channel, bool& is_eof,
141
                                         const UniqueId& load_id,
142
                                         const PTabletWriterAddBlockRequest& request,
143
55.0k
                                         PTabletWriterAddBlockResult* response) {
144
55.0k
    is_eof = false;
145
55.0k
    std::unique_lock<std::mutex> l(_lock);
146
55.0k
    auto it = _load_channels.find(load_id);
147
55.0k
    if (it == _load_channels.end()) {
148
9
        Cache::Handle* handle = _load_state_channels->lookup(load_id.to_string());
149
9
        if (handle != nullptr) {
150
            // load is cancelled
151
9
            if (auto* value = _load_state_channels->value(handle); value != nullptr) {
152
1
                const auto* cache_value = reinterpret_cast<CacheValue*>(value);
153
1
                const auto& cancel_reason = cache_value->_cancel_reason;
154
1
                _load_state_channels->release(handle);
155
1
                if (!cancel_reason.empty()) {
156
1
                    LOG(INFO) << fmt::format(
157
1
                            "The channel has been cancelled, load_id = {}, error = {}",
158
1
                            print_id(load_id), cancel_reason);
159
1
                    return Status::Cancelled(cancel_reason);
160
1
                }
161
8
            } else {
162
                // load is success, success only when eos be true
163
8
                _load_state_channels->release(handle);
164
8
                if (request.has_eos() && request.eos()) {
165
8
                    if (request.need_final_tablet_result()) {
166
7
                        auto* result_handle =
167
7
                                _final_tablet_result_cache->lookup(load_id.to_string());
168
7
                        if (result_handle == nullptr) {
169
1
                            LOG(WARNING) << "Final tablet result is unavailable for retried load "
170
1
                                         << load_id << ", sender_id=" << request.sender_id();
171
1
                            return Status::InternalError<false>(
172
1
                                    "Final tablet result is unavailable for retried load {}, "
173
1
                                    "index_id={}, sender_id={}",
174
1
                                    load_id.to_string(), request.index_id(), request.sender_id());
175
1
                        }
176
6
                        l.unlock();
177
6
                        RETURN_IF_ERROR(_copy_cached_final_tablet_result(load_id, request,
178
6
                                                                         result_handle, response));
179
6
                    }
180
6
                    is_eof = true;
181
6
                    return Status::OK();
182
8
                }
183
8
            }
184
9
        }
185
186
0
        return Status::InternalError<false>(
187
0
                "Fail to add batch in load channel: unknown load_id={}. "
188
0
                "This may be due to a BE restart. Please retry the load.",
189
0
                load_id.to_string());
190
9
    }
191
55.0k
    channel = it->second;
192
55.0k
    if (request.eos() && request.need_final_tablet_result()) {
193
1
        channel->_reserve_final_tablet_result(request.index_id());
194
1
    }
195
55.0k
    return Status::OK();
196
55.0k
}
197
198
Status LoadChannelMgr::add_batch(const PTabletWriterAddBlockRequest& request,
199
                                 PTabletWriterAddBlockResult* response,
200
55.0k
                                 google::protobuf::Closure** done) {
201
55.0k
    UniqueId load_id(request.id());
202
    // 1. get load channel
203
55.0k
    std::shared_ptr<LoadChannel> channel;
204
55.0k
    bool is_eof;
205
55.0k
    auto status = _get_load_channel(channel, is_eof, load_id, request, response);
206
55.0k
    if (!status.ok() || is_eof) {
207
9
        return status;
208
9
    }
209
55.0k
    SCOPED_TIMER(channel->get_mgr_add_batch_timer());
210
211
55.0k
    if (!channel->is_high_priority()) {
212
        // 2. check if mem consumption exceed limit
213
        // If this is a high priority load task, do not handle this.
214
        // because this may block for a while, which may lead to rpc timeout.
215
54.4k
        SCOPED_TIMER(channel->get_handle_mem_limit_timer());
216
54.4k
        ExecEnv::GetInstance()->memtable_memory_limiter()->handle_memtable_flush(
217
54.4k
                [channel]() { return channel->is_cancelled(); }, channel->workload_group().get());
218
54.4k
        if (channel->is_cancelled()) {
219
0
            return Status::Cancelled("LoadChannel has been cancelled: {}.", load_id.to_string());
220
0
        }
221
54.4k
    }
222
223
    // 3. add batch to load channel
224
    // batch may not exist in request(eg: eos request without batch),
225
    // this case will be handled in load channel's add batch method.
226
55.0k
    Status st = channel->add_batch(request, response, done);
227
55.0k
    if (UNLIKELY(!st.ok())) {
228
43
        RETURN_IF_ERROR(channel->cancel(st));
229
43
        return st;
230
43
    }
231
232
    // 4. handle finish
233
55.0k
    if (channel->is_finished()) {
234
32.5k
        _finish_load_channel(load_id, channel);
235
32.5k
    }
236
55.0k
    return Status::OK();
237
55.0k
}
238
239
void LoadChannelMgr::_finish_load_channel(const UniqueId load_id,
240
32.5k
                                          const std::shared_ptr<LoadChannel>& channel) {
241
32.5k
    const std::string cache_key = load_id.to_string();
242
32.5k
    bool need_final_tablet_result = false;
243
32.5k
    {
244
32.5k
        std::lock_guard<std::mutex> l(_lock);
245
32.5k
        const auto channel_it = _load_channels.find(load_id);
246
32.5k
        if (channel_it == _load_channels.end() || channel_it->second != channel) {
247
343
            return;
248
343
        }
249
32.1k
        need_final_tablet_result = channel->need_final_tablet_result();
250
32.2k
        if (!need_final_tablet_result) {
251
32.2k
            _load_channels.erase(channel_it);
252
32.2k
            auto* handle = _load_state_channels->insert(cache_key, nullptr, 1, 1);
253
32.2k
            _load_state_channels->release(handle);
254
18.4E
        } else if (!_finishing_load_channels.emplace(load_id).second) {
255
0
            return;
256
0
        }
257
32.1k
    }
258
32.2k
    if (!need_final_tablet_result) {
259
32.2k
        VLOG_CRITICAL << "removed load channel " << load_id;
260
32.2k
        return;
261
32.2k
    }
262
18.4E
    bool finishing_registered = true;
263
18.4E
    Defer clear_finishing([this, load_id, &finishing_registered] {
264
3
        if (finishing_registered) {
265
0
            std::lock_guard<std::mutex> l(_lock);
266
0
            _finishing_load_channels.erase(load_id);
267
0
        }
268
3
    });
269
18.4E
    std::unique_ptr<FinalTabletResultCache::CacheValue> cache_value;
270
18.4E
    DBUG_EXECUTE_IF("LoadChannelMgr.finish.before_copy", DBUG_RUN_CALLBACK());
271
18.4E
    size_t cache_size = 0;
272
18.4E
    std::unordered_map<int64_t, LoadChannel::FinalTabletResult> final_tablet_results;
273
18.4E
    bool oversized = false;
274
18.4E
    size_t result_bytes = 0;
275
18.4E
    const size_t cache_overhead =
276
18.4E
            sizeof(FinalTabletResultCache::CacheValue) + sizeof(LRUHandle) - 1 + cache_key.size();
277
18.4E
    DORIS_CHECK(channel->copy_final_tablet_results(
278
18.4E
            &final_tablet_results, FinalTabletResultCache::MAX_BYTES - cache_overhead, &oversized,
279
18.4E
            &result_bytes));
280
18.4E
    if (!oversized && !final_tablet_results.empty()) {
281
3
        cache_value = std::make_unique<FinalTabletResultCache::CacheValue>();
282
3
        cache_value->_results = std::move(final_tablet_results);
283
3
        cache_size = sizeof(FinalTabletResultCache::CacheValue) + result_bytes;
284
18.4E
    } else if (oversized) {
285
0
        LOG(WARNING) << "Skip caching final tablet result for load " << load_id
286
0
                     << ", oversized=" << oversized;
287
0
    }
288
18.4E
    Cache::Handle* result_handle = nullptr;
289
18.4E
    Defer release_result_handle([this, &result_handle] {
290
3
        if (result_handle != nullptr) {
291
3
            _final_tablet_result_cache->release(result_handle);
292
3
        }
293
3
    });
294
18.4E
    _final_tablet_result_cache->erase(cache_key);
295
18.4E
    if (cache_value != nullptr) {
296
3
        result_handle = _final_tablet_result_cache->insert(cache_key, cache_value.get(), cache_size,
297
3
                                                           cache_size);
298
3
        cache_value.release();
299
3
    }
300
18.4E
    bool published = false;
301
18.4E
    {
302
18.4E
        std::lock_guard<std::mutex> l(_lock);
303
18.4E
        const auto channel_it = _load_channels.find(load_id);
304
18.4E
        if (channel_it != _load_channels.end() && channel_it->second == channel) {
305
3
            _load_channels.erase(channel_it);
306
3
            auto* handle = _load_state_channels->insert(cache_key, nullptr, 1, 1);
307
3
            _load_state_channels->release(handle);
308
3
            published = true;
309
3
        }
310
18.4E
        _finishing_load_channels.erase(load_id);
311
18.4E
        finishing_registered = false;
312
18.4E
    }
313
18.4E
    if (!published) {
314
0
        _final_tablet_result_cache->erase(cache_key);
315
0
        return;
316
0
    }
317
18.4E
    VLOG_CRITICAL << "removed load channel " << load_id;
318
18.4E
}
319
320
128
Status LoadChannelMgr::cancel(const PTabletWriterCancelRequest& params) {
321
128
    UniqueId load_id(params.id());
322
128
    std::shared_ptr<LoadChannel> cancelled_channel;
323
128
    {
324
128
        std::lock_guard<std::mutex> l(_lock);
325
128
        const bool finish_in_progress =
326
128
                !_finishing_load_channels.empty() && _finishing_load_channels.contains(load_id);
327
        // Once final-result publication starts, finish wins so a success marker can never outlive
328
        // its reproducible result because of a concurrent cancel.
329
128
        if (const auto channel_it = _load_channels.find(load_id);
330
128
            !finish_in_progress && channel_it != _load_channels.end()) {
331
77
            cancelled_channel = channel_it->second;
332
77
            _load_channels.erase(channel_it);
333
77
        }
334
128
        if (!finish_in_progress) {
335
            // We just need to record the first cancel msg
336
127
            auto* existing_handle = _load_state_channels->lookup(load_id.to_string());
337
127
            if (existing_handle == nullptr) {
338
75
                if (params.has_cancel_reason() && !params.cancel_reason().empty()) {
339
75
                    std::unique_ptr<CacheValue> cancel_reason_ptr = std::make_unique<CacheValue>();
340
75
                    cancel_reason_ptr->_cancel_reason = params.cancel_reason();
341
75
                    size_t cache_capacity =
342
75
                            cancel_reason_ptr->_cancel_reason.capacity() + sizeof(CacheValue);
343
75
                    auto* handle = _load_state_channels->insert(
344
75
                            load_id.to_string(), cancel_reason_ptr.get(), 1, cache_capacity);
345
75
                    cancel_reason_ptr.release();
346
75
                    _load_state_channels->release(handle);
347
75
                    LOG(INFO) << fmt::format("load_id = {}, record_error reason = {}",
348
75
                                             print_id(load_id), params.cancel_reason());
349
75
                }
350
75
            } else {
351
52
                _load_state_channels->release(existing_handle);
352
52
            }
353
127
        }
354
128
    }
355
356
128
    if (cancelled_channel != nullptr) {
357
77
        const Status reason = params.has_cancel_reason()
358
77
                                      ? Status::Cancelled(params.cancel_reason())
359
77
                                      : Status::Cancelled("Load channel cancelled");
360
77
        RETURN_IF_ERROR(cancelled_channel->cancel(reason));
361
77
        LOG(INFO) << "load channel has been cancelled: " << load_id;
362
77
    }
363
364
128
    return Status::OK();
365
128
}
366
367
7
Status LoadChannelMgr::_start_bg_worker() {
368
7
    RETURN_IF_ERROR(Thread::create(
369
7
            "LoadChannelMgr", "cancel_timeout_load_channels",
370
7
            [this]() {
371
7
                while (!_stop_background_threads_latch.wait_for(
372
7
                        std::chrono::seconds(START_BG_INTERVAL))) {
373
7
                    static_cast<void>(_start_load_channels_clean());
374
7
                }
375
7
            },
376
7
            &_load_channels_clean_thread));
377
378
7
    return Status::OK();
379
7
}
380
381
225
Status LoadChannelMgr::_start_load_channels_clean() {
382
225
    std::vector<std::shared_ptr<LoadChannel>> need_delete_channels;
383
225
    LOG(INFO) << "cleaning timed out load channels";
384
225
    time_t now = time(nullptr);
385
225
    {
386
225
        std::vector<UniqueId> need_delete_channel_ids;
387
225
        std::lock_guard<std::mutex> l(_lock);
388
225
        int i = 0;
389
246
        for (auto& kv : _load_channels) {
390
246
            if (!_finishing_load_channels.empty() && _finishing_load_channels.contains(kv.first)) {
391
0
                continue;
392
0
            }
393
246
            VLOG_CRITICAL << "load channel[" << i++ << "]: " << *(kv.second);
394
246
            time_t last_updated_time = kv.second->last_updated_time();
395
246
            if (difftime(now, last_updated_time) >= kv.second->timeout()) {
396
0
                need_delete_channel_ids.emplace_back(kv.first);
397
0
                need_delete_channels.emplace_back(kv.second);
398
0
            }
399
246
        }
400
401
225
        for (auto& key : need_delete_channel_ids) {
402
0
            _load_channels.erase(key);
403
0
            LOG(INFO) << "erase timeout load channel: " << key;
404
0
        }
405
225
    }
406
407
    // we must cancel these load channels before destroying them.
408
    // otherwise some object may be invalid before trying to visit it.
409
    // eg: MemTracker in load channel
410
225
    for (auto& channel : need_delete_channels) {
411
0
        RETURN_IF_ERROR(channel->cancel(Status::TimedOut("Load channel timed out")));
412
0
        LOG(INFO) << "load channel has been safely deleted: " << channel->load_id()
413
0
                  << ", timeout(s): " << channel->timeout();
414
0
    }
415
416
225
    return Status::OK();
417
225
}
418
} // namespace doris