be/src/storage/adaptive_thread_pool_controller.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 <bthread/unstable.h> |
21 | | |
22 | | #include <atomic> |
23 | | #include <cstdint> |
24 | | #include <functional> |
25 | | #include <map> |
26 | | #include <mutex> |
27 | | #include <string> |
28 | | #include <vector> |
29 | | |
30 | | namespace doris { |
31 | | |
32 | | class ThreadPool; |
33 | | class SystemMetrics; |
34 | | class AdaptiveThreadPoolController; |
35 | | |
36 | | // Each pool group's timer state. Heap-allocated; shared between the controller |
37 | | // and the brpc TimerThread callback. |
38 | | struct TimerArg { |
39 | | AdaptiveThreadPoolController* ctrl; // never null |
40 | | std::string name; |
41 | | int64_t interval_ms; |
42 | | |
43 | | // Set before cancel() acquires mu, preventing further adjustment/re-registration. |
44 | | std::atomic<bool> stopped {false}; |
45 | | |
46 | | // Updated and read under mu, including the initial registration in add(). |
47 | | std::atomic<bthread_timer_t> timer_id {0}; |
48 | | |
49 | | // Serializes initial registration, adjustment, re-registration and cancellation. |
50 | | // Taking this lock alone does not join a callback that has not acquired it yet. |
51 | | std::mutex mu; |
52 | | }; |
53 | | |
54 | | // AdaptiveThreadPoolController dynamically adjusts thread pool sizes based on |
55 | | // system load (IO utilisation, CPU utilisation, flush queue depth). |
56 | | // |
57 | | // Each registered pool group runs as a one-shot bthread_timer_add chain: the |
58 | | // callback fires, adjusts the pool, then re-registers the next one-shot timer. |
59 | | // All groups share the single brpc TimerThread, keeping the overhead minimal. |
60 | | // |
61 | | // Usage: |
62 | | // AdaptiveThreadPoolController ctrl; |
63 | | // ctrl.init(system_metrics, s3_pool); |
64 | | // ctrl.add("flush", {pool1, pool2}, |
65 | | // AdaptiveThreadPoolController::make_flush_adjust_func(&ctrl, pool1), |
66 | | // max_per_cpu, min_per_cpu); |
67 | | // // ... later ... |
68 | | // ctrl.cancel("flush"); // or ctrl.stop() |
69 | | class AdaptiveThreadPoolController { |
70 | | public: |
71 | | using AdjustFunc = |
72 | | std::function<int(int current, int min_threads, int max_threads, std::string& reason)>; |
73 | | |
74 | | static constexpr int kDefaultIntervalMs = 10000; |
75 | | |
76 | | static constexpr int kQueueThreshold = 10; |
77 | | static constexpr int kIOBusyThresholdPercent = 90; |
78 | | static constexpr int kCPUBusyThresholdPercent = 90; |
79 | | static constexpr int kS3QueueBusyThreshold = 10; |
80 | | |
81 | 1.00k | AdaptiveThreadPoolController() = default; |
82 | 1.00k | ~AdaptiveThreadPoolController() { stop(); } |
83 | | |
84 | | // Initialize with system-level dependencies. |
85 | | void init(SystemMetrics* system_metrics, ThreadPool* s3_file_upload_pool); |
86 | | |
87 | | // Permanently stop registration and cancel all groups before pools are destroyed. |
88 | | void stop(); |
89 | | |
90 | | // Register a timer chain, draining an existing registration with the same name. |
91 | | // Lifecycle methods must not be called from an AdjustFunc. |
92 | | void add(std::string name, std::vector<ThreadPool*> pools, AdjustFunc adjust_func, |
93 | | double max_threads_per_cpu, double min_threads_per_cpu, |
94 | | int64_t interval_ms = kDefaultIntervalMs); |
95 | | |
96 | | // Cancel the timer chain and remove the pool group. Blocks until any |
97 | | // in-flight callback finishes, then returns. Safe to call before pool teardown. |
98 | | void cancel(const std::string& name); |
99 | | |
100 | | // Fire all registered groups once, ignoring the schedule. For testing. |
101 | | void adjust_once(); |
102 | | |
103 | | // Get current thread count for a named group. For testing/debugging. |
104 | | int get_current_threads(const std::string& name) const; |
105 | | |
106 | | // System-state helpers; safe to call from inside an AdjustFunc. |
107 | | bool is_io_busy(); |
108 | | bool is_cpu_busy(); |
109 | | |
110 | | // Factory: standard flush-pool adjust function. |
111 | | static AdjustFunc make_flush_adjust_func(AdaptiveThreadPoolController* controller, |
112 | | ThreadPool* flush_pool); |
113 | | |
114 | | // Callback registered with bthread_timer_add. Public only for the C linkage |
115 | | // requirement; do not call directly. |
116 | | static void _on_timer(void* arg); |
117 | | |
118 | | private: |
119 | | struct PoolGroup { |
120 | | std::string name; |
121 | | std::vector<ThreadPool*> pools; |
122 | | AdjustFunc adjust_func; |
123 | | double max_threads_per_cpu = 4.0; |
124 | | double min_threads_per_cpu = 0.5; |
125 | | int current_threads = 0; |
126 | | TimerArg* timer_arg = nullptr; // owned; freed by cancel() |
127 | | |
128 | | int get_max_threads() const; |
129 | | int get_min_threads() const; |
130 | | }; |
131 | | |
132 | | // Run one group's adjustment. Called from _on_timer (no lock on entry). |
133 | | void _fire_group(const std::string& name); |
134 | | |
135 | | void _apply_thread_count(PoolGroup& group, int target_threads, const std::string& reason); |
136 | | |
137 | | // Requires _lifecycle_mutex. |
138 | | void _cancel(const std::string& name); |
139 | | |
140 | | private: |
141 | | SystemMetrics* _system_metrics = nullptr; |
142 | | ThreadPool* _s3_file_upload_pool = nullptr; |
143 | | |
144 | | // Serializes add/cancel/stop so concurrent teardown also waits for cancellation. |
145 | | std::mutex _lifecycle_mutex; |
146 | | bool _stopped = false; |
147 | | mutable std::mutex _mutex; |
148 | | mutable std::mutex _metrics_state_mutex; |
149 | | std::map<std::string, PoolGroup> _pool_groups; |
150 | | |
151 | | // Last successfully computed IO-busy result. Returned as-is when the |
152 | | // measurement interval is too short to produce a valid new delta. |
153 | | bool _last_io_busy = false; |
154 | | |
155 | | // For disk IO util calculation (used by is_io_busy). |
156 | | std::map<std::string, int64_t> _last_disk_io_time; |
157 | | int64_t _last_check_time_sec = 0; |
158 | | |
159 | | // For CPU util calculation (used by is_cpu_busy). The counters come from |
160 | | // SystemMetrics' existing cpu_* metrics and are compared as deltas. |
161 | | bool _last_cpu_busy = false; |
162 | | int64_t _last_cpu_total_time = -1; |
163 | | int64_t _last_cpu_idle_time = -1; |
164 | | }; |
165 | | |
166 | | } // namespace doris |