Coverage Report

Created: 2026-08-06 12:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/load/channel/load_channel.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.h"
19
20
#include <gen_cpp/internal_service.pb.h>
21
#include <glog/logging.h>
22
23
#include "cloud/cloud_tablets_channel.h"
24
#include "cloud/config.h"
25
#include "common/logging.h"
26
#include "load/channel/tablets_channel.h"
27
#include "runtime/exec_env.h"
28
#include "runtime/fragment_mgr.h"
29
#include "runtime/memory/mem_tracker.h"
30
#include "runtime/thread_context.h"
31
#include "runtime/workload_group/workload_group.h"
32
#include "runtime/workload_group/workload_group_manager.h"
33
#include "storage/storage_engine.h"
34
#include "util/debug_points.h"
35
36
namespace doris {
37
38
bvar::Adder<int64_t> g_loadchannel_cnt("loadchannel_cnt");
39
40
LoadChannel::LoadChannel(const UniqueId& load_id, int64_t timeout_s, bool is_high_priority,
41
                         std::string sender_ip, int64_t backend_id, bool enable_profile,
42
                         int64_t wg_id)
43
30.9k
        : _load_id(load_id),
44
30.9k
          _timeout_s(timeout_s),
45
30.9k
          _is_high_priority(is_high_priority),
46
30.9k
          _sender_ip(std::move(sender_ip)),
47
30.9k
          _backend_id(backend_id),
48
30.9k
          _enable_profile(enable_profile) {
49
30.9k
    std::shared_ptr<QueryContext> query_context =
50
30.9k
            ExecEnv::GetInstance()->fragment_mgr()->get_query_ctx(_load_id.to_thrift());
51
52
30.9k
    if (query_context != nullptr) {
53
30.9k
        _resource_ctx = query_context->resource_ctx();
54
30.9k
    } else {
55
2
        _resource_ctx = ResourceContext::create_shared();
56
2
        _resource_ctx->task_controller()->set_task_id(_load_id.to_thrift());
57
        // when memtable on sink is not enabled, load can not find queryctx
58
2
        std::shared_ptr<MemTrackerLimiter> mem_tracker = MemTrackerLimiter::create_shared(
59
2
                MemTrackerLimiter::Type::LOAD,
60
2
                fmt::format("(FromLoadChannel)Load#Id={}", _load_id.to_string()));
61
2
        _resource_ctx->memory_context()->set_mem_tracker(mem_tracker);
62
2
        WorkloadGroupPtr wg_ptr = nullptr;
63
2
        if (wg_id > 0) {
64
2
            std::vector<uint64_t> id_set;
65
2
            id_set.push_back(wg_id);
66
2
            wg_ptr = ExecEnv::GetInstance()->workload_group_mgr()->get_group(id_set);
67
2
            _resource_ctx->set_workload_group(wg_ptr);
68
2
        }
69
2
    }
70
71
30.9k
    g_loadchannel_cnt << 1;
72
    // _last_updated_time should be set before being inserted to
73
    // _load_channels in load_channel_mgr, or it may be erased
74
    // immediately by gc thread.
75
30.9k
    _last_updated_time.store(time(nullptr));
76
30.9k
    if (enable_profile) {
77
8
        _init_profile();
78
8
    }
79
30.9k
}
80
81
30.9k
LoadChannel::~LoadChannel() {
82
30.9k
    g_loadchannel_cnt << -1;
83
30.9k
    std::stringstream rows_str;
84
31.7k
    for (const auto& entry : _tablets_channels_rows) {
85
31.7k
        rows_str << ", index id: " << entry.first << ", total_received_rows: " << entry.second.first
86
31.7k
                 << ", num_rows_filtered: " << entry.second.second;
87
31.7k
    }
88
30.9k
    LOG(INFO) << "load channel removed"
89
30.9k
              << " load_id=" << _load_id << ", is high priority=" << _is_high_priority
90
30.9k
              << ", sender_ip=" << _sender_ip << rows_str.str();
91
30.9k
}
92
93
8
void LoadChannel::_init_profile() {
94
8
    DCHECK(_enable_profile);
95
8
    _profile = std::make_unique<RuntimeProfile>("LoadChannels");
96
8
    _mgr_add_batch_timer = ADD_TIMER(_profile, "LoadChannelMgrAddBatchTime");
97
8
    _handle_mem_limit_timer = ADD_TIMER(_profile, "HandleMemLimitTime");
98
8
    _self_profile =
99
8
            _profile->create_child(fmt::format("LoadChannel load_id={} (host={}, backend_id={})",
100
8
                                               _load_id.to_string(), _sender_ip, _backend_id),
101
8
                                   true, true);
102
8
    _add_batch_number_counter = ADD_COUNTER(_self_profile, "NumberBatchAdded", TUnit::UNIT);
103
8
    _add_batch_timer = ADD_TIMER(_self_profile, "AddBatchTime");
104
8
    _handle_eos_timer = ADD_CHILD_TIMER(_self_profile, "HandleEosTime", "AddBatchTime");
105
8
    _add_batch_times = ADD_COUNTER(_self_profile, "AddBatchTimes", TUnit::UNIT);
106
8
}
107
108
47.0k
Status LoadChannel::open(const PTabletWriterOpenRequest& params) {
109
47.0k
    if (config::is_cloud_mode() && params.txn_expiration() <= 0) {
110
0
        return Status::InternalError(
111
0
                "The txn expiration of PTabletWriterOpenRequest is invalid, value={}",
112
0
                params.txn_expiration());
113
0
    }
114
47.0k
    if (_resource_ctx->workload_group() != nullptr) {
115
47.0k
        RETURN_IF_ERROR(_resource_ctx->workload_group()->add_resource_ctx(
116
47.0k
                _resource_ctx->task_controller()->task_id(), _resource_ctx));
117
47.0k
    }
118
47.0k
    SCOPED_ATTACH_TASK(_resource_ctx);
119
120
47.0k
    int64_t index_id = params.index_id();
121
47.0k
    std::shared_ptr<BaseTabletsChannel> channel;
122
47.0k
    {
123
47.0k
        std::lock_guard<std::mutex> l(_lock);
124
47.0k
        auto it = _tablets_channels.find(index_id);
125
47.0k
        if (it != _tablets_channels.end()) {
126
15.2k
            channel = it->second;
127
31.8k
        } else {
128
            // just for VLOG
129
31.8k
            if (_txn_id == 0) [[unlikely]] {
130
30.9k
                _txn_id = params.txn_id();
131
30.9k
            }
132
            // create a new tablets channel
133
31.8k
            TabletsChannelKey key(params.id(), index_id);
134
31.8k
            BaseStorageEngine& engine = ExecEnv::GetInstance()->storage_engine();
135
31.8k
            if (config::is_cloud_mode()) {
136
30.2k
                channel = std::make_shared<CloudTabletsChannel>(engine.to_cloud(), key, _load_id,
137
30.2k
                                                                _is_high_priority, _self_profile);
138
30.2k
            } else {
139
1.60k
                channel = std::make_shared<TabletsChannel>(engine.to_local(), key, _load_id,
140
1.60k
                                                           _is_high_priority, _self_profile);
141
1.60k
            }
142
31.8k
            {
143
31.8k
                std::lock_guard<std::mutex> lt(_tablets_channels_lock);
144
31.8k
                _tablets_channels.insert({index_id, channel});
145
31.8k
            }
146
31.8k
        }
147
47.0k
    }
148
149
47.0k
    if (params.is_incremental()) {
150
        // incremental open would ensure not to open tablet repeatedly
151
185
        RETURN_IF_ERROR(channel->incremental_open(params));
152
46.8k
    } else {
153
46.8k
        RETURN_IF_ERROR(channel->open(params));
154
46.8k
    }
155
156
47.0k
    _opened = true;
157
47.0k
    _last_updated_time.store(time(nullptr));
158
47.0k
    return Status::OK();
159
47.0k
}
160
161
Status LoadChannel::_get_tablets_channel(std::shared_ptr<BaseTabletsChannel>& channel,
162
53.2k
                                         bool& is_finished, const int64_t index_id) {
163
53.2k
    std::lock_guard<std::mutex> l(_lock);
164
53.2k
    auto it = _tablets_channels.find(index_id);
165
53.2k
    if (it == _tablets_channels.end()) {
166
0
        if (_finished_channel_ids.find(index_id) != _finished_channel_ids.end()) {
167
            // this channel is already finished, just return OK
168
0
            is_finished = true;
169
0
            return Status::OK();
170
0
        }
171
0
        std::stringstream ss;
172
0
        ss << "load channel " << _load_id << " add batch with unknown index id: " << index_id;
173
0
        return Status::InternalError(ss.str());
174
0
    }
175
176
53.2k
    is_finished = false;
177
53.2k
    channel = it->second;
178
53.2k
    return Status::OK();
179
53.2k
}
180
181
Status LoadChannel::add_batch(const PTabletWriterAddBlockRequest& request,
182
53.2k
                              PTabletWriterAddBlockResult* response) {
183
53.2k
    DBUG_EXECUTE_IF("LoadChannel.add_batch.failed",
184
53.2k
                    { return Status::InternalError("fault injection"); });
185
53.2k
    SCOPED_TIMER(_add_batch_timer);
186
53.2k
    if (_enable_profile) {
187
38
        COUNTER_UPDATE(_add_batch_times, 1);
188
38
    }
189
53.2k
    SCOPED_ATTACH_TASK(_resource_ctx);
190
53.2k
    int64_t index_id = request.index_id();
191
    // 1. get tablets channel
192
53.2k
    std::shared_ptr<BaseTabletsChannel> channel;
193
53.2k
    bool is_finished = false;
194
53.2k
    Status st = _get_tablets_channel(channel, is_finished, index_id);
195
53.3k
    if (!st.ok() || is_finished) {
196
0
        return st;
197
0
    }
198
199
    // 2. add block to tablets channel
200
53.2k
    if (request.has_block()) {
201
34.5k
        RETURN_IF_ERROR(channel->add_batch(request, response));
202
34.5k
        if (_enable_profile) {
203
9
            _add_batch_number_counter->update(1);
204
9
        }
205
34.5k
    }
206
207
    // 3. handle eos
208
    // if channel is incremental, maybe hang on close until all close request arrived.
209
53.3k
    if (request.has_eos() && request.eos()) {
210
46.8k
        st = _handle_eos(channel.get(), request, response);
211
46.8k
        _report_profile(response);
212
46.8k
        if (!st.ok()) {
213
41
            return st;
214
41
        }
215
46.8k
    } else if (_enable_profile && _add_batch_number_counter->value() % 100 == 1) {
216
1
        _report_profile(response);
217
1
    }
218
53.2k
    _last_updated_time.store(time(nullptr));
219
53.2k
    return st;
220
53.2k
}
221
222
Status LoadChannel::_handle_eos(BaseTabletsChannel* channel,
223
                                const PTabletWriterAddBlockRequest& request,
224
46.8k
                                PTabletWriterAddBlockResult* response) {
225
46.8k
    if (_enable_profile) {
226
37
        _self_profile->add_info_string("EosHost", fmt::format("{}", request.backend_id()));
227
37
    }
228
46.8k
    bool finished = false;
229
46.8k
    auto index_id = request.index_id();
230
231
46.8k
    RETURN_IF_ERROR(channel->close(this, request, response, &finished));
232
233
    // for init node, we close waiting(hang on) all close request and let them return together.
234
46.7k
    if (request.has_hang_wait() && request.hang_wait()) {
235
0
        DCHECK(!channel->is_incremental_channel());
236
0
        VLOG_DEBUG << fmt::format("txn {}: reciever index {} close waiting by sender {}", _txn_id,
237
0
                                  request.index_id(), request.sender_id());
238
0
        int count = 0;
239
0
        while (!channel->is_finished()) {
240
0
            bthread_usleep(1000);
241
0
            count++;
242
0
        }
243
        // now maybe finished or cancelled.
244
0
        VLOG_TRACE << "reciever close wait finished!" << request.sender_id();
245
0
        if (count >= 1000 * _timeout_s) { // maybe config::streaming_load_rpc_max_alive_time_sec
246
0
            return Status::InternalError("Tablets channel didn't wait all close");
247
0
        }
248
0
    }
249
250
46.7k
    if (finished) {
251
31.7k
        std::lock_guard<std::mutex> l(_lock);
252
31.7k
        {
253
31.7k
            std::lock_guard<std::mutex> lt(_tablets_channels_lock);
254
31.7k
            _tablets_channels_rows.insert(std::make_pair(
255
31.7k
                    index_id,
256
31.7k
                    std::make_pair(channel->total_received_rows(), channel->num_rows_filtered())));
257
31.7k
            _tablets_channels.erase(index_id);
258
31.7k
        }
259
31.7k
        LOG(INFO) << "txn " << _txn_id << " closed tablets_channel " << index_id;
260
31.7k
        _finished_channel_ids.emplace(index_id);
261
31.7k
    }
262
46.7k
    return Status::OK();
263
46.7k
}
264
265
46.8k
void LoadChannel::_report_profile(PTabletWriterAddBlockResult* response) {
266
46.8k
    if (!_enable_profile) {
267
46.7k
        return;
268
46.7k
    }
269
270
    // TabletSink and LoadChannel in BE are M: N relationship,
271
    // Every once in a while LoadChannel will randomly return its own runtime profile to a TabletSink,
272
    // so usually all LoadChannel runtime profiles are saved on each TabletSink,
273
    // and the timeliness of the same LoadChannel profile saved on different TabletSinks is different,
274
    // and each TabletSink will periodically send fe reports all the LoadChannel profiles saved by itself,
275
    // and ensures to update the latest LoadChannel profile according to the timestamp.
276
33
    _self_profile->set_timestamp(_last_updated_time);
277
278
33
    {
279
33
        std::lock_guard<std::mutex> l(_tablets_channels_lock);
280
33
        for (auto& it : _tablets_channels) {
281
30
            it.second->refresh_profile();
282
30
        }
283
33
    }
284
285
33
    TRuntimeProfileTree tprofile;
286
33
    ThriftSerializer ser(false, 4096);
287
33
    uint8_t* buf = nullptr;
288
33
    uint32_t len = 0;
289
33
    std::lock_guard<std::mutex> l(_profile_serialize_lock);
290
33
    _profile->to_thrift(&tprofile);
291
33
    auto st = ser.serialize(&tprofile, &len, &buf);
292
38
    if (st.ok()) {
293
38
        response->set_load_channel_profile(std::string((const char*)buf, len));
294
18.4E
    } else {
295
18.4E
        LOG(WARNING) << "load channel TRuntimeProfileTree serialize failed, errmsg=" << st;
296
18.4E
    }
297
33
}
298
299
53.2k
bool LoadChannel::is_finished() {
300
53.2k
    if (!_opened) {
301
0
        return false;
302
0
    }
303
53.2k
    std::lock_guard<std::mutex> l(_lock);
304
53.2k
    return _tablets_channels.empty();
305
53.2k
}
306
307
121
Status LoadChannel::cancel() {
308
121
    _cancelled.store(true);
309
121
    std::lock_guard<std::mutex> l(_lock);
310
121
    for (auto& it : _tablets_channels) {
311
121
        static_cast<void>(it.second->cancel());
312
121
    }
313
121
    return Status::OK();
314
121
}
315
316
} // namespace doris