Coverage Report

Created: 2026-08-07 13:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/load/channel/tablets_channel.cpp
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#include "load/channel/tablets_channel.h"
19
20
#include <bvar/bvar.h>
21
#include <fmt/format.h>
22
#include <gen_cpp/internal_service.pb.h>
23
#include <gen_cpp/types.pb.h>
24
25
#include <ctime>
26
27
#include "common/compiler_util.h" // IWYU pragma: keep
28
#include "common/status.h"
29
// IWYU pragma: no_include <bits/chrono.h>
30
#include <chrono> // IWYU pragma: keep
31
#include <initializer_list>
32
#include <optional>
33
#include <set>
34
#include <thread>
35
#include <utility>
36
37
#ifdef DEBUG
38
#include <unordered_set>
39
#endif
40
41
#include "common/logging.h"
42
#include "common/metrics/doris_metrics.h"
43
#include "common/metrics/metrics.h"
44
#include "core/block/block.h"
45
#include "load/channel/load_channel.h"
46
#include "load/delta_writer/delta_writer.h"
47
#include "storage/storage_engine.h"
48
#include "storage/tablet/tablet_manager.h"
49
#include "storage/tablet_info.h"
50
#include "storage/txn/txn_manager.h"
51
#include "util/defer_op.h"
52
53
namespace doris {
54
class SlotDescriptor;
55
56
bvar::Adder<int64_t> g_tablets_channel_send_data_allocated_size(
57
        "tablets_channel_send_data_allocated_size");
58
59
DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(tablet_writer_count, MetricUnit::NOUNIT);
60
61
std::atomic<uint64_t> BaseTabletsChannel::_s_tablet_writer_count;
62
63
BaseTabletsChannel::BaseTabletsChannel(const TabletsChannelKey& key, const UniqueId& load_id,
64
                                       bool is_high_priority, RuntimeProfile* profile)
65
33.4k
        : _key(key),
66
33.4k
          _state(kInitialized),
67
33.4k
          _load_id(load_id),
68
33.4k
          _closed_senders(64),
69
33.4k
          _is_high_priority(is_high_priority) {
70
33.4k
    static std::once_flag once_flag;
71
33.4k
    if (profile != nullptr) {
72
8
        _init_profile(profile);
73
8
    }
74
33.4k
    std::call_once(once_flag, [] {
75
5
        REGISTER_HOOK_METRIC(tablet_writer_count, [&]() { return _s_tablet_writer_count.load(); });
76
5
    });
77
33.4k
}
78
79
TabletsChannel::TabletsChannel(StorageEngine& engine, const TabletsChannelKey& key,
80
                               const UniqueId& load_id, bool is_high_priority,
81
                               RuntimeProfile* profile)
82
3.20k
        : BaseTabletsChannel(key, load_id, is_high_priority, profile), _engine(engine) {}
83
84
33.4k
BaseTabletsChannel::~BaseTabletsChannel() {
85
33.4k
    _s_tablet_writer_count -= _tablet_writers.size();
86
33.4k
}
87
88
TabletsChannel::~TabletsChannel() = default;
89
90
Status BaseTabletsChannel::_get_current_seq(int64_t& cur_seq,
91
36.1k
                                            const PTabletWriterAddBlockRequest& request) {
92
36.1k
    std::lock_guard<std::mutex> l(_lock);
93
36.1k
    if (_state != kOpened) {
94
0
        return _state == kFinished ? _close_status
95
0
                                   : Status::InternalError("TabletsChannel {} state: {}",
96
0
                                                           _key.to_string(), _state);
97
0
    }
98
36.1k
    cur_seq = _next_seqs[request.sender_id()];
99
    // check packet
100
36.1k
    if (request.packet_seq() > cur_seq) {
101
0
        LOG(WARNING) << "lost data packet, expect_seq=" << cur_seq
102
0
                     << ", recept_seq=" << request.packet_seq();
103
0
        return Status::InternalError("lost data packet");
104
0
    }
105
36.1k
    return Status::OK();
106
36.1k
}
107
108
8
void BaseTabletsChannel::_init_profile(RuntimeProfile* profile) {
109
8
    DCHECK(profile != nullptr);
110
8
    _profile =
111
8
            profile->create_child(fmt::format("TabletsChannel {}", _key.to_string()), true, true);
112
8
    _add_batch_number_counter = ADD_COUNTER(_profile, "NumberBatchAdded", TUnit::UNIT);
113
114
8
    auto* memory_usage = _profile->create_child("PeakMemoryUsage", true, true);
115
8
    _add_batch_timer = ADD_TIMER(_profile, "AddBatchTime");
116
8
    _write_block_timer = ADD_TIMER(_profile, "WriteBlockTime");
117
8
    _incremental_open_timer = ADD_TIMER(_profile, "IncrementalOpenTabletTime");
118
8
    _memory_usage_counter = memory_usage->AddHighWaterMarkCounter("Total", TUnit::BYTES);
119
8
    _write_memory_usage_counter = memory_usage->AddHighWaterMarkCounter("Write", TUnit::BYTES);
120
8
    _flush_memory_usage_counter = memory_usage->AddHighWaterMarkCounter("Flush", TUnit::BYTES);
121
8
    _max_tablet_memory_usage_counter =
122
8
            memory_usage->AddHighWaterMarkCounter("MaxTablet", TUnit::BYTES);
123
8
    _max_tablet_write_memory_usage_counter =
124
8
            memory_usage->AddHighWaterMarkCounter("MaxTabletWrite", TUnit::BYTES);
125
8
    _max_tablet_flush_memory_usage_counter =
126
8
            memory_usage->AddHighWaterMarkCounter("MaxTabletFlush", TUnit::BYTES);
127
8
}
128
129
0
void TabletsChannel::_init_profile(RuntimeProfile* profile) {
130
0
    DCHECK(profile != nullptr);
131
0
    BaseTabletsChannel::_init_profile(profile);
132
0
    _slave_replica_timer = ADD_TIMER(_profile, "SlaveReplicaTime");
133
0
}
134
135
49.9k
Status BaseTabletsChannel::open(const PTabletWriterOpenRequest& request) {
136
49.9k
    std::lock_guard<std::mutex> l(_lock);
137
    // if _state is kOpened, it's a normal case, already open by other sender
138
    // if _state is kFinished, already cancelled by other sender
139
49.9k
    if (_state == kOpened) {
140
16.4k
        RETURN_IF_ERROR(_init_adaptive_random_bucket_state(request));
141
16.4k
        return Status::OK();
142
16.4k
    }
143
33.4k
    if (_state == kFinished) {
144
0
        return Status::OK();
145
0
    }
146
33.4k
    _txn_id = request.txn_id();
147
33.4k
    _index_id = request.index_id();
148
33.4k
    _schema = std::make_shared<OlapTableSchemaParam>();
149
33.4k
    RETURN_IF_ERROR(_schema->init(request.schema()));
150
33.4k
    _tuple_desc = _schema->tuple_desc();
151
33.4k
    int max_sender = request.num_senders();
152
    /*
153
     * a tablets channel in reciever is related to a bulk of VNodeChannel of sender. each instance one or none.
154
     * there are two possibilities:
155
     *  1. there's partitions originally broadcasted by FE. so all sender(instance) know it at start. and open() will be 
156
     *     called directly, not by incremental_open(). and after _state changes to kOpened. _open_by_incremental will never 
157
     *     be true. in this case, _num_remaining_senders will keep same with senders number. when all sender sent close rpc,
158
     *     the tablets channel will close. and if for auto partition table, these channel's closing will hang on reciever and
159
     *     return together to avoid close-then-incremental-open problem.
160
     *  2. this tablets channel is opened by incremental_open of sender's sink node. so only this sender will know this partition
161
     *     (this TabletsChannel) at that time. and we are not sure how many sender will know in the end. it depends on data
162
     *     distribution. in this situation open() is called by incremental_open() at first time. so _open_by_incremental is true.
163
     *     then _num_remaining_senders will not be set here. but inc every time when incremental_open() called. so it's dynamic
164
     *     and also need same number of senders' close to close. but will not hang.
165
     */
166
33.4k
    if (_open_by_incremental) {
167
0
        DCHECK(_num_remaining_senders == 0) << _num_remaining_senders;
168
33.4k
    } else {
169
33.4k
        _num_remaining_senders = max_sender;
170
33.4k
    }
171
33.4k
    LOG(INFO) << fmt::format(
172
33.4k
            "open tablets channel {}, tablets num: {} timeout(s): {}, init senders {} with "
173
33.4k
            "incremental {}",
174
33.4k
            _key.to_string(), request.tablets().size(), request.load_channel_timeout_s(),
175
33.4k
            _num_remaining_senders, _open_by_incremental ? "on" : "off");
176
    // just use max_sender no matter incremental or not cuz we dont know how many senders will open.
177
33.4k
    _next_seqs.resize(max_sender, 0);
178
33.4k
    _closed_senders.Reset(max_sender);
179
180
33.4k
    RETURN_IF_ERROR(_open_all_writers(request));
181
33.4k
    RETURN_IF_ERROR(_init_adaptive_random_bucket_state(request));
182
183
33.4k
    _state = kOpened;
184
33.4k
    return Status::OK();
185
33.4k
}
186
187
185
Status BaseTabletsChannel::incremental_open(const PTabletWriterOpenRequest& params) {
188
185
    SCOPED_TIMER(_incremental_open_timer);
189
190
    // current node first opened by incremental open
191
185
    if (_state == kInitialized) {
192
0
        _open_by_incremental = true;
193
0
        RETURN_IF_ERROR(open(params));
194
0
    }
195
196
185
    std::lock_guard<std::mutex> l(_lock);
197
198
    // one sender may incremental_open many times. but only close one time. so dont count duplicately.
199
185
    if (_open_by_incremental) {
200
0
        if (params.has_sender_id() && !_recieved_senders.contains(params.sender_id())) {
201
0
            _recieved_senders.insert(params.sender_id());
202
0
            _num_remaining_senders++;
203
0
        } else if (!params.has_sender_id()) { // for compatible
204
0
            _num_remaining_senders++;
205
0
        }
206
0
        VLOG_DEBUG << fmt::format("txn {}: TabletsChannel {} inc senders to {}", _txn_id, _index_id,
207
0
                                  _num_remaining_senders);
208
0
    }
209
210
185
    std::vector<SlotDescriptor*>* index_slots = nullptr;
211
185
    int32_t schema_hash = 0;
212
213
185
    for (const auto& index : _schema->indexes()) {
214
185
        if (index->index_id == _index_id) {
215
185
            index_slots = &index->slots;
216
185
            schema_hash = index->schema_hash;
217
185
            break;
218
185
        }
219
185
    }
220
185
    if (index_slots == nullptr) {
221
0
        return Status::InternalError("unknown index id, key={}", _key.to_string());
222
0
    }
223
    // update tablets
224
185
    size_t incremental_tablet_num = 0;
225
185
    std::stringstream ss;
226
185
    ss << "LocalTabletsChannel txn_id: " << _txn_id << " load_id: " << print_id(params.id())
227
185
       << " incremental open delta writer: ";
228
229
    // every change will hold _lock. this find in under _lock too. so no need _tablet_writers_lock again.
230
6.64k
    for (const auto& tablet : params.tablets()) {
231
6.64k
        if (_tablet_writers.find(tablet.tablet_id()) != _tablet_writers.end()) {
232
96
            continue;
233
96
        }
234
6.55k
        incremental_tablet_num++;
235
236
6.55k
        WriteRequest wrequest;
237
6.55k
        wrequest.index_id = params.index_id();
238
6.55k
        wrequest.tablet_id = tablet.tablet_id();
239
6.55k
        wrequest.schema_hash = schema_hash;
240
6.55k
        wrequest.txn_id = _txn_id;
241
6.55k
        wrequest.partition_id = tablet.partition_id();
242
6.55k
        wrequest.load_id = params.id();
243
6.55k
        wrequest.tuple_desc = _tuple_desc;
244
6.55k
        wrequest.slots = index_slots;
245
6.55k
        wrequest.is_high_priority = _is_high_priority;
246
6.55k
        wrequest.table_schema_param = _schema;
247
6.55k
        wrequest.txn_expiration = params.txn_expiration(); // Required by CLOUD.
248
6.55k
        wrequest.write_file_cache = params.write_file_cache();
249
6.55k
        wrequest.storage_vault_id = params.storage_vault_id();
250
6.55k
        wrequest.enable_table_memtable_backpressure = params.is_adaptive_random_bucket();
251
6.55k
        if (tablet.has_binlog_tablet_id()) {
252
0
            wrequest.binlog_tablet_id = tablet.binlog_tablet_id();
253
0
        }
254
255
6.55k
        auto delta_writer = create_delta_writer(wrequest);
256
6.55k
        {
257
            // here we modify _tablet_writers. so need lock.
258
6.55k
            std::lock_guard<std::mutex> lt(_tablet_writers_lock);
259
6.55k
            _tablet_writers.emplace(tablet.tablet_id(), std::move(delta_writer));
260
6.55k
        }
261
262
6.55k
        ss << "[" << tablet.tablet_id() << "]";
263
6.55k
    }
264
265
185
    _s_tablet_writer_count += incremental_tablet_num;
266
185
    LOG(INFO) << ss.str();
267
185
    RETURN_IF_ERROR(_init_adaptive_random_bucket_state(params));
268
269
185
    _state = kOpened;
270
185
    return Status::OK();
271
185
}
272
273
Status BaseTabletsChannel::_init_adaptive_random_bucket_state(
274
50.1k
        const PTabletWriterOpenRequest& request) {
275
50.1k
    if (!request.is_adaptive_random_bucket() || request.random_bucket_partitions_size() == 0) {
276
40.2k
        return Status::OK();
277
40.2k
    }
278
9.90k
    if (_adaptive_random_bucket_state == nullptr) {
279
2.66k
        _adaptive_random_bucket_state = std::make_shared<AdaptiveRandomBucketState>(_load_id);
280
2.66k
    }
281
10.1k
    for (const auto& partition : request.random_bucket_partitions()) {
282
10.1k
        if (partition.ordered_tablet_ids_size() == 0) {
283
0
            return Status::InternalError(
284
0
                    "ordered_tablet_ids is empty for adaptive random bucket, load_id={}, "
285
0
                    "sender_id={}, partition_id={}",
286
0
                    print_id(_load_id), request.sender_id(), partition.partition_id());
287
0
        }
288
10.1k
        std::vector<int32_t> ordered_positions;
289
10.1k
        ordered_positions.reserve(partition.ordered_tablet_ids_size());
290
45.8k
        for (int i = 0; i < partition.ordered_tablet_ids_size(); ++i) {
291
35.7k
            ordered_positions.push_back(cast_set<int32_t>(i));
292
35.7k
        }
293
10.1k
        std::vector<int64_t> ordered_tablet_ids;
294
10.1k
        ordered_tablet_ids.reserve(partition.ordered_tablet_ids_size());
295
35.7k
        for (auto tablet_id : partition.ordered_tablet_ids()) {
296
35.7k
            ordered_tablet_ids.push_back(tablet_id);
297
35.7k
        }
298
10.1k
        RETURN_IF_ERROR(_adaptive_random_bucket_state->init_partition(
299
10.1k
                partition.partition_id(), ordered_tablet_ids, ordered_positions, 0));
300
10.1k
    }
301
9.90k
    return Status::OK();
302
9.90k
}
303
304
29.6k
std::unique_ptr<BaseDeltaWriter> TabletsChannel::create_delta_writer(const WriteRequest& request) {
305
29.6k
    DCHECK(request.write_req_type == WriteRequestType::DATA);
306
29.6k
    DCHECK(request.table_schema_param != nullptr);
307
308
    // whether to write binlog is decided by binlog_tablet_id: it is set only when this backend
309
    // also owns the binlog tablet that is paired with the base tablet.
310
29.6k
    if (request.binlog_tablet_id <= 0) {
311
29.6k
        return std::make_unique<DeltaWriter>(_engine, request, _profile, _load_id);
312
29.6k
    }
313
314
0
    int64_t row_binlog_index_id = 0;
315
0
    for (const auto* index_schema : request.table_schema_param->indexes()) {
316
0
        if (index_schema->index_id == request.index_id) {
317
0
            row_binlog_index_id = index_schema->row_binlog_id;
318
0
            break;
319
0
        }
320
0
    }
321
0
    DCHECK(row_binlog_index_id > 0);
322
323
0
    const auto* row_binlog_index_schema =
324
0
            request.table_schema_param->row_binlog_index_schema(row_binlog_index_id);
325
0
    DCHECK(row_binlog_index_schema != nullptr);
326
327
    // group_build_req is only for the group wrapper itself. It provides the group semantics and
328
    // metadata used by BaseDeltaWriter/GroupRowsetBuilder to expose tablet_id, txn_id,
329
    // partition_id, load_id and profile information, while concrete rowset builders use the
330
    // sub requests below.
331
0
    WriteRequest group_build_req = request;
332
0
    group_build_req.write_req_type = WriteRequestType::GROUP;
333
334
0
    WriteRequest sub_data_req = request;
335
0
    sub_data_req.write_req_type = WriteRequestType::DATA;
336
337
0
    WriteRequest sub_row_binlog_req = request;
338
0
    sub_row_binlog_req.write_req_type = WriteRequestType::ROW_BINLOG;
339
0
    sub_row_binlog_req.tablet_id = request.binlog_tablet_id;
340
0
    sub_row_binlog_req.index_id = row_binlog_index_schema->index_id;
341
0
    sub_row_binlog_req.schema_hash = row_binlog_index_schema->schema_hash;
342
343
0
    return std::make_unique<DeltaWriter>(_engine, group_build_req, sub_data_req, sub_row_binlog_req,
344
0
                                         _profile, _load_id);
345
29.6k
}
346
347
Status TabletsChannel::close(LoadChannel* parent, const PTabletWriterAddBlockRequest& req,
348
4.63k
                             PTabletWriterAddBlockResult* res, bool* finished) {
349
4.63k
    int sender_id = req.sender_id();
350
4.63k
    int64_t backend_id = req.backend_id();
351
4.63k
    const auto& partition_ids = req.partition_ids();
352
4.63k
    auto* tablet_errors = res->mutable_tablet_errors();
353
4.63k
    std::lock_guard<std::mutex> l(_lock);
354
4.63k
    if (_state == kFinished) {
355
0
        return _close_status;
356
0
    }
357
4.63k
    if (_closed_senders.Get(sender_id)) {
358
        // Double close from one sender, just return OK
359
0
        *finished = (_num_remaining_senders == 0);
360
0
        return _close_status;
361
0
    }
362
363
187k
    for (auto pid : partition_ids) {
364
187k
        _partition_ids.emplace(pid);
365
187k
    }
366
4.63k
    _closed_senders.Set(sender_id, true);
367
4.63k
    _num_remaining_senders--;
368
4.63k
    *finished = (_num_remaining_senders == 0);
369
370
4.63k
    LOG(INFO) << fmt::format(
371
4.63k
            "txn {}: close tablets channel of index {} , sender id: {}, backend {}, remain "
372
4.63k
            "senders: {}",
373
4.63k
            _txn_id, _index_id, sender_id, backend_id, _num_remaining_senders);
374
375
4.63k
    if (!*finished) {
376
1.42k
        return Status::OK();
377
1.42k
    }
378
379
3.20k
    _state = kFinished;
380
    // All senders are closed
381
    // 1. close all delta writers
382
3.20k
    std::set<DeltaWriter*> need_wait_writers;
383
    // under _lock. no need _tablet_writers_lock again.
384
29.6k
    for (auto&& [tablet_id, writer] : _tablet_writers) {
385
29.6k
        if (_partition_ids.contains(writer->partition_id())) {
386
27.9k
            auto st = writer->close();
387
27.9k
            if (!st.ok()) {
388
0
                auto err_msg = fmt::format(
389
0
                        "close tablet writer failed, tablet_id={}, "
390
0
                        "transaction_id={}, err={}",
391
0
                        tablet_id, _txn_id, st.to_string());
392
0
                LOG(WARNING) << err_msg;
393
0
                PTabletError* tablet_error = tablet_errors->Add();
394
0
                tablet_error->set_tablet_id(tablet_id);
395
0
                tablet_error->set_msg(st.to_string());
396
                // just skip this tablet(writer) and continue to close others
397
0
                continue;
398
0
            }
399
            // tablet writer in `_broken_tablets` should not call `build_rowset` and
400
            // `commit_txn` method, after that, the publish-version task will success,
401
            // which can cause the replica inconsistency.
402
27.9k
            if (_is_broken_tablet(writer->tablet_id())) {
403
0
                LOG(WARNING) << "SHOULD NOT HAPPEN, tablet writer is broken but not cancelled"
404
0
                             << ", tablet_id=" << tablet_id << ", transaction_id=" << _txn_id;
405
0
                continue;
406
0
            }
407
27.9k
            need_wait_writers.insert(static_cast<DeltaWriter*>(writer.get()));
408
27.9k
        } else {
409
1.66k
            auto st = writer->cancel();
410
1.66k
            if (!st.ok()) {
411
0
                LOG(WARNING) << "cancel tablet writer failed, tablet_id=" << tablet_id
412
0
                             << ", transaction_id=" << _txn_id;
413
                // just skip this tablet(writer) and continue to close others
414
0
                continue;
415
0
            }
416
1.66k
            VLOG_PROGRESS << "cancel tablet writer successfully, tablet_id=" << tablet_id
417
0
                          << ", transaction_id=" << _txn_id;
418
1.66k
        }
419
29.6k
    }
420
421
3.20k
    _write_single_replica = req.write_single_replica();
422
423
    // 2. wait all writer finished flush.
424
27.9k
    for (auto* writer : need_wait_writers) {
425
27.9k
        RETURN_IF_ERROR((writer->wait_flush()));
426
27.9k
    }
427
428
    // 3. build rowset
429
31.1k
    for (auto it = need_wait_writers.begin(); it != need_wait_writers.end();) {
430
27.9k
        Status st = (*it)->build_rowset();
431
27.9k
        if (!st.ok()) {
432
0
            _add_error_tablet(tablet_errors, (*it)->tablet_id(), st);
433
0
            it = need_wait_writers.erase(it);
434
0
            continue;
435
0
        }
436
        // 3.1 calculate delete bitmap for Unique Key MoW tables
437
27.9k
        st = (*it)->submit_calc_delete_bitmap_task();
438
27.9k
        if (!st.ok()) {
439
0
            _add_error_tablet(tablet_errors, (*it)->tablet_id(), st);
440
0
            it = need_wait_writers.erase(it);
441
0
            continue;
442
0
        }
443
27.9k
        it++;
444
27.9k
    }
445
446
    // 4. wait for delete bitmap calculation complete if necessary
447
31.1k
    for (auto it = need_wait_writers.begin(); it != need_wait_writers.end();) {
448
27.9k
        Status st = (*it)->wait_calc_delete_bitmap();
449
27.9k
        if (!st.ok()) {
450
0
            _add_error_tablet(tablet_errors, (*it)->tablet_id(), st);
451
0
            it = need_wait_writers.erase(it);
452
0
            continue;
453
0
        }
454
27.9k
        it++;
455
27.9k
    }
456
457
    // 5. commit all writers
458
459
27.9k
    for (auto* writer : need_wait_writers) {
460
        // close may return failed, but no need to handle it here.
461
        // tablet_vec will only contains success tablet, and then let FE judge it.
462
27.9k
        _commit_txn(writer, req, res);
463
27.9k
    }
464
465
3.20k
    if (_write_single_replica) {
466
0
        auto* success_slave_tablet_node_ids = res->mutable_success_slave_tablet_node_ids();
467
        // The operation waiting for all slave replicas to complete must end before the timeout,
468
        // so that there is enough time to collect completed replica. Otherwise, the task may
469
        // timeout and fail even though most of the replicas are completed. Here we set 0.9
470
        // times the timeout as the maximum waiting time.
471
0
        SCOPED_TIMER(_slave_replica_timer);
472
0
        while (!need_wait_writers.empty() &&
473
0
               (time(nullptr) - parent->last_updated_time()) < (parent->timeout() * 0.9)) {
474
0
            std::set<DeltaWriter*>::iterator it;
475
0
            for (it = need_wait_writers.begin(); it != need_wait_writers.end();) {
476
0
                bool is_done = (*it)->check_slave_replicas_done(success_slave_tablet_node_ids);
477
0
                if (is_done) {
478
0
                    need_wait_writers.erase(it++);
479
0
                } else {
480
0
                    it++;
481
0
                }
482
0
            }
483
0
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
484
0
        }
485
0
        for (auto* writer : need_wait_writers) {
486
0
            writer->add_finished_slave_replicas(success_slave_tablet_node_ids);
487
0
        }
488
0
        _engine.txn_manager()->clear_txn_tablet_delta_writer(_txn_id);
489
0
    }
490
491
3.20k
    return Status::OK();
492
3.20k
}
493
494
void TabletsChannel::_commit_txn(DeltaWriter* writer, const PTabletWriterAddBlockRequest& req,
495
27.9k
                                 PTabletWriterAddBlockResult* res) {
496
27.9k
    PSlaveTabletNodes slave_nodes;
497
27.9k
    if (_write_single_replica) {
498
0
        auto& nodes_map = req.slave_tablet_nodes();
499
0
        auto it = nodes_map.find(writer->tablet_id());
500
0
        if (it != nodes_map.end()) {
501
0
            slave_nodes = it->second;
502
0
        }
503
0
    }
504
27.9k
    Status st = writer->commit_txn(slave_nodes);
505
27.9k
    if (st.ok()) [[likely]] {
506
27.9k
        auto* tablet_vec = res->mutable_tablet_vec();
507
27.9k
        PTabletInfo* tablet_info = tablet_vec->Add();
508
27.9k
        tablet_info->set_tablet_id(writer->tablet_id());
509
        // unused required field.
510
27.9k
        tablet_info->set_schema_hash(0);
511
27.9k
        tablet_info->set_received_rows(writer->total_received_rows());
512
27.9k
        tablet_info->set_num_rows_filtered(writer->num_rows_filtered());
513
        // report the row binlog tablet as a normal tablet so FE advances its version.
514
27.9k
        if (writer->binlog_tablet_id() > 0) {
515
0
            PTabletInfo* binlog_tablet_info = tablet_vec->Add();
516
0
            binlog_tablet_info->set_tablet_id(writer->binlog_tablet_id());
517
0
            binlog_tablet_info->set_schema_hash(0);
518
0
            binlog_tablet_info->set_received_rows(writer->total_received_rows());
519
0
            binlog_tablet_info->set_num_rows_filtered(writer->num_rows_filtered());
520
0
        }
521
27.9k
        _total_received_rows += writer->total_received_rows();
522
27.9k
        _num_rows_filtered += writer->num_rows_filtered();
523
27.9k
    } else {
524
0
        _add_error_tablet(res->mutable_tablet_errors(), writer->tablet_id(), st);
525
0
    }
526
27.9k
}
527
528
void BaseTabletsChannel::_add_error_tablet(
529
        google::protobuf::RepeatedPtrField<PTabletError>* tablet_errors, int64_t tablet_id,
530
0
        Status error) const {
531
0
    PTabletError* tablet_error = tablet_errors->Add();
532
0
    tablet_error->set_tablet_id(tablet_id);
533
0
    tablet_error->set_msg(error.to_string());
534
0
    VLOG_PROGRESS << "close wait failed tablet " << tablet_id << " transaction_id " << _txn_id
535
0
                  << "err msg " << error;
536
0
}
537
538
58
void BaseTabletsChannel::refresh_profile() {
539
58
    int64_t write_mem_usage = 0;
540
58
    int64_t flush_mem_usage = 0;
541
58
    int64_t max_tablet_mem_usage = 0;
542
58
    int64_t max_tablet_write_mem_usage = 0;
543
58
    int64_t max_tablet_flush_mem_usage = 0;
544
58
    {
545
58
        std::lock_guard<std::mutex> l(_tablet_writers_lock);
546
277
        for (auto&& [tablet_id, writer] : _tablet_writers) {
547
277
            int64_t write_mem = writer->mem_consumption(MemType::WRITE_FINISHED);
548
277
            write_mem_usage += write_mem;
549
277
            int64_t flush_mem = writer->mem_consumption(MemType::FLUSH);
550
277
            flush_mem_usage += flush_mem;
551
277
            if (write_mem > max_tablet_write_mem_usage) {
552
0
                max_tablet_write_mem_usage = write_mem;
553
0
            }
554
277
            if (flush_mem > max_tablet_flush_mem_usage) {
555
0
                max_tablet_flush_mem_usage = flush_mem;
556
0
            }
557
277
            if (write_mem + flush_mem > max_tablet_mem_usage) {
558
0
                max_tablet_mem_usage = write_mem + flush_mem;
559
0
            }
560
277
        }
561
58
    }
562
58
    COUNTER_SET(_memory_usage_counter, write_mem_usage + flush_mem_usage);
563
58
    COUNTER_SET(_write_memory_usage_counter, write_mem_usage);
564
58
    COUNTER_SET(_flush_memory_usage_counter, flush_mem_usage);
565
58
    COUNTER_SET(_max_tablet_memory_usage_counter, max_tablet_mem_usage);
566
58
    COUNTER_SET(_max_tablet_write_memory_usage_counter, max_tablet_write_mem_usage);
567
58
    COUNTER_SET(_max_tablet_flush_memory_usage_counter, max_tablet_flush_mem_usage);
568
58
}
569
570
33.4k
Status BaseTabletsChannel::_open_all_writers(const PTabletWriterOpenRequest& request) {
571
33.4k
    std::vector<SlotDescriptor*>* index_slots = nullptr;
572
33.4k
    int32_t schema_hash = 0;
573
37.4k
    for (const auto& index : _schema->indexes()) {
574
37.4k
        if (index->index_id == _index_id) {
575
33.4k
            index_slots = &index->slots;
576
33.4k
            schema_hash = index->schema_hash;
577
33.4k
            break;
578
33.4k
        }
579
37.4k
    }
580
33.4k
    if (index_slots == nullptr) {
581
0
        return Status::InternalError("unknown index id, key={}", _key.to_string());
582
0
    }
583
584
#ifdef DEBUG
585
    // check: tablet ids should be unique
586
    {
587
        std::unordered_set<int64_t> tablet_ids;
588
        for (const auto& tablet : request.tablets()) {
589
            CHECK(tablet_ids.count(tablet.tablet_id()) == 0)
590
                    << "found duplicate tablet id: " << tablet.tablet_id();
591
            tablet_ids.insert(tablet.tablet_id());
592
        }
593
    }
594
#endif
595
596
33.4k
    int tablet_cnt = 0;
597
    // under _lock. no need _tablet_writers_lock again.
598
289k
    for (const auto& tablet : request.tablets()) {
599
289k
        if (_tablet_writers.find(tablet.tablet_id()) != _tablet_writers.end()) {
600
0
            continue;
601
0
        }
602
289k
        tablet_cnt++;
603
289k
        WriteRequest wrequest {
604
289k
                .tablet_id = tablet.tablet_id(),
605
289k
                .schema_hash = schema_hash,
606
289k
                .txn_id = _txn_id,
607
289k
                .txn_expiration = request.txn_expiration(), // Required by CLOUD.
608
289k
                .index_id = request.index_id(),
609
289k
                .partition_id = tablet.partition_id(),
610
289k
                .load_id = request.id(),
611
289k
                .tuple_desc = _tuple_desc,
612
289k
                .slots = index_slots,
613
289k
                .table_schema_param = _schema,
614
289k
                .is_high_priority = _is_high_priority,
615
289k
                .write_file_cache = request.write_file_cache(),
616
289k
                .storage_vault_id = request.storage_vault_id(),
617
289k
                .enable_table_memtable_backpressure = request.is_adaptive_random_bucket(),
618
289k
        };
619
289k
        if (tablet.has_binlog_tablet_id()) {
620
0
            wrequest.binlog_tablet_id = tablet.binlog_tablet_id();
621
0
        }
622
623
289k
        auto delta_writer = create_delta_writer(wrequest);
624
289k
        {
625
289k
            std::lock_guard<std::mutex> l(_tablet_writers_lock);
626
289k
            _tablet_writers.emplace(tablet.tablet_id(), std::move(delta_writer));
627
289k
        }
628
289k
    }
629
33.4k
    _s_tablet_writer_count += _tablet_writers.size();
630
33.4k
    DCHECK_EQ(_tablet_writers.size(), tablet_cnt);
631
33.4k
    return Status::OK();
632
33.4k
}
633
634
122
Status BaseTabletsChannel::cancel() {
635
122
    std::lock_guard<std::mutex> l(_lock);
636
122
    if (_state == kFinished) {
637
84
        return _close_status;
638
84
    }
639
1.46k
    for (auto& it : _tablet_writers) {
640
1.46k
        static_cast<void>(it.second->cancel());
641
1.46k
    }
642
38
    _state = kFinished;
643
644
38
    return Status::OK();
645
122
}
646
647
0
Status TabletsChannel::cancel() {
648
0
    RETURN_IF_ERROR(BaseTabletsChannel::cancel());
649
0
    if (_write_single_replica) {
650
0
        _engine.txn_manager()->clear_txn_tablet_delta_writer(_txn_id);
651
0
    }
652
0
    return Status::OK();
653
0
}
654
655
33.4k
std::string TabletsChannelKey::to_string() const {
656
33.4k
    std::stringstream ss;
657
33.4k
    ss << *this;
658
33.4k
    return ss.str();
659
33.4k
}
660
661
78.6k
std::ostream& operator<<(std::ostream& os, const TabletsChannelKey& key) {
662
78.6k
    os << "(load_id=" << key.id << ", index_id=" << key.index_id << ")";
663
78.6k
    return os;
664
78.6k
}
665
666
Status BaseTabletsChannel::_write_block_data(
667
        const PTabletWriterAddBlockRequest& request, int64_t cur_seq,
668
        std::unordered_map<int64_t, TabletAddRowsPayload>& tablet_to_rows,
669
31.7k
        PTabletWriterAddBlockResult* response) {
670
31.7k
    Block send_data;
671
31.7k
    [[maybe_unused]] size_t uncompressed_size = 0;
672
31.7k
    [[maybe_unused]] int64_t uncompressed_time = 0;
673
31.7k
    RETURN_IF_ERROR(send_data.deserialize(request.block(), &uncompressed_size, &uncompressed_time));
674
31.7k
    if (send_data.rows() != request.tablet_ids_size()) {
675
0
        return Status::InternalError(
676
0
                "invalid add block request row count, load_id={}, index_id={}, packet_seq={}, "
677
0
                "block_rows={}, tablet_ids_size={}",
678
0
                print_id(_load_id), _index_id, request.packet_seq(), send_data.rows(),
679
0
                request.tablet_ids_size());
680
0
    }
681
31.7k
    bool has_row_binlog_lsn = request.row_binlog_lsns_size() > 0;
682
31.7k
    if (has_row_binlog_lsn) {
683
0
        if (send_data.rows() != request.row_binlog_lsns_size()) {
684
0
            return Status::InternalError(
685
0
                    "invalid add block request row-binlog lsn count, load_id={}, index_id={}, "
686
0
                    "packet_seq={}, block_rows={}, row_binlog_lsns_size={}",
687
0
                    print_id(_load_id), _index_id, request.packet_seq(), send_data.rows(),
688
0
                    request.row_binlog_lsns_size());
689
0
        }
690
0
    }
691
692
31.7k
    g_tablets_channel_send_data_allocated_size << send_data.allocated_bytes();
693
31.7k
    Defer defer {
694
31.8k
            [&]() { g_tablets_channel_send_data_allocated_size << -send_data.allocated_bytes(); }};
695
696
31.7k
    auto write_tablet_data = [&](int64_t tablet_id,
697
125k
                                 std::function<Status(BaseDeltaWriter * writer)> write_func) {
698
125k
        google::protobuf::RepeatedPtrField<PTabletError>* tablet_errors =
699
125k
                response->mutable_tablet_errors();
700
701
        // add_batch may concurrency with inc_open but not under _lock.
702
        // so need to protect it with _tablet_writers_lock.
703
125k
        BaseDeltaWriter* tablet_writer = nullptr;
704
125k
        {
705
125k
            std::lock_guard<std::mutex> l(_tablet_writers_lock);
706
125k
            auto tablet_writer_it = _tablet_writers.find(tablet_id);
707
125k
            if (tablet_writer_it == _tablet_writers.end()) {
708
0
                return Status::InternalError("unknown tablet to append data, tablet={}", tablet_id);
709
0
            }
710
125k
            tablet_writer = tablet_writer_it->second.get();
711
125k
        }
712
713
0
        Status st = write_func(tablet_writer);
714
125k
        if (!st.ok()) {
715
0
            auto err_msg =
716
0
                    fmt::format("tablet writer write failed, tablet_id={}, txn_id={}, err={}",
717
0
                                tablet_id, _txn_id, st.to_string());
718
0
            LOG(WARNING) << err_msg;
719
0
            PTabletError* error = tablet_errors->Add();
720
0
            error->set_tablet_id(tablet_id);
721
0
            error->set_msg(err_msg);
722
0
            static_cast<void>(tablet_writer->cancel_with_status(st));
723
0
            _add_broken_tablet(tablet_id);
724
            // continue write to other tablet.
725
            // the error will return back to sender.
726
0
        }
727
125k
        return Status::OK();
728
125k
    };
729
730
31.7k
    SCOPED_TIMER(_write_block_timer);
731
31.7k
    auto* tablet_load_infos = response->mutable_tablet_load_rowset_num_infos();
732
125k
    for (const auto& tablet_to_rows_it : tablet_to_rows) {
733
125k
        bool memtable_flushed = false;
734
125k
        RETURN_IF_ERROR(write_tablet_data(tablet_to_rows_it.first, [&](BaseDeltaWriter* writer) {
735
125k
            return writer->write(&send_data, tablet_to_rows_it.second, &memtable_flushed);
736
125k
        }));
737
738
125k
        BaseDeltaWriter* tablet_writer = nullptr;
739
125k
        {
740
125k
            std::lock_guard<std::mutex> l(_tablet_writers_lock);
741
125k
            auto tablet_writer_it = _tablet_writers.find(tablet_to_rows_it.first);
742
125k
            if (tablet_writer_it != _tablet_writers.end()) {
743
125k
                tablet_writer = tablet_writer_it->second.get();
744
125k
            }
745
125k
        }
746
125k
        if (tablet_writer != nullptr) {
747
125k
            tablet_writer->set_tablet_load_rowset_num_info(tablet_load_infos);
748
125k
        }
749
125k
    }
750
751
31.7k
    {
752
31.7k
        std::lock_guard<std::mutex> l(_lock);
753
31.7k
        _next_seqs[request.sender_id()] = cur_seq + 1;
754
31.7k
    }
755
31.7k
    return Status::OK();
756
31.7k
}
757
758
4.42k
std::shared_ptr<std::mutex> BaseTabletsChannel::_get_partition_route_lock(int64_t partition_id) {
759
4.42k
    std::lock_guard<std::mutex> l(_partition_route_locks_lock);
760
4.42k
    auto& lock = _partition_route_locks[partition_id];
761
4.42k
    if (lock == nullptr) {
762
2.71k
        lock = std::make_shared<std::mutex>();
763
2.71k
    }
764
4.42k
    return lock;
765
4.42k
}
766
767
Status BaseTabletsChannel::_write_block_data_for_adaptive_random_bucket(
768
        const PTabletWriterAddBlockRequest& request, int64_t cur_seq,
769
        std::unordered_map<int64_t, DorisVector<uint32_t>>& partition_to_rowidxs,
770
4.31k
        PTabletWriterAddBlockResult* response) {
771
4.31k
    Block send_data;
772
4.31k
    [[maybe_unused]] size_t uncompressed_size = 0;
773
4.31k
    [[maybe_unused]] int64_t uncompressed_time = 0;
774
4.31k
    RETURN_IF_ERROR(send_data.deserialize(request.block(), &uncompressed_size, &uncompressed_time));
775
4.31k
    if (send_data.rows() != request.partition_ids_size()) {
776
0
        return Status::InternalError(
777
0
                "invalid adaptive random bucket add block request row count, load_id={}, "
778
0
                "index_id={}, packet_seq={}, block_rows={}, partition_ids_size={}",
779
0
                print_id(_load_id), _index_id, request.packet_seq(), send_data.rows(),
780
0
                request.partition_ids_size());
781
0
    }
782
783
4.31k
    {
784
4.31k
        std::lock_guard<std::mutex> l(_lock);
785
4.42k
        for (const auto& [partition_id, _] : partition_to_rowidxs) {
786
4.42k
            _partition_ids.emplace(partition_id);
787
4.42k
        }
788
4.31k
    }
789
790
4.31k
    g_tablets_channel_send_data_allocated_size << send_data.allocated_bytes();
791
4.31k
    Defer defer {
792
4.31k
            [&]() { g_tablets_channel_send_data_allocated_size << -send_data.allocated_bytes(); }};
793
794
4.31k
    auto* tablet_errors = response->mutable_tablet_errors();
795
4.31k
    auto* tablet_load_infos = response->mutable_tablet_load_rowset_num_infos();
796
797
4.31k
    auto write_partition_data = [&](int64_t partition_id,
798
4.42k
                                    const DorisVector<uint32_t>& row_idxs) -> Status {
799
4.42k
        auto partition_lock = _get_partition_route_lock(partition_id);
800
4.42k
        std::lock_guard<std::mutex> partition_guard(*partition_lock);
801
4.42k
        int64_t tablet_id = -1;
802
4.42k
        if (_adaptive_random_bucket_state == nullptr) {
803
0
            return Status::InternalError(
804
0
                    "adaptive random bucket state is not initialized, load_id={}, "
805
0
                    "index_id={}, packet_seq={}, partition_id={}",
806
0
                    print_id(_load_id), _index_id, request.packet_seq(), partition_id);
807
0
        }
808
4.42k
        tablet_id = _adaptive_random_bucket_state->current_tablet(partition_id);
809
4.42k
        if (tablet_id < 0) {
810
0
            return Status::InternalError(
811
0
                    "invalid current tablet for adaptive random bucket, load_id={}, "
812
0
                    "index_id={}, sender_id={}, packet_seq={}, partition_id={}",
813
0
                    print_id(_load_id), _index_id, request.sender_id(), request.packet_seq(),
814
0
                    partition_id);
815
0
        }
816
4.42k
        VLOG_DEBUG << "FIND_TABLET_RANDOM_BUCKET: route+write begin"
817
0
                   << ", load_id=" << _load_id << ", index_id=" << _index_id
818
0
                   << ", sender_id=" << request.sender_id()
819
0
                   << ", packet_seq=" << request.packet_seq() << ", partition_id=" << partition_id
820
0
                   << ", tablet_id=" << tablet_id << ", row_count=" << row_idxs.size();
821
822
4.42k
        {
823
4.42k
            std::shared_lock<std::shared_mutex> broken_rlock(_broken_tablets_lock);
824
4.42k
            if (_is_broken_tablet(tablet_id)) {
825
0
                return Status::InternalError(
826
0
                        "current tablet is broken for adaptive random bucket, load_id={}, "
827
0
                        "index_id={}, sender_id={}, packet_seq={}, partition_id={}, tablet_id={}",
828
0
                        print_id(_load_id), _index_id, request.sender_id(), request.packet_seq(),
829
0
                        partition_id, tablet_id);
830
0
            }
831
4.42k
        }
832
833
4.42k
        BaseDeltaWriter* tablet_writer = nullptr;
834
4.42k
        {
835
4.42k
            std::lock_guard<std::mutex> l(_tablet_writers_lock);
836
4.42k
            auto tablet_writer_it = _tablet_writers.find(tablet_id);
837
4.42k
            if (tablet_writer_it == _tablet_writers.end()) {
838
0
                return Status::InternalError("unknown tablet to append data, tablet={}", tablet_id);
839
0
            }
840
4.42k
            tablet_writer = tablet_writer_it->second.get();
841
4.42k
        }
842
4.42k
        RETURN_IF_ERROR(_prepare_adaptive_random_bucket_writer(tablet_writer));
843
844
4.42k
        TabletAddRowsPayload rows {.row_idxs = row_idxs};
845
4.42k
        if (request.row_binlog_lsns_size() > 0) {
846
0
            rows.row_binlog_lsns.reserve(row_idxs.size());
847
0
            for (auto row_idx : row_idxs) {
848
0
                rows.row_binlog_lsns.emplace_back(request.row_binlog_lsns(row_idx));
849
0
            }
850
0
        }
851
4.42k
        bool memtable_flushed = false;
852
4.42k
        Status st = tablet_writer->write(&send_data, rows, &memtable_flushed);
853
4.42k
        if (!st.ok()) {
854
0
            auto err_msg =
855
0
                    fmt::format("tablet writer write failed, tablet_id={}, txn_id={}, err={}",
856
0
                                tablet_id, _txn_id, st.to_string());
857
0
            LOG(WARNING) << err_msg;
858
0
            PTabletError* error = tablet_errors->Add();
859
0
            error->set_tablet_id(tablet_id);
860
0
            error->set_msg(err_msg);
861
0
            static_cast<void>(tablet_writer->cancel_with_status(st));
862
0
            _add_broken_tablet(tablet_id);
863
0
            return Status::OK();
864
0
        }
865
866
4.42k
        VLOG_DEBUG << "FIND_TABLET_RANDOM_BUCKET: route+write done"
867
0
                   << ", load_id=" << _load_id << ", index_id=" << _index_id
868
0
                   << ", sender_id=" << request.sender_id()
869
0
                   << ", packet_seq=" << request.packet_seq() << ", partition_id=" << partition_id
870
0
                   << ", tablet_id=" << tablet_id << ", row_count=" << row_idxs.size()
871
0
                   << ", memtable_flushed=" << memtable_flushed;
872
4.42k
        if (memtable_flushed) {
873
0
            _adaptive_random_bucket_state->rotate_by_tablet(partition_id, tablet_id);
874
0
        }
875
4.42k
        tablet_writer->set_tablet_load_rowset_num_info(tablet_load_infos);
876
4.42k
        return Status::OK();
877
4.42k
    };
878
879
4.31k
    SCOPED_TIMER(_write_block_timer);
880
4.42k
    for (const auto& [partition_id, row_idxs] : partition_to_rowidxs) {
881
4.42k
        RETURN_IF_ERROR(write_partition_data(partition_id, row_idxs));
882
4.42k
    }
883
884
4.31k
    {
885
4.31k
        std::lock_guard<std::mutex> l(_lock);
886
4.31k
        _next_seqs[request.sender_id()] = cur_seq + 1;
887
4.31k
    }
888
4.31k
    return Status::OK();
889
4.31k
}
890
891
0
Status BaseTabletsChannel::_prepare_adaptive_random_bucket_writer(BaseDeltaWriter*) {
892
0
    return Status::OK();
893
0
}
894
895
Status BaseTabletsChannel::_build_partition_to_rowidxs_for_adaptive_random_bucket(
896
        const PTabletWriterAddBlockRequest& request,
897
4.31k
        std::unordered_map<int64_t, DorisVector<uint32_t>>* partition_to_rowidxs) {
898
4.31k
    if (_adaptive_random_bucket_state == nullptr) {
899
0
        return Status::InternalError(
900
0
                "adaptive random bucket state is not initialized, load_id={}, index_id={}, "
901
0
                "packet_seq={}",
902
0
                print_id(_load_id), _index_id, request.packet_seq());
903
0
    }
904
4.31k
    if (request.partition_ids_size() == 0) {
905
0
        return Status::InternalError(
906
0
                "empty partition ids for adaptive random bucket add block, load_id={}, "
907
0
                "index_id={}, packet_seq={}",
908
0
                print_id(_load_id), _index_id, request.packet_seq());
909
0
    }
910
2.06M
    for (uint32_t i = 0; i < request.partition_ids_size(); ++i) {
911
2.05M
        int64_t partition_id = request.partition_ids(i);
912
2.05M
        auto it = partition_to_rowidxs->find(partition_id);
913
2.05M
        if (it == partition_to_rowidxs->end()) {
914
4.41k
            partition_to_rowidxs->emplace(partition_id, std::initializer_list<uint32_t> {i});
915
2.05M
        } else {
916
2.05M
            it->second.emplace_back(i);
917
2.05M
        }
918
2.05M
    }
919
4.31k
    return Status::OK();
920
4.31k
}
921
922
Status TabletsChannel::add_batch(const PTabletWriterAddBlockRequest& request,
923
3.09k
                                 PTabletWriterAddBlockResult* response) {
924
3.09k
    SCOPED_TIMER(_add_batch_timer);
925
3.09k
    int64_t cur_seq = 0;
926
3.09k
    if (_add_batch_number_counter) {
927
0
        _add_batch_number_counter->update(1);
928
0
    }
929
930
3.09k
    auto status = _get_current_seq(cur_seq, request);
931
3.09k
    if (UNLIKELY(!status.ok())) {
932
0
        return status;
933
0
    }
934
935
3.09k
    if (request.packet_seq() < cur_seq) {
936
0
        LOG(INFO) << "packet has already recept before, expect_seq=" << cur_seq
937
0
                  << ", recept_seq=" << request.packet_seq();
938
0
        return Status::OK();
939
0
    }
940
941
    // Adaptive random bucket add-block RPCs carry partition ids. The receiver maps rows to
942
    // the current tablet and advances its selected-tablet state after memtable flush.
943
3.09k
    if (request.is_adaptive_random_bucket()) {
944
0
        std::unordered_map<int64_t /* partition_id */, DorisVector<uint32_t> /* row index */>
945
0
                partition_to_rowidxs;
946
0
        RETURN_IF_ERROR(_build_partition_to_rowidxs_for_adaptive_random_bucket(
947
0
                request, &partition_to_rowidxs));
948
0
        return _write_block_data_for_adaptive_random_bucket(request, cur_seq, partition_to_rowidxs,
949
0
                                                            response);
950
0
    }
951
952
3.09k
    std::unordered_map<int64_t /* tablet_id */, TabletAddRowsPayload> tablet_to_rows;
953
3.09k
    _build_tablet_to_rows(request, &tablet_to_rows);
954
955
3.09k
    return _write_block_data(request, cur_seq, tablet_to_rows, response);
956
3.09k
}
957
958
0
void BaseTabletsChannel::_add_broken_tablet(int64_t tablet_id) {
959
0
    std::unique_lock<std::shared_mutex> wlock(_broken_tablets_lock);
960
0
    _broken_tablets.insert(tablet_id);
961
0
}
962
963
34.0M
bool BaseTabletsChannel::_is_broken_tablet(int64_t tablet_id) const {
964
34.0M
    return _broken_tablets.find(tablet_id) != _broken_tablets.end();
965
34.0M
}
966
967
void BaseTabletsChannel::_build_tablet_to_rows(
968
        const PTabletWriterAddBlockRequest& request,
969
31.8k
        std::unordered_map<int64_t, TabletAddRowsPayload>* tablet_to_rows) {
970
    // just add a coarse-grained read lock here rather than each time when visiting _broken_tablets
971
    // tests show that a relatively coarse-grained read lock here performs better under multicore scenario
972
    // see: https://github.com/apache/doris/pull/28552
973
31.8k
    std::shared_lock<std::shared_mutex> rlock(_broken_tablets_lock);
974
31.8k
    bool has_row_binlog_lsn = request.row_binlog_lsns_size() > 0;
975
31.8k
    if (request.is_single_tablet_block()) {
976
        // The cloud mode need the tablet ids to prepare rowsets.
977
0
        int64_t tablet_id = request.tablet_ids(0);
978
0
        auto& rows = (*tablet_to_rows)[tablet_id];
979
0
        rows.row_idxs.emplace_back(0);
980
0
        if (has_row_binlog_lsn) {
981
0
            rows.row_binlog_lsns.emplace_back(request.row_binlog_lsns(0));
982
0
        }
983
0
        return;
984
0
    }
985
33.9M
    for (uint32_t i = 0; i < request.tablet_ids_size(); ++i) {
986
33.9M
        int64_t tablet_id = request.tablet_ids(i);
987
33.9M
        if (_is_broken_tablet(tablet_id)) {
988
            // skip broken tablets
989
0
            VLOG_PROGRESS << "skip broken tablet tablet=" << tablet_id;
990
0
            continue;
991
0
        }
992
33.9M
        auto& rows = (*tablet_to_rows)[tablet_id];
993
33.9M
        rows.row_idxs.emplace_back(i);
994
33.9M
        if (has_row_binlog_lsn) {
995
0
            rows.row_binlog_lsns.emplace_back(request.row_binlog_lsns(i));
996
0
        }
997
33.9M
    }
998
31.8k
}
999
1000
} // namespace doris