Coverage Report

Created: 2026-08-13 07:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/exec/sink/writer/vtablet_writer.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 "exec/sink/writer/vtablet_writer.h"
19
20
#include <brpc/http_method.h>
21
#include <bthread/bthread.h>
22
#include <fmt/format.h>
23
#include <gen_cpp/DataSinks_types.h>
24
#include <gen_cpp/Descriptors_types.h>
25
#include <gen_cpp/Exprs_types.h>
26
#include <gen_cpp/FrontendService.h>
27
#include <gen_cpp/FrontendService_types.h>
28
#include <gen_cpp/HeartbeatService_types.h>
29
#include <gen_cpp/Metrics_types.h>
30
#include <gen_cpp/Types_types.h>
31
#include <gen_cpp/data.pb.h>
32
#include <gen_cpp/internal_service.pb.h>
33
#include <glog/logging.h>
34
#include <google/protobuf/stubs/common.h>
35
#include <sys/param.h>
36
37
#include <algorithm>
38
#include <initializer_list>
39
#include <memory>
40
#include <mutex>
41
#include <sstream>
42
#include <string>
43
#include <unordered_map>
44
#include <unordered_set>
45
#include <utility>
46
#include <vector>
47
48
#include "cloud/config.h"
49
#include "common/compiler_util.h" // IWYU pragma: keep
50
#include "common/config.h"
51
#include "common/logging.h"
52
#include "common/metrics/doris_metrics.h"
53
#include "common/object_pool.h"
54
#include "common/signal_handler.h"
55
#include "common/status.h"
56
#include "core/block/block.h"
57
#include "core/column/column.h"
58
#include "core/column/column_const.h"
59
#include "core/data_type/data_type.h"
60
#include "core/data_type/data_type_nullable.h"
61
#include "cpp/sync_point.h"
62
#include "exec/sink/autoinc_buffer.h"
63
#include "exec/sink/vrow_distribution.h"
64
#include "exec/sink/vtablet_block_convertor.h"
65
#include "exec/sink/vtablet_finder.h"
66
#include "exprs/vexpr.h"
67
#include "exprs/vexpr_fwd.h"
68
#include "load/memtable/memtable_memory_limiter.h"
69
#include "runtime/descriptors.h"
70
#include "runtime/exec_env.h"
71
#include "runtime/memory/memory_reclamation.h"
72
#include "runtime/query_context.h"
73
#include "runtime/runtime_profile.h"
74
#include "runtime/runtime_state.h"
75
#include "runtime/thread_context.h"
76
#include "runtime/workload_group/workload_group.h"
77
#include "service/backend_options.h"
78
#include "storage/binlog.h"
79
#include "storage/tablet_info.h"
80
#include "util/brpc_closure.h"
81
#include "util/debug_points.h"
82
#include "util/defer_op.h"
83
#include "util/mem_info.h"
84
#include "util/network_util.h"
85
#include "util/proto_util.h"
86
#include "util/threadpool.h"
87
#include "util/thrift_rpc_helper.h"
88
#include "util/thrift_util.h"
89
#include "util/time.h"
90
#include "util/uid_util.h"
91
92
namespace doris {
93
class TExpr;
94
95
bvar::Adder<int64_t> g_sink_write_bytes;
96
bvar::PerSecond<bvar::Adder<int64_t>> g_sink_write_bytes_per_second("sink_throughput_byte",
97
                                                                    &g_sink_write_bytes, 60);
98
bvar::Adder<int64_t> g_sink_write_rows;
99
bvar::PerSecond<bvar::Adder<int64_t>> g_sink_write_rows_per_second("sink_throughput_row",
100
                                                                   &g_sink_write_rows, 60);
101
bvar::Adder<int64_t> g_sink_load_back_pressure_version_time_ms(
102
        "load_back_pressure_version_time_ms");
103
104
static const OlapTableIndexTablets* find_partition_index(const VOlapTablePartition& partition,
105
43.9k
                                                         int64_t index_id) {
106
44.0k
    for (const auto& index : partition.indexes) {
107
44.1k
        if (index.index_id == index_id) {
108
44.1k
            return &index;
109
44.1k
        }
110
44.0k
    }
111
18.4E
    return nullptr;
112
43.9k
}
113
114
static int64_t adaptive_bucket_be_id(const VOlapTablePartition& partition,
115
34.6k
                                     const OlapTableIndexTablets* index) {
116
34.6k
    if (index != nullptr && index->__isset.bucket_be_id) {
117
30.1k
        return index->bucket_be_id > 0 ? index->bucket_be_id : BackendOptions::get_backend_id();
118
30.1k
    }
119
4.40k
    return partition.bucket_be_id > 0 ? partition.bucket_be_id : BackendOptions::get_backend_id();
120
34.6k
}
121
122
static const std::vector<int32_t>& adaptive_local_bucket_seqs(const VOlapTablePartition& partition,
123
9.22k
                                                              const OlapTableIndexTablets* index) {
124
9.23k
    if (index != nullptr && index->__isset.local_bucket_seqs) {
125
9.03k
        return index->local_bucket_seqs;
126
9.03k
    }
127
186
    return partition.local_bucket_seqs;
128
9.22k
}
129
130
static constexpr int64_t CLOSE_WAIT_EVENT_FALLBACK_MS = 1000;
131
132
Status IndexChannel::init(RuntimeState* state, const std::vector<TTabletWithPartition>& tablets,
133
46.6k
                          bool incremental) {
134
46.6k
    SCOPED_CONSUME_MEM_TRACKER(_index_channel_tracker.get());
135
373k
    for (const auto& tablet : tablets) {
136
        // First find the location BEs of this tablet
137
373k
        auto* tablet_locations = _parent->_location->find_tablet(tablet.tablet_id);
138
373k
        if (tablet_locations == nullptr) {
139
0
            return Status::InternalError("unknown tablet, tablet_id={}", tablet.tablet_id);
140
0
        }
141
373k
        std::vector<std::shared_ptr<VNodeChannel>> channels;
142
        // For tablet, deal with its' all replica (in some node).
143
373k
        for (auto& replica_node_id : tablet_locations->node_ids) {
144
373k
            std::shared_ptr<VNodeChannel> channel;
145
373k
            auto it = _node_channels.find(replica_node_id);
146
            // when we prepare for TableSink or incremental open tablet, we need init
147
373k
            if (it == _node_channels.end()) {
148
                // NodeChannel is not added to the _parent->_pool.
149
                // Because the deconstruction of NodeChannel may take a long time to wait rpc finish.
150
                // but the ObjectPool will hold a spin lock to delete objects.
151
47.6k
                channel =
152
47.6k
                        std::make_shared<VNodeChannel>(_parent, this, replica_node_id, incremental);
153
47.6k
                _node_channels.emplace(replica_node_id, channel);
154
                // incremental opened new node. when close we have use two-stage close.
155
47.6k
                if (incremental) {
156
0
                    _has_inc_node = true;
157
0
                }
158
47.6k
                VLOG_CRITICAL << "init new node for instance " << _parent->_sender_id
159
674
                              << ", node id:" << replica_node_id << ", incremantal:" << incremental;
160
325k
            } else {
161
325k
                channel = it->second;
162
325k
            }
163
373k
            channel->add_tablet(tablet);
164
373k
            if (_parent->_tablet_finder->is_adaptive_random_bucket() && config::is_cloud_mode()) {
165
43.8k
                for (const auto* part : _parent->_vpartition->get_partitions()) {
166
43.8k
                    if (part->id != tablet.partition_id) {
167
9.33k
                        continue;
168
9.33k
                    }
169
34.4k
                    const auto* index = find_partition_index(*part, _index_id);
170
34.4k
                    const auto bucket_be_id = adaptive_bucket_be_id(*part, index);
171
34.4k
                    if (bucket_be_id != replica_node_id) {
172
0
                        continue;
173
0
                    }
174
34.4k
                    _channels_by_partition.emplace(tablet.partition_id, channel);
175
34.4k
                    break;
176
34.4k
                }
177
34.7k
            }
178
373k
            channels.push_back(channel);
179
373k
            _tablets_by_channel[replica_node_id].insert(tablet.tablet_id);
180
373k
        }
181
373k
        _channels_by_tablet.emplace(tablet.tablet_id, std::move(channels));
182
373k
    }
183
47.8k
    for (auto& it : _node_channels) {
184
47.8k
        RETURN_IF_ERROR(it.second->init(state));
185
47.8k
    }
186
46.6k
    if (_where_clause != nullptr) {
187
33
        RETURN_IF_ERROR(_where_clause->prepare(state, *_parent->_output_row_desc));
188
33
        RETURN_IF_ERROR(_where_clause->open(state));
189
33
    }
190
191
46.6k
    return Status::OK();
192
46.6k
}
193
194
void IndexChannel::mark_as_failed(const VNodeChannel* node_channel, const std::string& err,
195
43
                                  int64_t tablet_id) {
196
43
    DCHECK(node_channel != nullptr);
197
43
    LOG(INFO) << "mark node_id:" << node_channel->channel_info() << " tablet_id: " << tablet_id
198
43
              << " as failed, err: " << err;
199
43
    auto node_id = node_channel->node_id();
200
43
    const auto& it = _tablets_by_channel.find(node_id);
201
43
    if (it == _tablets_by_channel.end()) {
202
0
        return;
203
0
    }
204
205
43
    {
206
43
        std::lock_guard<std::mutex> l(_fail_lock);
207
43
        if (tablet_id == -1) {
208
136
            for (const auto the_tablet_id : it->second) {
209
136
                _failed_channels[the_tablet_id].insert(node_id);
210
136
                _failed_channels_msgs.emplace(the_tablet_id,
211
136
                                              err + ", host: " + node_channel->host());
212
136
                if (_failed_channels[the_tablet_id].size() > _max_failed_replicas(the_tablet_id)) {
213
136
                    _intolerable_failure_status = Status::Error<ErrorCode::INTERNAL_ERROR, false>(
214
136
                            _failed_channels_msgs[the_tablet_id]);
215
136
                }
216
136
            }
217
43
        } else {
218
0
            _failed_channels[tablet_id].insert(node_id);
219
0
            _failed_channels_msgs.emplace(tablet_id, err + ", host: " + node_channel->host());
220
0
            if (_failed_channels[tablet_id].size() > _max_failed_replicas(tablet_id)) {
221
0
                _intolerable_failure_status = Status::Error<ErrorCode::INTERNAL_ERROR, false>(
222
0
                        _failed_channels_msgs[tablet_id]);
223
0
            }
224
0
        }
225
43
    }
226
43
}
227
228
136
int IndexChannel::_max_failed_replicas(int64_t tablet_id) {
229
136
    auto [total_replicas_num, load_required_replicas_num] =
230
136
            _parent->_tablet_replica_info[tablet_id];
231
136
    int max_failed_replicas = total_replicas_num == 0
232
136
                                      ? (_parent->_num_replicas - 1) / 2
233
136
                                      : total_replicas_num - load_required_replicas_num;
234
136
    return max_failed_replicas;
235
136
}
236
237
245k
int IndexChannel::_load_required_replicas_num(int64_t tablet_id) {
238
245k
    auto [total_replicas_num, load_required_replicas_num] =
239
245k
            _parent->_tablet_replica_info[tablet_id];
240
245k
    if (total_replicas_num == 0) {
241
0
        return (_parent->_num_replicas + 1) / 2;
242
0
    }
243
245k
    return load_required_replicas_num;
244
245k
}
245
246
144k
Status IndexChannel::check_intolerable_failure() {
247
144k
    std::lock_guard<std::mutex> l(_fail_lock);
248
144k
    return _intolerable_failure_status;
249
144k
}
250
251
47.7k
void IndexChannel::set_error_tablet_in_state(RuntimeState* state) {
252
47.7k
    std::vector<TErrorTabletInfo> error_tablet_infos;
253
254
47.7k
    {
255
47.7k
        std::lock_guard<std::mutex> l(_fail_lock);
256
47.7k
        for (const auto& it : _failed_channels_msgs) {
257
0
            TErrorTabletInfo error_info;
258
0
            error_info.__set_tabletId(it.first);
259
0
            error_info.__set_msg(it.second);
260
0
            error_tablet_infos.emplace_back(error_info);
261
0
        }
262
47.7k
    }
263
47.7k
    state->add_error_tablet_infos(error_tablet_infos);
264
47.7k
}
265
266
void IndexChannel::set_tablets_received_rows(
267
47.8k
        const std::vector<std::pair<int64_t, int64_t>>& tablets_received_rows, int64_t node_id) {
268
203k
    for (const auto& [tablet_id, rows_num] : tablets_received_rows) {
269
203k
        _tablets_received_rows[tablet_id].emplace_back(node_id, rows_num);
270
203k
    }
271
47.8k
}
272
273
void IndexChannel::set_tablets_filtered_rows(
274
47.8k
        const std::vector<std::pair<int64_t, int64_t>>& tablets_filtered_rows, int64_t node_id) {
275
203k
    for (const auto& [tablet_id, rows_num] : tablets_filtered_rows) {
276
203k
        _tablets_filtered_rows[tablet_id].emplace_back(node_id, rows_num);
277
203k
    }
278
47.8k
}
279
280
0
Status IndexChannel::check_tablet_received_rows_consistency() {
281
0
    for (auto& tablet : _tablets_received_rows) {
282
0
        for (size_t i = 0; i < tablet.second.size(); i++) {
283
0
            VLOG_NOTICE << "check_tablet_received_rows_consistency, load_id: " << _parent->_load_id
284
0
                        << ", txn_id: " << std::to_string(_parent->_txn_id)
285
0
                        << ", tablet_id: " << tablet.first
286
0
                        << ", node_id: " << tablet.second[i].first
287
0
                        << ", rows_num: " << tablet.second[i].second;
288
0
            if (i == 0) {
289
0
                continue;
290
0
            }
291
0
            if (tablet.second[i].second != tablet.second[0].second) {
292
0
                return Status::InternalError(
293
0
                        "rows num written by multi replicas doest't match, load_id={}, txn_id={}, "
294
0
                        "tablt_id={}, node_id={}, rows_num={}, node_id={}, rows_num={}",
295
0
                        print_id(_parent->_load_id), _parent->_txn_id, tablet.first,
296
0
                        tablet.second[i].first, tablet.second[i].second, tablet.second[0].first,
297
0
                        tablet.second[0].second);
298
0
            }
299
0
        }
300
0
    }
301
0
    return Status::OK();
302
0
}
303
304
89
Status IndexChannel::check_tablet_filtered_rows_consistency() {
305
95
    for (auto& tablet : _tablets_filtered_rows) {
306
190
        for (size_t i = 0; i < tablet.second.size(); i++) {
307
95
            VLOG_NOTICE << "check_tablet_filtered_rows_consistency, load_id: " << _parent->_load_id
308
0
                        << ", txn_id: " << std::to_string(_parent->_txn_id)
309
0
                        << ", tablet_id: " << tablet.first
310
0
                        << ", node_id: " << tablet.second[i].first
311
0
                        << ", rows_num: " << tablet.second[i].second;
312
95
            if (i == 0) {
313
95
                continue;
314
95
            }
315
0
            if (tablet.second[i].second != tablet.second[0].second) {
316
0
                return Status::InternalError(
317
0
                        "rows num filtered by multi replicas doest't match, load_id={}, txn_id={}, "
318
0
                        "tablt_id={}, node_id={}, rows_num={}, node_id={}, rows_num={}",
319
0
                        print_id(_parent->_load_id), _parent->_txn_id, tablet.first,
320
0
                        tablet.second[i].first, tablet.second[i].second, tablet.second[0].first,
321
0
                        tablet.second[0].second);
322
0
            }
323
0
        }
324
95
    }
325
89
    return Status::OK();
326
89
}
327
328
static Status cancel_channel_and_check_intolerable_failure(Status status,
329
                                                           const std::string& err_msg,
330
43
                                                           IndexChannel& ich, VNodeChannel& nch) {
331
43
    LOG(WARNING) << nch.channel_info() << ", close channel failed, err: " << err_msg;
332
43
    ich.mark_as_failed(&nch, err_msg, -1);
333
    // cancel the node channel in best effort
334
43
    nch.cancel(err_msg);
335
336
    // check if index has intolerable failure
337
43
    if (Status index_st = ich.check_intolerable_failure(); !index_st.ok()) {
338
43
        status = std::move(index_st);
339
43
    } else if (Status receive_st = ich.check_tablet_received_rows_consistency(); !receive_st.ok()) {
340
0
        status = std::move(receive_st);
341
0
    } else if (Status filter_st = ich.check_tablet_filtered_rows_consistency(); !filter_st.ok()) {
342
0
        status = std::move(filter_st);
343
0
    }
344
43
    return status;
345
43
}
346
347
49.5k
void IndexChannel::wait_for_close_event(int64_t observed_version, int64_t timeout_ms) {
348
49.5k
    std::unique_lock<bthread::Mutex> lock(_close_wait_mutex);
349
49.5k
    if (observed_version != close_wait_version()) {
350
16
        return;
351
16
    }
352
49.5k
    static_cast<void>(_close_wait_cv.wait_for(lock, timeout_ms * 1000));
353
49.5k
}
354
355
48.0k
void IndexChannel::notify_close_wait() {
356
48.0k
    _close_wait_version.fetch_add(1, std::memory_order_acq_rel);
357
48.0k
    std::lock_guard<bthread::Mutex> lock(_close_wait_mutex);
358
48.0k
    _close_wait_cv.notify_all();
359
48.0k
}
360
361
Status IndexChannel::close_wait(
362
        RuntimeState* state, WriterStats* writer_stats,
363
        std::unordered_map<int64_t, AddBatchCounter>* node_add_batch_counter_map,
364
        std::unordered_set<int64_t> unfinished_node_channel_ids,
365
47.8k
        bool need_wait_after_quorum_success) {
366
47.8k
    DBUG_EXECUTE_IF("IndexChannel.close_wait.timeout",
367
47.8k
                    { return Status::TimedOut("injected timeout"); });
368
47.8k
    Status status = Status::OK();
369
    // 1. wait quorum success
370
47.8k
    std::unordered_set<int64_t> need_finish_tablets;
371
47.8k
    auto partition_ids = _parent->_tablet_finder->partition_ids();
372
63.4k
    for (const auto& part : _parent->_vpartition->get_partitions()) {
373
63.4k
        if (partition_ids.contains(part->id)) {
374
53.2k
            for (const auto& index : part->indexes) {
375
283k
                for (const auto& tablet_id : index.tablets) {
376
283k
                    need_finish_tablets.insert(tablet_id);
377
283k
                }
378
53.2k
            }
379
36.6k
        }
380
63.4k
    }
381
97.5k
    while (true) {
382
97.5k
        int64_t close_wait_version = this->close_wait_version();
383
97.5k
        RETURN_IF_ERROR(check_each_node_channel_close(
384
97.5k
                &unfinished_node_channel_ids, node_add_batch_counter_map, writer_stats, status));
385
97.5k
        bool quorum_success = _quorum_success(unfinished_node_channel_ids, need_finish_tablets);
386
97.5k
        if (unfinished_node_channel_ids.empty() || quorum_success) {
387
47.8k
            LOG(INFO) << "quorum_success: " << quorum_success
388
47.8k
                      << ", is all finished: " << unfinished_node_channel_ids.empty()
389
47.8k
                      << ", txn_id: " << _parent->_txn_id
390
47.8k
                      << ", load_id: " << print_id(_parent->_load_id);
391
47.8k
            break;
392
47.8k
        }
393
49.6k
        wait_for_close_event(close_wait_version, CLOSE_WAIT_EVENT_FALLBACK_MS);
394
49.6k
    }
395
396
    // 2. wait for all node channel to complete as much as possible
397
47.8k
    if (!unfinished_node_channel_ids.empty() && need_wait_after_quorum_success) {
398
0
        int64_t arrival_quorum_success_time = UnixMillis();
399
0
        int64_t max_wait_time_ms = _calc_max_wait_time_ms(unfinished_node_channel_ids);
400
0
        while (true) {
401
0
            int64_t close_wait_version = this->close_wait_version();
402
0
            RETURN_IF_ERROR(check_each_node_channel_close(&unfinished_node_channel_ids,
403
0
                                                          node_add_batch_counter_map, writer_stats,
404
0
                                                          status));
405
0
            if (unfinished_node_channel_ids.empty()) {
406
0
                break;
407
0
            }
408
0
            int64_t elapsed_ms = UnixMillis() - arrival_quorum_success_time;
409
0
            if (elapsed_ms > max_wait_time_ms ||
410
0
                _parent->_load_channel_timeout_s - elapsed_ms / 1000 <
411
0
                        config::quorum_success_remaining_timeout_seconds) {
412
                // cancel unfinished node channel
413
0
                std::stringstream unfinished_node_channel_host_str;
414
0
                for (auto& it : unfinished_node_channel_ids) {
415
0
                    unfinished_node_channel_host_str << _node_channels[it]->host() << ",";
416
0
                    _node_channels[it]->cancel("timeout");
417
0
                }
418
0
                LOG(WARNING) << "reach max wait time, max_wait_time_ms: " << max_wait_time_ms
419
0
                             << ", cancel unfinished node channel and finish close"
420
0
                             << ", load id: " << print_id(_parent->_load_id)
421
0
                             << ", txn_id: " << _parent->_txn_id << ", unfinished node channel: "
422
0
                             << unfinished_node_channel_host_str.str();
423
0
                break;
424
0
            }
425
0
            wait_for_close_event(close_wait_version, std::min(CLOSE_WAIT_EVENT_FALLBACK_MS,
426
0
                                                              max_wait_time_ms - elapsed_ms));
427
0
        }
428
0
    }
429
47.8k
    return status;
430
47.8k
}
431
432
Status IndexChannel::check_each_node_channel_close(
433
        std::unordered_set<int64_t>* unfinished_node_channel_ids,
434
        std::unordered_map<int64_t, AddBatchCounter>* node_add_batch_counter_map,
435
97.5k
        WriterStats* writer_stats, Status status) {
436
97.5k
    Status final_status = Status::OK();
437
97.5k
    for (auto& it : _node_channels) {
438
97.5k
        std::shared_ptr<VNodeChannel> node_channel = it.second;
439
        // If the node channel is not in the unfinished_node_channel_ids,
440
        // it means the node channel is already closed.
441
97.5k
        if (!unfinished_node_channel_ids->contains(it.first)) {
442
0
            continue;
443
0
        }
444
97.5k
        bool node_channel_closed = false;
445
97.5k
        auto close_status = it.second->close_wait(_parent->_state, &node_channel_closed);
446
97.5k
        if (node_channel_closed) {
447
47.9k
            close_status = it.second->after_close_handle(_parent->_state, writer_stats,
448
47.9k
                                                         node_add_batch_counter_map);
449
47.9k
            unfinished_node_channel_ids->erase(it.first);
450
47.9k
        }
451
97.5k
        DBUG_EXECUTE_IF("IndexChannel.check_each_node_channel_close.close_status_not_ok",
452
97.5k
                        { close_status = Status::InternalError("injected close status not ok"); });
453
97.5k
        if (!close_status.ok()) {
454
43
            final_status = cancel_channel_and_check_intolerable_failure(
455
43
                    std::move(final_status), close_status.to_string(), *this, *it.second);
456
43
        }
457
97.5k
    }
458
459
97.5k
    return final_status;
460
97.5k
}
461
462
bool IndexChannel::_quorum_success(const std::unordered_set<int64_t>& unfinished_node_channel_ids,
463
97.2k
                                   const std::unordered_set<int64_t>& need_finish_tablets) {
464
97.2k
    if (!config::enable_quorum_success_write) {
465
0
        return false;
466
0
    }
467
97.2k
    if (need_finish_tablets.empty()) [[unlikely]] {
468
27.9k
        return false;
469
27.9k
    }
470
471
    // 1. collect all write tablets and finished tablets
472
69.3k
    std::unordered_map<int64_t, int64_t> finished_tablets_replica;
473
69.5k
    for (const auto& [node_id, node_channel] : _node_channels) {
474
69.5k
        if (unfinished_node_channel_ids.contains(node_id) || !node_channel->check_status().ok()) {
475
35.8k
            continue;
476
35.8k
        }
477
293k
        for (const auto& tablet_id : _tablets_by_channel[node_id]) {
478
            // Only count non-gap backends for quorum success.
479
            // Gap backends' success doesn't count toward majority write.
480
293k
            auto gap_it = _parent->_tablet_version_gap_backends.find(tablet_id);
481
293k
            if (gap_it == _parent->_tablet_version_gap_backends.end() ||
482
293k
                gap_it->second.find(node_id) == gap_it->second.end()) {
483
293k
                finished_tablets_replica[tablet_id]++;
484
293k
            }
485
293k
        }
486
33.7k
    }
487
488
    // 2. check if quorum success
489
245k
    for (const auto& tablet_id : need_finish_tablets) {
490
245k
        if (finished_tablets_replica[tablet_id] < _load_required_replicas_num(tablet_id)) {
491
37.3k
            return false;
492
37.3k
        }
493
245k
    }
494
495
32.0k
    return true;
496
69.3k
}
497
498
int64_t IndexChannel::_calc_max_wait_time_ms(
499
0
        const std::unordered_set<int64_t>& unfinished_node_channel_ids) {
500
    // 1. calculate avg speed of all unfinished node channel
501
0
    int64_t elapsed_ms = UnixMillis() - _start_time;
502
0
    int64_t total_bytes = 0;
503
0
    int finished_count = 0;
504
0
    for (const auto& [node_id, node_channel] : _node_channels) {
505
0
        if (unfinished_node_channel_ids.contains(node_id)) {
506
0
            continue;
507
0
        }
508
0
        total_bytes += node_channel->write_bytes();
509
0
        finished_count++;
510
0
    }
511
    // no data loaded in index channel, return 0
512
0
    if (total_bytes == 0 || finished_count == 0) {
513
0
        return 0;
514
0
    }
515
    // if elapsed_ms is equal to 0, explain the loaded data is too small
516
0
    if (elapsed_ms <= 0) {
517
0
        return config::quorum_success_min_wait_seconds * 1000;
518
0
    }
519
0
    double avg_speed =
520
0
            static_cast<double>(total_bytes) / (static_cast<double>(elapsed_ms) * finished_count);
521
522
    // 2. calculate max wait time of each unfinished node channel and return the max value
523
0
    int64_t max_wait_time_ms = 0;
524
0
    for (int64_t id : unfinished_node_channel_ids) {
525
0
        int64_t bytes = _node_channels[id]->write_bytes();
526
0
        int64_t wait =
527
0
                avg_speed > 0 ? static_cast<int64_t>(static_cast<double>(bytes) / avg_speed) : 0;
528
0
        max_wait_time_ms = std::max(max_wait_time_ms, wait);
529
0
    }
530
531
    // 3. calculate max wait time
532
    // introduce quorum_success_min_wait_seconds to avoid jitter of small load
533
0
    max_wait_time_ms -= UnixMillis() - _start_time;
534
0
    max_wait_time_ms =
535
0
            std::max(static_cast<int64_t>(static_cast<double>(max_wait_time_ms) *
536
0
                                          (1.0 + config::quorum_success_max_wait_multiplier)),
537
0
                     config::quorum_success_min_wait_seconds * 1000);
538
539
0
    return max_wait_time_ms;
540
0
}
541
542
220k
static Status none_of(std::initializer_list<bool> vars) {
543
441k
    bool none = std::none_of(vars.begin(), vars.end(), [](bool var) { return var; });
544
220k
    Status st = Status::OK();
545
220k
    if (!none) {
546
43
        std::string vars_str;
547
43
        std::for_each(vars.begin(), vars.end(),
548
86
                      [&vars_str](bool var) -> void { vars_str += (var ? "1/" : "0/"); });
549
43
        if (!vars_str.empty()) {
550
43
            vars_str.pop_back(); // 0/1/0/ -> 0/1/0
551
43
        }
552
43
        st = Status::Uninitialized(vars_str);
553
43
    }
554
555
220k
    return st;
556
220k
}
557
558
VNodeChannel::VNodeChannel(VTabletWriter* parent, IndexChannel* index_channel, int64_t node_id,
559
                           bool is_incremental)
560
46.8k
        : _parent(parent),
561
46.8k
          _index_channel(index_channel),
562
46.8k
          _node_id(node_id),
563
46.8k
          _is_incremental(is_incremental) {
564
46.8k
    _cur_add_block_request = std::make_shared<PTabletWriterAddBlockRequest>();
565
46.8k
    _node_channel_tracker = std::make_shared<MemTracker>(
566
46.8k
            fmt::format("NodeChannel:indexID={}:threadId={}",
567
46.8k
                        std::to_string(_index_channel->_index_id), ThreadContext::get_thread_id()));
568
46.8k
    _load_mem_limit = MemInfo::mem_limit() * config::load_process_max_memory_limit_percent / 100;
569
46.8k
}
570
571
48.0k
VNodeChannel::~VNodeChannel() = default;
572
573
48.0k
void VNodeChannel::clear_all_blocks() {
574
48.0k
    std::lock_guard<std::mutex> lg(_pending_batches_lock);
575
48.0k
    std::queue<AddBlockReq> empty;
576
48.0k
    std::swap(_pending_blocks, empty);
577
48.0k
    _cur_mutable_block.reset();
578
48.0k
}
579
580
// we don't need to send tablet_writer_cancel rpc request when
581
// init failed, so set _is_closed to true.
582
// if "_cancelled" is set to true,
583
// no need to set _cancel_msg because the error will be
584
// returned directly via "TabletSink::prepare()" method.
585
47.1k
Status VNodeChannel::init(RuntimeState* state) {
586
47.1k
    if (_inited) {
587
186
        return Status::OK();
588
186
    }
589
590
47.0k
    SCOPED_CONSUME_MEM_TRACKER(_node_channel_tracker.get());
591
47.0k
    _task_exec_ctx = state->get_task_execution_context();
592
47.0k
    _tuple_desc = _parent->_output_tuple_desc;
593
47.0k
    _state = state;
594
    // get corresponding BE node.
595
47.0k
    const auto* node = _parent->_nodes_info->find_node(_node_id);
596
47.0k
    if (node == nullptr) {
597
0
        _cancelled = true;
598
0
        _is_closed = true;
599
0
        return Status::InternalError("unknown node id, id={}", _node_id);
600
0
    }
601
47.0k
    _node_info = *node;
602
603
47.0k
    _load_info = "load_id=" + print_id(_parent->_load_id) +
604
47.0k
                 ", txn_id=" + std::to_string(_parent->_txn_id);
605
606
47.0k
    _row_desc = std::make_unique<RowDescriptor>(_tuple_desc);
607
47.0k
    _batch_size = state->batch_size();
608
609
47.0k
    _stub = state->exec_env()->brpc_internal_client_cache()->get_client(_node_info.host,
610
47.0k
                                                                        _node_info.brpc_port);
611
47.0k
    if (_stub == nullptr) {
612
0
        _cancelled = true;
613
0
        _is_closed = true;
614
0
        return Status::InternalError("Get rpc stub failed, host={}, port={}, info={}",
615
0
                                     _node_info.host, _node_info.brpc_port, channel_info());
616
0
    }
617
618
47.0k
    _rpc_timeout_ms = state->execution_timeout() * 1000;
619
47.0k
    _timeout_watch.start();
620
621
    // Initialize _cur_add_block_request
622
48.0k
    if (!_cur_add_block_request->has_id()) {
623
48.0k
        *(_cur_add_block_request->mutable_id()) = _parent->_load_id;
624
48.0k
    }
625
47.0k
    _cur_add_block_request->set_index_id(_index_channel->_index_id);
626
47.0k
    _cur_add_block_request->set_sender_id(_parent->_sender_id);
627
47.0k
    _cur_add_block_request->set_backend_id(_node_id);
628
47.0k
    _cur_add_block_request->set_eos(false);
629
    // Adaptive random bucket add-block RPCs carry partition ids because the receiver
630
    // chooses the current tablet from its local adaptive state.
631
47.0k
    _cur_add_block_request->set_is_adaptive_random_bucket(
632
47.0k
            _parent->_tablet_finder->is_adaptive_random_bucket());
633
634
    // add block closure
635
    // Has to using value to capture _task_exec_ctx because tablet writer may destroyed during callback.
636
47.0k
    _send_block_callback = WriteBlockCallback<PTabletWriterAddBlockResult>::create_shared();
637
47.0k
    _send_block_callback->addFailedHandler(
638
47.0k
            [&, task_exec_ctx = _task_exec_ctx](const WriteBlockCallbackContext& ctx) {
639
0
                std::shared_ptr<TaskExecutionContext> ctx_lock = task_exec_ctx.lock();
640
0
                if (ctx_lock == nullptr) {
641
0
                    return;
642
0
                }
643
0
                _add_block_failed_callback(ctx);
644
0
            });
645
646
47.0k
    _send_block_callback->addSuccessHandler(
647
47.0k
            [&, task_exec_ctx = _task_exec_ctx](const PTabletWriterAddBlockResult& result,
648
54.3k
                                                const WriteBlockCallbackContext& ctx) {
649
54.3k
                std::shared_ptr<TaskExecutionContext> ctx_lock = task_exec_ctx.lock();
650
54.3k
                if (ctx_lock == nullptr) {
651
0
                    return;
652
0
                }
653
54.3k
                _add_block_success_callback(result, ctx);
654
54.3k
            });
655
656
47.0k
    _name = fmt::format("VNodeChannel[{}-{}]", _index_channel->_index_id, _node_id);
657
    // The node channel will send _batch_size rows of data each rpc. When the
658
    // number of tablets is large, the number of data rows received by each
659
    // tablet is small, TabletsChannel need to traverse each tablet for import.
660
    // so the import performance is poor. Therefore, we set _batch_size to
661
    // a relatively large value to improve the import performance.
662
47.0k
    _batch_size = std::max(_batch_size, 8192);
663
664
47.8k
    if (_state) {
665
47.8k
        QueryContext* query_ctx = _state->get_query_ctx();
666
47.8k
        if (query_ctx) {
667
47.6k
            auto wg_ptr = query_ctx->workload_group();
668
47.8k
            if (wg_ptr) {
669
47.8k
                _wg_id = wg_ptr->id();
670
47.8k
            }
671
47.6k
        }
672
47.8k
    }
673
674
47.0k
    _inited = true;
675
47.0k
    return Status::OK();
676
47.0k
}
677
678
9.05k
void VNodeChannel::_set_adaptive_random_bucket_open_request(PTabletWriterOpenRequest* request) {
679
9.05k
    std::unordered_map<int64_t, std::vector<int64_t>> partition_to_ordered_tablets;
680
9.05k
    std::unordered_map<int64_t, std::unordered_set<int64_t>> partition_to_local_tablets;
681
35.3k
    for (const auto& tablet : _all_tablets) {
682
35.3k
        partition_to_ordered_tablets[tablet.partition_id].push_back(tablet.tablet_id);
683
35.3k
        partition_to_local_tablets[tablet.partition_id].insert(tablet.tablet_id);
684
35.3k
    }
685
9.05k
    std::unordered_map<int64_t, const VOlapTablePartition*> id_to_partition;
686
9.60k
    for (const auto* part : _parent->_vpartition->get_partitions()) {
687
9.60k
        id_to_partition.emplace(part->id, part);
688
9.60k
    }
689
9.66k
    for (const auto& [partition_id, ordered_tablets] : partition_to_ordered_tablets) {
690
9.66k
        auto partition_it = id_to_partition.find(partition_id);
691
9.66k
        if (partition_it == id_to_partition.end()) {
692
0
            LOG(WARNING) << "unknown partition for adaptive random bucket, load_id="
693
0
                         << _parent->_load_id << ", partition_id=" << partition_id;
694
0
            continue;
695
0
        }
696
9.66k
        const auto* index_info =
697
9.66k
                find_partition_index(*partition_it->second, _index_channel->_index_id);
698
9.66k
        if (index_info == nullptr) {
699
0
            LOG(WARNING) << "unknown index for adaptive random bucket, load_id="
700
0
                         << _parent->_load_id << ", partition_id=" << partition_id
701
0
                         << ", index_id=" << _index_channel->_index_id;
702
0
            continue;
703
0
        }
704
9.66k
        std::vector<int64_t> selected_ordered_tablets;
705
9.66k
        const auto& local_bucket_seqs =
706
9.66k
                adaptive_local_bucket_seqs(*partition_it->second, index_info);
707
9.66k
        if (!local_bucket_seqs.empty()) {
708
8.92k
            const auto& full_ordered_tablets = index_info->tablets;
709
30.4k
            for (auto bucket_seq : local_bucket_seqs) {
710
30.4k
                if (bucket_seq < 0 ||
711
30.4k
                    bucket_seq >= cast_set<int32_t>(full_ordered_tablets.size())) {
712
0
                    LOG(WARNING) << "invalid local bucket seq, load_id=" << _parent->_load_id
713
0
                                 << ", partition_id=" << partition_id
714
0
                                 << ", bucket_seq=" << bucket_seq
715
0
                                 << ", full_ordered_tablets_size=" << full_ordered_tablets.size();
716
0
                    continue;
717
0
                }
718
30.4k
                auto tablet_id = full_ordered_tablets[bucket_seq];
719
30.4k
                if (!partition_to_local_tablets[partition_id].contains(tablet_id)) {
720
0
                    LOG(WARNING) << "skip non-local tablet selected by local bucket seq, load_id="
721
0
                                 << _parent->_load_id << ", partition_id=" << partition_id
722
0
                                 << ", bucket_seq=" << bucket_seq << ", tablet_id=" << tablet_id
723
0
                                 << ", node_id=" << _node_id;
724
0
                    continue;
725
0
                }
726
30.4k
                selected_ordered_tablets.push_back(tablet_id);
727
30.4k
            }
728
8.92k
        } else {
729
742
            selected_ordered_tablets = ordered_tablets;
730
742
        }
731
9.66k
        if (selected_ordered_tablets.empty()) {
732
0
            VLOG_DEBUG << "skip adaptive random bucket partition without selected local "
733
0
                          "tablet, load_id="
734
0
                       << _parent->_load_id << ", partition_id=" << partition_id
735
0
                       << ", node_id=" << _node_id;
736
0
            continue;
737
0
        }
738
9.66k
        _adaptive_partition_compat_tablets[partition_id] = selected_ordered_tablets.front();
739
9.66k
        auto* random_bucket_partition = request->add_random_bucket_partitions();
740
9.66k
        random_bucket_partition->set_partition_id(partition_id);
741
35.4k
        for (auto tablet_id : selected_ordered_tablets) {
742
35.4k
            random_bucket_partition->add_ordered_tablet_ids(tablet_id);
743
35.4k
        }
744
9.66k
    }
745
9.05k
}
746
747
47.7k
void VNodeChannel::_open_internal(bool is_incremental) {
748
47.7k
    if (_tablets_wait_open.empty()) {
749
0
        return;
750
0
    }
751
47.7k
    SCOPED_CONSUME_MEM_TRACKER(_node_channel_tracker.get());
752
47.7k
    auto request = std::make_shared<PTabletWriterOpenRequest>();
753
47.7k
    request->mutable_id()->CopyFrom(_parent->_load_id);
754
47.7k
    request->set_index_id(_index_channel->_index_id);
755
47.7k
    request->set_txn_id(_parent->_txn_id);
756
47.7k
    request->set_sender_id(_parent->_sender_id);
757
47.7k
    request->mutable_schema()->CopyFrom(*_parent->_schema->to_protobuf());
758
47.7k
    if (_parent->_t_sink.olap_table_sink.__isset.storage_vault_id) {
759
0
        request->set_storage_vault_id(_parent->_t_sink.olap_table_sink.storage_vault_id);
760
0
    }
761
    // Adaptive random bucket open RPCs initialize receiver-side selected-tablet state.
762
47.7k
    request->set_is_adaptive_random_bucket(_parent->_tablet_finder->is_adaptive_random_bucket());
763
47.7k
    std::set<int64_t> deduper;
764
372k
    for (auto& tablet : _tablets_wait_open) {
765
372k
        if (deduper.contains(tablet.tablet_id)) {
766
0
            continue;
767
0
        }
768
372k
        auto* ptablet = request->add_tablets();
769
372k
        ptablet->set_partition_id(tablet.partition_id);
770
372k
        ptablet->set_tablet_id(tablet.tablet_id);
771
        // only write binlog on backends that also own the binlog tablet.
772
372k
        int64_t binlog_tablet_id =
773
372k
                _parent->_location->get_binlog_tablet_id(tablet.tablet_id, _node_id);
774
372k
        if (binlog_tablet_id > 0) {
775
0
            ptablet->set_binlog_tablet_id(binlog_tablet_id);
776
0
        }
777
372k
        deduper.insert(tablet.tablet_id);
778
372k
        _all_tablets.push_back(std::move(tablet));
779
372k
    }
780
47.7k
    _tablets_wait_open.clear();
781
782
47.7k
    request->set_num_senders(_parent->_num_senders);
783
47.7k
    request->set_need_gen_rollup(false); // Useless but it is a required field in pb
784
47.7k
    request->set_load_channel_timeout_s(_parent->_load_channel_timeout_s);
785
47.7k
    request->set_is_high_priority(_parent->_is_high_priority);
786
47.7k
    request->set_sender_ip(BackendOptions::get_localhost());
787
47.7k
    request->set_is_vectorized(true);
788
47.7k
    request->set_backend_id(_node_id);
789
47.7k
    request->set_enable_profile(_state->enable_profile());
790
47.7k
    request->set_is_incremental(is_incremental);
791
47.7k
    request->set_txn_expiration(_parent->_txn_expiration);
792
47.7k
    request->set_write_file_cache(_parent->_write_file_cache);
793
794
47.7k
    if (_parent->_tablet_finder->is_adaptive_random_bucket()) {
795
9.33k
        _set_adaptive_random_bucket_open_request(request.get());
796
9.33k
    }
797
798
47.7k
    if (_wg_id > 0) {
799
47.5k
        request->set_workload_group_id(_wg_id);
800
47.5k
    }
801
802
47.7k
    auto open_callback = DummyBrpcCallback<PTabletWriterOpenResult>::create_shared();
803
47.7k
    auto open_closure = AutoReleaseClosure<
804
47.7k
            PTabletWriterOpenRequest,
805
47.7k
            DummyBrpcCallback<PTabletWriterOpenResult>>::create_unique(request, open_callback);
806
47.7k
    open_callback->cntl_->set_timeout_ms(config::tablet_writer_open_rpc_timeout_sec * 1000);
807
47.7k
    if (config::tablet_writer_ignore_eovercrowded) {
808
47.5k
        open_callback->cntl_->ignore_eovercrowded();
809
47.5k
    }
810
47.7k
    VLOG_DEBUG << fmt::format("txn {}: open NodeChannel to {}, incremental: {}, senders: {}",
811
65
                              _parent->_txn_id, _node_id, is_incremental, _parent->_num_senders);
812
    // the real transmission here. the corresponding BE's load mgr will open load channel for it.
813
47.7k
    _stub->tablet_writer_open(open_closure->cntl_.get(), open_closure->request_.get(),
814
47.7k
                              open_closure->response_.get(), open_closure.get());
815
47.7k
    open_closure.release();
816
47.7k
    _open_callbacks.push_back(open_callback);
817
47.7k
}
818
819
47.7k
void VNodeChannel::open() {
820
47.7k
    _open_internal(false);
821
47.7k
}
822
823
186
void VNodeChannel::incremental_open() {
824
186
    VLOG_DEBUG << "incremental opening node channel" << _node_id;
825
186
    _open_internal(true);
826
186
}
827
828
47.8k
Status VNodeChannel::open_wait() {
829
47.8k
    Status status;
830
48.1k
    for (auto& open_callback : _open_callbacks) {
831
        // because of incremental open, we will wait multi times. so skip the closures which have been checked and set to nullptr in previous rounds
832
48.1k
        if (open_callback == nullptr) {
833
0
            continue;
834
0
        }
835
836
48.1k
        open_callback->join();
837
48.1k
        SCOPED_CONSUME_MEM_TRACKER(_node_channel_tracker.get());
838
48.1k
        if (open_callback->cntl_->Failed()) {
839
0
            if (!ExecEnv::GetInstance()->brpc_internal_client_cache()->available(
840
0
                        _stub, _node_info.host, _node_info.brpc_port)) {
841
0
                ExecEnv::GetInstance()->brpc_internal_client_cache()->erase(
842
0
                        open_callback->cntl_->remote_side());
843
0
            }
844
0
            _cancelled = true;
845
0
            auto error_code = open_callback->cntl_->ErrorCode();
846
0
            auto error_text = open_callback->cntl_->ErrorText();
847
0
            if (error_text.find("Reached timeout") != std::string::npos) {
848
0
                LOG(WARNING) << "failed to open tablet writer may caused by timeout. increase BE "
849
0
                                "config `tablet_writer_open_rpc_timeout_sec` if you are sure that "
850
0
                                "your table building and data are reasonable.";
851
0
            }
852
0
            return Status::Error<ErrorCode::INTERNAL_ERROR, false>(
853
0
                    "failed to open tablet writer, error={}, error_text={}, info={}",
854
0
                    berror(error_code), error_text, channel_info());
855
0
        }
856
48.1k
        status = Status::create(open_callback->response_->status());
857
858
48.1k
        if (!status.ok()) {
859
0
            _cancelled = true;
860
0
            return status;
861
0
        }
862
48.1k
    }
863
864
47.8k
    return status;
865
47.8k
}
866
867
41.8k
Status VNodeChannel::add_block(Block* block, const Payload* payload) {
868
41.8k
    SCOPED_CONSUME_MEM_TRACKER(_node_channel_tracker.get());
869
41.8k
    if (payload->row_part_tablet_ids == nullptr || payload->row_ids == nullptr ||
870
41.8k
        payload->row_ids->empty()) {
871
0
        return Status::OK();
872
0
    }
873
41.8k
    DCHECK_EQ(payload->row_ids->size(), payload->route_idxs.size());
874
    // If add_block() when _eos_is_produced==true, there must be sth wrong, we can only mark this channel as failed.
875
41.8k
    auto st = none_of({_cancelled, _eos_is_produced});
876
41.8k
    if (!st.ok()) {
877
0
        if (_cancelled) {
878
0
            std::lock_guard<std::mutex> l(_cancel_msg_lock);
879
0
            return Status::Error<ErrorCode::INTERNAL_ERROR, false>("add row failed. {}",
880
0
                                                                   _cancel_msg);
881
0
        } else {
882
0
            return std::move(st.prepend("already stopped, can't add row. cancelled/eos: "));
883
0
        }
884
0
    }
885
886
    // We use OlapTableSink mem_tracker which has the same ancestor of _plan node,
887
    // so in the ideal case, mem limit is a matter for _plan node.
888
    // But there is still some unfinished things, we do mem limit here temporarily.
889
    // _cancelled may be set by rpc callback, and it's possible that _cancelled might be set in any of the steps below.
890
    // It's fine to do a fake add_block() and return OK, because we will check _cancelled in next add_block() or mark_close().
891
41.8k
    constexpr int64_t kBackPressureSleepMs = 10;
892
41.8k
    auto* memtable_limiter = ExecEnv::GetInstance()->memtable_memory_limiter();
893
41.8k
    while (true) {
894
41.8k
        bool is_exceed_soft_mem_limit = GlobalMemoryArbitrator::is_exceed_soft_mem_limit();
895
41.8k
        int64_t memtable_mem =
896
41.8k
                (memtable_limiter != nullptr && memtable_limiter->mem_tracker() != nullptr)
897
41.8k
                        ? memtable_limiter->mem_tracker()->consumption()
898
41.8k
                        : 0;
899
        // Note: Memtable memory is not included in load memory statistics (MemoryProfile::load_current_usage())
900
        // for performance and memory control complexity reasons. Therefore, we explicitly add memtable memory
901
        // consumption here to ensure accurate back pressure decisions and prevent OOM during heavy loads.
902
41.8k
        auto current_load_mem_value = MemoryProfile::load_current_usage() + memtable_mem;
903
41.8k
        bool mem_limit_exceeded = is_exceed_soft_mem_limit ||
904
41.8k
                                  current_load_mem_value > _load_mem_limit ||
905
41.8k
                                  _pending_batches_bytes > _max_pending_batches_bytes;
906
41.8k
        bool need_back_pressure = !_cancelled && !_state->is_cancelled() &&
907
41.8k
                                  _pending_batches_num > 0 && mem_limit_exceeded;
908
41.8k
        if (!need_back_pressure) {
909
41.8k
            break;
910
41.8k
        }
911
0
        SCOPED_RAW_TIMER(&_stat.mem_exceeded_block_ns);
912
0
        std::this_thread::sleep_for(std::chrono::milliseconds(kBackPressureSleepMs));
913
0
    }
914
915
41.8k
    if (UNLIKELY(!_cur_mutable_block)) {
916
33.7k
        _cur_mutable_block = MutableBlock::create_unique(block->clone_empty());
917
33.7k
    }
918
919
41.8k
    SCOPED_RAW_TIMER(&_stat.append_node_channel_ns);
920
41.8k
    st = block->append_to_block_by_selector(_cur_mutable_block.get(), *payload->row_ids);
921
41.8k
    if (!st.ok()) {
922
0
        _cancel_with_msg(fmt::format("{}, err: {}", channel_info(), st.to_string()));
923
0
        return st;
924
0
    }
925
41.8k
    auto* row_part_tablet_ids = payload->row_part_tablet_ids;
926
36.0M
    for (uint32_t route_idx : payload->route_idxs) {
927
36.0M
        auto partition_id = row_part_tablet_ids->partition_ids[route_idx];
928
36.0M
        _cur_add_block_request->add_partition_ids(partition_id);
929
36.0M
        if (_parent->_tablet_finder->is_adaptive_random_bucket()) {
930
2.06M
            auto tablet_it = _adaptive_partition_compat_tablets.find(partition_id);
931
2.06M
            if (tablet_it == _adaptive_partition_compat_tablets.end()) {
932
0
                return Status::InternalError(
933
0
                        "{}, err: missing adaptive random bucket compatible tablet, "
934
0
                        "partition_id={}",
935
0
                        channel_info(), partition_id);
936
0
            }
937
2.06M
            _cur_add_block_request->add_tablet_ids(tablet_it->second);
938
33.9M
        } else {
939
33.9M
            _cur_add_block_request->add_tablet_ids(row_part_tablet_ids->tablet_ids[route_idx]);
940
33.9M
        }
941
36.0M
    }
942
41.8k
    for (auto row_binlog_lsn : payload->row_binlog_lsns) {
943
0
        _cur_add_block_request->add_row_binlog_lsns(row_binlog_lsn);
944
0
    }
945
41.8k
    _write_bytes.fetch_add(_cur_mutable_block->bytes());
946
947
41.8k
    if (_cur_mutable_block->rows() >= _batch_size ||
948
41.8k
        _cur_mutable_block->bytes() > config::doris_scanner_row_bytes) {
949
2.19k
        {
950
2.19k
            SCOPED_ATOMIC_TIMER(&_queue_push_lock_ns);
951
2.19k
            std::lock_guard<std::mutex> l(_pending_batches_lock);
952
            // To simplify the add_row logic, postpone adding block into req until the time of sending req
953
2.19k
            _pending_batches_bytes += _cur_mutable_block->allocated_bytes();
954
2.19k
            _cur_add_block_request->set_eos(
955
2.19k
                    false); // for multi-add, only when marking close we set it eos.
956
            // Copy the request to tmp request to add to pend block queue
957
2.19k
            auto tmp_add_block_request = std::make_shared<PTabletWriterAddBlockRequest>();
958
2.19k
            *tmp_add_block_request = *_cur_add_block_request;
959
2.19k
            _pending_blocks.emplace(std::move(_cur_mutable_block), tmp_add_block_request);
960
2.19k
            _pending_batches_num++;
961
2.19k
            VLOG_DEBUG << "VTabletWriter:" << _parent << " VNodeChannel:" << this
962
0
                       << " pending_batches_bytes:" << _pending_batches_bytes
963
0
                       << " jobid:" << std::to_string(_state->load_job_id())
964
0
                       << " loadinfo:" << _load_info;
965
2.19k
        }
966
2.19k
        _cur_mutable_block = MutableBlock::create_unique(block->clone_empty());
967
2.19k
        _cur_add_block_request->clear_tablet_ids();
968
2.19k
        _cur_add_block_request->clear_partition_ids();
969
2.19k
        _cur_add_block_request->clear_row_binlog_lsns();
970
2.19k
    }
971
972
41.8k
    return Status::OK();
973
41.8k
}
974
975
0
static void injection_full_gc_fn() {
976
0
    MemoryReclamation::revoke_process_memory("injection_full_gc_fn");
977
0
}
978
979
int VNodeChannel::try_send_and_fetch_status(RuntimeState* state,
980
11.3M
                                            std::unique_ptr<ThreadPoolToken>& thread_pool_token) {
981
11.3M
    DBUG_EXECUTE_IF("VNodeChannel.try_send_and_fetch_status_full_gc", {
982
11.3M
        std::thread t(injection_full_gc_fn);
983
11.3M
        t.join();
984
11.3M
    });
985
986
11.6M
    if (_cancelled || _send_finished) { // not run
987
656k
        return 0;
988
656k
    }
989
990
10.7M
    auto load_back_pressure_version_wait_time_ms = _load_back_pressure_version_wait_time_ms.load();
991
10.7M
    if (UNLIKELY(load_back_pressure_version_wait_time_ms > 0)) {
992
0
        std::this_thread::sleep_for(
993
0
                std::chrono::milliseconds(load_back_pressure_version_wait_time_ms));
994
0
        _load_back_pressure_version_block_ms.fetch_add(
995
0
                load_back_pressure_version_wait_time_ms); // already in milliseconds
996
0
        _load_back_pressure_version_wait_time_ms = 0;
997
0
    }
998
999
    // set closure for sending block.
1000
10.7M
    if (!_send_block_callback->try_set_in_flight()) {
1001
        // There is packet in flight, skip.
1002
984k
        return _send_finished ? 0 : 1;
1003
984k
    }
1004
1005
    // We are sure that try_send_batch is not running
1006
9.74M
    if (_pending_batches_num > 0) {
1007
54.3k
        auto s = thread_pool_token->submit_func([this, state] { try_send_pending_block(state); });
1008
54.3k
        if (!s.ok()) {
1009
0
            _cancel_with_msg("submit send_batch task to send_batch_thread_pool failed");
1010
            // sending finished. clear in flight
1011
0
            _send_block_callback->clear_in_flight();
1012
0
        }
1013
        // in_flight is cleared in closure::Run
1014
9.68M
    } else {
1015
        // sending finished. clear in flight
1016
9.68M
        _send_block_callback->clear_in_flight();
1017
9.68M
    }
1018
9.74M
    return _send_finished ? 0 : 1;
1019
10.7M
}
1020
1021
212
void VNodeChannel::_cancel_with_msg(const std::string& msg) {
1022
212
    LOG(WARNING) << "cancel node channel " << channel_info() << ", error message: " << msg;
1023
212
    {
1024
212
        std::lock_guard<std::mutex> l(_cancel_msg_lock);
1025
212
        if (_cancel_msg.empty()) {
1026
170
            _cancel_msg = msg;
1027
170
        }
1028
212
    }
1029
212
    _cancelled = true;
1030
212
    _index_channel->notify_close_wait();
1031
212
}
1032
1033
void VNodeChannel::_refresh_back_pressure_version_wait_time(
1034
        const ::google::protobuf::RepeatedPtrField<::doris::PTabletLoadRowsetInfo>&
1035
54.2k
                tablet_load_infos) {
1036
54.2k
    int64_t max_rowset_num_gap = 0;
1037
    // if any one tablet is under high load pressure, we would make the whole procedure
1038
    // sleep to prevent the corresponding BE return -235
1039
54.2k
    std::for_each(
1040
54.2k
            tablet_load_infos.begin(), tablet_load_infos.end(),
1041
54.2k
            [&max_rowset_num_gap](auto& load_info) {
1042
0
                int64_t cur_rowset_num = load_info.current_rowset_nums();
1043
0
                int64_t high_load_point = load_info.max_config_rowset_nums() *
1044
0
                                          (config::load_back_pressure_version_threshold / 100);
1045
0
                DCHECK(cur_rowset_num > high_load_point);
1046
0
                max_rowset_num_gap = std::max(max_rowset_num_gap, cur_rowset_num - high_load_point);
1047
0
            });
1048
    // to slow down the high load pressure
1049
    // we would use the rowset num gap to calculate one sleep time
1050
    // for example:
1051
    // if the max tablet version is 2000, there are 3 BE
1052
    // A: ====================  1800
1053
    // B: ===================   1700
1054
    // C: ==================    1600
1055
    //    ==================    1600
1056
    //                      ^
1057
    //                      the high load point
1058
    // then then max gap is 1800 - (max tablet version * config::load_back_pressure_version_threshold / 100) = 200,
1059
    // we would make the whole send procesure sleep
1060
    // 1200ms for compaction to be done toe reduce the high pressure
1061
54.2k
    auto max_time = config::max_load_back_pressure_version_wait_time_ms;
1062
54.2k
    if (UNLIKELY(max_rowset_num_gap > 0)) {
1063
0
        _load_back_pressure_version_wait_time_ms.store(
1064
0
                std::min(max_rowset_num_gap + 1000, max_time));
1065
0
        LOG(INFO) << "try to back pressure version, wait time(ms): "
1066
0
                  << _load_back_pressure_version_wait_time_ms
1067
0
                  << ", load id: " << print_id(_parent->_load_id)
1068
0
                  << ", max_rowset_num_gap: " << max_rowset_num_gap;
1069
0
    }
1070
54.2k
}
1071
1072
54.3k
void VNodeChannel::try_send_pending_block(RuntimeState* state) {
1073
54.3k
    SCOPED_ATTACH_TASK(state);
1074
54.3k
    SCOPED_CONSUME_MEM_TRACKER(_node_channel_tracker);
1075
54.3k
    SCOPED_ATOMIC_TIMER(&_actual_consume_ns);
1076
54.3k
    signal::set_signal_task_id(_parent->_load_id);
1077
54.3k
    AddBlockReq send_block;
1078
54.3k
    {
1079
54.3k
        std::lock_guard<std::mutex> l(_pending_batches_lock);
1080
54.3k
        DCHECK(!_pending_blocks.empty());
1081
54.3k
        send_block = std::move(_pending_blocks.front());
1082
54.3k
        _pending_blocks.pop();
1083
54.3k
        _pending_batches_num--;
1084
54.3k
        _pending_batches_bytes -= send_block.first->allocated_bytes();
1085
54.3k
    }
1086
1087
54.3k
    auto mutable_block = std::move(send_block.first);
1088
54.3k
    auto request = std::move(send_block.second); // doesn't need to be saved in heap
1089
1090
    // tablet_ids has already set when add row
1091
54.3k
    request->set_packet_seq(_next_packet_seq);
1092
54.3k
    auto block = mutable_block->to_block();
1093
54.3k
    int request_rows = request->is_adaptive_random_bucket() && !request->eos()
1094
54.3k
                               ? request->partition_ids_size()
1095
54.3k
                               : request->tablet_ids_size();
1096
54.3k
    if (block.rows() != request_rows) {
1097
0
        cancel(
1098
0
                fmt::format("{}, err: invalid add block request row count, block rows: {}, "
1099
0
                            "request rows: {}, adaptive_random_bucket: {}, eos: {}",
1100
0
                            channel_info(), block.rows(), request_rows,
1101
0
                            request->is_adaptive_random_bucket(), request->eos()));
1102
0
        _send_block_callback->clear_in_flight();
1103
0
        return;
1104
0
    }
1105
54.3k
    if (block.rows() > 0) {
1106
35.6k
        SCOPED_ATOMIC_TIMER(&_serialize_batch_ns);
1107
35.6k
        size_t uncompressed_bytes = 0, compressed_bytes = 0;
1108
35.6k
        int64_t compressed_time = 0;
1109
35.6k
        Status st = block.serialize(state->be_exec_version(), request->mutable_block(),
1110
35.6k
                                    &uncompressed_bytes, &compressed_bytes, &compressed_time,
1111
35.6k
                                    state->fragement_transmission_compression_type(),
1112
35.6k
                                    _parent->_transfer_large_data_by_brpc);
1113
35.6k
        TEST_INJECTION_POINT_CALLBACK("VNodeChannel::try_send_block", &st);
1114
35.6k
        if (!st.ok()) {
1115
0
            cancel(fmt::format("{}, err: {}", channel_info(), st.to_string()));
1116
0
            _send_block_callback->clear_in_flight();
1117
0
            return;
1118
0
        }
1119
35.6k
        if (double(compressed_bytes) >= double(config::brpc_max_body_size) * 0.95F) {
1120
0
            LOG(WARNING) << "send block too large, this rpc may failed. send size: "
1121
0
                         << compressed_bytes << ", threshold: " << config::brpc_max_body_size
1122
0
                         << ", " << channel_info();
1123
0
        }
1124
35.6k
    }
1125
1126
54.3k
    auto remain_ms = _rpc_timeout_ms - _timeout_watch.elapsed_time() / NANOS_PER_MILLIS;
1127
54.3k
    if (UNLIKELY(remain_ms < config::min_load_rpc_timeout_ms)) {
1128
16
        if (remain_ms <= 0 && !request->eos()) {
1129
0
            cancel(fmt::format("{}, err: load timeout after {} ms", channel_info(),
1130
0
                               _rpc_timeout_ms));
1131
0
            _send_block_callback->clear_in_flight();
1132
0
            return;
1133
16
        } else {
1134
16
            remain_ms = config::min_load_rpc_timeout_ms;
1135
16
        }
1136
16
    }
1137
1138
54.3k
    _send_block_callback->reset();
1139
54.3k
    _send_block_callback->cntl_->set_timeout_ms(remain_ms);
1140
54.3k
    if (config::tablet_writer_ignore_eovercrowded) {
1141
54.2k
        _send_block_callback->cntl_->ignore_eovercrowded();
1142
54.2k
    }
1143
1144
54.3k
    if (request->eos()) {
1145
47.9k
        if (!request->is_adaptive_random_bucket() || !request->has_block()) {
1146
47.9k
            for (auto pid : _parent->_tablet_finder->partition_ids()) {
1147
36.6k
                request->add_partition_ids(pid);
1148
36.6k
            }
1149
47.9k
        }
1150
1151
        // eos request must be the last request-> it's a signal makeing callback function to set _add_batch_finished true.
1152
        // end_mark makes is_last_rpc true when rpc finished and call callbacks.
1153
47.9k
        _send_block_callback->end_mark();
1154
47.9k
        _send_finished = true;
1155
18.4E
        CHECK(_pending_batches_num == 0) << _pending_batches_num;
1156
47.9k
    }
1157
1158
54.3k
    auto send_block_closure = AutoReleaseClosure<
1159
54.3k
            PTabletWriterAddBlockRequest,
1160
54.3k
            WriteBlockCallback<PTabletWriterAddBlockResult>>::create_unique(request,
1161
54.3k
                                                                            _send_block_callback);
1162
54.3k
    if (_parent->_transfer_large_data_by_brpc && request->has_block() &&
1163
54.3k
        request->block().has_column_values() && request->ByteSizeLong() > MIN_HTTP_BRPC_SIZE) {
1164
0
        Status st = request_embed_attachment_contain_blockv2(send_block_closure->request_.get(),
1165
0
                                                             send_block_closure);
1166
0
        if (!st.ok()) {
1167
0
            cancel(fmt::format("{}, err: {}", channel_info(), st.to_string()));
1168
0
            _send_block_callback->clear_in_flight();
1169
0
            return;
1170
0
        }
1171
1172
0
        std::string host = _node_info.host;
1173
0
        auto dns_cache = ExecEnv::GetInstance()->dns_cache();
1174
0
        if (dns_cache == nullptr) {
1175
0
            LOG(WARNING) << "DNS cache is not initialized, skipping hostname resolve";
1176
0
        } else if (!is_valid_ip(_node_info.host)) {
1177
0
            Status status = dns_cache->get(_node_info.host, &host);
1178
0
            if (!status.ok()) {
1179
0
                LOG(WARNING) << "failed to get ip from host " << _node_info.host << ": "
1180
0
                             << status.to_string();
1181
0
                cancel(fmt::format("failed to get ip from host {}", _node_info.host));
1182
0
                _send_block_callback->clear_in_flight();
1183
0
                return;
1184
0
            }
1185
0
        }
1186
        //format an ipv6 address
1187
0
        std::string brpc_url = get_brpc_http_url(host, _node_info.brpc_port);
1188
0
        std::shared_ptr<PBackendService_Stub> _brpc_http_stub =
1189
0
                _state->exec_env()->brpc_internal_client_cache()->get_new_client_no_cache(brpc_url,
1190
0
                                                                                          "http");
1191
0
        if (_brpc_http_stub == nullptr) {
1192
0
            cancel(fmt::format("{}, failed to open brpc http client to {}", channel_info(),
1193
0
                               brpc_url));
1194
0
            _send_block_callback->clear_in_flight();
1195
0
            return;
1196
0
        }
1197
0
        _send_block_callback->cntl_->http_request().uri() =
1198
0
                brpc_url + "/PInternalServiceImpl/tablet_writer_add_block_by_http";
1199
0
        _send_block_callback->cntl_->http_request().set_method(brpc::HTTP_METHOD_POST);
1200
0
        _send_block_callback->cntl_->http_request().set_content_type("application/json");
1201
1202
0
        {
1203
0
            _brpc_http_stub->tablet_writer_add_block_by_http(
1204
0
                    send_block_closure->cntl_.get(), nullptr, send_block_closure->response_.get(),
1205
0
                    send_block_closure.get());
1206
0
            send_block_closure.release();
1207
0
        }
1208
54.3k
    } else {
1209
54.3k
        _send_block_callback->cntl_->http_request().Clear();
1210
54.3k
        {
1211
54.3k
            _stub->tablet_writer_add_block(
1212
54.3k
                    send_block_closure->cntl_.get(), send_block_closure->request_.get(),
1213
54.3k
                    send_block_closure->response_.get(), send_block_closure.get());
1214
54.3k
            send_block_closure.release();
1215
54.3k
        }
1216
54.3k
    }
1217
1218
54.3k
    _next_packet_seq++;
1219
54.3k
}
1220
1221
void VNodeChannel::_add_block_success_callback(const PTabletWriterAddBlockResult& result,
1222
54.2k
                                               const WriteBlockCallbackContext& ctx) {
1223
54.2k
    std::lock_guard<std::mutex> l(this->_closed_lock);
1224
54.2k
    if (this->_is_closed) {
1225
        // if the node channel is closed, no need to call the following logic,
1226
        // and notice that _index_channel may already be destroyed.
1227
0
        return;
1228
0
    }
1229
54.2k
    SCOPED_ATTACH_TASK(_state);
1230
54.2k
    Status status(Status::create(result.status()));
1231
54.2k
    if (status.ok()) {
1232
54.2k
        _refresh_back_pressure_version_wait_time(result.tablet_load_rowset_num_infos());
1233
        // if has error tablet, handle them first
1234
54.2k
        for (const auto& error : result.tablet_errors()) {
1235
0
            _index_channel->mark_as_failed(this, "tablet error: " + error.msg(), error.tablet_id());
1236
0
        }
1237
1238
54.2k
        Status st = _index_channel->check_intolerable_failure();
1239
54.2k
        if (!st.ok()) {
1240
0
            _cancel_with_msg(st.to_string());
1241
54.2k
        } else if (ctx._is_last_rpc) {
1242
203k
            for (const auto& tablet : result.tablet_vec()) {
1243
203k
                DBUG_EXECUTE_IF("VNodeChannel.add_block_success_callback.incomplete_commit_info", {
1244
203k
                    auto target_tablet_id = dp->param<int64_t>("tablet_id", -1);
1245
203k
                    if (tablet.tablet_id() == target_tablet_id) {
1246
203k
                        LOG(INFO) << "skip tablet info: " << tablet.tablet_id()
1247
203k
                                  << ", backend_id: " << _node_id;
1248
203k
                        continue;
1249
203k
                    }
1250
203k
                });
1251
203k
                TTabletCommitInfo commit_info;
1252
203k
                commit_info.tabletId = tablet.tablet_id();
1253
203k
                commit_info.backendId = _node_id;
1254
203k
                _tablet_commit_infos.emplace_back(std::move(commit_info));
1255
203k
                if (tablet.has_received_rows()) {
1256
203k
                    _tablets_received_rows.emplace_back(tablet.tablet_id(), tablet.received_rows());
1257
203k
                }
1258
203k
                if (tablet.has_num_rows_filtered()) {
1259
203k
                    _tablets_filtered_rows.emplace_back(tablet.tablet_id(),
1260
203k
                                                        tablet.num_rows_filtered());
1261
203k
                }
1262
203k
                VLOG_CRITICAL << "master replica commit info: tabletId=" << tablet.tablet_id()
1263
0
                              << ", backendId=" << _node_id
1264
0
                              << ", master node id: " << this->node_id()
1265
0
                              << ", host: " << this->host() << ", txn_id=" << _parent->_txn_id;
1266
203k
            }
1267
47.8k
            _add_batches_finished = true;
1268
47.8k
            _index_channel->notify_close_wait();
1269
47.8k
        }
1270
54.2k
    } else {
1271
25
        _cancel_with_msg(fmt::format("{}, add batch req success but status isn't ok, err: {}",
1272
25
                                     channel_info(), status.to_string()));
1273
25
    }
1274
1275
54.3k
    if (result.has_execution_time_us()) {
1276
54.3k
        _add_batch_counter.add_batch_execution_time_us += result.execution_time_us();
1277
54.3k
        _add_batch_counter.add_batch_wait_execution_time_us += result.wait_execution_time_us();
1278
54.3k
        _add_batch_counter.add_batch_num++;
1279
54.3k
    }
1280
54.2k
    if (result.has_load_channel_profile()) {
1281
21
        TRuntimeProfileTree tprofile;
1282
21
        const auto* buf = (const uint8_t*)result.load_channel_profile().data();
1283
21
        auto len = cast_set<uint32_t>(result.load_channel_profile().size());
1284
21
        auto st = deserialize_thrift_msg(buf, &len, false, &tprofile);
1285
21
        if (st.ok()) {
1286
21
            _state->load_channel_profile()->update(tprofile);
1287
21
        } else {
1288
0
            LOG(WARNING) << "load channel TRuntimeProfileTree deserialize failed, errmsg=" << st;
1289
0
        }
1290
21
    }
1291
54.2k
}
1292
1293
0
void VNodeChannel::_add_block_failed_callback(const WriteBlockCallbackContext& ctx) {
1294
0
    std::lock_guard<std::mutex> l(this->_closed_lock);
1295
0
    if (this->_is_closed) {
1296
        // if the node channel is closed, no need to call `mark_as_failed`,
1297
        // and notice that _index_channel may already be destroyed.
1298
0
        return;
1299
0
    }
1300
0
    SCOPED_ATTACH_TASK(_state);
1301
    // If rpc failed, mark all tablets on this node channel as failed
1302
0
    _index_channel->mark_as_failed(this,
1303
0
                                   fmt::format("rpc failed, error code:{}, error text:{}",
1304
0
                                               _send_block_callback->cntl_->ErrorCode(),
1305
0
                                               _send_block_callback->cntl_->ErrorText()),
1306
0
                                   -1);
1307
0
    if (_send_block_callback->cntl_->ErrorText().find("Reached timeout") != std::string::npos) {
1308
0
        LOG(WARNING) << "rpc failed may caused by timeout. increase BE config "
1309
0
                        "`min_load_rpc_timeout_ms` of to avoid this if you are sure that your "
1310
0
                        "table building and data are reasonable.";
1311
0
    }
1312
0
    Status st = _index_channel->check_intolerable_failure();
1313
0
    if (!st.ok()) {
1314
0
        _cancel_with_msg(fmt::format("{}, err: {}", channel_info(), st.to_string()));
1315
0
    } else if (ctx._is_last_rpc) {
1316
        // if this is last rpc, will must set _add_batches_finished. otherwise, node channel's close_wait
1317
        // will be blocked.
1318
0
        _add_batches_finished = true;
1319
0
        _index_channel->notify_close_wait();
1320
0
    }
1321
0
}
1322
1323
// When _cancelled is true, we still need to send a tablet_writer_cancel
1324
// rpc request to truly release the load channel
1325
213
void VNodeChannel::cancel(const std::string& cancel_msg) {
1326
213
    if (_is_closed) {
1327
        // skip the channels that have been canceled or close_wait.
1328
43
        return;
1329
43
    }
1330
170
    SCOPED_CONSUME_MEM_TRACKER(_node_channel_tracker.get());
1331
    // set _is_closed to true finally
1332
170
    Defer set_closed {[&]() {
1333
170
        std::lock_guard<std::mutex> l(_closed_lock);
1334
170
        _is_closed = true;
1335
170
    }};
1336
    // we don't need to wait last rpc finished, cause closure's release/reset will join.
1337
    // But do we need brpc::StartCancel(call_id)?
1338
170
    _cancel_with_msg(cancel_msg);
1339
    // if not inited, _stub will be nullptr, skip sending cancel rpc
1340
170
    if (!_inited) {
1341
0
        return;
1342
0
    }
1343
1344
170
    auto request = std::make_shared<PTabletWriterCancelRequest>();
1345
170
    request->mutable_id()->CopyFrom(_parent->_load_id);
1346
170
    request->set_index_id(_index_channel->_index_id);
1347
170
    request->set_sender_id(_parent->_sender_id);
1348
170
    request->set_cancel_reason(cancel_msg);
1349
1350
    // cancel is already in post-processing, so error status could be ignored. so not keeping cancel_callback is acceptable.
1351
170
    auto cancel_callback = DummyBrpcCallback<PTabletWriterCancelResult>::create_shared();
1352
170
    auto closure = AutoReleaseClosure<
1353
170
            PTabletWriterCancelRequest,
1354
170
            DummyBrpcCallback<PTabletWriterCancelResult>>::create_unique(request, cancel_callback);
1355
1356
170
    auto remain_ms = _rpc_timeout_ms - _timeout_watch.elapsed_time() / NANOS_PER_MILLIS;
1357
170
    if (UNLIKELY(remain_ms < config::min_load_rpc_timeout_ms)) {
1358
0
        remain_ms = config::min_load_rpc_timeout_ms;
1359
0
    }
1360
170
    cancel_callback->cntl_->set_timeout_ms(remain_ms);
1361
170
    if (config::tablet_writer_ignore_eovercrowded) {
1362
170
        closure->cntl_->ignore_eovercrowded();
1363
170
    }
1364
170
    _stub->tablet_writer_cancel(closure->cntl_.get(), closure->request_.get(),
1365
170
                                closure->response_.get(), closure.get());
1366
170
    closure.release();
1367
170
}
1368
1369
97.5k
Status VNodeChannel::close_wait(RuntimeState* state, bool* is_closed) {
1370
97.5k
    DBUG_EXECUTE_IF("VNodeChannel.close_wait_full_gc", {
1371
97.5k
        std::thread t(injection_full_gc_fn);
1372
97.5k
        t.join();
1373
97.5k
    });
1374
97.5k
    SCOPED_CONSUME_MEM_TRACKER(_node_channel_tracker.get());
1375
1376
97.5k
    *is_closed = true;
1377
1378
97.5k
    auto st = none_of({_cancelled, !_eos_is_produced});
1379
97.5k
    if (!st.ok()) {
1380
43
        if (_cancelled) {
1381
43
            std::lock_guard<std::mutex> l(_cancel_msg_lock);
1382
43
            return Status::Error<ErrorCode::INTERNAL_ERROR, false>("wait close failed. {}",
1383
43
                                                                   _cancel_msg);
1384
43
        } else {
1385
0
            return std::move(
1386
0
                    st.prepend("already stopped, skip waiting for close. cancelled/!eos: "));
1387
0
        }
1388
43
    }
1389
1390
97.4k
    DBUG_EXECUTE_IF("VNodeChannel.close_wait.cancelled", {
1391
97.4k
        _cancelled = true;
1392
97.4k
        _cancel_msg = "injected cancel";
1393
97.4k
    });
1394
1395
97.4k
    if (state->is_cancelled()) {
1396
0
        _cancel_with_msg(state->cancel_reason().to_string());
1397
0
    }
1398
1399
    // Waiting for finished until _add_batches_finished changed by rpc's finished callback.
1400
    // it may take a long time, so we couldn't set a timeout
1401
    // For pipeline engine, the close is called in async writer's process block method,
1402
    // so that it will not block pipeline thread.
1403
97.4k
    if (!_add_batches_finished && !_cancelled && !state->is_cancelled()) {
1404
49.5k
        *is_closed = false;
1405
49.5k
        return Status::OK();
1406
49.5k
    }
1407
18.4E
    VLOG_CRITICAL << _parent->_sender_id << " close wait finished";
1408
47.8k
    return Status::OK();
1409
97.4k
}
1410
1411
Status VNodeChannel::after_close_handle(
1412
        RuntimeState* state, WriterStats* writer_stats,
1413
47.9k
        std::unordered_map<int64_t, AddBatchCounter>* node_add_batch_counter_map) {
1414
47.9k
    Status st = Status::Error<ErrorCode::INTERNAL_ERROR, false>(get_cancel_msg());
1415
47.9k
    _close_time_ms = UnixMillis() - _close_time_ms;
1416
1417
47.9k
    if (_add_batches_finished) {
1418
47.8k
        _close_check();
1419
47.8k
        _state->add_tablet_commit_infos(_tablet_commit_infos);
1420
1421
47.8k
        _index_channel->set_error_tablet_in_state(state);
1422
47.8k
        _index_channel->set_tablets_received_rows(_tablets_received_rows, _node_id);
1423
47.8k
        _index_channel->set_tablets_filtered_rows(_tablets_filtered_rows, _node_id);
1424
1425
47.8k
        std::lock_guard<std::mutex> l(_closed_lock);
1426
        // only when normal close, we set _is_closed to true.
1427
        // otherwise, we will set it to true in cancel().
1428
47.8k
        _is_closed = true;
1429
47.8k
        st = Status::OK();
1430
47.8k
    }
1431
1432
47.9k
    time_report(node_add_batch_counter_map, writer_stats);
1433
47.9k
    return st;
1434
47.9k
}
1435
1436
33.7k
Status VNodeChannel::check_status() {
1437
33.7k
    return none_of({_cancelled, !_eos_is_produced});
1438
33.7k
}
1439
1440
47.8k
void VNodeChannel::_close_check() {
1441
47.8k
    std::lock_guard<std::mutex> lg(_pending_batches_lock);
1442
18.4E
    CHECK(_pending_blocks.empty()) << name();
1443
18.4E
    CHECK(_cur_mutable_block == nullptr) << name();
1444
47.8k
}
1445
1446
47.9k
void VNodeChannel::mark_close(bool hang_wait) {
1447
47.9k
    auto st = none_of({_cancelled, _eos_is_produced});
1448
47.9k
    if (!st.ok()) {
1449
0
        return;
1450
0
    }
1451
1452
47.9k
    bool need_adaptive_random_bucket_eos = _cur_add_block_request->is_adaptive_random_bucket();
1453
47.9k
    {
1454
47.9k
        std::lock_guard<std::mutex> l(_pending_batches_lock);
1455
47.9k
        if (!_cur_mutable_block) [[unlikely]] {
1456
            // never had a block arrived. add a dummy block
1457
14.2k
            _cur_mutable_block = MutableBlock::create_unique();
1458
14.2k
        }
1459
47.9k
        if (need_adaptive_random_bucket_eos && _cur_mutable_block->rows() > 0) {
1460
4.18k
            _cur_add_block_request->set_eos(false);
1461
4.18k
            auto tmp_add_block_request =
1462
4.18k
                    std::make_shared<PTabletWriterAddBlockRequest>(*_cur_add_block_request);
1463
4.18k
            _pending_blocks.emplace(std::move(_cur_mutable_block), tmp_add_block_request);
1464
4.18k
            _pending_batches_num++;
1465
4.18k
            _cur_add_block_request->clear_tablet_ids();
1466
4.18k
            _cur_add_block_request->clear_partition_ids();
1467
4.18k
            _cur_mutable_block = MutableBlock::create_unique();
1468
4.18k
        }
1469
47.9k
        _cur_add_block_request->set_eos(true);
1470
47.9k
        _cur_add_block_request->set_hang_wait(hang_wait);
1471
47.9k
        auto tmp_add_block_request =
1472
47.9k
                std::make_shared<PTabletWriterAddBlockRequest>(*_cur_add_block_request);
1473
        // when prepare to close, add block to queue so that try_send_pending_block thread will send it.
1474
47.9k
        _pending_blocks.emplace(std::move(_cur_mutable_block), tmp_add_block_request);
1475
47.9k
        _pending_batches_num++;
1476
47.9k
        DCHECK(_pending_blocks.back().second->eos());
1477
47.9k
        _close_time_ms = UnixMillis();
1478
47.9k
        LOG(INFO) << channel_info()
1479
47.9k
                  << " mark closed, left pending batch size: " << _pending_blocks.size()
1480
47.9k
                  << " hang_wait: " << hang_wait;
1481
47.9k
    }
1482
1483
47.9k
    _eos_is_produced = true;
1484
47.9k
}
1485
1486
VTabletWriter::VTabletWriter(const TDataSink& t_sink, const VExprContextSPtrs& output_exprs,
1487
                             std::shared_ptr<Dependency> dep, std::shared_ptr<Dependency> fin_dep)
1488
46.5k
        : AsyncResultWriter(output_exprs, dep, fin_dep), _t_sink(t_sink) {
1489
46.5k
    _transfer_large_data_by_brpc = config::transfer_large_data_by_brpc;
1490
46.5k
}
1491
1492
46.7k
void VTabletWriter::_send_batch_process() {
1493
46.7k
    SCOPED_TIMER(_non_blocking_send_timer);
1494
46.7k
    SCOPED_ATTACH_TASK(_state);
1495
46.7k
    SCOPED_CONSUME_MEM_TRACKER(_mem_tracker);
1496
1497
46.7k
    int sleep_time = int(config::olap_table_sink_send_interval_microseconds *
1498
46.7k
                         (_vpartition->is_auto_partition()
1499
46.7k
                                  ? config::olap_table_sink_send_interval_auto_partition_factor
1500
46.7k
                                  : 1));
1501
1502
8.89M
    while (true) {
1503
        // incremental open will temporarily make channels into abnormal state. stop checking when this.
1504
8.89M
        std::unique_lock<bthread::Mutex> l(_stop_check_channel);
1505
1506
8.89M
        int running_channels_num = 0;
1507
8.89M
        int opened_nodes = 0;
1508
11.6M
        for (const auto& index_channel : _channels) {
1509
11.6M
            index_channel->for_each_node_channel([&running_channels_num,
1510
11.6M
                                                  this](const std::shared_ptr<VNodeChannel>& ch) {
1511
                // if this channel all completed(cancelled), got 0. else 1.
1512
11.6M
                running_channels_num +=
1513
11.6M
                        ch->try_send_and_fetch_status(_state, this->_send_batch_thread_pool_token);
1514
11.6M
            });
1515
11.6M
            opened_nodes += index_channel->num_node_channels();
1516
11.6M
        }
1517
1518
        // auto partition table may have no node channel temporarily. wait to open.
1519
8.89M
        if (opened_nodes != 0 && running_channels_num == 0) {
1520
46.7k
            LOG(INFO) << "All node channels are stopped(maybe finished/offending/cancelled), "
1521
46.7k
                         "sender thread exit. "
1522
46.7k
                      << print_id(_load_id);
1523
46.7k
            return;
1524
46.7k
        }
1525
1526
        // for auto partition tables, there's a situation: we haven't open any node channel but decide to cancel the task.
1527
        // then the judge in front will never be true because opened_nodes won't increase. so we have to specially check wether we called close.
1528
        // we must RECHECK opened_nodes below, after got closed signal, because it may changed. Think of this:
1529
        //      checked opened_nodes = 0 ---> new block arrived ---> task finished, close() was called ---> we got _try_close here
1530
        // if we don't check again, we may lose the last package.
1531
8.84M
        if (_try_close.load(std::memory_order_acquire)) {
1532
251k
            opened_nodes = 0;
1533
251k
            std::ranges::for_each(_channels,
1534
1.49M
                                  [&opened_nodes](const std::shared_ptr<IndexChannel>& ich) {
1535
1.49M
                                      opened_nodes += ich->num_node_channels();
1536
1.49M
                                  });
1537
251k
            if (opened_nodes == 0) {
1538
0
                LOG(INFO) << "No node channel have ever opened but now we have to close. sender "
1539
0
                             "thread exit. "
1540
0
                          << print_id(_load_id);
1541
0
                return;
1542
0
            }
1543
251k
        }
1544
8.84M
        bthread_usleep(sleep_time);
1545
8.84M
    }
1546
46.7k
}
1547
1548
46.7k
static void* periodic_send_batch(void* writer) {
1549
46.7k
    auto* tablet_writer = (VTabletWriter*)(writer);
1550
46.7k
    tablet_writer->_send_batch_process();
1551
46.7k
    return nullptr;
1552
46.7k
}
1553
1554
46.7k
Status VTabletWriter::open(doris::RuntimeState* state, doris::RuntimeProfile* profile) {
1555
46.7k
    RETURN_IF_ERROR(_init(state, profile));
1556
46.7k
    signal::set_signal_task_id(_load_id);
1557
46.7k
    SCOPED_TIMER(profile->total_time_counter());
1558
46.7k
    SCOPED_TIMER(_open_timer);
1559
46.7k
    SCOPED_CONSUME_MEM_TRACKER(_mem_tracker.get());
1560
1561
46.7k
    fmt::memory_buffer buf;
1562
47.8k
    for (const auto& index_channel : _channels) {
1563
47.8k
        fmt::format_to(buf, "index id:{}", index_channel->_index_id);
1564
47.8k
        index_channel->for_each_node_channel(
1565
47.8k
                [](const std::shared_ptr<VNodeChannel>& ch) { ch->open(); });
1566
47.8k
    }
1567
46.7k
    VLOG_DEBUG << "list of open index id = " << fmt::to_string(buf);
1568
1569
47.9k
    for (const auto& index_channel : _channels) {
1570
47.9k
        index_channel->set_start_time(UnixMillis());
1571
47.9k
        index_channel->for_each_node_channel([&index_channel](
1572
47.9k
                                                     const std::shared_ptr<VNodeChannel>& ch) {
1573
47.6k
            auto st = ch->open_wait();
1574
47.6k
            if (!st.ok()) {
1575
                // The open() phase is mainly to generate DeltaWriter instances on the nodes corresponding to each node channel.
1576
                // This phase will not fail due to a single tablet.
1577
                // Therefore, if the open() phase fails, all tablets corresponding to the node need to be marked as failed.
1578
0
                index_channel->mark_as_failed(
1579
0
                        ch.get(),
1580
0
                        fmt::format("{}, open failed, err: {}", ch->channel_info(), st.to_string()),
1581
0
                        -1);
1582
0
            }
1583
47.6k
        });
1584
1585
47.9k
        RETURN_IF_ERROR(index_channel->check_intolerable_failure());
1586
47.9k
    }
1587
46.7k
    _send_batch_thread_pool_token = state->exec_env()->send_batch_thread_pool()->new_token(
1588
46.7k
            ThreadPool::ExecutionMode::CONCURRENT, _send_batch_parallelism);
1589
1590
    // start to send batch continually. this must be called after _init
1591
46.7k
    if (bthread_start_background(&_sender_thread, nullptr, periodic_send_batch, (void*)this) != 0) {
1592
0
        return Status::Error<ErrorCode::INTERNAL_ERROR>("bthread_start_backgroud failed");
1593
0
    }
1594
46.7k
    return Status::OK();
1595
46.7k
}
1596
1597
186
Status VTabletWriter::on_partitions_created(TCreatePartitionResult* result) {
1598
    // add new tablet locations. it will use by address. so add to pool
1599
186
    auto* new_locations = _pool->add(new std::vector<TTabletLocation>(result->tablets));
1600
186
    _location->add_locations(*new_locations);
1601
    // update new node info
1602
186
    _nodes_info->add_nodes(result->nodes);
1603
1604
    // incremental open node channel
1605
186
    RETURN_IF_ERROR(_incremental_open_node_channel(result->partitions));
1606
1607
186
    return Status::OK();
1608
186
}
1609
1610
186
static Status on_partitions_created(void* writer, TCreatePartitionResult* result) {
1611
186
    return static_cast<VTabletWriter*>(writer)->on_partitions_created(result);
1612
186
}
1613
1614
45.7k
Status VTabletWriter::_init_row_distribution() {
1615
45.7k
    _row_distribution.init({.state = _state,
1616
45.7k
                            .block_convertor = _block_convertor.get(),
1617
45.7k
                            .tablet_finder = _tablet_finder.get(),
1618
45.7k
                            .vpartition = _vpartition,
1619
45.7k
                            .add_partition_request_timer = _add_partition_request_timer,
1620
45.7k
                            .txn_id = _txn_id,
1621
45.7k
                            .pool = _pool,
1622
45.7k
                            .location = _location,
1623
45.7k
                            .vec_output_expr_ctxs = &_vec_output_expr_ctxs,
1624
45.7k
                            .schema = _schema,
1625
45.7k
                            .caller = this,
1626
45.7k
                            .create_partition_callback = &::doris::on_partitions_created});
1627
1628
45.7k
    return _row_distribution.open(_output_row_desc);
1629
45.7k
}
1630
1631
46.7k
Status VTabletWriter::_init(RuntimeState* state, RuntimeProfile* profile) {
1632
46.7k
    DCHECK(_t_sink.__isset.olap_table_sink);
1633
46.7k
    _pool = state->obj_pool();
1634
46.7k
    auto& table_sink = _t_sink.olap_table_sink;
1635
46.7k
    if (table_sink.__isset.write_single_replica && table_sink.write_single_replica) {
1636
0
        return Status::NotSupported("single replica load has been removed");
1637
0
    }
1638
46.7k
    _load_id.set_hi(table_sink.load_id.hi);
1639
46.7k
    _load_id.set_lo(table_sink.load_id.lo);
1640
46.7k
    _txn_id = table_sink.txn_id;
1641
46.7k
    _num_replicas = table_sink.num_replicas;
1642
46.7k
    _tuple_desc_id = table_sink.tuple_id;
1643
46.7k
    _write_file_cache = table_sink.write_file_cache;
1644
46.7k
    _schema.reset(new OlapTableSchemaParam());
1645
46.7k
    RETURN_IF_ERROR(_schema->init(table_sink.schema));
1646
46.7k
    bool has_row_binlog = std::any_of(_schema->indexes().begin(), _schema->indexes().end(),
1647
47.6k
                                      [](const auto* index) { return index->row_binlog_id > 0; });
1648
46.7k
    if (has_row_binlog) {
1649
0
        _row_binlog_lsn_buffer = GlobalAutoIncBuffers::GetInstance()->get_auto_inc_buffer(
1650
0
                _schema->db_id(), _schema->table_id(), kBinlogLsnAutoIncId);
1651
0
    }
1652
46.7k
    _schema->set_timestamp_ms(state->timestamp_ms());
1653
46.7k
    _schema->set_nano_seconds(state->nano_seconds());
1654
46.7k
    _schema->set_timezone(state->timezone());
1655
46.7k
    _location = _pool->add(new OlapTableLocationParam(table_sink.location));
1656
46.7k
    _nodes_info = _pool->add(new DorisNodesInfo(table_sink.nodes_info));
1657
46.7k
    if (config::is_cloud_mode() &&
1658
46.7k
        (!table_sink.__isset.txn_timeout_s || table_sink.txn_timeout_s <= 0)) {
1659
0
        return Status::InternalError("The txn_timeout_s of TDataSink is invalid");
1660
0
    }
1661
46.7k
    _txn_expiration = ::time(nullptr) + table_sink.txn_timeout_s;
1662
1663
46.7k
    if (table_sink.__isset.load_channel_timeout_s) {
1664
45.9k
        _load_channel_timeout_s = table_sink.load_channel_timeout_s;
1665
45.9k
    } else {
1666
823
        _load_channel_timeout_s = config::streaming_load_rpc_max_alive_time_sec;
1667
823
    }
1668
46.7k
    if (table_sink.__isset.send_batch_parallelism && table_sink.send_batch_parallelism > 1) {
1669
1
        _send_batch_parallelism = table_sink.send_batch_parallelism;
1670
1
    }
1671
    // If distributed column list is empty, the table uses random distribution.
1672
    // Mode priority (highest to lowest):
1673
    //   1. FIND_TABLET_EVERY_SINK: load_to_single_tablet=true (legacy single-tablet mode).
1674
    //   2. FIND_TABLET_RANDOM_BUCKET: FE set enable_adaptive_random_bucket on the sink,
1675
    //      meaning enable_adaptive_random_bucket_load is ON. Using a sink-level flag (mirroring
1676
    //      load_to_single_tablet) ensures the mode is fixed correctly when the initial
1677
    //      partition list is empty (e.g. auto-partition tables on first load).
1678
    //   3. FIND_TABLET_EVERY_BATCH: default round-robin per batch.
1679
46.7k
    auto find_tablet_mode = OlapTabletFinder::FindTabletMode::FIND_TABLET_EVERY_ROW;
1680
46.7k
    if (table_sink.partition.distributed_columns.empty()) {
1681
9.29k
        if (table_sink.__isset.load_to_single_tablet && table_sink.load_to_single_tablet) {
1682
8
            find_tablet_mode = OlapTabletFinder::FindTabletMode::FIND_TABLET_EVERY_SINK;
1683
9.25k
        } else if (table_sink.__isset.enable_adaptive_random_bucket &&
1684
9.29k
                   table_sink.enable_adaptive_random_bucket && config::is_cloud_mode()) {
1685
9.23k
            find_tablet_mode = OlapTabletFinder::FindTabletMode::FIND_TABLET_RANDOM_BUCKET;
1686
9.23k
        } else {
1687
19
            find_tablet_mode = OlapTabletFinder::FindTabletMode::FIND_TABLET_EVERY_BATCH;
1688
19
        }
1689
9.25k
    }
1690
46.7k
    _vpartition = _pool->add(new doris::VOlapTablePartitionParam(_schema, table_sink.partition));
1691
46.7k
    _tablet_finder = std::make_unique<OlapTabletFinder>(_vpartition, find_tablet_mode);
1692
46.7k
    RETURN_IF_ERROR(_vpartition->init());
1693
1694
46.7k
    _state = state;
1695
46.7k
    _operator_profile = profile;
1696
1697
46.7k
    _sender_id = state->per_fragment_instance_idx();
1698
46.7k
    _num_senders = state->num_per_fragment_instances();
1699
46.7k
    _is_high_priority =
1700
46.7k
            (state->execution_timeout() <= config::load_task_high_priority_threshold_second);
1701
46.7k
    DBUG_EXECUTE_IF("VTabletWriter._init.is_high_priority", { _is_high_priority = true; });
1702
    // profile must add to state's object pool
1703
46.7k
    _mem_tracker =
1704
46.7k
            std::make_shared<MemTracker>("OlapTableSink:" + std::to_string(state->load_job_id()));
1705
46.7k
    SCOPED_TIMER(profile->total_time_counter());
1706
46.7k
    SCOPED_CONSUME_MEM_TRACKER(_mem_tracker.get());
1707
1708
    // get table's tuple descriptor
1709
46.7k
    _output_tuple_desc = state->desc_tbl().get_tuple_descriptor(_tuple_desc_id);
1710
46.7k
    if (_output_tuple_desc == nullptr) {
1711
0
        LOG(WARNING) << "unknown destination tuple descriptor, id=" << _tuple_desc_id;
1712
0
        return Status::InternalError("unknown destination tuple descriptor");
1713
0
    }
1714
1715
46.7k
    if (!_vec_output_expr_ctxs.empty() &&
1716
46.7k
        _output_tuple_desc->slots().size() != _vec_output_expr_ctxs.size()) {
1717
0
        LOG(WARNING) << "output tuple slot num should be equal to num of output exprs, "
1718
0
                     << "output_tuple_slot_num " << _output_tuple_desc->slots().size()
1719
0
                     << " output_expr_num " << _vec_output_expr_ctxs.size();
1720
0
        return Status::InvalidArgument(
1721
0
                "output_tuple_slot_num {} should be equal to output_expr_num {}",
1722
0
                _output_tuple_desc->slots().size(), _vec_output_expr_ctxs.size());
1723
0
    }
1724
1725
46.7k
    _block_convertor = std::make_unique<OlapTableBlockConvertor>(_output_tuple_desc);
1726
    // if partition_type is OLAP_TABLE_SINK_HASH_PARTITIONED, we handle the processing of auto_increment column
1727
    // on exchange node rather than on TabletWriter
1728
46.7k
    _block_convertor->init_autoinc_info(
1729
46.7k
            _schema->db_id(), _schema->table_id(), _state->batch_size(),
1730
46.7k
            _schema->is_fixed_partial_update() && !_schema->auto_increment_coulumn().empty(),
1731
46.7k
            _schema->auto_increment_column_unique_id());
1732
46.7k
    _output_row_desc = _pool->add(new RowDescriptor(_output_tuple_desc));
1733
1734
    // add all counter
1735
46.7k
    _input_rows_counter = ADD_COUNTER(profile, "RowsRead", TUnit::UNIT);
1736
46.7k
    _output_rows_counter = ADD_COUNTER(profile, "RowsProduced", TUnit::UNIT);
1737
46.7k
    _filtered_rows_counter = ADD_COUNTER(profile, "RowsFiltered", TUnit::UNIT);
1738
46.7k
    _send_data_timer = ADD_TIMER(profile, "SendDataTime");
1739
46.7k
    _wait_mem_limit_timer = ADD_CHILD_TIMER(profile, "WaitMemLimitTime", "SendDataTime");
1740
46.7k
    _row_distribution_timer = ADD_CHILD_TIMER(profile, "RowDistributionTime", "SendDataTime");
1741
46.7k
    _filter_timer = ADD_CHILD_TIMER(profile, "FilterTime", "SendDataTime");
1742
46.7k
    _where_clause_timer = ADD_CHILD_TIMER(profile, "WhereClauseTime", "SendDataTime");
1743
46.7k
    _append_node_channel_timer = ADD_CHILD_TIMER(profile, "AppendNodeChannelTime", "SendDataTime");
1744
46.7k
    _add_partition_request_timer =
1745
46.7k
            ADD_CHILD_TIMER(profile, "AddPartitionRequestTime", "SendDataTime");
1746
46.7k
    _validate_data_timer = ADD_TIMER(profile, "ValidateDataTime");
1747
46.7k
    _open_timer = ADD_TIMER(profile, "OpenTime");
1748
46.7k
    _close_timer = ADD_TIMER(profile, "CloseWaitTime");
1749
46.7k
    _non_blocking_send_timer = ADD_TIMER(profile, "NonBlockingSendTime");
1750
46.7k
    _non_blocking_send_work_timer =
1751
46.7k
            ADD_CHILD_TIMER(profile, "NonBlockingSendWorkTime", "NonBlockingSendTime");
1752
46.7k
    _serialize_batch_timer =
1753
46.7k
            ADD_CHILD_TIMER(profile, "SerializeBatchTime", "NonBlockingSendWorkTime");
1754
46.7k
    _total_add_batch_exec_timer = ADD_TIMER(profile, "TotalAddBatchExecTime");
1755
46.7k
    _max_add_batch_exec_timer = ADD_TIMER(profile, "MaxAddBatchExecTime");
1756
46.7k
    _total_wait_exec_timer = ADD_TIMER(profile, "TotalWaitExecTime");
1757
46.7k
    _max_wait_exec_timer = ADD_TIMER(profile, "MaxWaitExecTime");
1758
46.7k
    _add_batch_number = ADD_COUNTER(profile, "NumberBatchAdded", TUnit::UNIT);
1759
46.7k
    _num_node_channels = ADD_COUNTER(profile, "NumberNodeChannels", TUnit::UNIT);
1760
46.7k
    _load_back_pressure_version_time_ms = ADD_TIMER(profile, "LoadBackPressureVersionTimeMs");
1761
1762
#ifdef DEBUG
1763
    // check: tablet ids should be unique
1764
    {
1765
        std::unordered_set<int64_t> tablet_ids;
1766
        const auto& partitions = _vpartition->get_partitions();
1767
        for (int i = 0; i < _schema->indexes().size(); ++i) {
1768
            for (const auto& partition : partitions) {
1769
                for (const auto& tablet : partition->indexes[i].tablets) {
1770
                    CHECK(tablet_ids.count(tablet) == 0) << "found duplicate tablet id: " << tablet;
1771
                    tablet_ids.insert(tablet);
1772
                }
1773
            }
1774
        }
1775
    }
1776
#endif
1777
1778
    // open all channels
1779
46.7k
    const auto& partitions = _vpartition->get_partitions();
1780
93.4k
    for (int i = 0; i < _schema->indexes().size(); ++i) {
1781
        // collect all tablets belong to this rollup
1782
46.6k
        std::vector<TTabletWithPartition> tablets;
1783
46.6k
        auto* index = _schema->indexes()[i];
1784
62.2k
        for (const auto& part : partitions) {
1785
366k
            for (const auto& tablet : part->indexes[i].tablets) {
1786
366k
                TTabletWithPartition tablet_with_partition;
1787
366k
                tablet_with_partition.partition_id = part->id;
1788
366k
                tablet_with_partition.tablet_id = tablet;
1789
366k
                tablets.emplace_back(std::move(tablet_with_partition));
1790
366k
                _build_tablet_replica_info(tablet, part);
1791
366k
            }
1792
62.2k
        }
1793
46.6k
        if (tablets.empty() && !_vpartition->is_auto_partition()) {
1794
0
            LOG(WARNING) << "load job:" << state->load_job_id() << " index: " << index->index_id
1795
0
                         << " would open 0 tablet";
1796
0
        }
1797
46.6k
        _channels.emplace_back(new IndexChannel(this, index->index_id, index->where_clause));
1798
46.6k
        _index_id_to_channel[index->index_id] = _channels.back();
1799
46.6k
        RETURN_IF_ERROR(_channels.back()->init(state, tablets));
1800
46.6k
    }
1801
1802
46.7k
    RETURN_IF_ERROR(_init_row_distribution());
1803
1804
46.7k
    _inited = true;
1805
46.7k
    return Status::OK();
1806
46.7k
}
1807
1808
Status VTabletWriter::_incremental_open_node_channel(
1809
186
        const std::vector<TOlapTablePartition>& partitions) {
1810
    // do what we did in prepare() for partitions. indexes which don't change when we create new partition is orthogonal to partitions.
1811
186
    std::unique_lock<bthread::Mutex> _l(_stop_check_channel);
1812
372
    for (int i = 0; i < _schema->indexes().size(); ++i) {
1813
186
        const OlapTableIndexSchema* index = _schema->indexes()[i];
1814
186
        std::vector<TTabletWithPartition> tablets;
1815
383
        for (const auto& t_part : partitions) {
1816
383
            VOlapTablePartition* part = nullptr;
1817
383
            RETURN_IF_ERROR(_vpartition->generate_partition_from(t_part, part));
1818
6.66k
            for (const auto& tablet : part->indexes[i].tablets) {
1819
6.66k
                TTabletWithPartition tablet_with_partition;
1820
6.66k
                tablet_with_partition.partition_id = part->id;
1821
6.66k
                tablet_with_partition.tablet_id = tablet;
1822
6.66k
                tablets.emplace_back(std::move(tablet_with_partition));
1823
6.66k
                _build_tablet_replica_info(tablet, part);
1824
6.66k
            }
1825
383
            DCHECK(!tablets.empty()) << "incremental open got nothing!";
1826
383
        }
1827
        // update and reinit for existing channels.
1828
186
        std::shared_ptr<IndexChannel> channel = _index_id_to_channel[index->index_id];
1829
186
        DCHECK(channel != nullptr);
1830
186
        RETURN_IF_ERROR(channel->init(_state, tablets, true)); // add tablets into it
1831
186
    }
1832
1833
186
    fmt::memory_buffer buf;
1834
186
    for (auto& channel : _channels) {
1835
        // incremental open new partition's tablet on storage side
1836
186
        channel->for_each_node_channel(
1837
186
                [](const std::shared_ptr<VNodeChannel>& ch) { ch->incremental_open(); });
1838
186
        fmt::format_to(buf, "index id:{}", channel->_index_id);
1839
186
        VLOG_DEBUG << "list of open index id = " << fmt::to_string(buf);
1840
1841
186
        channel->for_each_node_channel([&channel](const std::shared_ptr<VNodeChannel>& ch) {
1842
186
            auto st = ch->open_wait();
1843
186
            if (!st.ok()) {
1844
                // The open() phase is mainly to generate DeltaWriter instances on the nodes corresponding to each node channel.
1845
                // This phase will not fail due to a single tablet.
1846
                // Therefore, if the open() phase fails, all tablets corresponding to the node need to be marked as failed.
1847
0
                channel->mark_as_failed(
1848
0
                        ch.get(),
1849
0
                        fmt::format("{}, open failed, err: {}", ch->channel_info(), st.to_string()),
1850
0
                        -1);
1851
0
            }
1852
186
        });
1853
1854
186
        RETURN_IF_ERROR(channel->check_intolerable_failure());
1855
186
    }
1856
1857
186
    return Status::OK();
1858
186
}
1859
1860
void VTabletWriter::_build_tablet_replica_info(const int64_t tablet_id,
1861
370k
                                               VOlapTablePartition* partition) {
1862
370k
    if (partition != nullptr) {
1863
370k
        int total_replicas_num =
1864
370k
                partition->total_replica_num == 0 ? _num_replicas : partition->total_replica_num;
1865
370k
        int load_required_replicas_num = partition->load_required_replica_num == 0
1866
370k
                                                 ? (_num_replicas + 1) / 2
1867
370k
                                                 : partition->load_required_replica_num;
1868
370k
        _tablet_replica_info.emplace(
1869
370k
                tablet_id, std::make_pair(total_replicas_num, load_required_replicas_num));
1870
        // Copy version gap backends info for this tablet
1871
370k
        if (auto it = partition->tablet_version_gap_backends.find(tablet_id);
1872
370k
            it != partition->tablet_version_gap_backends.end()) {
1873
0
            _tablet_version_gap_backends[tablet_id] = it->second;
1874
0
        }
1875
18.4E
    } else {
1876
18.4E
        _tablet_replica_info.emplace(tablet_id,
1877
18.4E
                                     std::make_pair(_num_replicas, (_num_replicas + 1) / 2));
1878
18.4E
    }
1879
370k
}
1880
1881
167
void VTabletWriter::_cancel_all_channel(Status status) {
1882
168
    for (const auto& index_channel : _channels) {
1883
168
        index_channel->for_each_node_channel([&status](const std::shared_ptr<VNodeChannel>& ch) {
1884
168
            ch->cancel(status.to_string());
1885
168
        });
1886
168
    }
1887
167
    LOG(INFO) << fmt::format(
1888
167
            "close olap table sink. load_id={}, txn_id={}, canceled all node channels due to "
1889
167
            "error: {}",
1890
167
            print_id(_load_id), _txn_id, status);
1891
167
}
1892
1893
87.7k
Status VTabletWriter::_send_new_partition_batch() {
1894
87.7k
    if (_row_distribution.need_deal_batching()) { // maybe try_close more than 1 time
1895
167
        RETURN_IF_ERROR(_row_distribution.automatic_create_partition());
1896
1897
167
        Block tmp_block = _row_distribution._batching_block->to_block(); // Borrow out, for lval ref
1898
1899
        // these order is unique.
1900
        //  1. clear batching stats(and flag goes true) so that we won't make a new batching process in dealing batched block.
1901
        //  2. deal batched block
1902
        //  3. now reuse the column of lval block. cuz write doesn't real adjust it. it generate a new block from that.
1903
167
        _row_distribution.clear_batching_stats();
1904
167
        Defer recover_batching_block([&]() {
1905
167
            _row_distribution._batching_block->set_mutable_columns(
1906
167
                    std::move(tmp_block).mutate_columns());
1907
167
            _row_distribution._batching_block->clear_column_data();
1908
167
        });
1909
167
        RETURN_IF_ERROR(this->write(_state, tmp_block));
1910
167
        _row_distribution._deal_batched = false;
1911
167
    }
1912
87.7k
    return Status::OK();
1913
87.7k
}
1914
1915
46.7k
void VTabletWriter::_do_try_close(RuntimeState* state, const Status& exec_status) {
1916
46.7k
    SCOPED_TIMER(_close_timer);
1917
46.7k
    Status status = exec_status;
1918
1919
    // must before set _try_close
1920
46.7k
    if (status.ok()) {
1921
46.6k
        SCOPED_TIMER(_operator_profile->total_time_counter());
1922
46.6k
        _row_distribution._deal_batched = true;
1923
46.6k
        status = _send_new_partition_batch();
1924
46.6k
    }
1925
1926
46.7k
    _try_close.store(true, std::memory_order_release); // will stop periodic thread
1927
46.7k
    if (status.ok()) {
1928
        // BE id -> add_batch method counter
1929
46.6k
        std::unordered_map<int64_t, AddBatchCounter> node_add_batch_counter_map;
1930
1931
        // only if status is ok can we call this _profile->total_time_counter().
1932
        // if status is not ok, this sink may not be prepared, so that _profile is null
1933
46.6k
        SCOPED_TIMER(_operator_profile->total_time_counter());
1934
47.9k
        for (const auto& index_channel : _channels) {
1935
            // two-step mark close. first we send close_origin to recievers to close all originly exist TabletsChannel.
1936
            // when they all closed, we are sure all Writer of instances called _do_try_close. that means no new channel
1937
            // will be opened. the refcount of recievers will be monotonically decreasing. then we are safe to close all
1938
            // our channels.
1939
47.9k
            if (index_channel->has_incremental_node_channel()) {
1940
0
                if (!status.ok()) {
1941
0
                    break;
1942
0
                }
1943
0
                VLOG_TRACE << _sender_id << " first stage close start " << _txn_id;
1944
0
                index_channel->for_init_node_channel(
1945
0
                        [&index_channel, &status, this](const std::shared_ptr<VNodeChannel>& ch) {
1946
0
                            if (!status.ok() || ch->is_closed()) {
1947
0
                                return;
1948
0
                            }
1949
0
                            VLOG_DEBUG << index_channel->_parent->_sender_id << "'s " << ch->host()
1950
0
                                       << "mark close1 for inits " << _txn_id;
1951
0
                            ch->mark_close(true);
1952
0
                            if (ch->is_cancelled()) {
1953
0
                                status = cancel_channel_and_check_intolerable_failure(
1954
0
                                        std::move(status), ch->get_cancel_msg(), *index_channel,
1955
0
                                        *ch);
1956
0
                            }
1957
0
                        });
1958
0
                if (!status.ok()) {
1959
0
                    break;
1960
0
                }
1961
                // Do not need to wait after quorum success,
1962
                // for first-stage close_wait only ensure incremental node channels load has been completed,
1963
                // unified waiting in the second-stage close_wait.
1964
0
                status = index_channel->close_wait(_state, nullptr, nullptr,
1965
0
                                                   index_channel->init_node_channel_ids(), false);
1966
0
                if (!status.ok()) {
1967
0
                    break;
1968
0
                }
1969
0
                VLOG_DEBUG << _sender_id << " first stage finished. closeing inc nodes " << _txn_id;
1970
0
                index_channel->for_inc_node_channel(
1971
0
                        [&index_channel, &status, this](const std::shared_ptr<VNodeChannel>& ch) {
1972
0
                            if (!status.ok() || ch->is_closed()) {
1973
0
                                return;
1974
0
                            }
1975
                            // only first try close, all node channels will mark_close()
1976
0
                            VLOG_DEBUG << index_channel->_parent->_sender_id << "'s " << ch->host()
1977
0
                                       << "mark close2 for inc " << _txn_id;
1978
0
                            ch->mark_close();
1979
0
                            if (ch->is_cancelled()) {
1980
0
                                status = cancel_channel_and_check_intolerable_failure(
1981
0
                                        std::move(status), ch->get_cancel_msg(), *index_channel,
1982
0
                                        *ch);
1983
0
                            }
1984
0
                        });
1985
47.9k
            } else { // not has_incremental_node_channel
1986
47.9k
                VLOG_TRACE << _sender_id << " has no incremental channels " << _txn_id;
1987
47.9k
                index_channel->for_each_node_channel(
1988
47.9k
                        [&index_channel, &status](const std::shared_ptr<VNodeChannel>& ch) {
1989
47.8k
                            if (!status.ok() || ch->is_closed()) {
1990
0
                                return;
1991
0
                            }
1992
                            // only first try close, all node channels will mark_close()
1993
47.8k
                            ch->mark_close();
1994
47.8k
                            if (ch->is_cancelled()) {
1995
0
                                status = cancel_channel_and_check_intolerable_failure(
1996
0
                                        std::move(status), ch->get_cancel_msg(), *index_channel,
1997
0
                                        *ch);
1998
0
                            }
1999
47.8k
                        });
2000
47.9k
            }
2001
47.9k
        } // end for index channels
2002
46.6k
    }
2003
2004
46.7k
    if (!status.ok()) {
2005
126
        _cancel_all_channel(status);
2006
126
        _close_status = status;
2007
126
    }
2008
46.7k
}
2009
2010
46.7k
Status VTabletWriter::close(Status exec_status) {
2011
46.7k
    if (!_inited) {
2012
0
        DCHECK(!exec_status.ok());
2013
0
        _cancel_all_channel(exec_status);
2014
0
        _close_status = exec_status;
2015
0
        return _close_status;
2016
0
    }
2017
2018
46.7k
    SCOPED_TIMER(_close_timer);
2019
46.7k
    SCOPED_TIMER(_operator_profile->total_time_counter());
2020
2021
    // will make the last batch of request-> close_wait will wait this finished.
2022
46.7k
    _do_try_close(_state, exec_status);
2023
46.7k
    TEST_INJECTION_POINT("VOlapTableSink::close");
2024
2025
46.7k
    DBUG_EXECUTE_IF("VTabletWriter.close.sleep", {
2026
46.7k
        auto sleep_sec = dp->param<int32_t>("sleep_sec", 1);
2027
46.7k
        auto token = dp->param<std::string>("token", "");
2028
46.7k
        LOG(INFO) << "hit debug point VTabletWriter.close.sleep, token=" << token;
2029
46.7k
        std::this_thread::sleep_for(std::chrono::seconds(sleep_sec));
2030
46.7k
    });
2031
46.7k
    DBUG_EXECUTE_IF("VTabletWriter.close.close_status_not_ok",
2032
46.7k
                    { _close_status = Status::InternalError("injected close status not ok"); });
2033
2034
    // If _close_status is not ok, all nodes have been canceled in try_close.
2035
46.7k
    if (_close_status.ok()) {
2036
46.5k
        auto status = Status::OK();
2037
        // BE id -> add_batch method counter
2038
46.5k
        std::unordered_map<int64_t, AddBatchCounter> node_add_batch_counter_map;
2039
46.5k
        WriterStats writer_stats;
2040
2041
47.9k
        for (const auto& index_channel : _channels) {
2042
47.9k
            if (!status.ok()) {
2043
0
                break;
2044
0
            }
2045
47.9k
            int64_t add_batch_exec_time = 0;
2046
47.9k
            int64_t wait_exec_time = 0;
2047
47.9k
            status = index_channel->close_wait(_state, &writer_stats, &node_add_batch_counter_map,
2048
47.9k
                                               index_channel->each_node_channel_ids(), true);
2049
2050
            // Due to the non-determinism of compaction, the rowsets of each replica may be different from each other on different
2051
            // BE nodes. The number of rows filtered in SegmentWriter depends on the historical rowsets located in the correspoding
2052
            // BE node. So we check the number of rows filtered on each succeccful BE to ensure the consistency of the current load
2053
47.9k
            if (status.ok() && _schema->is_strict_mode() && _schema->is_partial_update()) {
2054
89
                if (Status st = index_channel->check_tablet_filtered_rows_consistency(); !st.ok()) {
2055
0
                    status = st;
2056
89
                } else {
2057
89
                    _state->set_num_rows_filtered_in_strict_mode_partial_update(
2058
89
                            index_channel->num_rows_filtered());
2059
89
                }
2060
89
            }
2061
2062
47.9k
            writer_stats.num_node_channels += index_channel->num_node_channels();
2063
47.9k
            writer_stats.max_add_batch_exec_time_ns =
2064
47.9k
                    std::max(add_batch_exec_time, writer_stats.max_add_batch_exec_time_ns);
2065
47.9k
            writer_stats.max_wait_exec_time_ns =
2066
47.9k
                    std::max(wait_exec_time, writer_stats.max_wait_exec_time_ns);
2067
47.9k
        } // end for index channels
2068
2069
46.5k
        if (status.ok()) {
2070
            // TODO need to be improved
2071
46.5k
            LOG(INFO) << "total mem_exceeded_block_ns="
2072
46.5k
                      << writer_stats.channel_stat.mem_exceeded_block_ns
2073
46.5k
                      << ", total queue_push_lock_ns=" << writer_stats.queue_push_lock_ns
2074
46.5k
                      << ", total actual_consume_ns=" << writer_stats.actual_consume_ns
2075
46.5k
                      << ", load id=" << print_id(_load_id) << ", txn_id=" << _txn_id;
2076
2077
46.5k
            COUNTER_SET(_input_rows_counter, _number_input_rows);
2078
46.5k
            COUNTER_SET(_output_rows_counter, _number_output_rows);
2079
46.5k
            COUNTER_SET(_filtered_rows_counter,
2080
46.5k
                        _block_convertor->num_filtered_rows() +
2081
46.5k
                                _tablet_finder->num_filtered_rows() +
2082
46.5k
                                _state->num_rows_filtered_in_strict_mode_partial_update());
2083
46.5k
            COUNTER_SET(_send_data_timer, _send_data_ns);
2084
46.5k
            COUNTER_SET(_row_distribution_timer, (int64_t)_row_distribution_watch.elapsed_time());
2085
46.5k
            COUNTER_SET(_filter_timer, _filter_ns);
2086
46.5k
            COUNTER_SET(_append_node_channel_timer,
2087
46.5k
                        writer_stats.channel_stat.append_node_channel_ns);
2088
46.5k
            COUNTER_SET(_where_clause_timer, writer_stats.channel_stat.where_clause_ns);
2089
46.5k
            COUNTER_SET(_wait_mem_limit_timer, writer_stats.channel_stat.mem_exceeded_block_ns);
2090
46.5k
            COUNTER_SET(_validate_data_timer, _block_convertor->validate_data_ns());
2091
46.5k
            COUNTER_SET(_serialize_batch_timer, writer_stats.serialize_batch_ns);
2092
46.5k
            COUNTER_SET(_non_blocking_send_work_timer, writer_stats.actual_consume_ns);
2093
46.5k
            COUNTER_SET(_total_add_batch_exec_timer, writer_stats.total_add_batch_exec_time_ns);
2094
46.5k
            COUNTER_SET(_max_add_batch_exec_timer, writer_stats.max_add_batch_exec_time_ns);
2095
46.5k
            COUNTER_SET(_total_wait_exec_timer, writer_stats.total_wait_exec_time_ns);
2096
46.5k
            COUNTER_SET(_max_wait_exec_timer, writer_stats.max_wait_exec_time_ns);
2097
46.5k
            COUNTER_SET(_add_batch_number, writer_stats.total_add_batch_num);
2098
46.5k
            COUNTER_SET(_num_node_channels, writer_stats.num_node_channels);
2099
46.5k
            COUNTER_SET(_load_back_pressure_version_time_ms,
2100
46.5k
                        writer_stats.load_back_pressure_version_time_ms);
2101
46.5k
            g_sink_load_back_pressure_version_time_ms
2102
46.5k
                    << writer_stats.load_back_pressure_version_time_ms;
2103
2104
            // _number_input_rows don't contain num_rows_load_filtered and num_rows_load_unselected in scan node
2105
46.5k
            int64_t num_rows_load_total = _number_input_rows + _state->num_rows_load_filtered() +
2106
46.5k
                                          _state->num_rows_load_unselected();
2107
46.5k
            _state->set_num_rows_load_total(num_rows_load_total);
2108
46.5k
            _state->update_num_rows_load_filtered(
2109
46.5k
                    _block_convertor->num_filtered_rows() + _tablet_finder->num_filtered_rows() +
2110
46.5k
                    _state->num_rows_filtered_in_strict_mode_partial_update());
2111
46.5k
            _state->update_num_rows_load_unselected(
2112
46.5k
                    _tablet_finder->num_immutable_partition_filtered_rows());
2113
2114
46.5k
            if (_state->enable_profile() && _state->profile_level() >= 2) {
2115
                // Output detailed profiling info for auto-partition requests
2116
19
                _row_distribution.output_profile_info(_operator_profile);
2117
19
            }
2118
2119
            // print log of add batch time of all node, for tracing load performance easily
2120
46.5k
            std::stringstream ss;
2121
46.5k
            ss << "finished to close olap table sink. load_id=" << print_id(_load_id)
2122
46.5k
               << ", txn_id=" << _txn_id
2123
46.5k
               << ", node add batch time(ms)/wait execution time(ms)/close time(ms)/num: ";
2124
46.5k
            for (auto const& pair : node_add_batch_counter_map) {
2125
46.5k
                ss << "{" << pair.first << ":(" << (pair.second.add_batch_execution_time_us / 1000)
2126
46.5k
                   << ")(" << (pair.second.add_batch_wait_execution_time_us / 1000) << ")("
2127
46.5k
                   << pair.second.close_wait_time_ms << ")(" << pair.second.add_batch_num << ")} ";
2128
46.5k
            }
2129
46.5k
            LOG(INFO) << ss.str();
2130
46.5k
        } else {
2131
15
            _cancel_all_channel(status);
2132
15
        }
2133
46.5k
        _close_status = status;
2134
46.5k
    }
2135
2136
    // Sender join() must put after node channels mark_close/cancel.
2137
    // But there is no specific sequence required between sender join() & close_wait().
2138
46.7k
    if (_sender_thread) {
2139
46.6k
        bthread_join(_sender_thread, nullptr);
2140
        // We have to wait all task in _send_batch_thread_pool_token finished,
2141
        // because it is difficult to handle concurrent problem if we just
2142
        // shutdown it.
2143
46.6k
        _send_batch_thread_pool_token->wait();
2144
46.6k
    }
2145
2146
    // We clear NodeChannels' batches here, cuz NodeChannels' batches destruction will use
2147
    // OlapTableSink::_mem_tracker and its parents.
2148
    // But their destructions are after OlapTableSink's.
2149
48.0k
    for (const auto& index_channel : _channels) {
2150
48.0k
        index_channel->for_each_node_channel(
2151
48.0k
                [](const std::shared_ptr<VNodeChannel>& ch) { ch->clear_all_blocks(); });
2152
48.0k
    }
2153
46.7k
    return _close_status;
2154
46.7k
}
2155
2156
Status VTabletWriter::_generate_one_index_channel_payload(
2157
        RowPartTabletIds& row_part_tablet_id, int32_t index_idx,
2158
42.1k
        ChannelDistributionPayload& channel_payload) {
2159
42.1k
    auto& row_ids = row_part_tablet_id.row_ids;
2160
42.1k
    auto& partition_ids = row_part_tablet_id.partition_ids;
2161
42.1k
    auto& tablet_ids = row_part_tablet_id.tablet_ids;
2162
2163
42.1k
    size_t row_cnt = row_ids.size();
2164
42.1k
    bool has_row_binlog = _schema->indexes()[index_idx]->row_binlog_id > 0;
2165
42.1k
    std::vector<int64_t> row_binlog_lsns;
2166
42.1k
    if (has_row_binlog && row_cnt > 0) {
2167
0
        DCHECK(_row_binlog_lsn_buffer != nullptr);
2168
0
        RETURN_IF_ERROR(allocate_binlog_lsn(_row_binlog_lsn_buffer, row_cnt, row_binlog_lsns));
2169
0
    }
2170
2171
36.0M
    for (size_t i = 0; i < row_ids.size(); i++) {
2172
36.0M
        if (_tablet_finder->is_adaptive_random_bucket() && config::is_cloud_mode()) {
2173
2.06M
            auto partition_it = _channels[index_idx]->_channels_by_partition.find(partition_ids[i]);
2174
2.06M
            if (partition_it == _channels[index_idx]->_channels_by_partition.end()) {
2175
0
                return Status::InternalError(
2176
0
                        "unknown partition channel, load_id={}, index_id={}, partition_id={}",
2177
0
                        print_id(_load_id), _channels[index_idx]->_index_id, partition_ids[i]);
2178
0
            }
2179
2.06M
            auto payload_it =
2180
2.06M
                    channel_payload.find(partition_it->second.get()); // <VNodeChannel*, Payload>
2181
2.06M
            if (payload_it == channel_payload.end()) {
2182
5.27k
                auto [tmp_it, _] = channel_payload.emplace(
2183
5.27k
                        partition_it->second.get(),
2184
5.27k
                        Payload {std::make_unique<IColumn::Selector>(), &row_part_tablet_id,
2185
5.27k
                                 std::vector<uint32_t>(), std::vector<int64_t>()});
2186
5.27k
                payload_it = tmp_it;
2187
5.27k
                payload_it->second.row_ids->reserve(row_cnt);
2188
5.27k
                payload_it->second.route_idxs.reserve(row_cnt);
2189
5.27k
                if (has_row_binlog) {
2190
0
                    payload_it->second.row_binlog_lsns.reserve(row_cnt);
2191
0
                }
2192
5.27k
            }
2193
2.06M
            payload_it->second.row_ids->push_back(row_ids[i]);
2194
2.06M
            payload_it->second.route_idxs.push_back(cast_set<uint32_t>(i));
2195
2.06M
            if (has_row_binlog) {
2196
0
                payload_it->second.row_binlog_lsns.push_back(row_binlog_lsns[i]);
2197
0
            }
2198
2.06M
            continue;
2199
2.06M
        }
2200
2201
        // (tablet_id, VNodeChannel) where this tablet locate
2202
33.9M
        auto it = _channels[index_idx]->_channels_by_tablet.find(tablet_ids[i]);
2203
33.9M
        if (it == _channels[index_idx]->_channels_by_tablet.end()) {
2204
0
            return Status::InternalError("unknown tablet, load_id={}, index_id={}, tablet_id={}",
2205
0
                                         print_id(_load_id), _channels[index_idx]->_index_id,
2206
0
                                         tablet_ids[i]);
2207
0
        }
2208
2209
33.9M
        std::vector<std::shared_ptr<VNodeChannel>>& tablet_locations = it->second;
2210
33.9M
        for (const auto& locate_node : tablet_locations) {
2211
33.9M
            auto payload_it = channel_payload.find(locate_node.get()); // <VNodeChannel*, Payload>
2212
33.9M
            if (payload_it == channel_payload.end()) {
2213
36.5k
                auto [tmp_it, _] = channel_payload.emplace(
2214
36.5k
                        locate_node.get(),
2215
36.5k
                        Payload {std::make_unique<IColumn::Selector>(), &row_part_tablet_id,
2216
36.5k
                                 std::vector<uint32_t>(), std::vector<int64_t>()});
2217
36.5k
                payload_it = tmp_it;
2218
36.5k
                payload_it->second.row_ids->reserve(row_cnt);
2219
36.5k
                payload_it->second.route_idxs.reserve(row_cnt);
2220
36.5k
                if (has_row_binlog) {
2221
0
                    payload_it->second.row_binlog_lsns.reserve(row_cnt);
2222
0
                }
2223
36.5k
            }
2224
33.9M
            payload_it->second.row_ids->push_back(row_ids[i]);
2225
33.9M
            payload_it->second.route_idxs.push_back(cast_set<uint32_t>(i));
2226
33.9M
            if (has_row_binlog) {
2227
0
                payload_it->second.row_binlog_lsns.push_back(row_binlog_lsns[i]);
2228
0
            }
2229
33.9M
        }
2230
33.9M
    }
2231
42.1k
    return Status::OK();
2232
42.1k
}
2233
2234
Status VTabletWriter::_generate_index_channels_payloads(
2235
        std::vector<RowPartTabletIds>& row_part_tablet_ids,
2236
41.1k
        ChannelDistributionPayloadVec& payload) {
2237
83.3k
    for (int i = 0; i < _schema->indexes().size(); i++) {
2238
42.1k
        RETURN_IF_ERROR(_generate_one_index_channel_payload(row_part_tablet_ids[i], i, payload[i]));
2239
42.1k
    }
2240
41.1k
    return Status::OK();
2241
41.1k
}
2242
2243
41.1k
Status VTabletWriter::write(RuntimeState* state, doris::Block& input_block) {
2244
41.1k
    SCOPED_CONSUME_MEM_TRACKER(_mem_tracker.get());
2245
41.1k
    Status status = Status::OK();
2246
2247
41.1k
    DCHECK(_state);
2248
41.1k
    DCHECK(_state->query_options().__isset.dry_run_query);
2249
41.1k
    if (_state->query_options().dry_run_query) {
2250
1
        return status;
2251
1
    }
2252
2253
    // check out of limit
2254
41.1k
    RETURN_IF_ERROR(_send_new_partition_batch());
2255
2256
41.1k
    const bool is_replaying_batched_block = _row_distribution._deal_batched;
2257
41.1k
    auto rows = input_block.rows();
2258
41.1k
    auto bytes = input_block.bytes();
2259
41.1k
    if (UNLIKELY(rows == 0)) {
2260
0
        return status;
2261
0
    }
2262
41.1k
    SCOPED_TIMER(_operator_profile->total_time_counter());
2263
41.1k
    SCOPED_RAW_TIMER(&_send_data_ns);
2264
2265
41.1k
    std::shared_ptr<Block> block;
2266
41.1k
    _number_input_rows += rows;
2267
    // update incrementally so that FE can get the progress.
2268
    // the real 'num_rows_load_total' will be set when sink being closed.
2269
41.1k
    _state->update_num_rows_load_total(rows);
2270
41.1k
    _state->update_num_bytes_load_total(bytes);
2271
41.1k
    if (!is_replaying_batched_block) {
2272
41.0k
        DorisMetrics::instance()->load_rows->increment(rows);
2273
41.0k
        DorisMetrics::instance()->load_bytes->increment(bytes);
2274
41.0k
    }
2275
2276
41.1k
    _row_distribution_watch.start();
2277
41.1k
    RETURN_IF_ERROR(_row_distribution.generate_rows_distribution(
2278
41.1k
            input_block, block, _row_part_tablet_ids, _number_input_rows));
2279
2280
41.1k
    ChannelDistributionPayloadVec channel_to_payload;
2281
2282
41.1k
    channel_to_payload.resize(_channels.size());
2283
41.1k
    Status generate_payload_status =
2284
41.1k
            _generate_index_channels_payloads(_row_part_tablet_ids, channel_to_payload);
2285
41.1k
    _row_distribution_watch.stop();
2286
41.1k
    RETURN_IF_ERROR(generate_payload_status);
2287
2288
    // Add block to node channel
2289
83.3k
    for (size_t i = 0; i < _channels.size(); i++) {
2290
42.1k
        for (const auto& entry : channel_to_payload[i]) {
2291
            // if this node channel is already failed, this add_row will be skipped
2292
            // entry.second is a [row -> tablet] mapping
2293
41.8k
            auto st = entry.first->add_block(block.get(), &entry.second);
2294
41.8k
            if (!st.ok()) {
2295
0
                _channels[i]->mark_as_failed(entry.first, st.to_string());
2296
0
            }
2297
41.8k
        }
2298
42.1k
    }
2299
2300
    // check intolerable failure
2301
42.1k
    for (const auto& index_channel : _channels) {
2302
42.1k
        RETURN_IF_ERROR(index_channel->check_intolerable_failure());
2303
42.1k
    }
2304
2305
41.1k
    g_sink_write_bytes << bytes;
2306
41.1k
    g_sink_write_rows << rows;
2307
41.1k
    return Status::OK();
2308
41.1k
}
2309
2310
} // namespace doris