Coverage Report

Created: 2026-08-31 18:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
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.3k
    ~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
247k
    int64_t last_record_time_us() const {
77
247k
        return _last_record_time_us.load(std::memory_order_relaxed);
78
247k
    }
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