Skip to main content

sz_rust_orm_facade/
pool_scaler.rs

1//! PoolScaler — 连接池动态扩容(P3 L3 调优)
2//!
3//! 监控连接池 acquire_timeout 命中率,高时扩容 max_connections,
4//! 低时回收空闲连接。受 sz-orm PoolConfig 上限约束。
5
6use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
7use std::sync::Arc;
8use std::time::Duration;
9
10/// PoolScaler 配置
11#[derive(Debug, Clone)]
12pub struct PoolScalerConfig {
13    /// 扩容阈值(acquire_timeout 命中率 > 此值时扩容)
14    pub scale_up_threshold: f64,
15    /// 回收阈值(空闲连接比例 > 此值时回收)
16    pub scale_down_threshold: f64,
17    /// 检查间隔
18    pub check_interval: Duration,
19    /// 最大连接数上限
20    pub max_connections: usize,
21    /// 最小连接数下限
22    pub min_connections: usize,
23}
24
25impl Default for PoolScalerConfig {
26    fn default() -> Self {
27        Self {
28            scale_up_threshold: 0.3,
29            scale_down_threshold: 0.7,
30            check_interval: Duration::from_secs(30),
31            max_connections: 100,
32            min_connections: 5,
33        }
34    }
35}
36
37/// 连接池指标快照
38#[derive(Debug, Clone)]
39pub struct PoolMetrics {
40    /// 当前连接数
41    pub current_connections: usize,
42    /// 空闲连接数
43    pub idle_connections: usize,
44    /// acquire_timeout 命中次数
45    pub timeout_count: u64,
46    /// 总 acquire 次数
47    pub total_acquire: u64,
48}
49
50impl PoolMetrics {
51    /// acquire_timeout 命中率
52    pub fn timeout_rate(&self) -> f64 {
53        if self.total_acquire == 0 {
54            0.0
55        } else {
56            self.timeout_count as f64 / self.total_acquire as f64
57        }
58    }
59
60    /// 空闲连接比例
61    pub fn idle_rate(&self) -> f64 {
62        if self.current_connections == 0 {
63            0.0
64        } else {
65            self.idle_connections as f64 / self.current_connections as f64
66        }
67    }
68}
69
70/// 连接池动态扩容器
71pub struct PoolScaler {
72    config: PoolScalerConfig,
73    target_connections: Arc<AtomicUsize>,
74    running: Arc<AtomicBool>,
75    scale_up_count: Arc<AtomicUsize>,
76    scale_down_count: Arc<AtomicUsize>,
77}
78
79impl PoolScaler {
80    /// 创建 PoolScaler
81    pub fn new(config: PoolScalerConfig) -> Self {
82        let initial = config.min_connections;
83        Self {
84            config,
85            target_connections: Arc::new(AtomicUsize::new(initial)),
86            running: Arc::new(AtomicBool::new(false)),
87            scale_up_count: Arc::new(AtomicUsize::new(0)),
88            scale_down_count: Arc::new(AtomicUsize::new(0)),
89        }
90    }
91
92    /// 当前目标连接数
93    pub fn target_connections(&self) -> usize {
94        self.target_connections.load(Ordering::Acquire)
95    }
96
97    /// 是否正在运行
98    pub fn is_running(&self) -> bool {
99        self.running.load(Ordering::Relaxed)
100    }
101
102    /// 扩容次数
103    pub fn scale_up_count(&self) -> usize {
104        self.scale_up_count.load(Ordering::Relaxed)
105    }
106
107    /// 回收次数
108    pub fn scale_down_count(&self) -> usize {
109        self.scale_down_count.load(Ordering::Relaxed)
110    }
111
112    /// 根据指标扩容
113    pub fn scale_up(&self, metrics: &PoolMetrics) {
114        if metrics.timeout_rate() > self.config.scale_up_threshold {
115            let current = self.target_connections.load(Ordering::Relaxed);
116            let new_target = (current + (current / 4)).min(self.config.max_connections);
117            self.target_connections.store(new_target, Ordering::Release);
118            self.scale_up_count.fetch_add(1, Ordering::Relaxed);
119        }
120    }
121
122    /// 根据指标回收
123    pub fn scale_down(&self, metrics: &PoolMetrics) {
124        if metrics.idle_rate() > self.config.scale_down_threshold {
125            let current = self.target_connections.load(Ordering::Relaxed);
126            let new_target = (current - (current / 4)).max(self.config.min_connections);
127            self.target_connections.store(new_target, Ordering::Release);
128            self.scale_down_count.fetch_add(1, Ordering::Relaxed);
129        }
130    }
131
132    /// 根据指标自动调整(扩容或回收)
133    pub fn adjust(&self, metrics: &PoolMetrics) {
134        if metrics.timeout_rate() > self.config.scale_up_threshold {
135            self.scale_up(metrics);
136        } else if metrics.idle_rate() > self.config.scale_down_threshold {
137            self.scale_down(metrics);
138        }
139    }
140}
141
142impl std::fmt::Debug for PoolScaler {
143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144        let target = self.target_connections.load(Ordering::Acquire);
145        write!(
146            f,
147            "PoolScaler {{ target: {target}, running: {} }}",
148            self.running.load(Ordering::Relaxed)
149        )
150    }
151}
152
153// ============================================================================
154// 单元测试
155// ============================================================================
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn test_pool_metrics_timeout_rate() {
163        let metrics = PoolMetrics {
164            current_connections: 10,
165            idle_connections: 3,
166            timeout_count: 5,
167            total_acquire: 100,
168        };
169        assert_eq!(metrics.timeout_rate(), 0.05);
170    }
171
172    #[test]
173    fn test_pool_metrics_timeout_rate_zero() {
174        let metrics = PoolMetrics {
175            current_connections: 10,
176            idle_connections: 3,
177            timeout_count: 0,
178            total_acquire: 0,
179        };
180        assert_eq!(metrics.timeout_rate(), 0.0);
181    }
182
183    #[test]
184    fn test_pool_metrics_idle_rate() {
185        let metrics = PoolMetrics {
186            current_connections: 10,
187            idle_connections: 7,
188            timeout_count: 0,
189            total_acquire: 100,
190        };
191        assert_eq!(metrics.idle_rate(), 0.7);
192    }
193
194    #[test]
195    fn test_config_default() {
196        let config = PoolScalerConfig::default();
197        assert_eq!(config.scale_up_threshold, 0.3);
198        assert_eq!(config.scale_down_threshold, 0.7);
199        assert_eq!(config.check_interval, Duration::from_secs(30));
200    }
201
202    #[test]
203    fn test_scaler_new() {
204        let config = PoolScalerConfig::default();
205        let scaler = PoolScaler::new(config);
206        assert_eq!(scaler.target_connections(), 5);
207        assert!(!scaler.is_running());
208    }
209
210    #[test]
211    fn test_scale_up() {
212        let config = PoolScalerConfig::default();
213        let scaler = PoolScaler::new(config);
214        scaler.target_connections.store(10, Ordering::Relaxed);
215        let metrics = PoolMetrics {
216            current_connections: 10,
217            idle_connections: 2,
218            timeout_count: 50,
219            total_acquire: 100,
220        };
221        scaler.scale_up(&metrics);
222        assert!(scaler.target_connections() > 10);
223        assert_eq!(scaler.scale_up_count(), 1);
224    }
225
226    #[test]
227    fn test_scale_down() {
228        let config = PoolScalerConfig::default();
229        let scaler = PoolScaler::new(config);
230        scaler.target_connections.store(20, Ordering::Relaxed);
231        let metrics = PoolMetrics {
232            current_connections: 20,
233            idle_connections: 18,
234            timeout_count: 0,
235            total_acquire: 100,
236        };
237        scaler.scale_down(&metrics);
238        assert!(scaler.target_connections() < 20);
239        assert_eq!(scaler.scale_down_count(), 1);
240    }
241
242    #[test]
243    fn test_scale_up_max_cap() {
244        let config = PoolScalerConfig::default();
245        let scaler = PoolScaler::new(config);
246        scaler.target_connections.store(95, Ordering::Relaxed);
247        let metrics = PoolMetrics {
248            current_connections: 95,
249            idle_connections: 0,
250            timeout_count: 50,
251            total_acquire: 100,
252        };
253        scaler.scale_up(&metrics);
254        assert_eq!(scaler.target_connections(), 100);
255    }
256
257    #[test]
258    fn test_scale_down_min_cap() {
259        let config = PoolScalerConfig::default();
260        let scaler = PoolScaler::new(config);
261        scaler.target_connections.store(6, Ordering::Relaxed);
262        let metrics = PoolMetrics {
263            current_connections: 6,
264            idle_connections: 5,
265            timeout_count: 0,
266            total_acquire: 100,
267        };
268        scaler.scale_down(&metrics);
269        // 6 - 6/4 = 6 - 1 = 5, max(5) = 5
270        assert_eq!(scaler.target_connections(), 5);
271    }
272
273    #[test]
274    fn test_adjust_scale_up() {
275        let config = PoolScalerConfig::default();
276        let scaler = PoolScaler::new(config);
277        scaler.target_connections.store(10, Ordering::Relaxed);
278        let metrics = PoolMetrics {
279            current_connections: 10,
280            idle_connections: 0,
281            timeout_count: 50,
282            total_acquire: 100,
283        };
284        scaler.adjust(&metrics);
285        assert_eq!(scaler.scale_up_count(), 1);
286        assert_eq!(scaler.scale_down_count(), 0);
287    }
288
289    #[test]
290    fn test_adjust_scale_down() {
291        let config = PoolScalerConfig::default();
292        let scaler = PoolScaler::new(config);
293        scaler.target_connections.store(20, Ordering::Relaxed);
294        let metrics = PoolMetrics {
295            current_connections: 20,
296            idle_connections: 18,
297            timeout_count: 0,
298            total_acquire: 100,
299        };
300        scaler.adjust(&metrics);
301        assert_eq!(scaler.scale_up_count(), 0);
302        assert_eq!(scaler.scale_down_count(), 1);
303    }
304}