Coverage Report

Created: 2026-07-14 21:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/exec/sink/writer/vtablet_writer.h
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
#pragma once
19
#include <brpc/controller.h>
20
#include <bthread/types.h>
21
#include <butil/errno.h>
22
#include <fmt/format.h>
23
#include <gen_cpp/Exprs_types.h>
24
#include <gen_cpp/FrontendService.h>
25
#include <gen_cpp/FrontendService_types.h>
26
#include <gen_cpp/PaloInternalService_types.h>
27
#include <gen_cpp/Types_types.h>
28
#include <gen_cpp/internal_service.pb.h>
29
#include <gen_cpp/types.pb.h>
30
#include <glog/logging.h>
31
#include <google/protobuf/stubs/callback.h>
32
33
// IWYU pragma: no_include <bits/chrono.h>
34
#include <bthread/condition_variable.h>
35
#include <bthread/mutex.h>
36
37
#include <atomic>
38
#include <chrono> // IWYU pragma: keep
39
#include <cstddef>
40
#include <cstdint>
41
#include <functional>
42
#include <map>
43
#include <memory>
44
#include <mutex>
45
#include <ostream>
46
#include <queue>
47
#include <sstream>
48
#include <string>
49
#include <thread>
50
#include <unordered_map>
51
#include <unordered_set>
52
#include <utility>
53
#include <vector>
54
55
#include "common/config.h"
56
#include "common/status.h"
57
#include "core/block/block.h"
58
#include "core/column/column.h"
59
#include "core/data_type/data_type.h"
60
#include "exec/sink/vrow_distribution.h"
61
#include "exec/sink/vtablet_block_convertor.h"
62
#include "exec/sink/vtablet_finder.h"
63
#include "exec/sink/writer/async_result_writer.h"
64
#include "exprs/vexpr_fwd.h"
65
#include "runtime/exec_env.h"
66
#include "runtime/memory/mem_tracker.h"
67
#include "runtime/runtime_profile.h"
68
#include "runtime/thread_context.h"
69
#include "storage/tablet_info.h"
70
#include "util/brpc_closure.h"
71
#include "util/stopwatch.hpp"
72
73
namespace doris {
74
class ObjectPool;
75
class RowDescriptor;
76
class RuntimeState;
77
class TDataSink;
78
class TExpr;
79
class Thread;
80
class ThreadPoolToken;
81
class TupleDescriptor;
82
class AutoIncIDBuffer;
83
84
// The counter of add_batch rpc of a single node
85
struct AddBatchCounter {
86
    // total execution time of a add_batch rpc
87
    int64_t add_batch_execution_time_us = 0;
88
    // lock waiting time in a add_batch rpc
89
    int64_t add_batch_wait_execution_time_us = 0;
90
    // number of add_batch call
91
    int64_t add_batch_num = 0;
92
    // time passed between marked close and finish close
93
    int64_t close_wait_time_ms = 0;
94
95
0
    AddBatchCounter& operator+=(const AddBatchCounter& rhs) {
96
0
        add_batch_execution_time_us += rhs.add_batch_execution_time_us;
97
0
        add_batch_wait_execution_time_us += rhs.add_batch_wait_execution_time_us;
98
0
        add_batch_num += rhs.add_batch_num;
99
0
        close_wait_time_ms += rhs.close_wait_time_ms;
100
0
        return *this;
101
0
    }
102
0
    friend AddBatchCounter operator+(const AddBatchCounter& lhs, const AddBatchCounter& rhs) {
103
0
        AddBatchCounter sum = lhs;
104
0
        sum += rhs;
105
0
        return sum;
106
0
    }
107
};
108
109
struct WriteBlockCallbackContext {
110
    std::atomic<bool> _is_last_rpc {false};
111
};
112
113
// It's very error-prone to guarantee the handler capture vars' & this closure's destruct sequence.
114
// So using create() to get the closure pointer is recommended. We can delete the closure ptr before the capture vars destruction.
115
// Delete this point is safe, don't worry about RPC callback will run after WriteBlockCallback deleted.
116
// "Ping-Pong" between sender and receiver, `try_set_in_flight` when send, `clear_in_flight` after rpc failure or callback,
117
// then next send will start, and it will wait for the rpc callback to complete when it is destroyed.
118
template <typename T>
119
class WriteBlockCallback final : public ::doris::DummyBrpcCallback<T> {
120
    ENABLE_FACTORY_CREATOR(WriteBlockCallback);
121
122
public:
123
0
    WriteBlockCallback() : cid(INVALID_BTHREAD_ID) {}
124
0
    ~WriteBlockCallback() override = default;
125
126
0
    void addFailedHandler(const std::function<void(const WriteBlockCallbackContext&)>& fn) {
127
0
        failed_handler = fn;
128
0
    }
129
    void addSuccessHandler(
130
0
            const std::function<void(const T&, const WriteBlockCallbackContext&)>& fn) {
131
0
        success_handler = fn;
132
0
    }
133
134
0
    void join() override {
135
        // We rely on in_flight to assure one rpc is running,
136
        // while cid is not reliable due to memory order.
137
        // in_flight is written before getting callid,
138
        // so we can not use memory fence to synchronize.
139
0
        while (_packet_in_flight) {
140
            // cid here is complicated
141
0
            if (cid != INVALID_BTHREAD_ID) {
142
                // actually cid may be the last rpc call id.
143
0
                brpc::Join(cid);
144
0
            }
145
0
            if (_packet_in_flight) {
146
0
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
147
0
            }
148
0
        }
149
0
    }
150
151
    // plz follow this order: reset() -> set_in_flight() -> send brpc batch
152
0
    void reset() {
153
0
        ::doris::DummyBrpcCallback<T>::cntl_->Reset();
154
0
        cid = ::doris::DummyBrpcCallback<T>::cntl_->call_id();
155
0
    }
156
157
    // if _packet_in_flight == false, set it to true. Return true.
158
    // if _packet_in_flight == true, Return false.
159
0
    bool try_set_in_flight() {
160
0
        bool value = false;
161
0
        return _packet_in_flight.compare_exchange_strong(value, true);
162
0
    }
163
164
0
    void clear_in_flight() { _packet_in_flight = false; }
165
166
    bool is_packet_in_flight() { return _packet_in_flight; }
167
168
0
    void end_mark() {
169
0
        DCHECK(_ctx._is_last_rpc == false);
170
0
        _ctx._is_last_rpc = true;
171
0
    }
172
173
0
    void call() override {
174
0
        DCHECK(_packet_in_flight);
175
0
        if (::doris::DummyBrpcCallback<T>::cntl_->Failed()) {
176
0
            LOG(WARNING) << "failed to send brpc batch, error="
177
0
                         << berror(::doris::DummyBrpcCallback<T>::cntl_->ErrorCode())
178
0
                         << ", error_text=" << ::doris::DummyBrpcCallback<T>::cntl_->ErrorText();
179
0
            failed_handler(_ctx);
180
0
        } else {
181
0
            success_handler(*(::doris::DummyBrpcCallback<T>::response_), _ctx);
182
0
        }
183
0
        clear_in_flight();
184
0
    }
185
186
private:
187
    brpc::CallId cid;
188
    std::atomic<bool> _packet_in_flight {false};
189
    WriteBlockCallbackContext _ctx;
190
    std::function<void(const WriteBlockCallbackContext&)> failed_handler;
191
    std::function<void(const T&, const WriteBlockCallbackContext&)> success_handler;
192
};
193
194
class IndexChannel;
195
class VTabletWriter;
196
197
class VNodeChannelStat {
198
public:
199
0
    VNodeChannelStat& operator+=(const VNodeChannelStat& stat) {
200
0
        mem_exceeded_block_ns += stat.mem_exceeded_block_ns;
201
0
        where_clause_ns += stat.where_clause_ns;
202
0
        append_node_channel_ns += stat.append_node_channel_ns;
203
0
        return *this;
204
0
    };
205
206
    int64_t mem_exceeded_block_ns = 0;
207
    int64_t where_clause_ns = 0;
208
    int64_t append_node_channel_ns = 0;
209
};
210
211
struct WriterStats {
212
    int64_t serialize_batch_ns = 0;
213
    int64_t queue_push_lock_ns = 0;
214
    int64_t actual_consume_ns = 0;
215
    int64_t total_add_batch_exec_time_ns = 0;
216
    int64_t max_add_batch_exec_time_ns = 0;
217
    int64_t total_wait_exec_time_ns = 0;
218
    int64_t max_wait_exec_time_ns = 0;
219
    int64_t total_add_batch_num = 0;
220
    int64_t num_node_channels = 0;
221
    int64_t load_back_pressure_version_time_ms = 0;
222
    VNodeChannelStat channel_stat;
223
};
224
225
struct Payload {
226
    std::unique_ptr<IColumn::Selector> row_ids;
227
    RowPartTabletIds* row_part_tablet_ids = nullptr;
228
    std::vector<uint32_t> route_idxs;
229
    std::vector<int64_t> row_binlog_lsns;
230
};
231
232
// every NodeChannel keeps a data transmission channel with one BE. for multiple times open, it has a dozen of requests and corresponding closures.
233
class VNodeChannel {
234
public:
235
    VNodeChannel(VTabletWriter* parent, IndexChannel* index_channel, int64_t node_id,
236
                 bool is_incremental = false);
237
238
    ~VNodeChannel();
239
240
    // called before open, used to add tablet located in this backend. called by IndexChannel::init
241
0
    void add_tablet(const TTabletWithPartition& tablet) { _tablets_wait_open.emplace_back(tablet); }
242
0
    std::string debug_tablets() const {
243
0
        std::stringstream ss;
244
0
        for (const auto& tab : _all_tablets) {
245
0
            tab.printTo(ss);
246
0
            ss << '\n';
247
0
        }
248
0
        return ss.str();
249
0
    }
250
251
0
    void add_slave_tablet_nodes(int64_t tablet_id, const std::vector<int64_t>& slave_nodes) {
252
0
        _slave_tablet_nodes[tablet_id] = slave_nodes;
253
0
    }
254
255
    // this function is NON_REENTRANT
256
    Status init(RuntimeState* state);
257
    /// these two functions will call open_internal. should keep that clear --- REENTRANT
258
    // build corresponding connect to BE. NON-REENTRANT
259
    void open();
260
    // for auto partition, we use this to open more tablet. KEEP IT REENTRANT
261
    void incremental_open();
262
    // this will block until all request transmission which were opened or incremental opened finished.
263
    // this function will called multi times. NON_REENTRANT
264
    Status open_wait();
265
266
    Status add_block(Block* block, const Payload* payload);
267
268
    // @return: 1 if running, 0 if finished.
269
    // @caller: VOlapTabletSink::_send_batch_process. it's a continual asynchronous process.
270
    int try_send_and_fetch_status(RuntimeState* state,
271
                                  std::unique_ptr<ThreadPoolToken>& thread_pool_token);
272
    // when there's pending block found by try_send_and_fetch_status(), we will awake a thread to send it.
273
    void try_send_pending_block(RuntimeState* state);
274
275
    void clear_all_blocks();
276
277
    // two ways to stop channel:
278
    // 1. mark_close()->close_wait() PS. close_wait() will block waiting for the last AddBatch rpc response.
279
    // 2. just cancel()
280
    // hang_wait = true will make reciever hang until all sender mark_closed.
281
    void mark_close(bool hang_wait = false);
282
283
0
    bool is_closed() const { return _is_closed; }
284
0
    bool is_cancelled() const { return _cancelled; }
285
0
    std::string get_cancel_msg() {
286
0
        std::lock_guard<std::mutex> l(_cancel_msg_lock);
287
0
        if (!_cancel_msg.empty()) {
288
0
            return _cancel_msg;
289
0
        }
290
0
        return fmt::format("{} is cancelled", channel_info());
291
0
    }
292
293
    // two ways to stop channel:
294
    // 1. mark_close()->close_wait() PS. close_wait() will block waiting for the last AddBatch rpc response.
295
    // 2. just cancel()
296
    Status close_wait(RuntimeState* state, bool* is_closed);
297
298
    Status after_close_handle(
299
            RuntimeState* state, WriterStats* writer_stats,
300
            std::unordered_map<int64_t, AddBatchCounter>* node_add_batch_counter_map);
301
302
    Status check_status();
303
304
    void cancel(const std::string& cancel_msg);
305
306
    void time_report(std::unordered_map<int64_t, AddBatchCounter>* add_batch_counter_map,
307
0
                     WriterStats* writer_stats) const {
308
0
        if (add_batch_counter_map != nullptr) {
309
0
            (*add_batch_counter_map)[_node_id] += _add_batch_counter;
310
0
            (*add_batch_counter_map)[_node_id].close_wait_time_ms = _close_time_ms;
311
0
        }
312
0
        if (writer_stats != nullptr) {
313
0
            writer_stats->serialize_batch_ns += _serialize_batch_ns;
314
0
            writer_stats->channel_stat += _stat;
315
0
            writer_stats->queue_push_lock_ns += _queue_push_lock_ns;
316
0
            writer_stats->actual_consume_ns += _actual_consume_ns;
317
0
            writer_stats->total_add_batch_exec_time_ns +=
318
0
                    (_add_batch_counter.add_batch_execution_time_us * 1000);
319
0
            writer_stats->total_wait_exec_time_ns +=
320
0
                    (_add_batch_counter.add_batch_wait_execution_time_us * 1000);
321
0
            writer_stats->total_add_batch_num += _add_batch_counter.add_batch_num;
322
0
            writer_stats->load_back_pressure_version_time_ms +=
323
0
                    _load_back_pressure_version_block_ms;
324
0
        }
325
0
    }
326
327
0
    int64_t node_id() const { return _node_id; }
328
0
    std::string host() const { return _node_info.host; }
329
0
    std::string name() const { return _name; }
330
331
0
    std::string channel_info() const {
332
0
        return fmt::format("{}, {}, node={}:{}", _name, _load_info, _node_info.host,
333
0
                           _node_info.brpc_port);
334
0
    }
335
336
0
    size_t get_pending_bytes() { return _pending_batches_bytes; }
337
338
0
    bool is_incremental() const { return _is_incremental; }
339
340
0
    int64_t write_bytes() const { return _write_bytes.load(); }
341
342
protected:
343
    // make a real open request for relative BE's load channel.
344
    void _open_internal(bool is_incremental);
345
    void _set_adaptive_random_bucket_open_request(PTabletWriterOpenRequest* request);
346
347
    void _close_check();
348
    void _cancel_with_msg(const std::string& msg);
349
350
    void _add_block_success_callback(const PTabletWriterAddBlockResult& result,
351
                                     const WriteBlockCallbackContext& ctx);
352
    void _add_block_failed_callback(const WriteBlockCallbackContext& ctx);
353
354
    void _refresh_back_pressure_version_wait_time(
355
            const ::google::protobuf::RepeatedPtrField<::doris::PTabletLoadRowsetInfo>&
356
                    tablet_load_infos);
357
358
    VTabletWriter* _parent = nullptr;
359
    IndexChannel* _index_channel = nullptr;
360
    int64_t _node_id = -1;
361
    std::string _load_info;
362
    std::string _name;
363
364
    std::shared_ptr<MemTracker> _node_channel_tracker;
365
    int64_t _load_mem_limit = -1;
366
367
    TupleDescriptor* _tuple_desc = nullptr;
368
    NodeInfo _node_info;
369
370
    // this should be set in init() using config
371
    int _rpc_timeout_ms = 60000;
372
    int64_t _next_packet_seq = 0;
373
    MonotonicStopWatch _timeout_watch;
374
375
    // the timestamp when this node channel be marked closed and finished closed
376
    uint64_t _close_time_ms = 0;
377
378
    // user cancel or get some errors
379
    std::atomic<bool> _cancelled {false};
380
    std::mutex _cancel_msg_lock;
381
    std::string _cancel_msg;
382
383
    // send finished means the consumer thread which send the rpc can exit
384
    std::atomic<bool> _send_finished {false};
385
386
    // add batches finished means the last rpc has be response, used to check whether this channel can be closed
387
    std::atomic<bool> _add_batches_finished {false}; // reuse for vectorized
388
389
    bool _eos_is_produced {false}; // only for restricting producer behaviors
390
391
    std::unique_ptr<RowDescriptor> _row_desc;
392
    int _batch_size = 0;
393
394
    // limit _pending_batches size
395
    std::atomic<size_t> _pending_batches_bytes {0};
396
    size_t _max_pending_batches_bytes {(size_t)config::nodechannel_pending_queue_max_bytes};
397
    std::mutex _pending_batches_lock;          // reuse for vectorized
398
    std::atomic<int> _pending_batches_num {0}; // reuse for vectorized
399
400
    std::shared_ptr<PBackendService_Stub> _stub;
401
    // because we have incremantal open, we should keep one relative closure for one request. it's similarly for adding block.
402
    std::vector<std::shared_ptr<DummyBrpcCallback<PTabletWriterOpenResult>>> _open_callbacks;
403
404
    std::vector<TTabletWithPartition> _all_tablets;
405
    std::vector<TTabletWithPartition> _tablets_wait_open;
406
    // For rolling-upgrade compatibility, adaptive random bucket add-block RPCs also carry
407
    // tablet_ids. New receivers ignore them and route by partition id, while old receivers use
408
    // this local tablet id instead of failing on an empty tablet_ids list.
409
    std::unordered_map<int64_t, int64_t> _adaptive_partition_compat_tablets;
410
    // map from tablet_id to node_id where slave replicas locate in
411
    std::unordered_map<int64_t, std::vector<int64_t>> _slave_tablet_nodes;
412
    std::vector<TTabletCommitInfo> _tablet_commit_infos;
413
414
    AddBatchCounter _add_batch_counter;
415
    std::atomic<int64_t> _serialize_batch_ns {0};
416
    std::atomic<int64_t> _queue_push_lock_ns {0};
417
    std::atomic<int64_t> _actual_consume_ns {0};
418
    std::atomic<int64_t> _load_back_pressure_version_block_ms {0};
419
420
    VNodeChannelStat _stat;
421
    // lock to protect _is_closed.
422
    // The methods in the IndexChannel are called back in the RpcClosure in the NodeChannel.
423
    // However, this rpc callback may occur after the whole task is finished (e.g. due to network latency),
424
    // and by that time the IndexChannel may have been destructured, so we should not call the
425
    // IndexChannel methods anymore, otherwise the BE will crash.
426
    // Therefore, we use the _is_closed and _closed_lock to ensure that the RPC callback
427
    // function will not call the IndexChannel method after the NodeChannel is closed.
428
    // The IndexChannel is definitely accessible until the NodeChannel is closed.
429
    std::mutex _closed_lock;
430
    bool _is_closed = false;
431
    bool _inited = false;
432
433
    RuntimeState* _state = nullptr;
434
    // A context lock for callbacks, the callback has to lock the ctx, to avoid
435
    // the object is deleted during callback is running.
436
    std::weak_ptr<TaskExecutionContext> _task_exec_ctx;
437
    // rows number received per tablet, tablet_id -> rows_num
438
    std::vector<std::pair<int64_t, int64_t>> _tablets_received_rows;
439
    // rows number filtered per tablet, tablet_id -> filtered_rows_num
440
    std::vector<std::pair<int64_t, int64_t>> _tablets_filtered_rows;
441
442
    // build a _cur_mutable_block and push into _pending_blocks. when not building, this block is empty.
443
    std::unique_ptr<MutableBlock> _cur_mutable_block;
444
    std::shared_ptr<PTabletWriterAddBlockRequest> _cur_add_block_request;
445
446
    using AddBlockReq =
447
            std::pair<std::unique_ptr<MutableBlock>, std::shared_ptr<PTabletWriterAddBlockRequest>>;
448
    std::queue<AddBlockReq> _pending_blocks;
449
    // send block to slave BE rely on this. dont reconstruct it.
450
    std::shared_ptr<WriteBlockCallback<PTabletWriterAddBlockResult>> _send_block_callback = nullptr;
451
452
    int64_t _wg_id = -1;
453
454
    bool _is_incremental;
455
456
    std::atomic<int64_t> _write_bytes {0};
457
    std::atomic<int64_t> _load_back_pressure_version_wait_time_ms {0};
458
};
459
460
// an IndexChannel is related to specific table and its rollup and mv
461
class IndexChannel {
462
public:
463
    IndexChannel(VTabletWriter* parent, int64_t index_id, VExprContextSPtr where_clause)
464
0
            : _parent(parent), _index_id(index_id), _where_clause(std::move(where_clause)) {
465
0
        _index_channel_tracker =
466
0
                std::make_unique<MemTracker>("IndexChannel:indexID=" + std::to_string(_index_id));
467
0
    }
468
0
    ~IndexChannel() = default;
469
470
    // allow to init multi times, for incremental open more tablets for one index(table)
471
    Status init(RuntimeState* state, const std::vector<TTabletWithPartition>& tablets,
472
                bool incremental = false);
473
474
    void for_each_node_channel(
475
0
            const std::function<void(const std::shared_ptr<VNodeChannel>&)>& func) {
476
0
        for (auto& it : _node_channels) {
477
0
            func(it.second);
478
0
        }
479
0
    }
480
481
    void for_init_node_channel(
482
0
            const std::function<void(const std::shared_ptr<VNodeChannel>&)>& func) {
483
0
        for (auto& it : _node_channels) {
484
0
            if (!it.second->is_incremental()) {
485
0
                func(it.second);
486
0
            }
487
0
        }
488
0
    }
489
490
    void for_inc_node_channel(
491
0
            const std::function<void(const std::shared_ptr<VNodeChannel>&)>& func) {
492
0
        for (auto& it : _node_channels) {
493
0
            if (it.second->is_incremental()) {
494
0
                func(it.second);
495
0
            }
496
0
        }
497
0
    }
498
499
0
    std::unordered_set<int64_t> init_node_channel_ids() {
500
0
        std::unordered_set<int64_t> node_channel_ids;
501
0
        for (auto& it : _node_channels) {
502
0
            if (!it.second->is_incremental()) {
503
0
                node_channel_ids.insert(it.first);
504
0
            }
505
0
        }
506
0
        return node_channel_ids;
507
0
    }
508
509
0
    std::unordered_set<int64_t> inc_node_channel_ids() {
510
0
        std::unordered_set<int64_t> node_channel_ids;
511
0
        for (auto& it : _node_channels) {
512
0
            if (it.second->is_incremental()) {
513
0
                node_channel_ids.insert(it.first);
514
0
            }
515
0
        }
516
0
        return node_channel_ids;
517
0
    }
518
519
0
    std::unordered_set<int64_t> each_node_channel_ids() {
520
0
        std::unordered_set<int64_t> node_channel_ids;
521
0
        for (auto& it : _node_channels) {
522
0
            node_channel_ids.insert(it.first);
523
0
        }
524
0
        return node_channel_ids;
525
0
    }
526
527
0
    bool has_incremental_node_channel() const { return _has_inc_node; }
528
529
    void mark_as_failed(const VNodeChannel* node_channel, const std::string& err,
530
                        int64_t tablet_id = -1);
531
    Status check_intolerable_failure();
532
533
    Status close_wait(RuntimeState* state, WriterStats* writer_stats,
534
                      std::unordered_map<int64_t, AddBatchCounter>* node_add_batch_counter_map,
535
                      std::unordered_set<int64_t> unfinished_node_channel_ids,
536
                      bool need_wait_after_quorum_success);
537
538
0
    int64_t close_wait_version() const {
539
0
        return _close_wait_version.load(std::memory_order_acquire);
540
0
    }
541
542
    void wait_for_close_event(int64_t observed_version, int64_t timeout_ms);
543
544
    void notify_close_wait();
545
546
    Status check_each_node_channel_close(
547
            std::unordered_set<int64_t>* unfinished_node_channel_ids,
548
            std::unordered_map<int64_t, AddBatchCounter>* node_add_batch_counter_map,
549
            WriterStats* writer_stats, Status status);
550
551
    // set error tablet info in runtime state, so that it can be returned to FE.
552
    void set_error_tablet_in_state(RuntimeState* state);
553
554
0
    size_t num_node_channels() const { return _node_channels.size(); }
555
556
0
    size_t get_pending_bytes() const {
557
0
        size_t mem_consumption = 0;
558
0
        for (const auto& kv : _node_channels) {
559
0
            mem_consumption += kv.second->get_pending_bytes();
560
0
        }
561
0
        return mem_consumption;
562
0
    }
563
564
    void set_tablets_received_rows(
565
            const std::vector<std::pair<int64_t, int64_t>>& tablets_received_rows, int64_t node_id);
566
567
    void set_tablets_filtered_rows(
568
            const std::vector<std::pair<int64_t, int64_t>>& tablets_filtered_rows, int64_t node_id);
569
570
0
    int64_t num_rows_filtered() {
571
        // the Unique table has no roll up or materilized view
572
        // we just add up filtered rows from all partitions
573
0
        return std::accumulate(_tablets_filtered_rows.cbegin(), _tablets_filtered_rows.cend(), 0,
574
0
                               [](int64_t sum, const auto& a) { return sum + a.second[0].second; });
575
0
    }
576
577
    // check whether the rows num written by different replicas is consistent
578
    Status check_tablet_received_rows_consistency();
579
580
    // check whether the rows num filtered by different replicas is consistent
581
    Status check_tablet_filtered_rows_consistency();
582
583
0
    void set_start_time(const int64_t& start_time) { _start_time = start_time; }
584
585
0
    VExprContextSPtr get_where_clause() { return _where_clause; }
586
587
private:
588
    friend class VNodeChannel;
589
    friend class VTabletWriter;
590
    friend class VRowDistribution;
591
592
    int _max_failed_replicas(int64_t tablet_id);
593
594
    int _load_required_replicas_num(int64_t tablet_id);
595
596
    bool _quorum_success(const std::unordered_set<int64_t>& unfinished_node_channel_ids,
597
                         const std::unordered_set<int64_t>& need_finish_tablets);
598
599
    int64_t _calc_max_wait_time_ms(const std::unordered_set<int64_t>& unfinished_node_channel_ids);
600
601
    VTabletWriter* _parent = nullptr;
602
    int64_t _index_id;
603
    VExprContextSPtr _where_clause;
604
605
    // from backend channel to tablet_id
606
    // ATTN: must be placed before `_node_channels` and `_channels_by_tablet`.
607
    // Because the destruct order of objects is opposite to the creation order.
608
    // So NodeChannel will be destructured first.
609
    // And the destructor function of NodeChannel waits for all RPCs to finish.
610
    // This ensures that it is safe to use `_tablets_by_channel` in the callback function for the end of the RPC.
611
    std::unordered_map<int64_t, std::unordered_set<int64_t>> _tablets_by_channel;
612
    // BeId -> channel
613
    std::unordered_map<int64_t, std::shared_ptr<VNodeChannel>> _node_channels;
614
    // from tablet_id to backend channel
615
    std::unordered_map<int64_t, std::vector<std::shared_ptr<VNodeChannel>>> _channels_by_tablet;
616
    // from partition_id to FE-planned bucket owner channel in cloud adaptive random bucket mode
617
    std::unordered_map<int64_t, std::shared_ptr<VNodeChannel>> _channels_by_partition;
618
    bool _has_inc_node = false;
619
620
    // lock to protect _failed_channels and _failed_channels_msgs
621
    mutable std::mutex _fail_lock;
622
    // key is tablet_id, value is a set of failed node id
623
    std::unordered_map<int64_t, std::unordered_set<int64_t>> _failed_channels;
624
    // key is tablet_id, value is error message
625
    std::unordered_map<int64_t, std::string> _failed_channels_msgs;
626
    Status _intolerable_failure_status = Status::OK();
627
628
    std::unique_ptr<MemTracker> _index_channel_tracker;
629
    // rows num received by DeltaWriter per tablet, tablet_id -> <node_Id, rows_num>
630
    // used to verify whether the rows num received by different replicas is consistent
631
    std::map<int64_t, std::vector<std::pair<int64_t, int64_t>>> _tablets_received_rows;
632
633
    // rows num filtered by DeltaWriter per tablet, tablet_id -> <node_Id, filtered_rows_num>
634
    // used to verify whether the rows num filtered by different replicas is consistent
635
    std::map<int64_t, std::vector<std::pair<int64_t, int64_t>>> _tablets_filtered_rows;
636
637
    int64_t _start_time = 0;
638
639
    std::atomic<int64_t> _close_wait_version {0};
640
    bthread::Mutex _close_wait_mutex;
641
    bthread::ConditionVariable _close_wait_cv;
642
};
643
} // namespace doris
644
645
namespace doris {
646
//
647
// write result to file
648
class VTabletWriter final : public AsyncResultWriter {
649
public:
650
    VTabletWriter(const TDataSink& t_sink, const VExprContextSPtrs& output_exprs,
651
                  std::shared_ptr<Dependency> dep, std::shared_ptr<Dependency> fin_dep);
652
653
    Status write(RuntimeState* state, Block& block) override;
654
655
    Status close(Status) override;
656
657
    Status open(RuntimeState* state, RuntimeProfile* profile) override;
658
659
    // the consumer func of sending pending batches in every NodeChannel.
660
    // use polling & NodeChannel::try_send_and_fetch_status() to achieve nonblocking sending.
661
    // only focus on pending batches and channel status, the internal errors of NodeChannels will be handled by the producer
662
    void _send_batch_process();
663
664
    Status on_partitions_created(TCreatePartitionResult* result);
665
666
    Status _send_new_partition_batch();
667
668
private:
669
    friend class VNodeChannel;
670
    friend class IndexChannel;
671
672
    using ChannelDistributionPayload = std::unordered_map<VNodeChannel*, Payload>;
673
    using ChannelDistributionPayloadVec = std::vector<std::unordered_map<VNodeChannel*, Payload>>;
674
675
    Status _init_row_distribution();
676
677
    Status _init(RuntimeState* state, RuntimeProfile* profile);
678
679
    Status _generate_one_index_channel_payload(RowPartTabletIds& row_part_tablet_tuple,
680
                                               int32_t index_idx,
681
                                               ChannelDistributionPayload& channel_payload);
682
683
    Status _generate_index_channels_payloads(std::vector<RowPartTabletIds>& row_part_tablet_ids,
684
                                             ChannelDistributionPayloadVec& payload);
685
686
    void _cancel_all_channel(Status status);
687
688
    Status _incremental_open_node_channel(const std::vector<TOlapTablePartition>& partitions);
689
690
    void _do_try_close(RuntimeState* state, const Status& exec_status);
691
692
    void _build_tablet_replica_info(const int64_t tablet_id, VOlapTablePartition* partition);
693
694
    TDataSink _t_sink;
695
696
    std::shared_ptr<MemTracker> _mem_tracker;
697
698
    ObjectPool* _pool = nullptr;
699
700
    bthread_t _sender_thread = 0;
701
702
    // unique load id
703
    PUniqueId _load_id;
704
    int64_t _txn_id = -1;
705
    int _num_replicas = -1;
706
    int _tuple_desc_id = -1;
707
708
    // this is tuple descriptor of destination OLAP table
709
    TupleDescriptor* _output_tuple_desc = nullptr;
710
    RowDescriptor* _output_row_desc = nullptr;
711
712
    // number of senders used to insert into OlapTable, if we only support single node insert,
713
    // all data from select should collectted and then send to OlapTable.
714
    // To support multiple senders, we maintain a channel for each sender.
715
    int _sender_id = -1;
716
    int _num_senders = -1;
717
    bool _is_high_priority = false;
718
719
    // TODO(zc): think about cache this data
720
    std::shared_ptr<OlapTableSchemaParam> _schema;
721
    OlapTableLocationParam* _location = nullptr;
722
    bool _write_single_replica = false;
723
    OlapTableLocationParam* _slave_location = nullptr;
724
    DorisNodesInfo* _nodes_info = nullptr;
725
726
    std::unique_ptr<OlapTabletFinder> _tablet_finder;
727
728
    // index_channel
729
    bthread::Mutex _stop_check_channel;
730
    std::vector<std::shared_ptr<IndexChannel>> _channels;
731
    std::unordered_map<int64_t, std::shared_ptr<IndexChannel>> _index_id_to_channel;
732
    // Table-level row-binlog LSN buffer
733
    std::shared_ptr<AutoIncIDBuffer> _row_binlog_lsn_buffer;
734
735
    std::unique_ptr<ThreadPoolToken> _send_batch_thread_pool_token;
736
737
    // support only one partition column now
738
    std::vector<std::vector<TStringLiteral>> _partitions_need_create;
739
740
    std::unique_ptr<OlapTableBlockConvertor> _block_convertor;
741
    // Stats for this
742
    int64_t _send_data_ns = 0;
743
    int64_t _number_input_rows = 0;
744
    int64_t _number_output_rows = 0;
745
    int64_t _filter_ns = 0;
746
747
    MonotonicStopWatch _row_distribution_watch;
748
749
    RuntimeProfile::Counter* _input_rows_counter = nullptr;
750
    RuntimeProfile::Counter* _output_rows_counter = nullptr;
751
    RuntimeProfile::Counter* _filtered_rows_counter = nullptr;
752
    RuntimeProfile::Counter* _send_data_timer = nullptr;
753
    RuntimeProfile::Counter* _row_distribution_timer = nullptr;
754
    RuntimeProfile::Counter* _append_node_channel_timer = nullptr;
755
    RuntimeProfile::Counter* _filter_timer = nullptr;
756
    RuntimeProfile::Counter* _where_clause_timer = nullptr;
757
    RuntimeProfile::Counter* _add_partition_request_timer = nullptr;
758
    RuntimeProfile::Counter* _wait_mem_limit_timer = nullptr;
759
    RuntimeProfile::Counter* _validate_data_timer = nullptr;
760
    RuntimeProfile::Counter* _open_timer = nullptr;
761
    RuntimeProfile::Counter* _close_timer = nullptr;
762
    RuntimeProfile::Counter* _non_blocking_send_timer = nullptr;
763
    RuntimeProfile::Counter* _non_blocking_send_work_timer = nullptr;
764
    RuntimeProfile::Counter* _serialize_batch_timer = nullptr;
765
    RuntimeProfile::Counter* _total_add_batch_exec_timer = nullptr;
766
    RuntimeProfile::Counter* _max_add_batch_exec_timer = nullptr;
767
    RuntimeProfile::Counter* _total_wait_exec_timer = nullptr;
768
    RuntimeProfile::Counter* _max_wait_exec_timer = nullptr;
769
    RuntimeProfile::Counter* _add_batch_number = nullptr;
770
    RuntimeProfile::Counter* _num_node_channels = nullptr;
771
    RuntimeProfile::Counter* _load_back_pressure_version_time_ms = nullptr;
772
773
    // the timeout of load channels opened by this tablet sink. in second
774
    int64_t _load_channel_timeout_s = 0;
775
    // the load txn absolute expiration time.
776
    int64_t _txn_expiration = 0;
777
778
    int32_t _send_batch_parallelism = 1;
779
    // Save the status of try_close() and close() method
780
    Status _close_status;
781
    // if we called try_close(), for auto partition the periodic send thread should stop if it's still waiting for node channels first-time open.
782
    // atomic: written by pthread (_do_try_close), read by bthread (_send_batch_process)
783
    std::atomic<bool> _try_close {false};
784
    bool _inited = false;
785
    bool _write_file_cache = false;
786
787
    // User can change this config at runtime, avoid it being modified during query or loading process.
788
    bool _transfer_large_data_by_brpc = false;
789
790
    VOlapTablePartitionParam* _vpartition = nullptr;
791
792
    RuntimeState* _state = nullptr; // not owned, set when open
793
794
    VRowDistribution _row_distribution;
795
    // reuse to avoid frequent memory allocation and release.
796
    std::vector<RowPartTabletIds> _row_part_tablet_ids;
797
798
    // tablet_id -> <total replicas num, load required replicas num>
799
    std::unordered_map<int64_t, std::pair<int, int>> _tablet_replica_info;
800
801
    // tablet_id -> set of backend_ids that have version gaps
802
    // these backends' success should not be counted for majority write
803
    std::unordered_map<int64_t, std::unordered_set<int64_t>> _tablet_version_gap_backends;
804
};
805
} // namespace doris