fraiseql_server/config/
pool_tuning.rs1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct PoolPressureMonitorConfig {
27 #[serde(default)]
29 pub enabled: bool,
30
31 #[serde(default = "default_min_pool_size")]
33 pub min_pool_size: u32,
34
35 #[serde(default = "default_max_pool_size")]
37 pub max_pool_size: u32,
38
39 #[serde(default = "default_target_queue_depth")]
41 pub target_queue_depth: u32,
42
43 #[serde(default = "default_scale_up_step")]
45 pub scale_up_step: u32,
46
47 #[serde(default = "default_scale_down_step")]
49 pub scale_down_step: u32,
50
51 #[serde(default = "default_scale_down_idle_ratio")]
54 pub scale_down_idle_ratio: f64,
55
56 #[serde(default = "default_tuning_interval_ms")]
58 pub tuning_interval_ms: u64,
59
60 #[serde(default = "default_samples_before_action")]
62 pub samples_before_action: u32,
63}
64
65const fn default_min_pool_size() -> u32 {
66 5
67}
68const fn default_max_pool_size() -> u32 {
69 50
70}
71const fn default_target_queue_depth() -> u32 {
72 3
73}
74const fn default_scale_up_step() -> u32 {
75 5
76}
77const fn default_scale_down_step() -> u32 {
78 2
79}
80const fn default_scale_down_idle_ratio() -> f64 {
81 0.5
82}
83const fn default_tuning_interval_ms() -> u64 {
84 30_000
85}
86const fn default_samples_before_action() -> u32 {
87 3
88}
89
90impl Default for PoolPressureMonitorConfig {
91 fn default() -> Self {
92 Self {
93 enabled: false,
94 min_pool_size: default_min_pool_size(),
95 max_pool_size: default_max_pool_size(),
96 target_queue_depth: default_target_queue_depth(),
97 scale_up_step: default_scale_up_step(),
98 scale_down_step: default_scale_down_step(),
99 scale_down_idle_ratio: default_scale_down_idle_ratio(),
100 tuning_interval_ms: default_tuning_interval_ms(),
101 samples_before_action: default_samples_before_action(),
102 }
103 }
104}
105
106#[deprecated(since = "2.0.1", note = "Use PoolPressureMonitorConfig")]
112pub type PoolTuningConfig = PoolPressureMonitorConfig;
113
114impl PoolPressureMonitorConfig {
115 pub fn validate(&self) -> Result<(), String> {
125 if self.min_pool_size >= self.max_pool_size {
126 return Err(format!(
127 "pool_tuning: min_pool_size ({}) must be less than max_pool_size ({})",
128 self.min_pool_size, self.max_pool_size
129 ));
130 }
131 if self.scale_up_step == 0 {
132 return Err("pool_tuning: scale_up_step must be > 0".to_string());
133 }
134 if self.scale_down_step == 0 {
135 return Err("pool_tuning: scale_down_step must be > 0".to_string());
136 }
137 if !(0.0..=1.0).contains(&self.scale_down_idle_ratio) {
138 return Err(format!(
139 "pool_tuning: scale_down_idle_ratio ({}) must be in [0.0, 1.0]",
140 self.scale_down_idle_ratio
141 ));
142 }
143 if self.tuning_interval_ms < 100 {
144 return Err(format!(
145 "pool_tuning: tuning_interval_ms ({}) must be >= 100",
146 self.tuning_interval_ms
147 ));
148 }
149 Ok(())
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 #[allow(clippy::wildcard_imports)]
156 use super::*;
158
159 #[test]
160 fn test_default_config_is_disabled() {
161 let cfg = PoolPressureMonitorConfig::default();
162 assert!(!cfg.enabled, "pool pressure monitoring should be off by default");
163 }
164
165 #[test]
166 fn test_default_bounds_are_sensible() {
167 let cfg = PoolPressureMonitorConfig::default();
168 assert!(cfg.min_pool_size < cfg.max_pool_size);
169 assert!(cfg.scale_up_step > 0);
170 assert!(cfg.scale_down_step > 0);
171 assert!(cfg.tuning_interval_ms >= 1000);
172 }
173
174 #[test]
175 fn test_validate_passes_for_defaults() {
176 PoolPressureMonitorConfig::default()
177 .validate()
178 .unwrap_or_else(|e| panic!("default pool monitor config should pass validation: {e}"));
179 }
180
181 #[test]
182 fn test_validate_min_lt_max() {
183 let cfg = PoolPressureMonitorConfig {
184 min_pool_size: 10,
185 max_pool_size: 5,
186 ..Default::default()
187 };
188 assert!(
189 cfg.validate().is_err(),
190 "min >= max should be invalid, got: {:?}",
191 cfg.validate()
192 );
193 }
194
195 #[test]
196 fn test_validate_min_equals_max_is_invalid() {
197 let cfg = PoolPressureMonitorConfig {
198 min_pool_size: 10,
199 max_pool_size: 10,
200 ..Default::default()
201 };
202 assert!(
203 cfg.validate().is_err(),
204 "min == max should be invalid, got: {:?}",
205 cfg.validate()
206 );
207 }
208
209 #[test]
210 fn test_validate_idle_ratio_above_one() {
211 let cfg = PoolPressureMonitorConfig {
212 scale_down_idle_ratio: 1.5,
213 ..Default::default()
214 };
215 assert!(
216 cfg.validate().is_err(),
217 "idle ratio > 1.0 should be invalid, got: {:?}",
218 cfg.validate()
219 );
220 }
221
222 #[test]
223 fn test_validate_idle_ratio_negative() {
224 let cfg = PoolPressureMonitorConfig {
225 scale_down_idle_ratio: -0.1,
226 ..Default::default()
227 };
228 assert!(
229 cfg.validate().is_err(),
230 "idle ratio < 0.0 should be invalid, got: {:?}",
231 cfg.validate()
232 );
233 }
234
235 #[test]
236 fn test_validate_zero_scale_up_step() {
237 let cfg = PoolPressureMonitorConfig {
238 scale_up_step: 0,
239 ..Default::default()
240 };
241 assert!(
242 cfg.validate().is_err(),
243 "scale_up_step == 0 should be invalid, got: {:?}",
244 cfg.validate()
245 );
246 }
247
248 #[test]
249 fn test_validate_zero_scale_down_step() {
250 let cfg = PoolPressureMonitorConfig {
251 scale_down_step: 0,
252 ..Default::default()
253 };
254 assert!(
255 cfg.validate().is_err(),
256 "scale_down_step == 0 should be invalid, got: {:?}",
257 cfg.validate()
258 );
259 }
260
261 #[test]
262 #[allow(deprecated)] fn test_pool_tuning_config_alias_works() {
264 let _cfg: PoolTuningConfig = PoolTuningConfig::default();
266 }
267
268 #[test]
269 fn test_validate_interval_too_short() {
270 let cfg = PoolPressureMonitorConfig {
271 tuning_interval_ms: 50,
272 ..Default::default()
273 };
274 assert!(
275 cfg.validate().is_err(),
276 "tuning_interval_ms < 100 should be invalid, got: {:?}",
277 cfg.validate()
278 );
279 }
280}