Coverage Report

Created: 2026-05-21 14:12

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
31.0k
        : _key(key),
66
31.0k
          _state(kInitialized),
67
31.0k
          _load_id(load_id),
68
31.0k
          _closed_senders(64),
69
31.0k
          _is_high_priority(is_high_priority) {
70
31.0k
    static std::once_flag once_flag;
71
31.0k
    if (profile != nullptr) {
72
3
        _init_profile(profile);
73
3
    }
74
31.0k
    std::call_once(once_flag, [] {
75
5
        REGISTER_HOOK_METRIC(tablet_writer_count, [&]() { return _s_tablet_writer_count.load(); });
76
5
    });
77
31.0k
}
78
79
TabletsChannel::TabletsChannel(StorageEngine& engine, const TabletsChannelKey& key,
80
                               const UniqueId& load_id, bool is_high_priority,
81
                               RuntimeProfile* profile)
82
1.44k
        : BaseTabletsChannel(key, load_id, is_high_priority, profile), _engine(engine) {}
83
84
31.0k
BaseTabletsChannel::~BaseTabletsChannel() {
85
31.0k
    _s_tablet_writer_count -= _tablet_writers.size();
86
31.0k
}
87
88
TabletsChannel::~TabletsChannel() = default;
89
90
Status BaseTabletsChannel::_get_current_seq(int64_t& cur_seq,
91
33.7k
                                            const PTabletWriterAddBlockRequest& request) {
92
33.7k
    std::lock_guard<std::mutex> l(_lock);
93
33.7k
    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
33.7k
    cur_seq = _next_seqs[request.sender_id()];
99
    // check packet
100
33.7k
    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
33.7k
    return Status::OK();
106
33.7k
}
107
108
3
void BaseTabletsChannel::_init_profile(RuntimeProfile* profile) {
109
3
    DCHECK(profile != nullptr);
110
3
    _profile =
111
3
            profile->create_child(fmt::format("TabletsChannel {}", _key.to_string()), true, true);
112
3
    _add_batch_number_counter = ADD_COUNTER(_profile, "NumberBatchAdded", TUnit::UNIT);
113
114
3
    auto* memory_usage = _profile->create_child("PeakMemoryUsage", true, true);
115
3
    _add_batch_timer = ADD_TIMER(_profile, "AddBatchTime");
116
3
    _write_block_timer = ADD_TIMER(_profile, "WriteBlockTime");
117
3
    _incremental_open_timer = ADD_TIMER(_profile, "IncrementalOpenTabletTime");
118
3
    _memory_usage_counter = memory_usage->AddHighWaterMarkCounter("Total", TUnit::BYTES);
119
3
    _write_memory_usage_counter = memory_usage->AddHighWaterMarkCounter("Write", TUnit::BYTES);
120
3
    _flush_memory_usage_counter = memory_usage->AddHighWaterMarkCounter("Flush", TUnit::BYTES);
121
3
    _max_tablet_memory_usage_counter =
122
3
            memory_usage->AddHighWaterMarkCounter("MaxTablet", TUnit::BYTES);
123
3
    _max_tablet_write_memory_usage_counter =
124
3
            memory_usage->AddHighWaterMarkCounter("MaxTabletWrite", TUnit::BYTES);
125
3
    _max_tablet_flush_memory_usage_counter =
126
3
            memory_usage->AddHighWaterMarkCounter("MaxTabletFlush", TUnit::BYTES);
127
3
}
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
44.9k
Status BaseTabletsChannel::open(const PTabletWriterOpenRequest& request) {
136
44.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
44.9k
    if (_state == kOpened || _state == kFinished) {
140
13.8k
        return Status::OK();
141
13.8k
    }
142
31.0k
    _txn_id = request.txn_id();
143
31.0k
    _index_id = request.index_id();
144
31.0k
    _schema = std::make_shared<OlapTableSchemaParam>();
145
31.0k
    RETURN_IF_ERROR(_schema->init(request.schema()));
146
31.0k
    _tuple_desc = _schema->tuple_desc();
147
148
31.0k
    int max_sender = request.num_senders();
149
    /*
150
     * a tablets channel in reciever is related to a bulk of VNodeChannel of sender. each instance one or none.
151
     * there are two possibilities:
152
     *  1. there's partitions originally broadcasted by FE. so all sender(instance) know it at start. and open() will be 
153
     *     called directly, not by incremental_open(). and after _state changes to kOpened. _open_by_incremental will never 
154
     *     be true. in this case, _num_remaining_senders will keep same with senders number. when all sender sent close rpc,
155
     *     the tablets channel will close. and if for auto partition table, these channel's closing will hang on reciever and
156
     *     return together to avoid close-then-incremental-open problem.
157
     *  2. this tablets channel is opened by incremental_open of sender's sink node. so only this sender will know this partition
158
     *     (this TabletsChannel) at that time. and we are not sure how many sender will know in the end. it depends on data
159
     *     distribution. in this situation open() is called by incremental_open() at first time. so _open_by_incremental is true.
160
     *     then _num_remaining_senders will not be set here. but inc every time when incremental_open() called. so it's dynamic
161
     *     and also need same number of senders' close to close. but will not hang.
162
     */
163
31.0k
    if (_open_by_incremental) {
164
0
        DCHECK(_num_remaining_senders == 0) << _num_remaining_senders;
165
31.0k
    } else {
166
31.0k
        _num_remaining_senders = max_sender;
167
31.0k
    }
168
31.0k
    LOG(INFO) << fmt::format(
169
31.0k
            "open tablets channel {}, tablets num: {} timeout(s): {}, init senders {} with "
170
31.0k
            "incremental {}",
171
31.0k
            _key.to_string(), request.tablets().size(), request.load_channel_timeout_s(),
172
31.0k
            _num_remaining_senders, _open_by_incremental ? "on" : "off");
173
    // just use max_sender no matter incremental or not cuz we dont know how many senders will open.
174
31.0k
    _next_seqs.resize(max_sender, 0);
175
31.0k
    _closed_senders.Reset(max_sender);
176
177
31.0k
    RETURN_IF_ERROR(_open_all_writers(request));
178
179
31.0k
    _state = kOpened;
180
31.0k
    return Status::OK();
181
31.0k
}
182
183
183
Status BaseTabletsChannel::incremental_open(const PTabletWriterOpenRequest& params) {
184
183
    SCOPED_TIMER(_incremental_open_timer);
185
186
    // current node first opened by incremental open
187
183
    if (_state == kInitialized) {
188
0
        _open_by_incremental = true;
189
0
        RETURN_IF_ERROR(open(params));
190
0
    }
191
192
183
    std::lock_guard<std::mutex> l(_lock);
193
194
    // one sender may incremental_open many times. but only close one time. so dont count duplicately.
195
183
    if (_open_by_incremental) {
196
0
        if (params.has_sender_id() && !_recieved_senders.contains(params.sender_id())) {
197
0
            _recieved_senders.insert(params.sender_id());
198
0
            _num_remaining_senders++;
199
0
        } else if (!params.has_sender_id()) { // for compatible
200
0
            _num_remaining_senders++;
201
0
        }
202
0
        VLOG_DEBUG << fmt::format("txn {}: TabletsChannel {} inc senders to {}", _txn_id, _index_id,
203
0
                                  _num_remaining_senders);
204
0
    }
205
206
183
    std::vector<SlotDescriptor*>* index_slots = nullptr;
207
183
    int32_t schema_hash = 0;
208
209
183
    for (const auto& index : _schema->indexes()) {
210
183
        if (index->index_id == _index_id) {
211
183
            index_slots = &index->slots;
212
183
            schema_hash = index->schema_hash;
213
183
            break;
214
183
        }
215
183
    }
216
183
    if (index_slots == nullptr) {
217
0
        return Status::InternalError("unknown index id, key={}", _key.to_string());
218
0
    }
219
    // update tablets
220
183
    size_t incremental_tablet_num = 0;
221
183
    std::stringstream ss;
222
183
    ss << "LocalTabletsChannel txn_id: " << _txn_id << " load_id: " << print_id(params.id())
223
183
       << " incremental open delta writer: ";
224
225
    // every change will hold _lock. this find in under _lock too. so no need _tablet_writers_lock again.
226
6.62k
    for (const auto& tablet : params.tablets()) {
227
6.62k
        if (_tablet_writers.find(tablet.tablet_id()) != _tablet_writers.end()) {
228
96
            continue;
229
96
        }
230
6.52k
        incremental_tablet_num++;
231
232
6.52k
        WriteRequest wrequest;
233
6.52k
        wrequest.index_id = params.index_id();
234
6.52k
        wrequest.tablet_id = tablet.tablet_id();
235
6.52k
        wrequest.schema_hash = schema_hash;
236
6.52k
        wrequest.txn_id = _txn_id;
237
6.52k
        wrequest.partition_id = tablet.partition_id();
238
6.52k
        wrequest.load_id = params.id();
239
6.52k
        wrequest.tuple_desc = _tuple_desc;
240
6.52k
        wrequest.slots = index_slots;
241
6.52k
        wrequest.is_high_priority = _is_high_priority;
242
6.52k
        wrequest.table_schema_param = _schema;
243
6.52k
        wrequest.txn_expiration = params.txn_expiration(); // Required by CLOUD.
244
6.52k
        wrequest.write_file_cache = params.write_file_cache();
245
6.52k
        wrequest.storage_vault_id = params.storage_vault_id();
246
247
6.52k
        auto delta_writer = create_delta_writer(wrequest);
248
6.52k
        {
249
            // here we modify _tablet_writers. so need lock.
250
6.52k
            std::lock_guard<std::mutex> lt(_tablet_writers_lock);
251
6.52k
            _tablet_writers.emplace(tablet.tablet_id(), std::move(delta_writer));
252
6.52k
        }
253
254
6.52k
        ss << "[" << tablet.tablet_id() << "]";
255
6.52k
    }
256
257
183
    _s_tablet_writer_count += incremental_tablet_num;
258
183
    LOG(INFO) << ss.str();
259
260
183
    _state = kOpened;
261
183
    return Status::OK();
262
183
}
263
264
12.9k
std::unique_ptr<BaseDeltaWriter> TabletsChannel::create_delta_writer(const WriteRequest& request) {
265
12.9k
    DCHECK(request.write_req_type == WriteRequestType::DATA);
266
12.9k
    DCHECK(request.table_schema_param != nullptr);
267
268
12.9k
    int64_t row_binlog_index_id = 0;
269
12.9k
    for (const auto* index_schema : request.table_schema_param->indexes()) {
270
12.9k
        if (index_schema->index_id == request.index_id) {
271
12.9k
            row_binlog_index_id = index_schema->row_binlog_id;
272
12.9k
            break;
273
12.9k
        }
274
12.9k
    }
275
12.9k
    if (row_binlog_index_id <= 0) {
276
12.9k
        return std::make_unique<DeltaWriter>(_engine, request, _profile, _load_id);
277
12.9k
    }
278
279
0
    const auto* row_binlog_index_schema = request.table_schema_param->row_binlog_index_schema();
280
0
    DCHECK(row_binlog_index_schema != nullptr);
281
0
    DCHECK(row_binlog_index_schema->index_id == row_binlog_index_id);
282
283
    // group_build_req is only for the group wrapper itself. It provides the group semantics and
284
    // metadata used by BaseDeltaWriter/GroupRowsetBuilder to expose tablet_id, txn_id,
285
    // partition_id, load_id and profile information, while concrete rowset builders use the
286
    // sub requests below.
287
0
    WriteRequest group_build_req = request;
288
0
    group_build_req.write_req_type = WriteRequestType::GROUP;
289
290
0
    WriteRequest sub_data_req = request;
291
0
    sub_data_req.write_req_type = WriteRequestType::DATA;
292
293
0
    WriteRequest sub_row_binlog_req = request;
294
0
    sub_row_binlog_req.write_req_type = WriteRequestType::ROW_BINLOG;
295
0
    sub_row_binlog_req.index_id = row_binlog_index_schema->index_id;
296
0
    sub_row_binlog_req.schema_hash = row_binlog_index_schema->schema_hash;
297
298
0
    return std::make_unique<DeltaWriter>(_engine, group_build_req, sub_data_req, sub_row_binlog_req,
299
0
                                         _profile, _load_id);
300
12.9k
}
301
302
Status TabletsChannel::close(LoadChannel* parent, const PTabletWriterAddBlockRequest& req,
303
2.18k
                             PTabletWriterAddBlockResult* res, bool* finished) {
304
2.18k
    int sender_id = req.sender_id();
305
2.18k
    int64_t backend_id = req.backend_id();
306
2.18k
    const auto& partition_ids = req.partition_ids();
307
2.18k
    auto* tablet_errors = res->mutable_tablet_errors();
308
2.18k
    std::lock_guard<std::mutex> l(_lock);
309
2.18k
    if (_state == kFinished) {
310
0
        return _close_status;
311
0
    }
312
2.18k
    if (_closed_senders.Get(sender_id)) {
313
        // Double close from one sender, just return OK
314
0
        *finished = (_num_remaining_senders == 0);
315
0
        return _close_status;
316
0
    }
317
318
2.18k
    for (auto pid : partition_ids) {
319
1.37k
        _partition_ids.emplace(pid);
320
1.37k
    }
321
2.18k
    _closed_senders.Set(sender_id, true);
322
2.18k
    _num_remaining_senders--;
323
2.18k
    *finished = (_num_remaining_senders == 0);
324
325
2.18k
    LOG(INFO) << fmt::format(
326
2.18k
            "txn {}: close tablets channel of index {} , sender id: {}, backend {}, remain "
327
2.18k
            "senders: {}",
328
2.18k
            _txn_id, _index_id, sender_id, backend_id, _num_remaining_senders);
329
330
2.18k
    if (!*finished) {
331
742
        return Status::OK();
332
742
    }
333
334
1.44k
    _state = kFinished;
335
    // All senders are closed
336
    // 1. close all delta writers
337
1.44k
    std::set<DeltaWriter*> need_wait_writers;
338
    // under _lock. no need _tablet_writers_lock again.
339
12.9k
    for (auto&& [tablet_id, writer] : _tablet_writers) {
340
12.9k
        if (_partition_ids.contains(writer->partition_id())) {
341
11.8k
            auto st = writer->close();
342
11.8k
            if (!st.ok()) {
343
0
                auto err_msg = fmt::format(
344
0
                        "close tablet writer failed, tablet_id={}, "
345
0
                        "transaction_id={}, err={}",
346
0
                        tablet_id, _txn_id, st.to_string());
347
0
                LOG(WARNING) << err_msg;
348
0
                PTabletError* tablet_error = tablet_errors->Add();
349
0
                tablet_error->set_tablet_id(tablet_id);
350
0
                tablet_error->set_msg(st.to_string());
351
                // just skip this tablet(writer) and continue to close others
352
0
                continue;
353
0
            }
354
            // tablet writer in `_broken_tablets` should not call `build_rowset` and
355
            // `commit_txn` method, after that, the publish-version task will success,
356
            // which can cause the replica inconsistency.
357
11.8k
            if (_is_broken_tablet(writer->tablet_id())) {
358
0
                LOG(WARNING) << "SHOULD NOT HAPPEN, tablet writer is broken but not cancelled"
359
0
                             << ", tablet_id=" << tablet_id << ", transaction_id=" << _txn_id;
360
0
                continue;
361
0
            }
362
11.8k
            need_wait_writers.insert(static_cast<DeltaWriter*>(writer.get()));
363
11.8k
        } else {
364
1.12k
            auto st = writer->cancel();
365
1.12k
            if (!st.ok()) {
366
0
                LOG(WARNING) << "cancel tablet writer failed, tablet_id=" << tablet_id
367
0
                             << ", transaction_id=" << _txn_id;
368
                // just skip this tablet(writer) and continue to close others
369
0
                continue;
370
0
            }
371
1.12k
            VLOG_PROGRESS << "cancel tablet writer successfully, tablet_id=" << tablet_id
372
0
                          << ", transaction_id=" << _txn_id;
373
1.12k
        }
374
12.9k
    }
375
376
1.44k
    _write_single_replica = req.write_single_replica();
377
378
    // 2. wait all writer finished flush.
379
11.8k
    for (auto* writer : need_wait_writers) {
380
11.8k
        RETURN_IF_ERROR((writer->wait_flush()));
381
11.8k
    }
382
383
    // 3. build rowset
384
13.2k
    for (auto it = need_wait_writers.begin(); it != need_wait_writers.end();) {
385
11.8k
        Status st = (*it)->build_rowset();
386
11.8k
        if (!st.ok()) {
387
0
            _add_error_tablet(tablet_errors, (*it)->tablet_id(), st);
388
0
            it = need_wait_writers.erase(it);
389
0
            continue;
390
0
        }
391
        // 3.1 calculate delete bitmap for Unique Key MoW tables
392
11.8k
        st = (*it)->submit_calc_delete_bitmap_task();
393
11.8k
        if (!st.ok()) {
394
0
            _add_error_tablet(tablet_errors, (*it)->tablet_id(), st);
395
0
            it = need_wait_writers.erase(it);
396
0
            continue;
397
0
        }
398
11.8k
        it++;
399
11.8k
    }
400
401
    // 4. wait for delete bitmap calculation complete if necessary
402
13.2k
    for (auto it = need_wait_writers.begin(); it != need_wait_writers.end();) {
403
11.8k
        Status st = (*it)->wait_calc_delete_bitmap();
404
11.8k
        if (!st.ok()) {
405
0
            _add_error_tablet(tablet_errors, (*it)->tablet_id(), st);
406
0
            it = need_wait_writers.erase(it);
407
0
            continue;
408
0
        }
409
11.8k
        it++;
410
11.8k
    }
411
412
    // 5. commit all writers
413
414
11.8k
    for (auto* writer : need_wait_writers) {
415
        // close may return failed, but no need to handle it here.
416
        // tablet_vec will only contains success tablet, and then let FE judge it.
417
11.8k
        _commit_txn(writer, req, res);
418
11.8k
    }
419
420
1.44k
    if (_write_single_replica) {
421
0
        auto* success_slave_tablet_node_ids = res->mutable_success_slave_tablet_node_ids();
422
        // The operation waiting for all slave replicas to complete must end before the timeout,
423
        // so that there is enough time to collect completed replica. Otherwise, the task may
424
        // timeout and fail even though most of the replicas are completed. Here we set 0.9
425
        // times the timeout as the maximum waiting time.
426
0
        SCOPED_TIMER(_slave_replica_timer);
427
0
        while (!need_wait_writers.empty() &&
428
0
               (time(nullptr) - parent->last_updated_time()) < (parent->timeout() * 0.9)) {
429
0
            std::set<DeltaWriter*>::iterator it;
430
0
            for (it = need_wait_writers.begin(); it != need_wait_writers.end();) {
431
0
                bool is_done = (*it)->check_slave_replicas_done(success_slave_tablet_node_ids);
432
0
                if (is_done) {
433
0
                    need_wait_writers.erase(it++);
434
0
                } else {
435
0
                    it++;
436
0
                }
437
0
            }
438
0
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
439
0
        }
440
0
        for (auto* writer : need_wait_writers) {
441
0
            writer->add_finished_slave_replicas(success_slave_tablet_node_ids);
442
0
        }
443
0
        _engine.txn_manager()->clear_txn_tablet_delta_writer(_txn_id);
444
0
    }
445
446
1.44k
    return Status::OK();
447
1.44k
}
448
449
void TabletsChannel::_commit_txn(DeltaWriter* writer, const PTabletWriterAddBlockRequest& req,
450
11.8k
                                 PTabletWriterAddBlockResult* res) {
451
11.8k
    PSlaveTabletNodes slave_nodes;
452
11.8k
    if (_write_single_replica) {
453
0
        auto& nodes_map = req.slave_tablet_nodes();
454
0
        auto it = nodes_map.find(writer->tablet_id());
455
0
        if (it != nodes_map.end()) {
456
0
            slave_nodes = it->second;
457
0
        }
458
0
    }
459
11.8k
    Status st = writer->commit_txn(slave_nodes);
460
11.8k
    if (st.ok()) [[likely]] {
461
11.8k
        auto* tablet_vec = res->mutable_tablet_vec();
462
11.8k
        PTabletInfo* tablet_info = tablet_vec->Add();
463
11.8k
        tablet_info->set_tablet_id(writer->tablet_id());
464
        // unused required field.
465
11.8k
        tablet_info->set_schema_hash(0);
466
11.8k
        tablet_info->set_received_rows(writer->total_received_rows());
467
11.8k
        tablet_info->set_num_rows_filtered(writer->num_rows_filtered());
468
11.8k
        _total_received_rows += writer->total_received_rows();
469
11.8k
        _num_rows_filtered += writer->num_rows_filtered();
470
11.8k
    } else {
471
0
        _add_error_tablet(res->mutable_tablet_errors(), writer->tablet_id(), st);
472
0
    }
473
11.8k
}
474
475
void BaseTabletsChannel::_add_error_tablet(
476
        google::protobuf::RepeatedPtrField<PTabletError>* tablet_errors, int64_t tablet_id,
477
0
        Status error) const {
478
0
    PTabletError* tablet_error = tablet_errors->Add();
479
0
    tablet_error->set_tablet_id(tablet_id);
480
0
    tablet_error->set_msg(error.to_string());
481
0
    VLOG_PROGRESS << "close wait failed tablet " << tablet_id << " transaction_id " << _txn_id
482
0
                  << "err msg " << error;
483
0
}
484
485
8
void BaseTabletsChannel::refresh_profile() {
486
8
    int64_t write_mem_usage = 0;
487
8
    int64_t flush_mem_usage = 0;
488
8
    int64_t max_tablet_mem_usage = 0;
489
8
    int64_t max_tablet_write_mem_usage = 0;
490
8
    int64_t max_tablet_flush_mem_usage = 0;
491
8
    {
492
8
        std::lock_guard<std::mutex> l(_tablet_writers_lock);
493
70
        for (auto&& [tablet_id, writer] : _tablet_writers) {
494
70
            int64_t write_mem = writer->mem_consumption(MemType::WRITE_FINISHED);
495
70
            write_mem_usage += write_mem;
496
70
            int64_t flush_mem = writer->mem_consumption(MemType::FLUSH);
497
70
            flush_mem_usage += flush_mem;
498
70
            if (write_mem > max_tablet_write_mem_usage) {
499
0
                max_tablet_write_mem_usage = write_mem;
500
0
            }
501
70
            if (flush_mem > max_tablet_flush_mem_usage) {
502
0
                max_tablet_flush_mem_usage = flush_mem;
503
0
            }
504
70
            if (write_mem + flush_mem > max_tablet_mem_usage) {
505
0
                max_tablet_mem_usage = write_mem + flush_mem;
506
0
            }
507
70
        }
508
8
    }
509
8
    COUNTER_SET(_memory_usage_counter, write_mem_usage + flush_mem_usage);
510
8
    COUNTER_SET(_write_memory_usage_counter, write_mem_usage);
511
8
    COUNTER_SET(_flush_memory_usage_counter, flush_mem_usage);
512
8
    COUNTER_SET(_max_tablet_memory_usage_counter, max_tablet_mem_usage);
513
8
    COUNTER_SET(_max_tablet_write_memory_usage_counter, max_tablet_write_mem_usage);
514
8
    COUNTER_SET(_max_tablet_flush_memory_usage_counter, max_tablet_flush_mem_usage);
515
8
}
516
517
31.0k
Status BaseTabletsChannel::_open_all_writers(const PTabletWriterOpenRequest& request) {
518
31.0k
    std::vector<SlotDescriptor*>* index_slots = nullptr;
519
31.0k
    int32_t schema_hash = 0;
520
35.0k
    for (const auto& index : _schema->indexes()) {
521
35.0k
        if (index->index_id == _index_id) {
522
31.0k
            index_slots = &index->slots;
523
31.0k
            schema_hash = index->schema_hash;
524
31.0k
            break;
525
31.0k
        }
526
35.0k
    }
527
31.0k
    if (index_slots == nullptr) {
528
0
        return Status::InternalError("unknown index id, key={}", _key.to_string());
529
0
    }
530
531
#ifdef DEBUG
532
    // check: tablet ids should be unique
533
    {
534
        std::unordered_set<int64_t> tablet_ids;
535
        for (const auto& tablet : request.tablets()) {
536
            CHECK(tablet_ids.count(tablet.tablet_id()) == 0)
537
                    << "found duplicate tablet id: " << tablet.tablet_id();
538
            tablet_ids.insert(tablet.tablet_id());
539
        }
540
    }
541
#endif
542
543
31.0k
    int tablet_cnt = 0;
544
    // under _lock. no need _tablet_writers_lock again.
545
277k
    for (const auto& tablet : request.tablets()) {
546
277k
        if (_tablet_writers.find(tablet.tablet_id()) != _tablet_writers.end()) {
547
0
            continue;
548
0
        }
549
277k
        tablet_cnt++;
550
277k
        WriteRequest wrequest {
551
277k
                .tablet_id = tablet.tablet_id(),
552
277k
                .schema_hash = schema_hash,
553
277k
                .txn_id = _txn_id,
554
277k
                .txn_expiration = request.txn_expiration(), // Required by CLOUD.
555
277k
                .index_id = request.index_id(),
556
277k
                .partition_id = tablet.partition_id(),
557
277k
                .load_id = request.id(),
558
277k
                .tuple_desc = _tuple_desc,
559
277k
                .slots = index_slots,
560
277k
                .table_schema_param = _schema,
561
277k
                .is_high_priority = _is_high_priority,
562
277k
                .write_file_cache = request.write_file_cache(),
563
277k
                .storage_vault_id = request.storage_vault_id(),
564
277k
        };
565
566
277k
        auto delta_writer = create_delta_writer(wrequest);
567
277k
        {
568
277k
            std::lock_guard<std::mutex> l(_tablet_writers_lock);
569
277k
            _tablet_writers.emplace(tablet.tablet_id(), std::move(delta_writer));
570
277k
        }
571
277k
    }
572
31.0k
    _s_tablet_writer_count += _tablet_writers.size();
573
31.0k
    DCHECK_EQ(_tablet_writers.size(), tablet_cnt);
574
31.0k
    return Status::OK();
575
31.0k
}
576
577
128
Status BaseTabletsChannel::cancel() {
578
128
    std::lock_guard<std::mutex> l(_lock);
579
128
    if (_state == kFinished) {
580
94
        return _close_status;
581
94
    }
582
1.42k
    for (auto& it : _tablet_writers) {
583
1.42k
        static_cast<void>(it.second->cancel());
584
1.42k
    }
585
34
    _state = kFinished;
586
587
34
    return Status::OK();
588
128
}
589
590
0
Status TabletsChannel::cancel() {
591
0
    RETURN_IF_ERROR(BaseTabletsChannel::cancel());
592
0
    if (_write_single_replica) {
593
0
        _engine.txn_manager()->clear_txn_tablet_delta_writer(_txn_id);
594
0
    }
595
0
    return Status::OK();
596
0
}
597
598
31.0k
std::string TabletsChannelKey::to_string() const {
599
31.0k
    std::stringstream ss;
600
31.0k
    ss << *this;
601
31.0k
    return ss.str();
602
31.0k
}
603
604
73.8k
std::ostream& operator<<(std::ostream& os, const TabletsChannelKey& key) {
605
73.8k
    os << "(load_id=" << key.id << ", index_id=" << key.index_id << ")";
606
73.8k
    return os;
607
73.8k
}
608
609
Status BaseTabletsChannel::_write_block_data(
610
        const PTabletWriterAddBlockRequest& request, int64_t cur_seq,
611
        std::unordered_map<int64_t, DorisVector<uint32_t>>& tablet_to_rowidxs,
612
33.7k
        PTabletWriterAddBlockResult* response) {
613
33.7k
    Block send_data;
614
33.7k
    [[maybe_unused]] size_t uncompressed_size = 0;
615
33.7k
    [[maybe_unused]] int64_t uncompressed_time = 0;
616
33.7k
    RETURN_IF_ERROR(send_data.deserialize(request.block(), &uncompressed_size, &uncompressed_time));
617
18.4E
    CHECK(send_data.rows() == request.tablet_ids_size())
618
18.4E
            << "block rows: " << send_data.rows()
619
18.4E
            << ", tablet_ids_size: " << request.tablet_ids_size();
620
621
33.7k
    g_tablets_channel_send_data_allocated_size << send_data.allocated_bytes();
622
33.7k
    Defer defer {
623
33.7k
            [&]() { g_tablets_channel_send_data_allocated_size << -send_data.allocated_bytes(); }};
624
625
33.7k
    auto write_tablet_data = [&](int64_t tablet_id,
626
120k
                                 std::function<Status(BaseDeltaWriter * writer)> write_func) {
627
120k
        google::protobuf::RepeatedPtrField<PTabletError>* tablet_errors =
628
120k
                response->mutable_tablet_errors();
629
630
        // add_batch may concurrency with inc_open but not under _lock.
631
        // so need to protect it with _tablet_writers_lock.
632
120k
        decltype(_tablet_writers.find(tablet_id)) tablet_writer_it;
633
120k
        {
634
120k
            std::lock_guard<std::mutex> l(_tablet_writers_lock);
635
120k
            tablet_writer_it = _tablet_writers.find(tablet_id);
636
120k
            if (tablet_writer_it == _tablet_writers.end()) {
637
0
                return Status::InternalError("unknown tablet to append data, tablet={}", tablet_id);
638
0
            }
639
120k
        }
640
641
120k
        Status st = write_func(tablet_writer_it->second.get());
642
120k
        if (!st.ok()) {
643
0
            auto err_msg =
644
0
                    fmt::format("tablet writer write failed, tablet_id={}, txn_id={}, err={}",
645
0
                                tablet_id, _txn_id, st.to_string());
646
0
            LOG(WARNING) << err_msg;
647
0
            PTabletError* error = tablet_errors->Add();
648
0
            error->set_tablet_id(tablet_id);
649
0
            error->set_msg(err_msg);
650
0
            static_cast<void>(tablet_writer_it->second->cancel_with_status(st));
651
0
            _add_broken_tablet(tablet_id);
652
            // continue write to other tablet.
653
            // the error will return back to sender.
654
0
        }
655
120k
        return Status::OK();
656
120k
    };
657
658
33.7k
    SCOPED_TIMER(_write_block_timer);
659
33.7k
    auto* tablet_load_infos = response->mutable_tablet_load_rowset_num_infos();
660
120k
    for (const auto& tablet_to_rowidxs_it : tablet_to_rowidxs) {
661
120k
        RETURN_IF_ERROR(write_tablet_data(tablet_to_rowidxs_it.first, [&](BaseDeltaWriter* writer) {
662
120k
            return writer->write(&send_data, tablet_to_rowidxs_it.second);
663
120k
        }));
664
665
120k
        auto tablet_writer_it = _tablet_writers.find(tablet_to_rowidxs_it.first);
666
120k
        if (tablet_writer_it != _tablet_writers.end()) {
667
120k
            tablet_writer_it->second->set_tablet_load_rowset_num_info(tablet_load_infos);
668
120k
        }
669
120k
    }
670
671
33.7k
    {
672
33.7k
        std::lock_guard<std::mutex> l(_lock);
673
33.7k
        _next_seqs[request.sender_id()] = cur_seq + 1;
674
33.7k
    }
675
33.7k
    return Status::OK();
676
33.7k
}
677
678
Status TabletsChannel::add_batch(const PTabletWriterAddBlockRequest& request,
679
1.37k
                                 PTabletWriterAddBlockResult* response) {
680
1.37k
    SCOPED_TIMER(_add_batch_timer);
681
1.37k
    int64_t cur_seq = 0;
682
1.37k
    if (_add_batch_number_counter) {
683
0
        _add_batch_number_counter->update(1);
684
0
    }
685
686
1.37k
    auto status = _get_current_seq(cur_seq, request);
687
1.37k
    if (UNLIKELY(!status.ok())) {
688
0
        return status;
689
0
    }
690
691
1.37k
    if (request.packet_seq() < cur_seq) {
692
0
        LOG(INFO) << "packet has already recept before, expect_seq=" << cur_seq
693
0
                  << ", recept_seq=" << request.packet_seq();
694
0
        return Status::OK();
695
0
    }
696
697
1.37k
    std::unordered_map<int64_t /* tablet_id */, DorisVector<uint32_t> /* row index */>
698
1.37k
            tablet_to_rowidxs;
699
1.37k
    _build_tablet_to_rowidxs(request, &tablet_to_rowidxs);
700
701
1.37k
    return _write_block_data(request, cur_seq, tablet_to_rowidxs, response);
702
1.37k
}
703
704
0
void BaseTabletsChannel::_add_broken_tablet(int64_t tablet_id) {
705
0
    std::unique_lock<std::shared_mutex> wlock(_broken_tablets_lock);
706
0
    _broken_tablets.insert(tablet_id);
707
0
}
708
709
35.8M
bool BaseTabletsChannel::_is_broken_tablet(int64_t tablet_id) const {
710
35.8M
    return _broken_tablets.find(tablet_id) != _broken_tablets.end();
711
35.8M
}
712
713
void BaseTabletsChannel::_build_tablet_to_rowidxs(
714
        const PTabletWriterAddBlockRequest& request,
715
33.7k
        std::unordered_map<int64_t, DorisVector<uint32_t>>* tablet_to_rowidxs) {
716
    // just add a coarse-grained read lock here rather than each time when visiting _broken_tablets
717
    // tests show that a relatively coarse-grained read lock here performs better under multicore scenario
718
    // see: https://github.com/apache/doris/pull/28552
719
33.7k
    std::shared_lock<std::shared_mutex> rlock(_broken_tablets_lock);
720
33.7k
    if (request.is_single_tablet_block()) {
721
        // The cloud mode need the tablet ids to prepare rowsets.
722
0
        int64_t tablet_id = request.tablet_ids(0);
723
0
        tablet_to_rowidxs->emplace(tablet_id, std::initializer_list<uint32_t> {0});
724
0
        return;
725
0
    }
726
35.7M
    for (uint32_t i = 0; i < request.tablet_ids_size(); ++i) {
727
35.7M
        int64_t tablet_id = request.tablet_ids(i);
728
35.7M
        if (_is_broken_tablet(tablet_id)) {
729
            // skip broken tablets
730
0
            VLOG_PROGRESS << "skip broken tablet tablet=" << tablet_id;
731
0
            continue;
732
0
        }
733
35.7M
        auto it = tablet_to_rowidxs->find(tablet_id);
734
35.7M
        if (it == tablet_to_rowidxs->end()) {
735
120k
            tablet_to_rowidxs->emplace(tablet_id, std::initializer_list<uint32_t> {i});
736
35.6M
        } else {
737
35.6M
            it->second.emplace_back(i);
738
35.6M
        }
739
35.7M
    }
740
33.7k
}
741
742
} // namespace doris