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 |