prax_query/connection/
pool.rs

1//! Connection pool configuration.
2
3use super::{ConnectionOptions, PoolOptions};
4use std::time::Duration;
5use tracing::info;
6
7/// Complete pool configuration combining connection and pool options.
8#[derive(Debug, Clone)]
9pub struct PoolConfig {
10    /// Connection options.
11    pub connection: ConnectionOptions,
12    /// Pool options.
13    pub pool: PoolOptions,
14    /// Number of retry attempts for failed connections.
15    pub retry_attempts: u32,
16    /// Delay between retry attempts.
17    pub retry_delay: Duration,
18    /// Health check interval.
19    pub health_check_interval: Option<Duration>,
20}
21
22impl Default for PoolConfig {
23    fn default() -> Self {
24        Self {
25            connection: ConnectionOptions::default(),
26            pool: PoolOptions::default(),
27            retry_attempts: 3,
28            retry_delay: Duration::from_millis(500),
29            health_check_interval: Some(Duration::from_secs(30)),
30        }
31    }
32}
33
34impl PoolConfig {
35    /// Create a new pool configuration.
36    pub fn new() -> Self {
37        Self::default()
38    }
39
40    /// Set connection options.
41    pub fn connection(mut self, options: ConnectionOptions) -> Self {
42        self.connection = options;
43        self
44    }
45
46    /// Set pool options.
47    pub fn pool(mut self, options: PoolOptions) -> Self {
48        self.pool = options;
49        self
50    }
51
52    /// Set max connections.
53    pub fn max_connections(mut self, n: u32) -> Self {
54        self.pool.max_connections = n;
55        self
56    }
57
58    /// Set min connections.
59    pub fn min_connections(mut self, n: u32) -> Self {
60        self.pool.min_connections = n;
61        self
62    }
63
64    /// Set connection timeout.
65    pub fn connect_timeout(mut self, timeout: Duration) -> Self {
66        self.connection.connect_timeout = timeout;
67        self
68    }
69
70    /// Set acquire timeout.
71    pub fn acquire_timeout(mut self, timeout: Duration) -> Self {
72        self.pool.acquire_timeout = timeout;
73        self
74    }
75
76    /// Set idle timeout.
77    pub fn idle_timeout(mut self, timeout: Duration) -> Self {
78        self.pool.idle_timeout = Some(timeout);
79        self
80    }
81
82    /// Set max lifetime.
83    pub fn max_lifetime(mut self, lifetime: Duration) -> Self {
84        self.pool.max_lifetime = Some(lifetime);
85        self
86    }
87
88    /// Set retry attempts.
89    pub fn retry_attempts(mut self, attempts: u32) -> Self {
90        self.retry_attempts = attempts;
91        self
92    }
93
94    /// Set retry delay.
95    pub fn retry_delay(mut self, delay: Duration) -> Self {
96        self.retry_delay = delay;
97        self
98    }
99
100    /// Set health check interval.
101    pub fn health_check_interval(mut self, interval: Duration) -> Self {
102        self.health_check_interval = Some(interval);
103        self
104    }
105
106    /// Disable health checks.
107    pub fn no_health_check(mut self) -> Self {
108        self.health_check_interval = None;
109        self
110    }
111
112    /// Create a configuration optimized for low-latency.
113    pub fn low_latency() -> Self {
114        info!(
115            max_connections = 20,
116            min_connections = 5,
117            "PoolConfig::low_latency() initialized"
118        );
119        Self {
120            connection: ConnectionOptions::new().connect_timeout(Duration::from_secs(5)),
121            pool: PoolOptions::new()
122                .max_connections(20)
123                .min_connections(5)
124                .acquire_timeout(Duration::from_secs(5))
125                .idle_timeout(Duration::from_secs(60)),
126            retry_attempts: 1,
127            retry_delay: Duration::from_millis(100),
128            health_check_interval: Some(Duration::from_secs(10)),
129        }
130    }
131
132    /// Create a configuration optimized for high throughput.
133    pub fn high_throughput() -> Self {
134        info!(
135            max_connections = 50,
136            min_connections = 10,
137            "PoolConfig::high_throughput() initialized"
138        );
139        Self {
140            connection: ConnectionOptions::new().connect_timeout(Duration::from_secs(30)),
141            pool: PoolOptions::new()
142                .max_connections(50)
143                .min_connections(10)
144                .acquire_timeout(Duration::from_secs(30))
145                .idle_timeout(Duration::from_secs(300)),
146            retry_attempts: 3,
147            retry_delay: Duration::from_secs(1),
148            health_check_interval: Some(Duration::from_secs(60)),
149        }
150    }
151
152    /// Create a configuration for development/testing.
153    pub fn development() -> Self {
154        info!(
155            max_connections = 5,
156            min_connections = 1,
157            "PoolConfig::development() initialized"
158        );
159        Self {
160            connection: ConnectionOptions::new().connect_timeout(Duration::from_secs(5)),
161            pool: PoolOptions::new()
162                .max_connections(5)
163                .min_connections(1)
164                .acquire_timeout(Duration::from_secs(5))
165                .test_before_acquire(false),
166            retry_attempts: 0,
167            retry_delay: Duration::from_millis(0),
168            health_check_interval: None,
169        }
170    }
171
172    /// Create a configuration optimized for read-heavy workloads.
173    ///
174    /// Features:
175    /// - More connections (reads can parallelize)
176    /// - Longer connection lifetime (cached statement benefits)
177    /// - Moderate health check interval
178    pub fn read_heavy() -> Self {
179        info!(
180            max_connections = 30,
181            min_connections = 5,
182            "PoolConfig::read_heavy() initialized"
183        );
184        Self {
185            connection: ConnectionOptions::new().connect_timeout(Duration::from_secs(10)),
186            pool: PoolOptions::new()
187                .max_connections(30)
188                .min_connections(5)
189                .acquire_timeout(Duration::from_secs(15))
190                .idle_timeout(Duration::from_secs(300))
191                .max_lifetime(Duration::from_secs(3600)), // 1 hour for cached statements
192            retry_attempts: 2,
193            retry_delay: Duration::from_millis(200),
194            health_check_interval: Some(Duration::from_secs(30)),
195        }
196    }
197
198    /// Create a configuration optimized for write-heavy workloads.
199    ///
200    /// Features:
201    /// - Fewer connections (writes are serialized)
202    /// - Shorter lifetime (avoid long-running transactions)
203    /// - Frequent health checks
204    pub fn write_heavy() -> Self {
205        info!(
206            max_connections = 15,
207            min_connections = 3,
208            "PoolConfig::write_heavy() initialized"
209        );
210        Self {
211            connection: ConnectionOptions::new().connect_timeout(Duration::from_secs(10)),
212            pool: PoolOptions::new()
213                .max_connections(15)
214                .min_connections(3)
215                .acquire_timeout(Duration::from_secs(20))
216                .idle_timeout(Duration::from_secs(120))
217                .max_lifetime(Duration::from_secs(900)), // 15 minutes
218            retry_attempts: 3,
219            retry_delay: Duration::from_millis(500),
220            health_check_interval: Some(Duration::from_secs(15)),
221        }
222    }
223
224    /// Create a configuration optimized for mixed workloads.
225    ///
226    /// Balanced settings for applications with both reads and writes.
227    pub fn mixed_workload() -> Self {
228        info!(
229            max_connections = 25,
230            min_connections = 5,
231            "PoolConfig::mixed_workload() initialized"
232        );
233        Self {
234            connection: ConnectionOptions::new().connect_timeout(Duration::from_secs(10)),
235            pool: PoolOptions::new()
236                .max_connections(25)
237                .min_connections(5)
238                .acquire_timeout(Duration::from_secs(15))
239                .idle_timeout(Duration::from_secs(180))
240                .max_lifetime(Duration::from_secs(1800)), // 30 minutes
241            retry_attempts: 2,
242            retry_delay: Duration::from_millis(300),
243            health_check_interval: Some(Duration::from_secs(30)),
244        }
245    }
246
247    /// Create a configuration optimized for batch processing.
248    ///
249    /// Features:
250    /// - Longer timeouts for batch operations
251    /// - More connections for parallel batch processing
252    /// - Infrequent health checks
253    pub fn batch_processing() -> Self {
254        info!(
255            max_connections = 40,
256            min_connections = 10,
257            "PoolConfig::batch_processing() initialized"
258        );
259        Self {
260            connection: ConnectionOptions::new().connect_timeout(Duration::from_secs(30)),
261            pool: PoolOptions::new()
262                .max_connections(40)
263                .min_connections(10)
264                .acquire_timeout(Duration::from_secs(60))
265                .idle_timeout(Duration::from_secs(600))
266                .max_lifetime(Duration::from_secs(7200)), // 2 hours
267            retry_attempts: 5,
268            retry_delay: Duration::from_secs(2),
269            health_check_interval: Some(Duration::from_secs(120)),
270        }
271    }
272
273    /// Create a configuration for serverless environments.
274    ///
275    /// Features:
276    /// - Quick connection acquisition
277    /// - Aggressive connection recycling
278    /// - No minimum connections (cold start friendly)
279    pub fn serverless() -> Self {
280        info!(
281            max_connections = 10,
282            min_connections = 0,
283            "PoolConfig::serverless() initialized"
284        );
285        Self {
286            connection: ConnectionOptions::new().connect_timeout(Duration::from_secs(3)),
287            pool: PoolOptions::new()
288                .max_connections(10)
289                .min_connections(0)
290                .acquire_timeout(Duration::from_secs(3))
291                .idle_timeout(Duration::from_secs(30))
292                .max_lifetime(Duration::from_secs(300)), // 5 minutes
293            retry_attempts: 1,
294            retry_delay: Duration::from_millis(50),
295            health_check_interval: None, // Skip health checks
296        }
297    }
298
299    /// Recommend a configuration based on expected queries per second.
300    ///
301    /// # Arguments
302    ///
303    /// * `qps` - Expected queries per second
304    /// * `avg_query_ms` - Average query duration in milliseconds
305    ///
306    /// # Example
307    ///
308    /// ```rust
309    /// use prax_query::connection::PoolConfig;
310    ///
311    /// // 100 queries/sec with 10ms average latency
312    /// let config = PoolConfig::for_workload(100, 10);
313    /// assert!(config.pool.max_connections >= 5);
314    /// ```
315    pub fn for_workload(qps: u32, avg_query_ms: u32) -> Self {
316        // Little's Law: connections = throughput * latency
317        // Add 20% headroom
318        let estimated_connections = ((qps * avg_query_ms) / 1000 + 1) * 120 / 100;
319        let max_connections = estimated_connections.clamp(5, 100);
320        let min_connections = (max_connections / 5).max(1);
321
322        Self {
323            connection: ConnectionOptions::new().connect_timeout(Duration::from_secs(10)),
324            pool: PoolOptions::new()
325                .max_connections(max_connections)
326                .min_connections(min_connections)
327                .acquire_timeout(Duration::from_secs(15))
328                .idle_timeout(Duration::from_secs(300)),
329            retry_attempts: 2,
330            retry_delay: Duration::from_millis(200),
331            health_check_interval: Some(Duration::from_secs(30)),
332        }
333    }
334}
335
336#[cfg(test)]
337mod tests {
338    use super::*;
339
340    #[test]
341    fn test_pool_config_builder() {
342        let config = PoolConfig::new()
343            .max_connections(30)
344            .min_connections(5)
345            .connect_timeout(Duration::from_secs(10))
346            .retry_attempts(5);
347
348        assert_eq!(config.pool.max_connections, 30);
349        assert_eq!(config.pool.min_connections, 5);
350        assert_eq!(config.connection.connect_timeout, Duration::from_secs(10));
351        assert_eq!(config.retry_attempts, 5);
352    }
353
354    #[test]
355    fn test_preset_configs() {
356        let low_latency = PoolConfig::low_latency();
357        assert_eq!(low_latency.pool.max_connections, 20);
358        assert_eq!(low_latency.retry_attempts, 1);
359
360        let high_throughput = PoolConfig::high_throughput();
361        assert_eq!(high_throughput.pool.max_connections, 50);
362
363        let dev = PoolConfig::development();
364        assert_eq!(dev.pool.max_connections, 5);
365        assert_eq!(dev.retry_attempts, 0);
366    }
367}