use std::collections::VecDeque;
#[derive(Debug, Clone, PartialEq)]
pub enum QueryPattern {
PointQuery,
RangeScan,
Aggregation,
Join,
}
impl QueryPattern {
pub fn is_oltp(&self) -> bool {
matches!(self, QueryPattern::PointQuery)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct AdaptiveConfig {
pub delta_max_rows: usize,
pub cache_max_mb: usize,
pub compaction_interval_secs: u64,
}
impl AdaptiveConfig {
pub fn oltp_heavy() -> Self {
Self {
delta_max_rows: 1_000, cache_max_mb: 64, compaction_interval_secs: 3600, }
}
pub fn olap_heavy() -> Self {
Self {
delta_max_rows: 100_000, cache_max_mb: 1_024, compaction_interval_secs: 300, }
}
pub fn balanced() -> Self {
Self {
delta_max_rows: 10_000,
cache_max_mb: 256,
compaction_interval_secs: 900,
}
}
}
pub struct WorkloadAnalyzer {
window: VecDeque<QueryPattern>,
window_size: usize,
}
impl WorkloadAnalyzer {
pub fn new(window_size: usize) -> Self {
Self {
window: VecDeque::with_capacity(window_size),
window_size,
}
}
pub fn default_window() -> Self {
Self::new(1_000)
}
pub fn record(&mut self, pattern: QueryPattern) {
if self.window.len() >= self.window_size {
self.window.pop_front();
}
self.window.push_back(pattern);
}
pub fn oltp_ratio(&self) -> f64 {
if self.window.is_empty() {
return 0.5;
}
let oltp_count = self.window.iter().filter(|p| p.is_oltp()).count();
oltp_count as f64 / self.window.len() as f64
}
pub fn recommended_config(&self) -> AdaptiveConfig {
let ratio = self.oltp_ratio();
if ratio > 0.7 {
AdaptiveConfig::oltp_heavy()
} else if ratio < 0.3 {
AdaptiveConfig::olap_heavy()
} else {
AdaptiveConfig::balanced()
}
}
pub fn window_len(&self) -> usize {
self.window.len()
}
pub fn reset(&mut self) {
self.window.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_oltp_ratio_empty() {
let analyzer = WorkloadAnalyzer::new(100);
assert!((analyzer.oltp_ratio() - 0.5).abs() < f64::EPSILON);
}
#[test]
fn test_oltp_ratio_pure_oltp() {
let mut analyzer = WorkloadAnalyzer::new(10);
for _ in 0..10 {
analyzer.record(QueryPattern::PointQuery);
}
assert!((analyzer.oltp_ratio() - 1.0).abs() < f64::EPSILON);
}
#[test]
fn test_oltp_ratio_pure_olap() {
let mut analyzer = WorkloadAnalyzer::new(10);
for _ in 0..5 {
analyzer.record(QueryPattern::Aggregation);
analyzer.record(QueryPattern::RangeScan);
}
assert!((analyzer.oltp_ratio() - 0.0).abs() < f64::EPSILON);
}
#[test]
fn test_recommended_config_oltp_heavy() {
let mut analyzer = WorkloadAnalyzer::new(100);
for _ in 0..80 {
analyzer.record(QueryPattern::PointQuery);
}
for _ in 0..20 {
analyzer.record(QueryPattern::RangeScan);
}
let config = analyzer.recommended_config();
assert_eq!(config, AdaptiveConfig::oltp_heavy());
}
#[test]
fn test_recommended_config_olap_heavy() {
let mut analyzer = WorkloadAnalyzer::new(100);
for _ in 0..10 {
analyzer.record(QueryPattern::PointQuery);
}
for _ in 0..90 {
analyzer.record(QueryPattern::Aggregation);
}
let config = analyzer.recommended_config();
assert_eq!(config, AdaptiveConfig::olap_heavy());
}
#[test]
fn test_recommended_config_balanced() {
let mut analyzer = WorkloadAnalyzer::new(100);
for _ in 0..50 {
analyzer.record(QueryPattern::PointQuery);
}
for _ in 0..50 {
analyzer.record(QueryPattern::Join);
}
let config = analyzer.recommended_config();
assert_eq!(config, AdaptiveConfig::balanced());
}
#[test]
fn test_sliding_window_eviction() {
let mut analyzer = WorkloadAnalyzer::new(5);
for _ in 0..5 {
analyzer.record(QueryPattern::Aggregation);
}
assert!((analyzer.oltp_ratio() - 0.0).abs() < f64::EPSILON);
for _ in 0..3 {
analyzer.record(QueryPattern::PointQuery);
}
assert!((analyzer.oltp_ratio() - 0.6).abs() < 1e-9);
}
#[test]
fn test_reset() {
let mut analyzer = WorkloadAnalyzer::new(10);
for _ in 0..5 {
analyzer.record(QueryPattern::PointQuery);
}
assert_eq!(analyzer.window_len(), 5);
analyzer.reset();
assert_eq!(analyzer.window_len(), 0);
assert!((analyzer.oltp_ratio() - 0.5).abs() < f64::EPSILON);
}
}