1use anyhow::{anyhow, Result};
23use chrono::{DateTime, Utc};
24use dashmap::DashMap;
25use parking_lot::RwLock;
26use serde::{Deserialize, Serialize};
27use std::collections::{HashMap, VecDeque};
28use std::sync::Arc;
29use std::time::{Duration, Instant};
30use tokio::sync::mpsc;
31use tracing::{debug, error, info, warn};
32use uuid::Uuid;
33
34struct MovingAverage {
38 window_size: usize,
39 values: VecDeque<f64>,
40 sum: f64,
41}
42
43impl MovingAverage {
44 fn new(window_size: usize) -> Self {
45 Self {
46 window_size,
47 values: VecDeque::with_capacity(window_size),
48 sum: 0.0,
49 }
50 }
51
52 fn add(&mut self, value: f64) {
53 if self.values.len() >= self.window_size {
54 if let Some(old) = self.values.pop_front() {
55 self.sum -= old;
56 }
57 }
58 self.values.push_back(value);
59 self.sum += value;
60 }
61
62 fn mean(&self) -> f64 {
63 if self.values.is_empty() {
64 0.0
65 } else {
66 self.sum / self.values.len() as f64
67 }
68 }
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
73pub enum ScalingMode {
74 Manual,
76 Horizontal,
78 Vertical,
80 Hybrid,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub enum ScalingDirection {
87 ScaleUp { amount: usize },
89 ScaleDown { amount: usize },
91 NoChange,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
97pub enum PartitionStrategy {
98 RoundRobin,
100 Hash { key_field: String },
102 Range { ranges: Vec<(i64, i64)> },
104 ConsistentHash { virtual_nodes: usize },
106 Custom { strategy_name: String },
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
112pub enum LoadBalancingStrategy {
113 RoundRobin,
115 LeastConnections,
117 LeastLoaded,
119 Weighted { weights: HashMap<String, f64> },
121 ConsistentHash,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct ResourceLimits {
128 pub max_cpu_cores: usize,
130 pub max_memory_bytes: u64,
132 pub max_network_bandwidth: u64,
134 pub max_partitions: usize,
136 pub min_partitions: usize,
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct ScalingConfig {
143 pub mode: ScalingMode,
145 pub partition_strategy: PartitionStrategy,
147 pub load_balancing: LoadBalancingStrategy,
149 pub resource_limits: ResourceLimits,
151 pub scale_up_threshold: f64,
153 pub scale_down_threshold: f64,
155 pub cooldown_period: Duration,
157 pub enable_adaptive_buffering: bool,
159 pub initial_buffer_size: usize,
161 pub max_buffer_size: usize,
163 pub min_buffer_size: usize,
165}
166
167impl Default for ScalingConfig {
168 fn default() -> Self {
169 Self {
170 mode: ScalingMode::Hybrid,
171 partition_strategy: PartitionStrategy::RoundRobin,
172 load_balancing: LoadBalancingStrategy::LeastLoaded,
173 resource_limits: ResourceLimits {
174 max_cpu_cores: std::thread::available_parallelism()
175 .map(|n| n.get())
176 .unwrap_or(1),
177 max_memory_bytes: 8 * 1024 * 1024 * 1024, max_network_bandwidth: 1_000_000_000, max_partitions: 100,
180 min_partitions: 1,
181 },
182 scale_up_threshold: 0.8,
183 scale_down_threshold: 0.3,
184 cooldown_period: Duration::from_secs(60),
185 enable_adaptive_buffering: true,
186 initial_buffer_size: 10000,
187 max_buffer_size: 1000000,
188 min_buffer_size: 1000,
189 }
190 }
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct Partition {
196 pub partition_id: String,
198 pub partition_number: usize,
200 pub owner_node: Option<String>,
202 pub replica_nodes: Vec<String>,
204 pub load: f64,
206 pub event_count: u64,
208 pub created_at: DateTime<Utc>,
210 pub last_updated: DateTime<Utc>,
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct Node {
217 pub node_id: String,
219 pub address: String,
221 pub partitions: Vec<usize>,
223 pub resource_usage: ResourceUsage,
225 pub health: NodeHealth,
227 pub last_heartbeat: DateTime<Utc>,
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize)]
233pub struct ResourceUsage {
234 pub cpu_usage: f64,
236 pub memory_usage: u64,
238 pub network_usage: u64,
240 pub events_per_second: f64,
242}
243
244#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
246pub enum NodeHealth {
247 Healthy,
248 Degraded,
249 Unhealthy,
250 Offline,
251}
252
253pub struct AdaptiveBuffer<T> {
255 config: ScalingConfig,
257 buffer: Arc<RwLock<VecDeque<T>>>,
259 current_size: Arc<RwLock<usize>>,
261 load_history: Arc<RwLock<VecDeque<f64>>>,
263 moving_avg: Arc<RwLock<MovingAverage>>,
265 last_resize: Arc<RwLock<Instant>>,
267}
268
269impl<T> AdaptiveBuffer<T> {
270 pub fn new(config: ScalingConfig) -> Self {
272 Self {
273 current_size: Arc::new(RwLock::new(config.initial_buffer_size)),
274 config,
275 buffer: Arc::new(RwLock::new(VecDeque::new())),
276 load_history: Arc::new(RwLock::new(VecDeque::with_capacity(100))),
277 moving_avg: Arc::new(RwLock::new(MovingAverage::new(10))),
278 last_resize: Arc::new(RwLock::new(Instant::now())),
279 }
280 }
281
282 pub fn push(&self, item: T) -> Result<()> {
284 let mut buffer = self.buffer.write();
285 let current_size = *self.current_size.read();
286
287 if buffer.len() >= current_size {
288 self.try_resize()?;
290
291 let new_size = *self.current_size.read();
292 if buffer.len() >= new_size {
293 return Err(anyhow!("Buffer full: {}/{}", buffer.len(), new_size));
294 }
295 }
296
297 buffer.push_back(item);
298 self.update_load_metrics(buffer.len(), current_size);
299 Ok(())
300 }
301
302 pub fn pop(&self) -> Option<T> {
304 let mut buffer = self.buffer.write();
305 let item = buffer.pop_front();
306
307 let current_size = *self.current_size.read();
308 self.update_load_metrics(buffer.len(), current_size);
309 item
310 }
311
312 pub fn utilization(&self) -> f64 {
314 let buffer = self.buffer.read();
315 let current_size = *self.current_size.read();
316 buffer.len() as f64 / current_size as f64
317 }
318
319 fn update_load_metrics(&self, buffer_len: usize, current_size: usize) {
321 let load = buffer_len as f64 / current_size as f64;
322
323 let mut history = self.load_history.write();
324 history.push_back(load);
325 if history.len() > 100 {
326 history.pop_front();
327 }
328
329 let mut moving_avg = self.moving_avg.write();
330 moving_avg.add(load);
331 }
332
333 fn try_resize(&self) -> Result<()> {
335 if !self.config.enable_adaptive_buffering {
336 return Ok(());
337 }
338
339 let last_resize = *self.last_resize.read();
340 if last_resize.elapsed() < Duration::from_secs(10) {
341 return Ok(());
343 }
344
345 let moving_avg = self.moving_avg.read();
346 let avg_load = moving_avg.mean();
347
348 let mut current_size = self.current_size.write();
349
350 if avg_load > self.config.scale_up_threshold {
351 let new_size = (*current_size * 2).min(self.config.max_buffer_size);
353 if new_size > *current_size {
354 *current_size = new_size;
355 *self.last_resize.write() = Instant::now();
356 info!("Scaled up buffer to {}", new_size);
357 }
358 } else if avg_load < self.config.scale_down_threshold {
359 let new_size = (*current_size / 2).max(self.config.min_buffer_size);
361 if new_size < *current_size {
362 *current_size = new_size;
363 *self.last_resize.write() = Instant::now();
364 info!("Scaled down buffer to {}", new_size);
365 }
366 }
367
368 Ok(())
369 }
370
371 pub fn size(&self) -> usize {
373 *self.current_size.read()
374 }
375
376 pub fn len(&self) -> usize {
378 self.buffer.read().len()
379 }
380
381 pub fn is_empty(&self) -> bool {
383 self.buffer.read().is_empty()
384 }
385}
386
387pub struct PartitionManager {
389 config: ScalingConfig,
391 partitions: Arc<DashMap<usize, Partition>>,
393 nodes: Arc<DashMap<String, Node>>,
395 assignments: Arc<DashMap<usize, String>>,
397 last_scaling: Arc<RwLock<Instant>>,
399 counter: Arc<RwLock<usize>>,
401}
402
403impl PartitionManager {
404 pub fn new(config: ScalingConfig) -> Self {
406 let manager = Self {
407 config: config.clone(),
408 partitions: Arc::new(DashMap::new()),
409 nodes: Arc::new(DashMap::new()),
410 assignments: Arc::new(DashMap::new()),
411 last_scaling: Arc::new(RwLock::new(Instant::now())),
412 counter: Arc::new(RwLock::new(0)),
413 };
414
415 for i in 0..config.resource_limits.min_partitions {
417 manager.create_partition(i);
418 }
419
420 manager
421 }
422
423 fn create_partition(&self, partition_number: usize) {
425 let partition = Partition {
426 partition_id: Uuid::new_v4().to_string(),
427 partition_number,
428 owner_node: None,
429 replica_nodes: Vec::new(),
430 load: 0.0,
431 event_count: 0,
432 created_at: Utc::now(),
433 last_updated: Utc::now(),
434 };
435
436 self.partitions.insert(partition_number, partition);
437 info!("Created partition {}", partition_number);
438 }
439
440 pub fn add_node(&self, node: Node) -> Result<()> {
442 let node_id = node.node_id.clone();
443 self.nodes.insert(node_id.clone(), node);
444
445 self.rebalance_partitions()?;
447
448 info!("Added node {}", node_id);
449 Ok(())
450 }
451
452 pub fn remove_node(&self, node_id: &str) -> Result<()> {
454 self.nodes.remove(node_id);
455
456 self.rebalance_partitions()?;
458
459 info!("Removed node {}", node_id);
460 Ok(())
461 }
462
463 fn rebalance_partitions(&self) -> Result<()> {
465 let nodes: Vec<_> = self.nodes.iter().map(|e| e.key().clone()).collect();
466
467 if nodes.is_empty() {
468 warn!("No nodes available for partition assignment");
469 return Ok(());
470 }
471
472 let partitions: Vec<_> = self.partitions.iter().map(|e| *e.key()).collect();
473
474 for (idx, partition_num) in partitions.iter().enumerate() {
476 let node_id = &nodes[idx % nodes.len()];
477 self.assignments.insert(*partition_num, node_id.clone());
478
479 if let Some(mut partition) = self.partitions.get_mut(partition_num) {
481 partition.owner_node = Some(node_id.clone());
482 partition.last_updated = Utc::now();
483 }
484
485 if let Some(mut node) = self.nodes.get_mut(node_id) {
487 if !node.partitions.contains(partition_num) {
488 node.partitions.push(*partition_num);
489 }
490 }
491 }
492
493 debug!(
494 "Rebalanced {} partitions across {} nodes",
495 partitions.len(),
496 nodes.len()
497 );
498 Ok(())
499 }
500
501 pub fn evaluate_scaling(&self) -> ScalingDirection {
503 if !matches!(
504 self.config.mode,
505 ScalingMode::Horizontal | ScalingMode::Hybrid
506 ) {
507 return ScalingDirection::NoChange;
508 }
509
510 if self.last_scaling.read().elapsed() < self.config.cooldown_period {
512 return ScalingDirection::NoChange;
513 }
514
515 let partitions: Vec<_> = self.partitions.iter().map(|e| e.clone()).collect();
517
518 if partitions.is_empty() {
519 return ScalingDirection::NoChange;
520 }
521
522 let avg_load = partitions.iter().map(|p| p.load).sum::<f64>() / partitions.len() as f64;
523
524 if avg_load > self.config.scale_up_threshold
525 && partitions.len() < self.config.resource_limits.max_partitions
526 {
527 let amount = ((partitions.len() as f64 * 0.5).ceil() as usize)
529 .min(self.config.resource_limits.max_partitions - partitions.len())
530 .max(1);
531 ScalingDirection::ScaleUp { amount }
532 } else if avg_load < self.config.scale_down_threshold
533 && partitions.len() > self.config.resource_limits.min_partitions
534 {
535 let amount = ((partitions.len() as f64 * 0.25).ceil() as usize)
537 .min(partitions.len() - self.config.resource_limits.min_partitions)
538 .max(1);
539 ScalingDirection::ScaleDown { amount }
540 } else {
541 ScalingDirection::NoChange
542 }
543 }
544
545 pub fn apply_scaling(&self, direction: &ScalingDirection) -> Result<()> {
547 match direction {
548 ScalingDirection::ScaleUp { amount } => {
549 let current_max = self.partitions.iter().map(|e| *e.key()).max().unwrap_or(0);
550
551 for i in 1..=*amount {
552 let partition_num = current_max + i;
553 if partition_num < self.config.resource_limits.max_partitions {
554 self.create_partition(partition_num);
555 }
556 }
557
558 self.rebalance_partitions()?;
559 *self.last_scaling.write() = Instant::now();
560
561 info!("Scaled up by {} partitions", amount);
562 }
563 ScalingDirection::ScaleDown { amount } => {
564 let partition_nums: Vec<_> = self.partitions.iter().map(|e| *e.key()).collect();
565
566 let mut removed = 0;
568 for partition_num in partition_nums.iter().rev() {
569 if removed >= *amount {
570 break;
571 }
572 if partition_nums.len() - removed > self.config.resource_limits.min_partitions {
573 self.partitions.remove(partition_num);
574 self.assignments.remove(partition_num);
575 removed += 1;
576 }
577 }
578
579 self.rebalance_partitions()?;
580 *self.last_scaling.write() = Instant::now();
581
582 info!("Scaled down by {} partitions", removed);
583 }
584 ScalingDirection::NoChange => {}
585 }
586
587 Ok(())
588 }
589
590 pub fn get_partition_for_key(&self, key: &str) -> usize {
592 match &self.config.partition_strategy {
593 PartitionStrategy::RoundRobin => {
594 let mut counter = self.counter.write();
596 let partition = *counter % self.partitions.len();
597 *counter = counter.wrapping_add(1);
598 partition
599 }
600 PartitionStrategy::Hash { .. } => {
601 let hash = self.hash_key(key);
603 (hash as usize) % self.partitions.len()
604 }
605 PartitionStrategy::ConsistentHash { .. } => {
606 let hash = self.hash_key(key);
608 (hash as usize) % self.partitions.len()
609 }
610 _ => 0, }
612 }
613
614 fn hash_key(&self, key: &str) -> u64 {
616 use std::collections::hash_map::DefaultHasher;
617 use std::hash::{Hash, Hasher};
618
619 let mut hasher = DefaultHasher::new();
620 key.hash(&mut hasher);
621 hasher.finish()
622 }
623
624 pub fn update_partition_load(&self, partition_num: usize, load: f64) {
626 if let Some(mut partition) = self.partitions.get_mut(&partition_num) {
627 partition.load = load;
628 partition.last_updated = Utc::now();
629 }
630 }
631
632 pub fn partition_count(&self) -> usize {
634 self.partitions.len()
635 }
636
637 pub fn node_count(&self) -> usize {
639 self.nodes.len()
640 }
641}
642
643pub struct AutoScaler {
645 config: ScalingConfig,
647 partition_manager: Arc<PartitionManager>,
649 monitoring_interval: Duration,
651 command_tx: mpsc::UnboundedSender<ScalingCommand>,
653 _background_task: Option<tokio::task::JoinHandle<()>>,
655}
656
657enum ScalingCommand {
659 Evaluate,
660 Stop,
661}
662
663impl AutoScaler {
664 pub fn new(config: ScalingConfig, partition_manager: Arc<PartitionManager>) -> Self {
666 let (command_tx, mut command_rx) = mpsc::unbounded_channel();
667
668 let monitoring_interval = Duration::from_secs(30);
669
670 let partition_manager_clone = partition_manager.clone();
671 let background_task = tokio::spawn(async move {
672 let mut interval = tokio::time::interval(monitoring_interval);
673
674 loop {
675 tokio::select! {
676 _ = interval.tick() => {
677 let decision = partition_manager_clone.evaluate_scaling();
678 if !matches!(decision, ScalingDirection::NoChange) {
679 info!("Auto-scaler decision: {:?}", decision);
680 if let Err(e) = partition_manager_clone.apply_scaling(&decision) {
681 error!("Failed to apply scaling: {}", e);
682 }
683 }
684 }
685 Some(cmd) = command_rx.recv() => {
686 match cmd {
687 ScalingCommand::Evaluate => {
688 let decision = partition_manager_clone.evaluate_scaling();
689 if !matches!(decision, ScalingDirection::NoChange) {
690 if let Err(e) = partition_manager_clone.apply_scaling(&decision) {
691 error!("Failed to apply scaling: {}", e);
692 }
693 }
694 }
695 ScalingCommand::Stop => break,
696 }
697 }
698 }
699 }
700 });
701
702 Self {
703 config,
704 partition_manager,
705 monitoring_interval,
706 command_tx,
707 _background_task: Some(background_task),
708 }
709 }
710
711 pub fn evaluate_now(&self) -> Result<()> {
713 self.command_tx
714 .send(ScalingCommand::Evaluate)
715 .map_err(|e| anyhow!("Failed to send command: {}", e))
716 }
717}
718
719#[cfg(test)]
720mod tests {
721 use super::*;
722
723 #[test]
724 fn test_adaptive_buffer() {
725 let config = ScalingConfig::default();
726 let buffer: AdaptiveBuffer<u64> = AdaptiveBuffer::new(config);
727
728 for i in 0..100 {
730 buffer.push(i).unwrap();
731 }
732
733 assert_eq!(buffer.len(), 100);
734
735 for _ in 0..50 {
737 assert!(buffer.pop().is_some());
738 }
739
740 assert_eq!(buffer.len(), 50);
741 }
742
743 #[test]
744 fn test_partition_manager() {
745 let config = ScalingConfig::default();
746 let manager = PartitionManager::new(config);
747
748 let node = Node {
750 node_id: "node-1".to_string(),
751 address: "localhost:8001".to_string(),
752 partitions: Vec::new(),
753 resource_usage: ResourceUsage {
754 cpu_usage: 0.5,
755 memory_usage: 1024 * 1024 * 1024,
756 network_usage: 1000000,
757 events_per_second: 1000.0,
758 },
759 health: NodeHealth::Healthy,
760 last_heartbeat: Utc::now(),
761 };
762
763 manager.add_node(node).unwrap();
764
765 assert_eq!(manager.node_count(), 1);
766 assert!(manager.partition_count() >= 1);
767 }
768
769 #[test]
770 fn test_partition_assignment() {
771 let config = ScalingConfig::default();
772 let manager = PartitionManager::new(config);
773
774 let partition = manager.get_partition_for_key("test-key");
775 assert!(partition < manager.partition_count());
776 }
777}