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()
121                .connect_timeout(Duration::from_secs(5)),
122            pool: PoolOptions::new()
123                .max_connections(20)
124                .min_connections(5)
125                .acquire_timeout(Duration::from_secs(5))
126                .idle_timeout(Duration::from_secs(60)),
127            retry_attempts: 1,
128            retry_delay: Duration::from_millis(100),
129            health_check_interval: Some(Duration::from_secs(10)),
130        }
131    }
132
133    /// Create a configuration optimized for high throughput.
134    pub fn high_throughput() -> Self {
135        info!(
136            max_connections = 50,
137            min_connections = 10,
138            "PoolConfig::high_throughput() initialized"
139        );
140        Self {
141            connection: ConnectionOptions::new()
142                .connect_timeout(Duration::from_secs(30)),
143            pool: PoolOptions::new()
144                .max_connections(50)
145                .min_connections(10)
146                .acquire_timeout(Duration::from_secs(30))
147                .idle_timeout(Duration::from_secs(300)),
148            retry_attempts: 3,
149            retry_delay: Duration::from_secs(1),
150            health_check_interval: Some(Duration::from_secs(60)),
151        }
152    }
153
154    /// Create a configuration for development/testing.
155    pub fn development() -> Self {
156        info!(
157            max_connections = 5,
158            min_connections = 1,
159            "PoolConfig::development() initialized"
160        );
161        Self {
162            connection: ConnectionOptions::new()
163                .connect_timeout(Duration::from_secs(5)),
164            pool: PoolOptions::new()
165                .max_connections(5)
166                .min_connections(1)
167                .acquire_timeout(Duration::from_secs(5))
168                .test_before_acquire(false),
169            retry_attempts: 0,
170            retry_delay: Duration::from_millis(0),
171            health_check_interval: None,
172        }
173    }
174
175    /// Create a configuration optimized for read-heavy workloads.
176    ///
177    /// Features:
178    /// - More connections (reads can parallelize)
179    /// - Longer connection lifetime (cached statement benefits)
180    /// - Moderate health check interval
181    pub fn read_heavy() -> Self {
182        info!(
183            max_connections = 30,
184            min_connections = 5,
185            "PoolConfig::read_heavy() initialized"
186        );
187        Self {
188            connection: ConnectionOptions::new()
189                .connect_timeout(Duration::from_secs(10)),
190            pool: PoolOptions::new()
191                .max_connections(30)
192                .min_connections(5)
193                .acquire_timeout(Duration::from_secs(15))
194                .idle_timeout(Duration::from_secs(300))
195                .max_lifetime(Duration::from_secs(3600)), // 1 hour for cached statements
196            retry_attempts: 2,
197            retry_delay: Duration::from_millis(200),
198            health_check_interval: Some(Duration::from_secs(30)),
199        }
200    }
201
202    /// Create a configuration optimized for write-heavy workloads.
203    ///
204    /// Features:
205    /// - Fewer connections (writes are serialized)
206    /// - Shorter lifetime (avoid long-running transactions)
207    /// - Frequent health checks
208    pub fn write_heavy() -> Self {
209        info!(
210            max_connections = 15,
211            min_connections = 3,
212            "PoolConfig::write_heavy() initialized"
213        );
214        Self {
215            connection: ConnectionOptions::new()
216                .connect_timeout(Duration::from_secs(10)),
217            pool: PoolOptions::new()
218                .max_connections(15)
219                .min_connections(3)
220                .acquire_timeout(Duration::from_secs(20))
221                .idle_timeout(Duration::from_secs(120))
222                .max_lifetime(Duration::from_secs(900)), // 15 minutes
223            retry_attempts: 3,
224            retry_delay: Duration::from_millis(500),
225            health_check_interval: Some(Duration::from_secs(15)),
226        }
227    }
228
229    /// Create a configuration optimized for mixed workloads.
230    ///
231    /// Balanced settings for applications with both reads and writes.
232    pub fn mixed_workload() -> Self {
233        info!(
234            max_connections = 25,
235            min_connections = 5,
236            "PoolConfig::mixed_workload() initialized"
237        );
238        Self {
239            connection: ConnectionOptions::new()
240                .connect_timeout(Duration::from_secs(10)),
241            pool: PoolOptions::new()
242                .max_connections(25)
243                .min_connections(5)
244                .acquire_timeout(Duration::from_secs(15))
245                .idle_timeout(Duration::from_secs(180))
246                .max_lifetime(Duration::from_secs(1800)), // 30 minutes
247            retry_attempts: 2,
248            retry_delay: Duration::from_millis(300),
249            health_check_interval: Some(Duration::from_secs(30)),
250        }
251    }
252
253    /// Create a configuration optimized for batch processing.
254    ///
255    /// Features:
256    /// - Longer timeouts for batch operations
257    /// - More connections for parallel batch processing
258    /// - Infrequent health checks
259    pub fn batch_processing() -> Self {
260        info!(
261            max_connections = 40,
262            min_connections = 10,
263            "PoolConfig::batch_processing() initialized"
264        );
265        Self {
266            connection: ConnectionOptions::new()
267                .connect_timeout(Duration::from_secs(30)),
268            pool: PoolOptions::new()
269                .max_connections(40)
270                .min_connections(10)
271                .acquire_timeout(Duration::from_secs(60))
272                .idle_timeout(Duration::from_secs(600))
273                .max_lifetime(Duration::from_secs(7200)), // 2 hours
274            retry_attempts: 5,
275            retry_delay: Duration::from_secs(2),
276            health_check_interval: Some(Duration::from_secs(120)),
277        }
278    }
279
280    /// Create a configuration for serverless environments.
281    ///
282    /// Features:
283    /// - Quick connection acquisition
284    /// - Aggressive connection recycling
285    /// - No minimum connections (cold start friendly)
286    pub fn serverless() -> Self {
287        info!(
288            max_connections = 10,
289            min_connections = 0,
290            "PoolConfig::serverless() initialized"
291        );
292        Self {
293            connection: ConnectionOptions::new()
294                .connect_timeout(Duration::from_secs(3)),
295            pool: PoolOptions::new()
296                .max_connections(10)
297                .min_connections(0)
298                .acquire_timeout(Duration::from_secs(3))
299                .idle_timeout(Duration::from_secs(30))
300                .max_lifetime(Duration::from_secs(300)), // 5 minutes
301            retry_attempts: 1,
302            retry_delay: Duration::from_millis(50),
303            health_check_interval: None, // Skip health checks
304        }
305    }
306
307    /// Recommend a configuration based on expected queries per second.
308    ///
309    /// # Arguments
310    ///
311    /// * `qps` - Expected queries per second
312    /// * `avg_query_ms` - Average query duration in milliseconds
313    ///
314    /// # Example
315    ///
316    /// ```rust
317    /// use prax_query::connection::PoolConfig;
318    ///
319    /// // 100 queries/sec with 10ms average latency
320    /// let config = PoolConfig::for_workload(100, 10);
321    /// assert!(config.pool.max_connections >= 5);
322    /// ```
323    pub fn for_workload(qps: u32, avg_query_ms: u32) -> Self {
324        // Little's Law: connections = throughput * latency
325        // Add 20% headroom
326        let estimated_connections = ((qps * avg_query_ms) / 1000 + 1) * 120 / 100;
327        let max_connections = estimated_connections.clamp(5, 100) as u32;
328        let min_connections = (max_connections / 5).max(1);
329
330        Self {
331            connection: ConnectionOptions::new()
332                .connect_timeout(Duration::from_secs(10)),
333            pool: PoolOptions::new()
334                .max_connections(max_connections)
335                .min_connections(min_connections)
336                .acquire_timeout(Duration::from_secs(15))
337                .idle_timeout(Duration::from_secs(300)),
338            retry_attempts: 2,
339            retry_delay: Duration::from_millis(200),
340            health_check_interval: Some(Duration::from_secs(30)),
341        }
342    }
343}
344
345#[cfg(test)]
346mod tests {
347    use super::*;
348
349    #[test]
350    fn test_pool_config_builder() {
351        let config = PoolConfig::new()
352            .max_connections(30)
353            .min_connections(5)
354            .connect_timeout(Duration::from_secs(10))
355            .retry_attempts(5);
356
357        assert_eq!(config.pool.max_connections, 30);
358        assert_eq!(config.pool.min_connections, 5);
359        assert_eq!(config.connection.connect_timeout, Duration::from_secs(10));
360        assert_eq!(config.retry_attempts, 5);
361    }
362
363    #[test]
364    fn test_preset_configs() {
365        let low_latency = PoolConfig::low_latency();
366        assert_eq!(low_latency.pool.max_connections, 20);
367        assert_eq!(low_latency.retry_attempts, 1);
368
369        let high_throughput = PoolConfig::high_throughput();
370        assert_eq!(high_throughput.pool.max_connections, 50);
371
372        let dev = PoolConfig::development();
373        assert_eq!(dev.pool.max_connections, 5);
374        assert_eq!(dev.retry_attempts, 0);
375    }
376}
377
378