1use super::ContentHash;
25use super::q_learning_cache::{AccessInfo, CacheAction, StateVector};
26use std::collections::{HashMap, VecDeque};
28use std::sync::Arc;
29use std::time::{SystemTime, UNIX_EPOCH};
30use tokio::sync::RwLock;
31
32pub trait EvictionStrategy: Send + Sync + std::fmt::Debug {
34 fn select_victim(
36 &self,
37 cache_state: &CacheState,
38 access_info: &HashMap<ContentHash, AccessInfo>,
39 ) -> Option<ContentHash>;
40
41 fn on_access(&mut self, content_hash: &ContentHash);
43
44 fn on_insert(&mut self, content_hash: &ContentHash);
46
47 fn name(&self) -> &str;
49}
50
51#[derive(Debug, Clone)]
53pub struct CacheState {
54 pub current_size: u64,
56 pub max_size: u64,
58 pub item_count: usize,
60 pub avg_access_frequency: f64,
62}
63
64#[derive(Debug)]
66pub struct LRUStrategy {
67 access_order: VecDeque<ContentHash>,
69 position_map: HashMap<ContentHash, usize>,
71}
72
73impl Default for LRUStrategy {
74 fn default() -> Self {
75 Self::new()
76 }
77}
78
79impl LRUStrategy {
80 pub fn new() -> Self {
81 Self {
82 access_order: VecDeque::new(),
83 position_map: HashMap::new(),
84 }
85 }
86}
87
88impl EvictionStrategy for LRUStrategy {
89 fn select_victim(
90 &self,
91 _cache_state: &CacheState,
92 access_info: &HashMap<ContentHash, AccessInfo>,
93 ) -> Option<ContentHash> {
94 self.access_order
96 .iter()
97 .find(|&hash| access_info.contains_key(hash))
98 .cloned()
99 }
100
101 fn on_access(&mut self, content_hash: &ContentHash) {
102 if let Some(&pos) = self.position_map.get(content_hash) {
104 self.access_order.remove(pos);
105 for (i, hash) in self.access_order.iter().enumerate().skip(pos) {
107 self.position_map.insert(*hash, i);
108 }
109 }
110
111 self.access_order.push_back(*content_hash);
113 self.position_map
114 .insert(*content_hash, self.access_order.len() - 1);
115 }
116
117 fn on_insert(&mut self, content_hash: &ContentHash) {
118 self.on_access(content_hash);
119 }
120
121 fn name(&self) -> &str {
122 "LRU"
123 }
124}
125
126#[derive(Debug)]
128pub struct LFUStrategy {
129 frequency_map: HashMap<ContentHash, u64>,
131}
132
133impl Default for LFUStrategy {
134 fn default() -> Self {
135 Self::new()
136 }
137}
138
139impl LFUStrategy {
140 pub fn new() -> Self {
141 Self {
142 frequency_map: HashMap::new(),
143 }
144 }
145}
146
147impl EvictionStrategy for LFUStrategy {
148 fn select_victim(
149 &self,
150 _cache_state: &CacheState,
151 access_info: &HashMap<ContentHash, AccessInfo>,
152 ) -> Option<ContentHash> {
153 access_info
155 .keys()
156 .min_by_key(|&hash| self.frequency_map.get(hash).unwrap_or(&0))
157 .cloned()
158 }
159
160 fn on_access(&mut self, content_hash: &ContentHash) {
161 *self.frequency_map.entry(*content_hash).or_insert(0) += 1;
162 }
163
164 fn on_insert(&mut self, content_hash: &ContentHash) {
165 self.frequency_map.insert(*content_hash, 1);
166 }
167
168 fn name(&self) -> &str {
169 "LFU"
170 }
171}
172
173#[derive(Debug)]
175pub struct FIFOStrategy {
176 insertion_order: VecDeque<ContentHash>,
178}
179
180impl Default for FIFOStrategy {
181 fn default() -> Self {
182 Self::new()
183 }
184}
185
186impl FIFOStrategy {
187 pub fn new() -> Self {
188 Self {
189 insertion_order: VecDeque::new(),
190 }
191 }
192}
193
194impl EvictionStrategy for FIFOStrategy {
195 fn select_victim(
196 &self,
197 _cache_state: &CacheState,
198 access_info: &HashMap<ContentHash, AccessInfo>,
199 ) -> Option<ContentHash> {
200 self.insertion_order
202 .iter()
203 .find(|&hash| access_info.contains_key(hash))
204 .cloned()
205 }
206
207 fn on_access(&mut self, _content_hash: &ContentHash) {
208 }
210
211 fn on_insert(&mut self, content_hash: &ContentHash) {
212 self.insertion_order.push_back(*content_hash);
213 }
214
215 fn name(&self) -> &str {
216 "FIFO"
217 }
218}
219
220#[derive(Debug, Clone, Default)]
222pub struct QValue {
223 pub value: f64,
224 pub updates: u32,
225}
226
227#[derive(Debug)]
229pub struct AdaptiveStrategy {
230 _q_table: Arc<RwLock<HashMap<(StateVector, CacheAction), QValue>>>,
232 _performance_history: VecDeque<f64>,
234 _max_history: usize,
236}
237
238#[allow(dead_code)]
239impl AdaptiveStrategy {
240 fn current_timestamp_secs() -> u64 {
242 SystemTime::now()
243 .duration_since(UNIX_EPOCH)
244 .map(|d| d.as_secs())
245 .unwrap_or(0)
246 }
247
248 pub fn new(q_table: Arc<RwLock<HashMap<(StateVector, CacheAction), QValue>>>) -> Self {
249 Self {
250 _q_table: q_table,
251 _performance_history: VecDeque::new(),
252 _max_history: 100,
253 }
254 }
255
256 async fn calculate_retention_value(
258 &self,
259 content_hash: &ContentHash,
260 access_info: &AccessInfo,
261 cache_state: &CacheState,
262 ) -> f64 {
263 let state = StateVector {
265 utilization_bucket: ((cache_state.current_size as f64 / cache_state.max_size as f64)
266 * 10.0) as u8,
267 frequency_bucket: self.frequency_bucket(access_info.count),
268 recency_bucket: self.recency_bucket(access_info.last_access_secs),
269 content_size_bucket: self.size_bucket(access_info.size),
270 };
271
272 let q_table = self._q_table.read().await;
274 let keep_value = q_table
275 .get(&(state, CacheAction::DoNothing))
276 .map(|qv| qv.value)
277 .unwrap_or(0.0);
278 let evict_value = q_table
279 .get(&(state, CacheAction::Evict(*content_hash)))
280 .map(|qv| qv.value)
281 .unwrap_or(0.0);
282
283 keep_value - evict_value
284 }
285
286 fn frequency_bucket(&self, count: u64) -> u8 {
287 match count {
288 0..=10 => 0,
289 11..=50 => 1,
290 51..=100 => 2,
291 101..=500 => 3,
292 501..=1000 => 4,
293 _ => 5,
294 }
295 }
296
297 fn recency_bucket(&self, last_access_secs: u64) -> u8 {
298 let now_secs = Self::current_timestamp_secs();
299 let age_secs = now_secs.saturating_sub(last_access_secs);
300
301 match age_secs {
302 0..=60 => 0, 61..=3_600 => 1, 3_601..=86_400 => 2, 86_401..=604_800 => 3, _ => 4, }
308 }
309
310 fn size_bucket(&self, size: u64) -> u8 {
311 match size {
312 0..=1_024 => 0, 1_025..=10_240 => 1, 10_241..=102_400 => 2, 102_401..=1_048_576 => 3, 1_048_577..=10_485_760 => 4, _ => 5, }
319 }
320}
321
322impl EvictionStrategy for AdaptiveStrategy {
323 fn select_victim(
324 &self,
325 _cache_state: &CacheState,
326 access_info: &HashMap<ContentHash, AccessInfo>,
327 ) -> Option<ContentHash> {
328 access_info
333 .iter()
334 .map(|(hash, info)| {
335 let frequency_score = (info.count as f64).log2() + 1.0;
337 let now_secs = Self::current_timestamp_secs();
338 let age_secs = now_secs.saturating_sub(info.last_access_secs) as f64;
339 let recency_score = 1.0 / (age_secs / 3600.0 + 1.0); let size_penalty = (info.size as f64 / 1_048_576.0).sqrt(); let score = frequency_score * recency_score / (size_penalty + 1.0);
343 (hash, score)
344 })
345 .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
346 .map(|(hash, _)| *hash)
347 }
348
349 fn on_access(&mut self, _content_hash: &ContentHash) {
350 }
352
353 fn on_insert(&mut self, _content_hash: &ContentHash) {
354 }
356
357 fn name(&self) -> &str {
358 "Adaptive"
359 }
360}
361
362#[derive(Debug, Clone)]
364pub enum EvictionStrategyType {
365 LRU,
366 LFU,
367 FIFO,
368 Adaptive(Arc<RwLock<HashMap<(StateVector, CacheAction), QValue>>>),
369}
370
371impl EvictionStrategyType {
372 pub fn create(self) -> Box<dyn EvictionStrategy> {
373 match self {
374 EvictionStrategyType::LRU => Box::new(LRUStrategy::new()),
375 EvictionStrategyType::LFU => Box::new(LFUStrategy::new()),
376 EvictionStrategyType::FIFO => Box::new(FIFOStrategy::new()),
377 EvictionStrategyType::Adaptive(q_table) => Box::new(AdaptiveStrategy::new(q_table)),
378 }
379 }
380}
381
382#[cfg(test)]
383mod tests {
384 use super::*;
385
386 fn create_test_access_info() -> HashMap<ContentHash, AccessInfo> {
387 let mut info = HashMap::new();
388
389 let now_secs = SystemTime::now()
390 .duration_since(UNIX_EPOCH)
391 .map(|d| d.as_secs())
392 .unwrap_or(0);
393
394 info.insert(
396 ContentHash::from("content1".as_bytes()),
397 AccessInfo {
398 count: 10,
399 last_access_secs: now_secs.saturating_sub(60),
400 size: 1024,
401 },
402 );
403
404 info.insert(
405 ContentHash::from("content2".as_bytes()),
406 AccessInfo {
407 count: 5,
408 last_access_secs: now_secs.saturating_sub(120),
409 size: 2048,
410 },
411 );
412
413 info.insert(
414 ContentHash::from("content3".as_bytes()),
415 AccessInfo {
416 count: 20,
417 last_access_secs: now_secs.saturating_sub(30),
418 size: 512,
419 },
420 );
421
422 info
423 }
424
425 #[test]
426 fn test_lru_eviction_removes_least_recently_used() {
427 let mut strategy = LRUStrategy::new();
428 let access_info = create_test_access_info();
429 let cache_state = CacheState {
430 current_size: 3584,
431 max_size: 4096,
432 item_count: 3,
433 avg_access_frequency: 11.67,
434 };
435
436 let hash1 = ContentHash::from("content1".as_bytes());
438 let hash2 = ContentHash::from("content2".as_bytes());
439 let hash3 = ContentHash::from("content3".as_bytes());
440
441 strategy.on_insert(&hash2); strategy.on_insert(&hash1);
443 strategy.on_insert(&hash3); strategy.on_access(&hash1);
447
448 let victim = strategy.select_victim(&cache_state, &access_info);
450 assert_eq!(victim, Some(hash2));
451 }
452
453 #[test]
454 fn test_lfu_eviction_removes_least_frequently_used() {
455 let mut strategy = LFUStrategy::new();
456 let access_info = create_test_access_info();
457 let cache_state = CacheState {
458 current_size: 3584,
459 max_size: 4096,
460 item_count: 3,
461 avg_access_frequency: 11.67,
462 };
463
464 let hash1 = ContentHash::from("content1".as_bytes());
466 let hash2 = ContentHash::from("content2".as_bytes());
467 let hash3 = ContentHash::from("content3".as_bytes());
468
469 for _ in 0..10 {
471 strategy.on_access(&hash1);
472 }
473 for _ in 0..5 {
474 strategy.on_access(&hash2);
475 }
476 for _ in 0..20 {
477 strategy.on_access(&hash3);
478 }
479
480 let victim = strategy.select_victim(&cache_state, &access_info);
482 assert_eq!(victim, Some(hash2));
483 }
484
485 #[test]
486 fn test_fifo_eviction_removes_oldest() {
487 let mut strategy = FIFOStrategy::new();
488 let access_info = create_test_access_info();
489 let cache_state = CacheState {
490 current_size: 3584,
491 max_size: 4096,
492 item_count: 3,
493 avg_access_frequency: 11.67,
494 };
495
496 let hash1 = ContentHash::from("content1".as_bytes());
498 let hash2 = ContentHash::from("content2".as_bytes());
499 let hash3 = ContentHash::from("content3".as_bytes());
500
501 strategy.on_insert(&hash2); strategy.on_insert(&hash1); strategy.on_insert(&hash3); strategy.on_access(&hash2);
507 strategy.on_access(&hash3);
508
509 let victim = strategy.select_victim(&cache_state, &access_info);
511 assert_eq!(victim, Some(hash2));
512 }
513
514 #[tokio::test]
515 async fn test_adaptive_eviction_uses_q_values() {
516 let q_table = Arc::new(RwLock::new(HashMap::new()));
517 let strategy = AdaptiveStrategy::new(q_table.clone());
518 let access_info = create_test_access_info();
519 let cache_state = CacheState {
520 current_size: 3584,
521 max_size: 4096,
522 item_count: 3,
523 avg_access_frequency: 11.67,
524 };
525
526 let state1 = StateVector {
528 utilization_bucket: 8,
529 frequency_bucket: 1,
530 recency_bucket: 1,
531 content_size_bucket: 1,
532 };
533
534 let hash1 = ContentHash::from("content1".as_bytes());
535 let hash2 = ContentHash::from("content2".as_bytes());
536
537 q_table.write().await.insert(
539 (state1, CacheAction::DoNothing),
540 QValue {
541 value: 10.0,
542 updates: 1,
543 },
544 );
545 q_table.write().await.insert(
546 (state1, CacheAction::Evict(hash1)),
547 QValue {
548 value: 5.0,
549 updates: 1,
550 },
551 );
552
553 q_table.write().await.insert(
555 (state1, CacheAction::DoNothing),
556 QValue {
557 value: 3.0,
558 updates: 1,
559 },
560 );
561 q_table.write().await.insert(
562 (state1, CacheAction::Evict(hash2)),
563 QValue {
564 value: 8.0,
565 updates: 1,
566 },
567 );
568
569 let victim = strategy.select_victim(&cache_state, &access_info);
571 assert!(victim.is_some());
572 }
573
574 #[test]
575 fn test_strategy_factory() {
576 let lru = EvictionStrategyType::LRU.create();
577 assert_eq!(lru.name(), "LRU");
578
579 let lfu = EvictionStrategyType::LFU.create();
580 assert_eq!(lfu.name(), "LFU");
581
582 let fifo = EvictionStrategyType::FIFO.create();
583 assert_eq!(fifo.name(), "FIFO");
584
585 let q_table = Arc::new(RwLock::new(HashMap::new()));
586 let adaptive = EvictionStrategyType::Adaptive(q_table).create();
587 assert_eq!(adaptive.name(), "Adaptive");
588 }
589}