1use featherdb_core::PageId;
9use std::collections::{HashMap, VecDeque};
10use std::time::Instant;
11
12pub trait EvictionPolicy: Send + Sync {
16 fn on_access(&mut self, page_id: PageId);
18
19 fn on_load(&mut self, page_id: PageId);
21
22 fn select_victim(&mut self) -> Option<PageId>;
25
26 fn remove(&mut self, page_id: PageId);
28
29 fn name(&self) -> &'static str;
31}
32
33pub struct ClockEviction {
43 pages: VecDeque<PageId>,
45 reference_bits: HashMap<PageId, bool>,
47 hand: usize,
49}
50
51impl ClockEviction {
52 pub fn new() -> Self {
53 ClockEviction {
54 pages: VecDeque::new(),
55 reference_bits: HashMap::new(),
56 hand: 0,
57 }
58 }
59}
60
61impl Default for ClockEviction {
62 fn default() -> Self {
63 Self::new()
64 }
65}
66
67impl EvictionPolicy for ClockEviction {
68 fn on_access(&mut self, page_id: PageId) {
69 self.reference_bits.insert(page_id, true);
70 }
71
72 fn on_load(&mut self, page_id: PageId) {
73 self.pages.push_back(page_id);
74 self.reference_bits.insert(page_id, true);
75 }
76
77 fn select_victim(&mut self) -> Option<PageId> {
78 let len = self.pages.len();
79 if len == 0 {
80 return None;
81 }
82
83 for _ in 0..len * 2 {
85 self.hand %= len;
86 let page_id = self.pages[self.hand];
87
88 if let Some(ref_bit) = self.reference_bits.get_mut(&page_id) {
89 if *ref_bit {
90 *ref_bit = false;
92 self.hand = (self.hand + 1) % len;
93 } else {
94 return Some(page_id);
96 }
97 } else {
98 return Some(page_id);
100 }
101 }
102
103 Some(self.pages[self.hand])
105 }
106
107 fn remove(&mut self, page_id: PageId) {
108 if let Some(pos) = self.pages.iter().position(|&id| id == page_id) {
109 self.pages.remove(pos);
110 if self.hand >= self.pages.len() && !self.pages.is_empty() {
112 self.hand = 0;
113 }
114 }
115 self.reference_bits.remove(&page_id);
116 }
117
118 fn name(&self) -> &'static str {
119 "Clock"
120 }
121}
122
123#[derive(Debug, Clone)]
129struct Lru2Entry {
130 last_access: Instant,
132 prev_access: Option<Instant>,
134 access_count: u32,
136 created_at: Instant,
138}
139
140impl Lru2Entry {
141 fn new() -> Self {
142 let now = Instant::now();
143 Lru2Entry {
144 last_access: now,
145 prev_access: None,
146 access_count: 1,
147 created_at: now,
148 }
149 }
150
151 fn record_access(&mut self) {
152 self.prev_access = Some(self.last_access);
153 self.last_access = Instant::now();
154 self.access_count = self.access_count.saturating_add(1);
155 }
156
157 fn is_hot(&self) -> bool {
159 self.prev_access.is_some()
160 }
161
162 fn k_distance(&self) -> Option<Instant> {
165 self.prev_access
166 }
167}
168
169pub struct Lru2Eviction {
178 entries: HashMap<PageId, Lru2Entry>,
180 correlated_reference_period_ms: u64,
183}
184
185impl Lru2Eviction {
186 pub fn new() -> Self {
187 Lru2Eviction {
188 entries: HashMap::new(),
189 correlated_reference_period_ms: 10,
191 }
192 }
193
194 pub fn with_crp(crp_ms: u64) -> Self {
196 Lru2Eviction {
197 entries: HashMap::new(),
198 correlated_reference_period_ms: crp_ms,
199 }
200 }
201}
202
203impl Default for Lru2Eviction {
204 fn default() -> Self {
205 Self::new()
206 }
207}
208
209impl EvictionPolicy for Lru2Eviction {
210 fn on_access(&mut self, page_id: PageId) {
211 let now = Instant::now();
212
213 if let Some(entry) = self.entries.get_mut(&page_id) {
214 let elapsed_ms = now.duration_since(entry.last_access).as_millis() as u64;
216 if elapsed_ms > self.correlated_reference_period_ms {
217 entry.record_access();
218 }
219 } else {
220 self.entries.insert(page_id, Lru2Entry::new());
222 }
223 }
224
225 fn on_load(&mut self, page_id: PageId) {
226 self.entries.insert(page_id, Lru2Entry::new());
227 }
228
229 fn select_victim(&mut self) -> Option<PageId> {
230 if self.entries.is_empty() {
231 return None;
232 }
233
234 let mut cold_pages: Vec<_> = self
244 .entries
245 .iter()
246 .filter(|(_, entry)| !entry.is_hot())
247 .map(|(&id, entry)| (id, entry.created_at))
248 .collect();
249
250 let mut hot_pages: Vec<_> = self
251 .entries
252 .iter()
253 .filter(|(_, entry)| entry.is_hot())
254 .map(|(&id, entry)| (id, entry.k_distance().unwrap()))
255 .collect();
256
257 if !cold_pages.is_empty() {
259 cold_pages.sort_by_key(|(_, created_at)| *created_at);
261 return Some(cold_pages[0].0);
262 }
263
264 if !hot_pages.is_empty() {
266 hot_pages.sort_by_key(|(_, k_distance)| *k_distance);
268 return Some(hot_pages[0].0);
269 }
270
271 None
272 }
273
274 fn remove(&mut self, page_id: PageId) {
275 self.entries.remove(&page_id);
276 }
277
278 fn name(&self) -> &'static str {
279 "LRU-2"
280 }
281}
282
283#[derive(Debug, Clone, Copy, PartialEq, Eq)]
289enum LirsStatus {
290 Lir,
292 HirResident,
294 HirNonResident,
296}
297
298#[derive(Debug, Clone)]
300struct LirsEntry {
301 status: LirsStatus,
302 recency: u64,
304 in_stack_s: bool,
306 in_list_q: bool,
308}
309
310pub struct LirsEviction {
321 entries: HashMap<PageId, LirsEntry>,
323 stack_s: VecDeque<PageId>,
325 list_q: VecDeque<PageId>,
327 lir_size_limit: usize,
329 lir_count: usize,
331 hir_count: usize,
333 recency_counter: u64,
335}
336
337impl LirsEviction {
338 pub fn new(capacity: usize) -> Self {
339 let lir_limit = (capacity as f64 * 0.99) as usize;
341
342 LirsEviction {
343 entries: HashMap::new(),
344 stack_s: VecDeque::new(),
345 list_q: VecDeque::new(),
346 lir_size_limit: lir_limit.max(1),
347 lir_count: 0,
348 hir_count: 0,
349 recency_counter: 0,
350 }
351 }
352
353 pub fn with_lir_ratio(mut self, ratio: f64) -> Self {
355 let total = self.lir_size_limit + 1; self.lir_size_limit = ((total as f64) * ratio) as usize;
357 self
358 }
359
360 fn move_to_stack_top(&mut self, page_id: PageId) {
362 self.stack_s.retain(|&id| id != page_id);
364 self.stack_s.push_back(page_id);
366
367 self.recency_counter += 1;
368 if let Some(entry) = self.entries.get_mut(&page_id) {
369 entry.recency = self.recency_counter;
370 entry.in_stack_s = true;
371 }
372 }
373
374 fn prune_stack(&mut self) {
376 while let Some(&page_id) = self.stack_s.front() {
377 let status = self.entries.get(&page_id).map(|e| e.status);
379
380 match status {
381 Some(LirsStatus::Lir) => {
382 break;
384 }
385 Some(status) => {
386 self.stack_s.pop_front();
388
389 if let Some(e) = self.entries.get_mut(&page_id) {
391 e.in_stack_s = false;
392 }
393
394 if status == LirsStatus::HirNonResident {
396 self.entries.remove(&page_id);
397 }
398 }
399 None => {
400 self.stack_s.pop_front();
402 }
403 }
404 }
405 }
406
407 fn demote_bottom_lir(&mut self) {
409 let mut bottom_lir = None;
411 for &page_id in &self.stack_s {
412 if let Some(entry) = self.entries.get(&page_id) {
413 if entry.status == LirsStatus::Lir {
414 bottom_lir = Some(page_id);
415 break;
416 }
417 }
418 }
419
420 if let Some(page_id) = bottom_lir {
421 if let Some(entry) = self.entries.get_mut(&page_id) {
422 entry.status = LirsStatus::HirResident;
423 entry.in_list_q = true;
424 self.lir_count = self.lir_count.saturating_sub(1);
425 self.hir_count += 1;
426 self.list_q.push_back(page_id);
427 }
428
429 self.prune_stack();
431 }
432 }
433}
434
435impl Default for LirsEviction {
436 fn default() -> Self {
437 Self::new(1000) }
439}
440
441impl EvictionPolicy for LirsEviction {
442 fn on_access(&mut self, page_id: PageId) {
443 if let Some(entry) = self.entries.get(&page_id).cloned() {
444 match entry.status {
445 LirsStatus::Lir => {
446 self.move_to_stack_top(page_id);
448 self.prune_stack();
449 }
450 LirsStatus::HirResident => {
451 if entry.in_stack_s {
452 if let Some(e) = self.entries.get_mut(&page_id) {
454 e.status = LirsStatus::Lir;
455 e.in_list_q = false;
456 self.list_q.retain(|&id| id != page_id);
457 self.lir_count += 1;
458 self.hir_count = self.hir_count.saturating_sub(1);
459 }
460
461 self.move_to_stack_top(page_id);
463
464 if self.lir_count > self.lir_size_limit {
466 self.demote_bottom_lir();
467 }
468 } else {
469 self.list_q.retain(|&id| id != page_id);
471 self.list_q.push_back(page_id);
472 self.move_to_stack_top(page_id);
473 }
474 }
475 LirsStatus::HirNonResident => {
476 if let Some(e) = self.entries.get_mut(&page_id) {
479 e.status = LirsStatus::HirResident;
480 e.in_list_q = true;
481 self.list_q.push_back(page_id);
482 self.hir_count += 1;
483 }
484 self.move_to_stack_top(page_id);
485 }
486 }
487 }
488 }
489
490 fn on_load(&mut self, page_id: PageId) {
491 let was_in_stack = self
493 .entries
494 .get(&page_id)
495 .map(|e| e.in_stack_s)
496 .unwrap_or(false);
497
498 if self.lir_count < self.lir_size_limit {
499 self.entries.insert(
501 page_id,
502 LirsEntry {
503 status: LirsStatus::Lir,
504 recency: self.recency_counter,
505 in_stack_s: true,
506 in_list_q: false,
507 },
508 );
509 self.lir_count += 1;
510 self.move_to_stack_top(page_id);
511 } else if was_in_stack {
512 if let Some(entry) = self.entries.get_mut(&page_id) {
514 entry.status = LirsStatus::Lir;
515 entry.in_list_q = false;
516 }
517 self.lir_count += 1;
518 self.move_to_stack_top(page_id);
519
520 if self.lir_count > self.lir_size_limit {
522 self.demote_bottom_lir();
523 }
524 } else {
525 self.entries.insert(
527 page_id,
528 LirsEntry {
529 status: LirsStatus::HirResident,
530 recency: self.recency_counter,
531 in_stack_s: true,
532 in_list_q: true,
533 },
534 );
535 self.hir_count += 1;
536 self.list_q.push_back(page_id);
537 self.move_to_stack_top(page_id);
538 }
539 }
540
541 fn select_victim(&mut self) -> Option<PageId> {
542 while let Some(page_id) = self.list_q.pop_front() {
544 if let Some(entry) = self.entries.get_mut(&page_id) {
545 if entry.status == LirsStatus::HirResident {
546 entry.in_list_q = false;
547
548 if entry.in_stack_s {
550 entry.status = LirsStatus::HirNonResident;
551 self.hir_count = self.hir_count.saturating_sub(1);
552 }
553
554 return Some(page_id);
555 }
556 }
557 }
558
559 if let Some(&page_id) = self.stack_s.front() {
561 return Some(page_id);
562 }
563
564 None
565 }
566
567 fn remove(&mut self, page_id: PageId) {
568 if let Some(entry) = self.entries.remove(&page_id) {
569 if entry.status == LirsStatus::Lir {
570 self.lir_count = self.lir_count.saturating_sub(1);
571 } else if entry.status == LirsStatus::HirResident {
572 self.hir_count = self.hir_count.saturating_sub(1);
573 }
574 }
575 self.stack_s.retain(|&id| id != page_id);
576 self.list_q.retain(|&id| id != page_id);
577 }
578
579 fn name(&self) -> &'static str {
580 "LIRS"
581 }
582}
583
584pub use featherdb_core::EvictionPolicyType;
590
591pub trait EvictionPolicyFactory {
593 fn create(&self, capacity: usize) -> Box<dyn EvictionPolicy>;
595}
596
597impl EvictionPolicyFactory for EvictionPolicyType {
598 fn create(&self, capacity: usize) -> Box<dyn EvictionPolicy> {
600 match self {
601 EvictionPolicyType::Clock => Box::new(ClockEviction::new()),
602 EvictionPolicyType::Lru2 => Box::new(Lru2Eviction::new()),
603 EvictionPolicyType::Lirs => Box::new(LirsEviction::new(capacity)),
604 }
605 }
606}
607
608#[cfg(test)]
613mod tests {
614 use super::*;
615
616 #[test]
617 fn test_clock_basic() {
618 let mut clock = ClockEviction::new();
619
620 clock.on_load(PageId(1));
622 clock.on_load(PageId(2));
623 clock.on_load(PageId(3));
624
625 let victim = clock.select_victim();
628 assert!(victim.is_some());
629
630 clock.on_access(PageId(2));
632
633 let victim = clock.select_victim();
635 assert!(victim.is_some());
636 assert_ne!(victim, Some(PageId(2)));
637 }
638
639 #[test]
640 fn test_clock_remove() {
641 let mut clock = ClockEviction::new();
642
643 clock.on_load(PageId(1));
644 clock.on_load(PageId(2));
645
646 clock.remove(PageId(1));
647
648 let victim = clock.select_victim();
650 assert_eq!(victim, Some(PageId(2)));
651 }
652
653 #[test]
654 fn test_lru2_scan_resistance() {
655 let mut lru2 = Lru2Eviction::new();
656
657 lru2.on_load(PageId(1));
659 std::thread::sleep(std::time::Duration::from_millis(20));
660 lru2.on_access(PageId(1)); for i in 2..=10 {
664 lru2.on_load(PageId(i as u64));
665 }
666
667 let victim = lru2.select_victim();
669 assert!(victim.is_some());
670 assert_ne!(
671 victim,
672 Some(PageId(1)),
673 "Hot page should not be evicted first"
674 );
675 }
676
677 #[test]
678 fn test_lru2_cold_page_eviction() {
679 let mut lru2 = Lru2Eviction::new();
680
681 lru2.on_load(PageId(1));
683 std::thread::sleep(std::time::Duration::from_millis(150)); lru2.on_load(PageId(2));
685
686 let victim = lru2.select_victim();
688 assert_eq!(victim, Some(PageId(1)));
689 }
690
691 #[test]
692 fn test_lirs_basic() {
693 let mut lirs = LirsEviction::new(10);
694
695 for i in 1..=5 {
697 lirs.on_load(PageId(i as u64));
698 }
699
700 assert_eq!(lirs.lir_count, 5);
701
702 lirs.on_access(PageId(1));
704 lirs.on_access(PageId(2));
705 }
706
707 #[test]
708 fn test_lirs_eviction() {
709 let mut lirs = LirsEviction::new(5);
710
711 for i in 1..=10 {
713 lirs.on_load(PageId(i as u64));
714 }
715
716 assert!(lirs.hir_count > 0);
718
719 let victim = lirs.select_victim();
721 assert!(victim.is_some());
722 }
723
724 #[test]
725 fn test_policy_type_create() {
726 let clock = EvictionPolicyType::Clock.create(100);
727 assert_eq!(clock.name(), "Clock");
728
729 let lru2 = EvictionPolicyType::Lru2.create(100);
730 assert_eq!(lru2.name(), "LRU-2");
731
732 let lirs = EvictionPolicyType::Lirs.create(100);
733 assert_eq!(lirs.name(), "LIRS");
734 }
735}