be/src/cloud/cloud_ms_backpressure_handler.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 <bvar/bvar.h> |
21 | | |
22 | | #include <array> |
23 | | #include <atomic> |
24 | | #include <chrono> |
25 | | #include <map> |
26 | | #include <memory> |
27 | | #include <mutex> |
28 | | #include <shared_mutex> |
29 | | #include <string> |
30 | | #include <string_view> |
31 | | #include <unordered_map> |
32 | | #include <vector> |
33 | | |
34 | | #include "cloud/cloud_throttle_state_machine.h" |
35 | | #include "util/countdown_latch.h" |
36 | | #include "util/thread.h" |
37 | | |
38 | | namespace doris::cloud { |
39 | | |
40 | | // Strict QPS limiter that doesn't allow burst |
41 | | // Unlike token bucket, it strictly enforces fixed intervals between requests |
42 | | class StrictQpsLimiter { |
43 | | public: |
44 | | using Clock = std::chrono::steady_clock; |
45 | | |
46 | | explicit StrictQpsLimiter(double qps); |
47 | | |
48 | | // Returns the time point when the request is allowed to execute |
49 | | // Caller should sleep until this time point |
50 | | Clock::time_point reserve(); |
51 | | |
52 | | // Dynamically update the QPS limit, optionally discarding queued reservations. |
53 | | void update_qps(double new_qps, bool reset_reservation = false); |
54 | | |
55 | | // Get current QPS limit |
56 | | double get_qps() const; |
57 | | |
58 | | private: |
59 | | mutable std::mutex _mtx; |
60 | | int64_t _interval_ns; |
61 | | Clock::time_point _next_allowed_time; |
62 | | }; |
63 | | |
64 | | // QPS counter for a single (table, RPC type) pair using bvar |
65 | | class TableRpcQpsCounter { |
66 | | public: |
67 | | TableRpcQpsCounter(int64_t table_id, LoadRelatedRpc rpc_type, int window_sec); |
68 | 26.6k | ~TableRpcQpsCounter() = default; |
69 | | |
70 | | // Record one RPC call |
71 | | void increment(); |
72 | | |
73 | | // Get current QPS (average over the configured time window) |
74 | | double get_qps() const; |
75 | | |
76 | 251k | int64_t last_record_time_us() const { |
77 | 251k | return _last_record_time_us.load(std::memory_order_relaxed); |
78 | 251k | } |
79 | | |
80 | 0 | int64_t table_id() const { return _table_id; } |
81 | 0 | LoadRelatedRpc rpc_type() const { return _rpc_type; } |
82 | | |
83 | | private: |
84 | | int64_t _table_id; |
85 | | LoadRelatedRpc _rpc_type; |
86 | | |
87 | | std::unique_ptr<bvar::Adder<int64_t>> _counter; |
88 | | std::unique_ptr<bvar::PerSecond<bvar::Adder<int64_t>>> _qps; |
89 | | std::atomic<int64_t> _last_record_time_us {0}; |
90 | | }; |
91 | | |
92 | | // Registry managing QPS counters for all tables |
93 | | class TableRpcQpsRegistry { |
94 | | public: |
95 | | TableRpcQpsRegistry(); |
96 | | TableRpcQpsRegistry(std::chrono::milliseconds cleanup_interval, |
97 | | std::chrono::milliseconds inactive_timeout); |
98 | | ~TableRpcQpsRegistry(); |
99 | | |
100 | | // Record one RPC call for the given table |
101 | | void record(LoadRelatedRpc rpc_type, int64_t table_id); |
102 | | |
103 | | // Get the top-k tables with highest QPS for the given RPC type |
104 | | // Returns: [(table_id, qps), ...] sorted by qps in descending order |
105 | | std::vector<std::pair<int64_t, double>> get_top_k_tables(LoadRelatedRpc rpc_type, int k) const; |
106 | | |
107 | | // Get QPS for a specific table on a specific RPC type |
108 | | double get_qps(LoadRelatedRpc rpc_type, int64_t table_id) const; |
109 | | |
110 | | // Clean up counters for tables that have been inactive for a long time |
111 | | size_t cleanup_inactive_tables(); |
112 | | |
113 | | size_t get_tracked_table_count(LoadRelatedRpc rpc_type) const; |
114 | | |
115 | | private: |
116 | | void _cleanup_thread_callback(); |
117 | | |
118 | | mutable std::shared_mutex _mutex; |
119 | | |
120 | | // rpc_type -> (table_id -> counter) |
121 | | std::array<std::unordered_map<int64_t, std::unique_ptr<TableRpcQpsCounter>>, |
122 | | static_cast<size_t>(LoadRelatedRpc::COUNT)> |
123 | | _counters; |
124 | | |
125 | | const std::chrono::milliseconds _cleanup_interval; |
126 | | const std::chrono::milliseconds _inactive_timeout; |
127 | | std::shared_ptr<Thread> _cleanup_thread; |
128 | | CountDownLatch _cleanup_stop_latch; |
129 | | }; |
130 | | |
131 | | struct TableRpcThrottleDecision { |
132 | | std::chrono::steady_clock::time_point wait_until; |
133 | | double qps_limit {0}; |
134 | | bool dry_run {false}; |
135 | | }; |
136 | | |
137 | | // Table-level throttler managing StrictQpsLimiter for each (RPC type, table) pair |
138 | | class TableRpcThrottler { |
139 | | public: |
140 | | TableRpcThrottler(); |
141 | 18 | ~TableRpcThrottler() = default; |
142 | | |
143 | | // Called before RPC execution, returns the time point when execution is allowed |
144 | | // Returns now if no limit is set |
145 | | std::chrono::steady_clock::time_point throttle(LoadRelatedRpc rpc_type, int64_t table_id); |
146 | | TableRpcThrottleDecision throttle(LoadRelatedRpc rpc_type, int64_t table_id, bool dry_run); |
147 | | |
148 | | // Log suppression is independent for every RPC type. |
149 | | bool should_log(LoadRelatedRpc rpc_type, int64_t now_us); |
150 | | |
151 | | // Set or update the QPS limit for a table |
152 | | void set_qps_limit(LoadRelatedRpc rpc_type, int64_t table_id, double qps_limit, |
153 | | bool reset_reservation = false); |
154 | | |
155 | | // Remove the QPS limit for a table |
156 | | void remove_qps_limit(LoadRelatedRpc rpc_type, int64_t table_id); |
157 | | |
158 | | // Get current QPS limit (returns 0 if not set) |
159 | | double get_qps_limit(LoadRelatedRpc rpc_type, int64_t table_id) const; |
160 | | |
161 | | // Check if a limit exists for the given (rpc_type, table_id) |
162 | | bool has_limit(LoadRelatedRpc rpc_type, int64_t table_id) const; |
163 | | |
164 | | // Get the number of throttled tables for a given RPC type |
165 | | size_t get_throttled_table_count(LoadRelatedRpc rpc_type) const; |
166 | | |
167 | | // Get all currently throttled entries: (rpc_type, table_id, qps_limit) |
168 | | struct ThrottleEntry { |
169 | | LoadRelatedRpc rpc_type; |
170 | | int64_t table_id; |
171 | | double qps_limit; |
172 | | }; |
173 | | std::vector<ThrottleEntry> get_all_throttled_entries() const; |
174 | | |
175 | | private: |
176 | | mutable std::shared_mutex _mutex; |
177 | | std::map<std::pair<LoadRelatedRpc, int64_t>, std::unique_ptr<StrictQpsLimiter>> _limiters; |
178 | | |
179 | | std::array<std::atomic<int64_t>, static_cast<size_t>(LoadRelatedRpc::COUNT)> _next_log_time_us; |
180 | | |
181 | | // bvar: current throttled table count per RPC type |
182 | | std::array<std::unique_ptr<bvar::Status<size_t>>, static_cast<size_t>(LoadRelatedRpc::COUNT)> |
183 | | _throttled_table_counts; |
184 | | }; |
185 | | |
186 | | // MS backpressure handler that coordinates QPS statistics, throttle upgrade and downgrade |
187 | | // Uses state machine for decisions, providing better testability |
188 | | class MSBackpressureHandler { |
189 | | public: |
190 | | MSBackpressureHandler(TableRpcQpsRegistry* qps_registry, TableRpcThrottler* throttler); |
191 | | ~MSBackpressureHandler(); |
192 | | |
193 | | // Called when receiving MS_BUSY response |
194 | | // Returns true if throttle upgrade was triggered |
195 | | bool on_ms_busy(); |
196 | | |
197 | | // Called before RPC execution and returns the actual or dry-run throttle decision. |
198 | | TableRpcThrottleDecision before_rpc(LoadRelatedRpc rpc_type, int64_t table_id); |
199 | | |
200 | | bool should_log_throttle(LoadRelatedRpc rpc_type, int64_t now_us); |
201 | | double get_current_qps(LoadRelatedRpc rpc_type, int64_t table_id) const; |
202 | | |
203 | | // Called after RPC execution, records QPS statistics |
204 | | void after_rpc(LoadRelatedRpc rpc_type, int64_t table_id); |
205 | | |
206 | | // Runtime update parameters |
207 | | void update_throttle_params(RpcThrottleParams params); |
208 | | void update_coordinator_params(ThrottleCoordinatorParams params); |
209 | | |
210 | | // Get seconds since last MS_BUSY (for monitoring) |
211 | | int64_t seconds_since_last_ms_busy() const; |
212 | | |
213 | | // Query current state |
214 | | size_t upgrade_level() const; |
215 | | int64_t ticks_since_last_ms_busy() const; |
216 | | int64_t ticks_since_last_upgrade() const; |
217 | | |
218 | | private: |
219 | | // Background thread that periodically advances time |
220 | | void _tick_thread_callback(); |
221 | | |
222 | | // Advance time by specified ticks, handle any triggered events (e.g., downgrade) |
223 | | void _advance_time(int64_t ticks); |
224 | | |
225 | | // Apply actions to the throttler |
226 | | void _apply_actions(const std::vector<RpcThrottleAction>& actions); |
227 | | |
228 | | // Build QPS snapshot from registry |
229 | | std::vector<RpcQpsSnapshot> _build_qps_snapshot() const; |
230 | | |
231 | | TableRpcQpsRegistry* _qps_registry; |
232 | | TableRpcThrottler* _throttler; |
233 | | mutable std::mutex _transition_mutex; |
234 | | |
235 | | // State machine components |
236 | | std::unique_ptr<RpcThrottleStateMachine> _state_machine; |
237 | | std::unique_ptr<RpcThrottleCoordinator> _coordinator; |
238 | | |
239 | | // Background thread for periodic tick |
240 | | std::shared_ptr<Thread> _tick_thread; |
241 | | CountDownLatch _stop_latch; |
242 | | |
243 | | // For bvar compatibility only - track approximate seconds since last MS_BUSY |
244 | | mutable std::mutex _mutex; |
245 | | std::chrono::steady_clock::time_point _last_ms_busy_time; |
246 | | }; |
247 | | |
248 | | // Global bvar metrics for backpressure handling |
249 | | extern bvar::Adder<uint64_t> g_backpressure_upgrade_count; |
250 | | extern bvar::Window<bvar::Adder<uint64_t>> g_backpressure_upgrade_60s; |
251 | | extern bvar::Adder<uint64_t> g_backpressure_downgrade_count; |
252 | | extern bvar::Window<bvar::Adder<uint64_t>> g_backpressure_downgrade_60s; |
253 | | extern bvar::Adder<uint64_t> g_ms_busy_count; |
254 | | extern bvar::Window<bvar::Adder<uint64_t>> g_ms_busy_60s; |
255 | | |
256 | | // Per-RPC-type throttle wait latency recorders |
257 | | bvar::LatencyRecorder* get_throttle_wait_recorder(LoadRelatedRpc rpc); |
258 | | |
259 | | } // namespace doris::cloud |