Coverage Report

Created: 2026-10-14 19:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/exec/operator/exchange_sink_buffer.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
20
#include <brpc/controller.h>
21
#include <butil/iobuf.h>
22
#include <gen_cpp/data.pb.h>
23
#include <gen_cpp/internal_service.pb.h>
24
#include <gen_cpp/types.pb.h>
25
#include <parallel_hashmap/phmap.h>
26
27
#include <atomic>
28
#include <cstdint>
29
#include <list>
30
#include <memory>
31
#include <mutex>
32
#include <queue>
33
#include <stack>
34
#include <string>
35
36
#include "common/global_types.h"
37
#include "common/status.h"
38
#include "runtime/runtime_state.h"
39
#include "service/backend_options.h"
40
#include "util/brpc_closure.h"
41
42
namespace brpc {
43
class SerializedRequest;
44
}
45
46
namespace doris {
47
class PTransmitDataParams;
48
class TUniqueId;
49
50
using InstanceLoId = int64_t;
51
52
class Dependency;
53
class ExchangeSinkLocalState;
54
55
class Channel;
56
57
// We use BroadcastPBlockHolder to hold a broadcasted PBlock. For broadcast shuffle, one PBlock
58
// will be shared between different channel, so we have to use a ref count to mark if this
59
// PBlock is available for next serialization.
60
class BroadcastPBlockHolderMemLimiter;
61
class BroadcastPBlockHolder {
62
    ENABLE_FACTORY_CREATOR(BroadcastPBlockHolder);
63
64
public:
65
22
    BroadcastPBlockHolder() { _pblock = std::make_unique<PBlock>(); }
66
    ~BroadcastPBlockHolder();
67
68
87
    PBlock* get_block() { return _pblock.get(); }
69
70
0
    void reset_block() { _pblock->Clear(); }
71
72
    // Called once by the producer, before publishing the holder to any channel.
73
    Status prepare_serialized_block(bool send_multi_blocks, size_t num_receivers);
74
75
425
    const butil::IOBuf& serialized_block() const { return _serialized_block; }
76
77
private:
78
    friend class BroadcastPBlockHolderMemLimiter;
79
    std::unique_ptr<PBlock> _pblock;
80
    // Includes the outer PTransmitDataParams.block/blocks field tag and length.
81
    // Keep _pblock for the existing protobuf and HTTP paths.
82
    butil::IOBuf _serialized_block;
83
    int64_t _queue_charge_bytes = 0;
84
    std::weak_ptr<BroadcastPBlockHolderMemLimiter> _parent_creator;
85
3
    void set_parent_creator(std::shared_ptr<BroadcastPBlockHolderMemLimiter> parent_creator) {
86
3
        _parent_creator = parent_creator;
87
3
    }
88
};
89
90
class BroadcastPBlockHolderMemLimiter
91
        : public std::enable_shared_from_this<BroadcastPBlockHolderMemLimiter> {
92
    ENABLE_FACTORY_CREATOR(BroadcastPBlockHolderMemLimiter);
93
94
public:
95
    BroadcastPBlockHolderMemLimiter() = delete;
96
97
    BroadcastPBlockHolderMemLimiter(std::shared_ptr<Dependency>& broadcast_dependency)
98
4
            : _total_queue_buffer_size_limit(config::exchg_node_buffer_size_bytes),
99
4
              _total_queue_blocks_count_limit(config::num_broadcast_buffer) {
100
4
        _broadcast_dependency = broadcast_dependency;
101
4
    }
102
103
0
    void set_low_memory_mode() {
104
0
        _total_queue_buffer_size_limit = 1024 * 1024;
105
0
        _total_queue_blocks_count_limit = 8;
106
0
    }
107
108
    void acquire(BroadcastPBlockHolder& holder);
109
    void release(const BroadcastPBlockHolder& holder);
110
111
private:
112
    std::atomic_int64_t _total_queue_buffer_size_limit {0};
113
    std::atomic_int64_t _total_queue_blocks_count_limit {0};
114
    std::atomic_int64_t _total_queue_buffer_size {0};
115
    std::atomic_int64_t _total_queue_blocks_count {0};
116
    std::shared_ptr<Dependency> _broadcast_dependency;
117
    std::mutex _holders_lock;
118
};
119
120
struct TransmitInfo {
121
    std::unique_ptr<PBlock> block;
122
    bool eos;
123
};
124
125
struct BroadcastTransmitInfo {
126
    std::shared_ptr<BroadcastPBlockHolder> block_holder = nullptr;
127
    bool eos;
128
};
129
130
struct RpcInstanceStatistics {
131
    int64_t rpc_count = 0;
132
    int64_t max_time = 0;
133
    int64_t min_time = INT64_MAX;
134
    int64_t sum_time = 0;
135
};
136
137
// Consolidated structure for RPC instance data
138
struct RpcInstance {
139
    // Constructor initializes the instance with the given ID
140
16
    RpcInstance(InstanceLoId id) : id(id) {}
141
142
    // Unique identifier for this RPC instance
143
    InstanceLoId id;
144
145
    // Mutex for thread-safe access to this instance's data
146
    std::unique_ptr<std::mutex> mutex;
147
148
    // Sequence number for RPC packets, incremented for each packet sent
149
    int64_t seq = 0;
150
151
    // Queue for regular data transmission requests
152
    std::unordered_map<Channel*, std::queue<TransmitInfo, std::list<TransmitInfo>>> package_queue;
153
154
    // Queue for broadcast data transmission requests
155
    std::unordered_map<Channel*,
156
                       std::queue<BroadcastTransmitInfo, std::list<BroadcastTransmitInfo>>>
157
            broadcast_package_queue;
158
159
    // RPC request parameters for data transmission
160
    std::shared_ptr<PTransmitDataParams> request;
161
162
    // Flag indicating if the RPC channel is currently idle (no active RPC)
163
    bool rpc_channel_is_idle = true;
164
165
    // Flag indicating if the RPC channel has been turned off (no more RPCs will be sent)
166
    bool rpc_channel_is_turn_off = false;
167
168
    // Statistics for monitoring RPC performance (latency, counts, etc.)
169
    RpcInstanceStatistics stats;
170
171
    // Count of active exchange sinks using this RPC instance
172
    int64_t running_sink_count = 0;
173
};
174
175
template <typename Response>
176
class ExchangeSendCallback : public ::doris::DummyBrpcCallback<Response> {
177
    ENABLE_FACTORY_CREATOR(ExchangeSendCallback);
178
179
public:
180
39
    ExchangeSendCallback() = default;
181
182
39
    void init(RpcInstance* ins, bool eos) {
183
39
        _ins = ins;
184
39
        _eos = eos;
185
39
    }
186
187
39
    ~ExchangeSendCallback() override = default;
188
    ExchangeSendCallback(const ExchangeSendCallback& other) = delete;
189
    ExchangeSendCallback& operator=(const ExchangeSendCallback& other) = delete;
190
39
    void addFailedHandler(const std::function<void(RpcInstance*, const std::string&)>& fail_fn) {
191
39
        _fail_fn = fail_fn;
192
39
    }
193
    void addSuccessHandler(const std::function<void(RpcInstance*, const bool&, const Response&,
194
39
                                                    const int64_t&)>& suc_fn) {
195
39
        _suc_fn = suc_fn;
196
39
    }
197
198
34
    void call() noexcept override {
199
34
        try {
200
34
            if (::doris::DummyBrpcCallback<Response>::cntl_->Failed()) {
201
3
                std::string err = fmt::format(
202
3
                        "failed to send brpc when exchange, error={}, error_text={}, client: {}, "
203
3
                        "latency = {}",
204
3
                        berror(::doris::DummyBrpcCallback<Response>::cntl_->ErrorCode()),
205
3
                        ::doris::DummyBrpcCallback<Response>::cntl_->ErrorText(),
206
3
                        BackendOptions::get_localhost(),
207
3
                        ::doris::DummyBrpcCallback<Response>::cntl_->latency_us());
208
3
                _fail_fn(_ins, err);
209
31
            } else {
210
31
                _suc_fn(_ins, _eos, *(::doris::DummyBrpcCallback<Response>::response_),
211
31
                        start_rpc_time);
212
31
            }
213
34
        } catch (const std::exception& exp) {
214
0
            LOG(FATAL) << "brpc callback error: " << exp.what();
215
0
        } catch (...) {
216
0
            LOG(FATAL) << "brpc callback error.";
217
0
            __builtin_unreachable();
218
0
        }
219
34
    }
220
    int64_t start_rpc_time;
221
222
private:
223
    std::function<void(RpcInstance*, const std::string&)> _fail_fn;
224
    std::function<void(RpcInstance*, const bool&, const Response&, const int64_t&)> _suc_fn;
225
    RpcInstance* _ins;
226
    bool _eos;
227
};
228
229
// ExchangeSinkBuffer can either be shared among multiple ExchangeSinkLocalState instances
230
// or be individually owned by each ExchangeSinkLocalState.
231
// The following describes the scenario where ExchangeSinkBuffer is shared among multiple ExchangeSinkLocalState instances.
232
// Of course, individual ownership can be seen as a special case where only one ExchangeSinkLocalState shares the buffer.
233
234
// A sink buffer contains multiple rpc_channels.
235
// Each rpc_channel corresponds to a target instance on the receiving side.
236
// Data is sent using a ping-pong mode within each rpc_channel,
237
// meaning that at most one RPC can exist in a single rpc_channel at a time.
238
// The next RPC can only be sent after the previous one has completed.
239
//
240
// Each exchange sink sends data to all target instances on the receiving side.
241
// If the concurrency is 3, a single rpc_channel will be used simultaneously by three exchange sinks.
242
243
/*                                                                                                                                                                                                                                                                                                                          
244
                          +-----------+          +-----------+        +-----------+      
245
                          |dest ins id|          |dest ins id|        |dest ins id|      
246
                          |           |          |           |        |           |      
247
                          +----+------+          +-----+-----+        +------+----+      
248
                               |                       |                     |           
249
                               |                       |                     |           
250
                      +----------------+      +----------------+     +----------------+  
251
                      |                |      |                |     |                |  
252
 sink buffer -------- |   rpc_channel  |      |  rpc_channel   |     |  rpc_channel   |  
253
                      |                |      |                |     |                |  
254
                      +-------+--------+      +----------------+     +----------------+  
255
                              |                        |                      |          
256
                              |------------------------+----------------------+          
257
                              |                        |                      |          
258
                              |                        |                      |          
259
                     +-----------------+       +-------+---------+    +-------+---------+
260
                     |                 |       |                 |    |                 |
261
                     |  exchange sink  |       |  exchange sink  |    |  exchange sink  |
262
                     |                 |       |                 |    |                 |
263
                     +-----------------+       +-----------------+    +-----------------+
264
*/
265
266
#if defined(BE_TEST) && !defined(BE_BENCHMARK)
267
void transmit_blockv2(PBackendService_Stub* stub,
268
                      std::unique_ptr<AutoReleaseClosure<PTransmitDataParams,
269
                                                         ExchangeSendCallback<PTransmitDataResult>>>
270
                              closure);
271
#endif
272
class ExchangeSinkBuffer : public HasTaskExecutionCtx {
273
public:
274
    ExchangeSinkBuffer(PUniqueId query_id, PlanNodeId dest_node_id, PlanNodeId node_id,
275
                       RuntimeState* state, const std::vector<InstanceLoId>& sender_ins_ids);
276
#ifdef BE_TEST
277
    ExchangeSinkBuffer(RuntimeState* state, int64_t sinknum)
278
6
            : HasTaskExecutionCtx(state), _state(state), _exchange_sink_num(sinknum) {};
279
#endif
280
281
14
    ~ExchangeSinkBuffer() override = default;
282
283
    void construct_request(TUniqueId);
284
285
    Status add_block(Channel* channel, TransmitInfo&& request);
286
    Status add_block(Channel* channel, BroadcastTransmitInfo&& request);
287
    void close();
288
2
    bool send_multi_blocks() const { return _send_multi_blocks; }
289
    void update_rpc_time(RpcInstance& ins, int64_t start_rpc_time, int64_t receive_rpc_time);
290
    void update_profile(RuntimeProfile* profile);
291
292
    void set_dependency(InstanceLoId sender_ins_id, std::shared_ptr<Dependency> queue_dependency,
293
3
                        ExchangeSinkLocalState* local_state) {
294
3
        std::lock_guard l(_m);
295
3
        _queue_deps.push_back(queue_dependency);
296
3
        _parents.push_back(local_state);
297
3
    }
298
299
0
    void set_low_memory_mode() { _queue_capacity = 8; }
300
    std::string debug_each_instance_queue_size();
301
#ifdef BE_TEST
302
public:
303
#else
304
private:
305
#endif
306
    friend class ExchangeSinkLocalState;
307
308
    // Single map to store all RPC instance data
309
    phmap::flat_hash_map<InstanceLoId, std::unique_ptr<RpcInstance>> _rpc_instances;
310
    std::atomic<size_t> _queue_capacity;
311
312
    // It is set to true only when an RPC fails. Currently, we do not have an error retry mechanism.
313
    // If an RPC error occurs, the query will be canceled.
314
    std::atomic<bool> _is_failed;
315
    PUniqueId _query_id;
316
    PlanNodeId _dest_node_id;
317
318
    PlanNodeId _node_id;
319
    std::atomic<int64_t> _rpc_count = 0;
320
    // The state may be from PipelineFragmentContext if it is shared among multi instances.
321
    RuntimeState* _state = nullptr;
322
    QueryContext* _context = nullptr;
323
324
    Status _send_rpc(RpcInstance& ins);
325
326
    static Status _serialize_broadcast_request(const PTransmitDataParams& header,
327
                                               const std::vector<BroadcastTransmitInfo>& requests,
328
                                               brpc::SerializedRequest* serialized_request);
329
330
#ifndef BE_TEST
331
    inline void _ended(RpcInstance& ins);
332
    inline void _failed(InstanceLoId id, const std::string& err);
333
    inline void _set_receiver_eof(RpcInstance& ins);
334
    inline void _turn_off_channel(RpcInstance& ins, std::unique_lock<std::mutex>& with_lock);
335
336
#else
337
    virtual void _ended(RpcInstance& ins);
338
    virtual void _failed(InstanceLoId id, const std::string& err);
339
    virtual void _set_receiver_eof(RpcInstance& ins);
340
    virtual void _turn_off_channel(RpcInstance& ins, std::unique_lock<std::mutex>& with_lock);
341
#endif
342
343
    void get_max_min_rpc_time(int64_t* max_time, int64_t* min_time);
344
    int64_t get_sum_rpc_time();
345
346
    // _total_queue_size is the sum of the sizes of all instance_to_package_queues.
347
    // Any modification to instance_to_package_queue requires a corresponding modification to _total_queue_size.
348
    std::atomic<int> _total_queue_size = 0;
349
350
    // protected the `_queue_deps` and `_parents`
351
    std::mutex _m;
352
    // _queue_deps is used for memory control.
353
    std::vector<std::shared_ptr<Dependency>> _queue_deps;
354
    // The ExchangeSinkLocalState in _parents is only used in _turn_off_channel.
355
    std::vector<ExchangeSinkLocalState*> _parents;
356
    const int64_t _exchange_sink_num;
357
    bool _send_multi_blocks = false;
358
    int _send_multi_blocks_byte_size = 256 * 1024;
359
};
360
361
} // namespace doris