Skip to main content

do_memory_storage_turso/pool/
mod.rs

1//! Connection pool for Turso/libSQL database connections
2//!
3//! Provides efficient connection management, concurrency limits, and performance monitoring.
4//!
5//! Note: libSQL's Database is already a connection factory. This pool adds:
6//! - Concurrency limits via semaphore
7//! - Connection health validation
8//! - Performance metrics and monitoring
9//! - Graceful lifecycle management
10//! - Adaptive pool sizing for variable loads
11//!
12//! ## Keep-Alive Pool
13//!
14//! The keep-alive pool (`keepalive.rs`) provides additional features:
15//! - Connection last-used tracking
16//! - Stale connection detection and automatic refresh
17//! - Proactive keep-alive pings
18//! - Reduced connection overhead (45ms -> ~5ms)
19//!
20//! ## Adaptive Pool
21//!
22//! The adaptive pool (`adaptive.rs`) provides automatic scaling:
23//! - Dynamically adjusts pool size based on load
24//! - Scales up when utilization exceeds threshold
25//! - Scales down during low utilization periods
26//! - 20% better performance under variable load
27
28pub mod adaptive;
29pub mod caching_pool;
30mod config;
31pub mod connection_wrapper;
32pub mod keepalive;
33#[cfg(test)]
34mod tests;
35
36pub use adaptive::{
37    AdaptiveConnectionPool, AdaptivePoolConfig, AdaptivePoolMetrics, AdaptivePooledConnection,
38    ConnectionCleanupCallback, ConnectionId,
39};
40pub use caching_pool::{CachingPool, CachingPoolConfig, CachingPoolStats, ConnectionGuard};
41pub use config::{PoolConfig, PoolMetrics, PoolMetricsSnapshot, PoolStatistics, PooledConnection};
42pub use connection_wrapper::PooledConnection as WrappedPooledConnection;
43pub use keepalive::{KeepAliveConfig, KeepAliveConnection, KeepAlivePool, KeepAliveStatistics};
44
45use do_memory_core::{Error, Result};
46use libsql::{Connection, Database};
47use parking_lot::RwLock;
48use std::sync::Arc;
49use std::time::{Duration, Instant};
50use tokio::sync::Semaphore;
51use tracing::{debug, info, warn};
52
53/// Connection pool for managing database connections
54///
55/// This pool provides:
56/// - Concurrency limits via semaphore (max_connections)
57/// - Connection health validation
58/// - Performance metrics
59/// - Graceful shutdown
60pub struct ConnectionPool {
61    db: Arc<Database>,
62    config: PoolConfig,
63    semaphore: Arc<Semaphore>,
64    stats: Arc<RwLock<PoolStatistics>>,
65    /// Atomic metrics for lock-free concurrent monitoring
66    pub(crate) metrics: Arc<PoolMetrics>,
67}
68
69impl ConnectionPool {
70    /// Create a new connection pool
71    ///
72    /// # Arguments
73    ///
74    /// * `db` - Database instance to create connections from
75    /// * `config` - Pool configuration
76    ///
77    /// # Example
78    ///
79    /// ```no_run
80    /// use std::sync::Arc;
81    /// use libsql::Builder;
82    /// use do_memory_storage_turso::pool::{ConnectionPool, PoolConfig};
83    ///
84    /// # async fn example() -> anyhow::Result<()> {
85    /// let db = Builder::new_local("test.db").build().await?;
86    /// let config = PoolConfig::default();
87    /// let pool = ConnectionPool::new(Arc::new(db), config).await?;
88    /// # Ok(())
89    /// # }
90    /// ```
91    pub async fn new(db: Arc<Database>, config: PoolConfig) -> Result<Self> {
92        info!(
93            "Creating connection pool with min={}, max={}",
94            config.min_connections, config.max_connections
95        );
96
97        // Create a semaphore wrapped in Arc for shared ownership
98        let semaphore = Arc::new(Semaphore::new(config.max_connections));
99        let stats = Arc::new(RwLock::new(PoolStatistics::default()));
100        let metrics = Arc::new(PoolMetrics::default());
101
102        let pool = Self {
103            db,
104            config,
105            semaphore,
106            stats,
107            metrics,
108        };
109
110        // Validate database connectivity
111        pool.validate_database().await?;
112
113        info!("Connection pool created successfully");
114        Ok(pool)
115    }
116
117    /// Validate database connectivity
118    async fn validate_database(&self) -> Result<()> {
119        let conn = self
120            .db
121            .connect()
122            .map_err(|e| Error::Storage(format!("Failed to connect to database: {}", e)))?;
123
124        conn.query("SELECT 1", ())
125            .await
126            .map_err(|e| Error::Storage(format!("Database validation failed: {}", e)))?;
127
128        Ok(())
129    }
130
131    /// Create a new database connection
132    async fn create_connection(&self) -> Result<Connection> {
133        let conn = self
134            .db
135            .connect()
136            .map_err(|e| Error::Storage(format!("Failed to create connection: {}", e)))?;
137
138        // Update statistics
139        {
140            let mut stats = self.stats.write();
141            stats.total_created += 1;
142        }
143
144        Ok(conn)
145    }
146
147    /// Get a connection from the pool
148    ///
149    /// This will:
150    /// 1. Wait for a semaphore permit (respects max_connections limit)
151    /// 2. Create a new connection from the database
152    /// 3. Optionally validate the connection health
153    /// 4. Return a PooledConnection guard that releases the permit on drop
154    ///
155    /// # Errors
156    ///
157    /// Returns error if:
158    /// - Timeout waiting for available connection slot
159    /// - Failed to create connection
160    /// - Connection health check fails
161    pub async fn get(&self) -> Result<PooledConnection> {
162        let start = Instant::now();
163
164        // Acquire an owned semaphore permit (limits concurrent connections)
165        let owned_permit_fut = Arc::clone(&self.semaphore).acquire_owned();
166        let permit = tokio::time::timeout(self.config.connection_timeout, owned_permit_fut)
167            .await
168            .map_err(|_| {
169                Error::Storage(format!(
170                    "Connection pool timeout after {:?}: max {} connections in use",
171                    self.config.connection_timeout, self.config.max_connections
172                ))
173            })?
174            .map_err(|e| Error::Storage(format!("Failed to acquire connection permit: {}", e)))?;
175
176        let wait_time = start.elapsed();
177
178        // Create a new connection
179        let conn = self.create_connection().await?;
180
181        // Validate connection health if enabled
182        if self.config.enable_health_check {
183            if let Err(e) = self.validate_connection_health(&conn).await {
184                let mut stats = self.stats.write();
185                stats.total_health_checks_failed += 1;
186                return Err(e);
187            }
188
189            let mut stats = self.stats.write();
190            stats.total_health_checks_passed += 1;
191        }
192
193        // Update statistics
194        {
195            let mut stats = self.stats.write();
196            stats.total_checkouts += 1;
197            stats.total_wait_time_ms += wait_time.as_millis() as u64;
198            stats.active_connections += 1;
199            stats.update_averages();
200        }
201
202        debug!(
203            "Connection acquired (wait: {:?}, active: {})",
204            wait_time,
205            self.stats.read().active_connections
206        );
207
208        // Record atomic metrics
209        self.metrics.record_acquire(wait_time.as_millis() as u64);
210
211        Ok(PooledConnection {
212            connection: Some(conn),
213            _permit: permit,
214            stats: Arc::clone(&self.stats),
215            metrics: Some(Arc::clone(&self.metrics)),
216        })
217    }
218
219    /// Validate a connection is still healthy
220    async fn validate_connection_health(&self, conn: &Connection) -> Result<()> {
221        tokio::time::timeout(self.config.health_check_timeout, conn.query("SELECT 1", ()))
222            .await
223            .map_err(|_| Error::Storage("Connection health check timeout".to_string()))?
224            .map_err(|e| Error::Storage(format!("Connection health check failed: {}", e)))?;
225
226        Ok(())
227    }
228
229    /// Get current pool statistics
230    pub async fn statistics(&self) -> PoolStatistics {
231        self.stats.read().clone()
232    }
233
234    /// Get current pool utilization (0.0 to 1.0)
235    pub async fn utilization(&self) -> f32 {
236        let stats = self.stats.read();
237        if self.config.max_connections == 0 {
238            return 0.0;
239        }
240        stats.active_connections as f32 / self.config.max_connections as f32
241    }
242
243    /// Get number of available connection slots
244    pub async fn available_connections(&self) -> usize {
245        let stats = self.stats.read();
246        self.config
247            .max_connections
248            .saturating_sub(stats.active_connections)
249    }
250
251    /// Check if pool has available capacity
252    pub async fn has_capacity(&self) -> bool {
253        self.available_connections().await > 0
254    }
255
256    /// Gracefully shutdown the pool
257    ///
258    /// Waits for active connections to be returned (up to 30 seconds).
259    pub async fn shutdown(&self) -> Result<()> {
260        info!("Shutting down connection pool");
261
262        let shutdown_timeout = Duration::from_secs(30);
263        let start = Instant::now();
264
265        while start.elapsed() < shutdown_timeout {
266            let active = self.stats.read().active_connections;
267            if active == 0 {
268                break;
269            }
270
271            debug!("Waiting for {} active connections to complete", active);
272            tokio::time::sleep(Duration::from_millis(100)).await;
273        }
274
275        let final_active = self.stats.read().active_connections;
276        if final_active > 0 {
277            warn!(
278                "Shutdown completed with {} active connections still in use",
279                final_active
280            );
281        } else {
282            info!("Connection pool shutdown complete");
283        }
284
285        Ok(())
286    }
287}