use crate::connection_pool::{ConnectionPool, ConnectionPoolConfig, ConnectionPoolStatsSnapshot};
use crate::error::{Result, TdbError};
use parking_lot::RwLock;
struct MetricRegistry;
impl MetricRegistry {
fn global() -> Self {
Self
}
}
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PoolOptimizerConfig {
pub enable_adaptive_sizing: bool,
pub min_size: usize,
pub max_size: usize,
pub target_utilization: f64,
pub enable_affinity: bool,
pub enable_priority_queue: bool,
pub quality_window_size: usize,
pub adjustment_interval: Duration,
pub warmup_delay: Duration,
}
impl Default for PoolOptimizerConfig {
fn default() -> Self {
Self {
enable_adaptive_sizing: true,
min_size: 2,
max_size: 50,
target_utilization: 0.75,
enable_affinity: true,
enable_priority_queue: true,
quality_window_size: 100,
adjustment_interval: Duration::from_secs(60),
warmup_delay: Duration::from_millis(100),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum LoadBalancingStrategy {
RoundRobin,
LeastConnections,
WeightedRoundRobin,
Random,
LeastResponseTime,
}
#[derive(Debug, Clone, Default)]
struct ConnectionQuality {
connection_id: u64,
success_count: u64,
failure_count: u64,
total_response_time: Duration,
operation_count: u64,
last_operation: Option<Instant>,
quality_score: f64,
}
impl ConnectionQuality {
fn new(connection_id: u64) -> Self {
Self {
connection_id,
quality_score: 1.0,
..Default::default()
}
}
fn record_success(&mut self, response_time: Duration) {
self.success_count += 1;
self.operation_count += 1;
self.total_response_time += response_time;
self.last_operation = Some(Instant::now());
self.update_quality_score();
}
fn record_failure(&mut self) {
self.failure_count += 1;
self.operation_count += 1;
self.last_operation = Some(Instant::now());
self.update_quality_score();
}
fn update_quality_score(&mut self) {
if self.operation_count == 0 {
self.quality_score = 1.0;
return;
}
let success_rate = self.success_count as f64 / self.operation_count as f64;
let avg_response_time =
self.total_response_time.as_secs_f64() / self.operation_count as f64;
let response_time_score = if avg_response_time < 0.01 {
1.0
} else if avg_response_time < 0.1 {
0.8
} else if avg_response_time < 1.0 {
0.5
} else {
0.2
};
self.quality_score = success_rate * 0.7 + response_time_score * 0.3;
}
fn avg_response_time(&self) -> Duration {
if self.operation_count == 0 {
Duration::ZERO
} else {
self.total_response_time / self.operation_count as u32
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Priority {
Low = 0,
Normal = 1,
High = 2,
Critical = 3,
}
#[derive(Debug)]
struct ConnectionRequest {
priority: Priority,
timestamp: Instant,
thread_id: Option<u64>,
}
struct AdaptivePoolSizer {
config: PoolOptimizerConfig,
current_size: AtomicUsize,
last_adjustment: RwLock<Instant>,
utilization_history: RwLock<VecDeque<f64>>,
growth_rate: AtomicUsize,
shrink_rate: AtomicUsize,
}
impl AdaptivePoolSizer {
fn new(config: PoolOptimizerConfig, initial_size: usize) -> Self {
Self {
config,
current_size: AtomicUsize::new(initial_size),
last_adjustment: RwLock::new(Instant::now()),
utilization_history: RwLock::new(VecDeque::with_capacity(60)),
growth_rate: AtomicUsize::new(2),
shrink_rate: AtomicUsize::new(1),
}
}
fn analyze_and_adjust(&self, current_utilization: f64) -> Option<usize> {
let mut last_adj = self.last_adjustment.write();
if last_adj.elapsed() < self.config.adjustment_interval {
return None;
}
let mut history = self.utilization_history.write();
history.push_back(current_utilization);
if history.len() > 60 {
history.pop_front();
}
let avg_utilization = if history.is_empty() {
current_utilization
} else {
history.iter().sum::<f64>() / history.len() as f64
};
let current_size = self.current_size.load(Ordering::SeqCst);
let new_size = if avg_utilization > self.config.target_utilization + 0.1 {
let growth = self.growth_rate.load(Ordering::SeqCst);
let new_size = (current_size + growth).min(self.config.max_size);
if avg_utilization > self.config.target_utilization + 0.2 {
self.growth_rate
.store(growth.saturating_mul(2).min(10), Ordering::SeqCst);
}
new_size
} else if avg_utilization < self.config.target_utilization - 0.2 {
let shrink = self.shrink_rate.load(Ordering::SeqCst);
let new_size = current_size
.saturating_sub(shrink)
.max(self.config.min_size);
self.growth_rate.store(2, Ordering::SeqCst);
new_size
} else {
return None;
};
if new_size != current_size {
self.current_size.store(new_size, Ordering::SeqCst);
*last_adj = Instant::now();
Some(new_size)
} else {
None
}
}
fn current_size(&self) -> usize {
self.current_size.load(Ordering::SeqCst)
}
}
pub struct ConnectionPoolOptimizer {
pool: Arc<ConnectionPool>,
config: PoolOptimizerConfig,
sizer: Option<AdaptivePoolSizer>,
quality_metrics: RwLock<HashMap<u64, ConnectionQuality>>,
load_balancing: LoadBalancingStrategy,
round_robin_counter: AtomicU64,
affinity_map: RwLock<HashMap<u64, u64>>,
priority_queue: RwLock<VecDeque<ConnectionRequest>>,
metrics: Arc<MetricRegistry>,
stats: OptimizerStats,
}
#[derive(Debug)]
struct OptimizerStats {
total_requests: AtomicU64,
priority_queue_hits: AtomicU64,
affinity_hits: AtomicU64,
size_adjustments: AtomicU64,
warmups_performed: AtomicU64,
}
impl Default for OptimizerStats {
fn default() -> Self {
Self {
total_requests: AtomicU64::new(0),
priority_queue_hits: AtomicU64::new(0),
affinity_hits: AtomicU64::new(0),
size_adjustments: AtomicU64::new(0),
warmups_performed: AtomicU64::new(0),
}
}
}
impl ConnectionPoolOptimizer {
pub fn new(
pool: Arc<ConnectionPool>,
config: PoolOptimizerConfig,
load_balancing: LoadBalancingStrategy,
) -> Self {
let sizer = if config.enable_adaptive_sizing {
Some(AdaptivePoolSizer::new(config.clone(), pool.size()))
} else {
None
};
let metrics = Arc::new(MetricRegistry::global());
Self {
pool,
config,
sizer,
quality_metrics: RwLock::new(HashMap::new()),
load_balancing,
round_robin_counter: AtomicU64::new(0),
affinity_map: RwLock::new(HashMap::new()),
priority_queue: RwLock::new(VecDeque::new()),
metrics,
stats: OptimizerStats::default(),
}
}
pub fn acquire_optimized(
&self,
priority: Priority,
) -> Result<crate::connection_pool::PooledConnection> {
self.stats.total_requests.fetch_add(1, Ordering::SeqCst);
if let Some(sizer) = &self.sizer {
let pool_stats = self.pool.stats();
let utilization = pool_stats.utilization_rate();
if let Some(new_size) = sizer.analyze_and_adjust(utilization) {
log::info!(
"Adjusting pool size to {} (utilization: {:.2}%)",
new_size,
utilization * 100.0
);
if let Err(e) = self.pool.resize(new_size) {
log::warn!("Failed to resize pool: {}", e);
}
self.stats.size_adjustments.fetch_add(1, Ordering::SeqCst);
}
}
if self.config.enable_affinity {
let thread_id = Self::get_thread_id();
let affinity_map = self.affinity_map.read();
if let Some(&connection_id) = affinity_map.get(&thread_id) {
self.stats.affinity_hits.fetch_add(1, Ordering::SeqCst);
}
}
let connection = self.pool.acquire()?;
let conn_id = connection.id();
let mut quality_metrics = self.quality_metrics.write();
quality_metrics
.entry(conn_id)
.or_insert_with(|| ConnectionQuality::new(conn_id));
if self.config.enable_affinity {
let thread_id = Self::get_thread_id();
let mut affinity_map = self.affinity_map.write();
affinity_map.insert(thread_id, conn_id);
}
Ok(connection)
}
pub fn record_operation(&self, connection_id: u64, success: bool, response_time: Duration) {
let mut quality_metrics = self.quality_metrics.write();
if let Some(quality) = quality_metrics.get_mut(&connection_id) {
if success {
quality.record_success(response_time);
} else {
quality.record_failure();
}
}
}
pub fn connection_quality(&self, connection_id: u64) -> Option<f64> {
let quality_metrics = self.quality_metrics.read();
quality_metrics.get(&connection_id).map(|q| q.quality_score)
}
pub fn best_connection_id(&self) -> Option<u64> {
let quality_metrics = self.quality_metrics.read();
quality_metrics
.values()
.max_by(|a, b| {
a.quality_score
.partial_cmp(&b.quality_score)
.unwrap_or(std::cmp::Ordering::Equal)
})
.map(|q| q.connection_id)
}
pub fn warmup_connections(&self, count: usize) -> Result<()> {
log::info!("Warming up {} connections", count);
for _ in 0..count {
match self.pool.acquire() {
Ok(_conn) => {
std::thread::sleep(self.config.warmup_delay);
self.stats.warmups_performed.fetch_add(1, Ordering::SeqCst);
}
Err(e) => {
log::warn!("Failed to warm up connection: {}", e);
}
}
}
Ok(())
}
pub fn optimizer_stats(&self) -> OptimizerStatsSnapshot {
OptimizerStatsSnapshot {
total_requests: self.stats.total_requests.load(Ordering::SeqCst),
priority_queue_hits: self.stats.priority_queue_hits.load(Ordering::SeqCst),
affinity_hits: self.stats.affinity_hits.load(Ordering::SeqCst),
size_adjustments: self.stats.size_adjustments.load(Ordering::SeqCst),
warmups_performed: self.stats.warmups_performed.load(Ordering::SeqCst),
current_pool_size: self.pool.size(),
available_connections: self.pool.available(),
affinity_map_size: self.affinity_map.read().len(),
quality_metrics_count: self.quality_metrics.read().len(),
}
}
pub fn reset_stats(&self) {
self.stats.total_requests.store(0, Ordering::SeqCst);
self.stats.priority_queue_hits.store(0, Ordering::SeqCst);
self.stats.affinity_hits.store(0, Ordering::SeqCst);
self.stats.size_adjustments.store(0, Ordering::SeqCst);
self.stats.warmups_performed.store(0, Ordering::SeqCst);
}
pub fn clear_affinity(&self) {
let mut affinity_map = self.affinity_map.write();
affinity_map.clear();
}
pub fn clear_quality_metrics(&self) {
let mut quality_metrics = self.quality_metrics.write();
quality_metrics.clear();
}
fn get_thread_id() -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let thread_id = std::thread::current().id();
let mut hasher = DefaultHasher::new();
thread_id.hash(&mut hasher);
hasher.finish()
}
pub fn all_quality_scores(&self) -> HashMap<u64, f64> {
let quality_metrics = self.quality_metrics.read();
quality_metrics
.iter()
.map(|(&id, quality)| (id, quality.quality_score))
.collect()
}
pub fn pool_stats(&self) -> ConnectionPoolStatsSnapshot {
self.pool.stats()
}
pub fn recommended_pool_size(&self) -> usize {
if let Some(sizer) = &self.sizer {
sizer.current_size()
} else {
self.pool.size()
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OptimizerStatsSnapshot {
pub total_requests: u64,
pub priority_queue_hits: u64,
pub affinity_hits: u64,
pub size_adjustments: u64,
pub warmups_performed: u64,
pub current_pool_size: usize,
pub available_connections: usize,
pub affinity_map_size: usize,
pub quality_metrics_count: usize,
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn create_test_pool() -> (Arc<ConnectionPool>, TempDir) {
let temp_dir = TempDir::new().unwrap();
let config = ConnectionPoolConfig {
min_connections: 2,
max_connections: 10,
..Default::default()
};
let pool = ConnectionPool::new(temp_dir.path(), config).unwrap();
(Arc::new(pool), temp_dir)
}
#[test]
fn test_optimizer_creation() {
let (pool, _temp_dir) = create_test_pool();
let config = PoolOptimizerConfig::default();
let optimizer =
ConnectionPoolOptimizer::new(pool, config, LoadBalancingStrategy::RoundRobin);
let stats = optimizer.optimizer_stats();
assert_eq!(stats.total_requests, 0);
assert_eq!(stats.size_adjustments, 0);
}
#[test]
fn test_acquire_optimized() {
let (pool, _temp_dir) = create_test_pool();
let config = PoolOptimizerConfig::default();
let optimizer =
ConnectionPoolOptimizer::new(pool, config, LoadBalancingStrategy::RoundRobin);
let conn = optimizer.acquire_optimized(Priority::Normal).unwrap();
let stats = optimizer.optimizer_stats();
assert_eq!(stats.total_requests, 1);
assert!(stats.quality_metrics_count > 0);
}
#[test]
fn test_connection_quality_tracking() {
let (pool, _temp_dir) = create_test_pool();
let config = PoolOptimizerConfig::default();
let optimizer =
ConnectionPoolOptimizer::new(pool, config, LoadBalancingStrategy::RoundRobin);
let conn = optimizer.acquire_optimized(Priority::Normal).unwrap();
let conn_id = conn.id();
optimizer.record_operation(conn_id, true, Duration::from_millis(10));
let quality = optimizer.connection_quality(conn_id).unwrap();
assert!(quality > 0.0);
assert!(quality <= 1.0);
}
#[test]
fn test_quality_degradation_on_failure() {
let (pool, _temp_dir) = create_test_pool();
let config = PoolOptimizerConfig::default();
let optimizer =
ConnectionPoolOptimizer::new(pool, config, LoadBalancingStrategy::RoundRobin);
let conn = optimizer.acquire_optimized(Priority::Normal).unwrap();
let conn_id = conn.id();
optimizer.record_operation(conn_id, true, Duration::from_millis(10));
let initial_quality = optimizer.connection_quality(conn_id).unwrap();
optimizer.record_operation(conn_id, false, Duration::ZERO);
optimizer.record_operation(conn_id, false, Duration::ZERO);
let degraded_quality = optimizer.connection_quality(conn_id).unwrap();
assert!(degraded_quality < initial_quality);
}
#[test]
fn test_warmup_connections() {
let (pool, _temp_dir) = create_test_pool();
let config = PoolOptimizerConfig::default();
let optimizer =
ConnectionPoolOptimizer::new(pool, config, LoadBalancingStrategy::RoundRobin);
optimizer.warmup_connections(3).unwrap();
let stats = optimizer.optimizer_stats();
assert_eq!(stats.warmups_performed, 3);
}
#[test]
fn test_adaptive_pool_sizer() {
let config = PoolOptimizerConfig {
min_size: 2,
max_size: 20,
target_utilization: 0.7,
adjustment_interval: Duration::from_millis(10),
..Default::default()
};
let sizer = AdaptivePoolSizer::new(config, 5);
assert_eq!(sizer.current_size(), 5);
std::thread::sleep(Duration::from_millis(20));
let new_size = sizer.analyze_and_adjust(0.95);
assert!(new_size.is_some());
assert!(new_size.unwrap() > 5);
}
#[test]
fn test_affinity_tracking() {
let (pool, _temp_dir) = create_test_pool();
let config = PoolOptimizerConfig {
enable_affinity: true,
..Default::default()
};
let optimizer =
ConnectionPoolOptimizer::new(pool, config, LoadBalancingStrategy::RoundRobin);
let _conn1 = optimizer.acquire_optimized(Priority::Normal).unwrap();
let _conn2 = optimizer.acquire_optimized(Priority::Normal).unwrap();
let stats = optimizer.optimizer_stats();
assert!(stats.affinity_map_size > 0);
}
#[test]
fn test_clear_affinity() {
let (pool, _temp_dir) = create_test_pool();
let config = PoolOptimizerConfig {
enable_affinity: true,
..Default::default()
};
let optimizer =
ConnectionPoolOptimizer::new(pool, config, LoadBalancingStrategy::RoundRobin);
let _conn = optimizer.acquire_optimized(Priority::Normal).unwrap();
optimizer.clear_affinity();
let stats = optimizer.optimizer_stats();
assert_eq!(stats.affinity_map_size, 0);
}
#[test]
fn test_best_connection_selection() {
let (pool, _temp_dir) = create_test_pool();
let config = PoolOptimizerConfig::default();
let optimizer =
ConnectionPoolOptimizer::new(pool, config, LoadBalancingStrategy::RoundRobin);
let conn1 = optimizer.acquire_optimized(Priority::Normal).unwrap();
let conn_id1 = conn1.id();
drop(conn1);
let conn2 = optimizer.acquire_optimized(Priority::Normal).unwrap();
let conn_id2 = conn2.id();
drop(conn2);
optimizer.record_operation(conn_id1, true, Duration::from_millis(5));
optimizer.record_operation(conn_id1, true, Duration::from_millis(5));
optimizer.record_operation(conn_id2, true, Duration::from_millis(50));
optimizer.record_operation(conn_id2, false, Duration::ZERO);
let best = optimizer.best_connection_id();
assert!(best.is_some());
}
#[test]
fn test_all_quality_scores() {
let (pool, _temp_dir) = create_test_pool();
let config = PoolOptimizerConfig::default();
let optimizer =
ConnectionPoolOptimizer::new(pool, config, LoadBalancingStrategy::RoundRobin);
let conn1 = optimizer.acquire_optimized(Priority::Normal).unwrap();
let conn_id1 = conn1.id();
drop(conn1);
optimizer.record_operation(conn_id1, true, Duration::from_millis(10));
let scores = optimizer.all_quality_scores();
assert!(!scores.is_empty());
assert!(scores.contains_key(&conn_id1));
}
#[test]
fn test_reset_stats() {
let (pool, _temp_dir) = create_test_pool();
let config = PoolOptimizerConfig::default();
let optimizer =
ConnectionPoolOptimizer::new(pool, config, LoadBalancingStrategy::RoundRobin);
let _conn = optimizer.acquire_optimized(Priority::Normal).unwrap();
assert!(optimizer.optimizer_stats().total_requests > 0);
optimizer.reset_stats();
assert_eq!(optimizer.optimizer_stats().total_requests, 0);
}
#[test]
fn test_priority_levels() {
assert!(Priority::Critical > Priority::High);
assert!(Priority::High > Priority::Normal);
assert!(Priority::Normal > Priority::Low);
}
#[test]
fn test_load_balancing_strategies() {
let strategies = [
LoadBalancingStrategy::RoundRobin,
LoadBalancingStrategy::LeastConnections,
LoadBalancingStrategy::WeightedRoundRobin,
LoadBalancingStrategy::Random,
LoadBalancingStrategy::LeastResponseTime,
];
for i in 0..strategies.len() {
for j in (i + 1)..strategies.len() {
assert_ne!(strategies[i], strategies[j]);
}
}
}
#[test]
fn test_recommended_pool_size() {
let (pool, _temp_dir) = create_test_pool();
let config = PoolOptimizerConfig {
enable_adaptive_sizing: true,
..Default::default()
};
let optimizer =
ConnectionPoolOptimizer::new(pool, config, LoadBalancingStrategy::RoundRobin);
let recommended = optimizer.recommended_pool_size();
assert!(recommended >= 2);
}
}