dbx_core/engine/
workload_analyzer.rs1use std::collections::VecDeque;
13
14#[derive(Debug, Clone, PartialEq)]
16pub enum QueryPattern {
17 PointQuery,
19 RangeScan,
21 Aggregation,
23 Join,
25}
26
27impl QueryPattern {
28 pub fn is_oltp(&self) -> bool {
30 matches!(self, QueryPattern::PointQuery)
31 }
32}
33
34#[derive(Debug, Clone, PartialEq)]
36pub struct AdaptiveConfig {
37 pub delta_max_rows: usize,
39 pub cache_max_mb: usize,
41 pub compaction_interval_secs: u64,
43}
44
45impl AdaptiveConfig {
46 pub fn oltp_heavy() -> Self {
48 Self {
49 delta_max_rows: 1_000, cache_max_mb: 64, compaction_interval_secs: 3600, }
53 }
54
55 pub fn olap_heavy() -> Self {
57 Self {
58 delta_max_rows: 100_000, cache_max_mb: 1_024, compaction_interval_secs: 300, }
62 }
63
64 pub fn balanced() -> Self {
66 Self {
67 delta_max_rows: 10_000,
68 cache_max_mb: 256,
69 compaction_interval_secs: 900,
70 }
71 }
72}
73
74pub struct WorkloadAnalyzer {
78 window: VecDeque<QueryPattern>,
79 window_size: usize,
80}
81
82impl WorkloadAnalyzer {
83 pub fn new(window_size: usize) -> Self {
85 Self {
86 window: VecDeque::with_capacity(window_size),
87 window_size,
88 }
89 }
90
91 pub fn default_window() -> Self {
93 Self::new(1_000)
94 }
95
96 pub fn record(&mut self, pattern: QueryPattern) {
98 if self.window.len() >= self.window_size {
99 self.window.pop_front();
100 }
101 self.window.push_back(pattern);
102 }
103
104 pub fn oltp_ratio(&self) -> f64 {
107 if self.window.is_empty() {
108 return 0.5;
109 }
110 let oltp_count = self.window.iter().filter(|p| p.is_oltp()).count();
111 oltp_count as f64 / self.window.len() as f64
112 }
113
114 pub fn recommended_config(&self) -> AdaptiveConfig {
116 let ratio = self.oltp_ratio();
117 if ratio > 0.7 {
118 AdaptiveConfig::oltp_heavy()
119 } else if ratio < 0.3 {
120 AdaptiveConfig::olap_heavy()
121 } else {
122 AdaptiveConfig::balanced()
123 }
124 }
125
126 pub fn window_len(&self) -> usize {
128 self.window.len()
129 }
130
131 pub fn reset(&mut self) {
133 self.window.clear();
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn test_oltp_ratio_empty() {
143 let analyzer = WorkloadAnalyzer::new(100);
144 assert!((analyzer.oltp_ratio() - 0.5).abs() < f64::EPSILON);
145 }
146
147 #[test]
148 fn test_oltp_ratio_pure_oltp() {
149 let mut analyzer = WorkloadAnalyzer::new(10);
150 for _ in 0..10 {
151 analyzer.record(QueryPattern::PointQuery);
152 }
153 assert!((analyzer.oltp_ratio() - 1.0).abs() < f64::EPSILON);
154 }
155
156 #[test]
157 fn test_oltp_ratio_pure_olap() {
158 let mut analyzer = WorkloadAnalyzer::new(10);
159 for _ in 0..5 {
160 analyzer.record(QueryPattern::Aggregation);
161 analyzer.record(QueryPattern::RangeScan);
162 }
163 assert!((analyzer.oltp_ratio() - 0.0).abs() < f64::EPSILON);
164 }
165
166 #[test]
167 fn test_recommended_config_oltp_heavy() {
168 let mut analyzer = WorkloadAnalyzer::new(100);
169 for _ in 0..80 {
170 analyzer.record(QueryPattern::PointQuery);
171 }
172 for _ in 0..20 {
173 analyzer.record(QueryPattern::RangeScan);
174 }
175 let config = analyzer.recommended_config();
176 assert_eq!(config, AdaptiveConfig::oltp_heavy());
177 }
178
179 #[test]
180 fn test_recommended_config_olap_heavy() {
181 let mut analyzer = WorkloadAnalyzer::new(100);
182 for _ in 0..10 {
183 analyzer.record(QueryPattern::PointQuery);
184 }
185 for _ in 0..90 {
186 analyzer.record(QueryPattern::Aggregation);
187 }
188 let config = analyzer.recommended_config();
189 assert_eq!(config, AdaptiveConfig::olap_heavy());
190 }
191
192 #[test]
193 fn test_recommended_config_balanced() {
194 let mut analyzer = WorkloadAnalyzer::new(100);
195 for _ in 0..50 {
196 analyzer.record(QueryPattern::PointQuery);
197 }
198 for _ in 0..50 {
199 analyzer.record(QueryPattern::Join);
200 }
201 let config = analyzer.recommended_config();
202 assert_eq!(config, AdaptiveConfig::balanced());
203 }
204
205 #[test]
206 fn test_sliding_window_eviction() {
207 let mut analyzer = WorkloadAnalyzer::new(5);
208 for _ in 0..5 {
210 analyzer.record(QueryPattern::Aggregation);
211 }
212 assert!((analyzer.oltp_ratio() - 0.0).abs() < f64::EPSILON);
213
214 for _ in 0..3 {
216 analyzer.record(QueryPattern::PointQuery);
217 }
218 assert!((analyzer.oltp_ratio() - 0.6).abs() < 1e-9);
220 }
221
222 #[test]
223 fn test_reset() {
224 let mut analyzer = WorkloadAnalyzer::new(10);
225 for _ in 0..5 {
226 analyzer.record(QueryPattern::PointQuery);
227 }
228 assert_eq!(analyzer.window_len(), 5);
229 analyzer.reset();
230 assert_eq!(analyzer.window_len(), 0);
231 assert!((analyzer.oltp_ratio() - 0.5).abs() < f64::EPSILON);
232 }
233}