quill_sql/utils/cache/
window_lfu.rs1use super::Replacer; use crate::buffer::FrameId;
3use crate::error::{QuillSQLError, QuillSQLResult};
4use std::collections::HashMap;
5use std::time::{Duration, Instant}; const DEFAULT_WINDOW_DURATION: Duration = Duration::from_secs(10); #[derive(Debug, Clone)]
10struct WindowLFUNode {
11 frequency: u64,
12 last_access_time: Instant,
13 is_evictable: bool,
14}
15
16impl WindowLFUNode {
17 fn new() -> Self {
18 Self {
19 frequency: 1, last_access_time: Instant::now(),
21 is_evictable: false, }
23 }
24}
25
26#[derive(Debug)]
27pub struct WindowLFUReplacer {
28 node_store: HashMap<FrameId, WindowLFUNode>,
29 capacity: usize,
30 current_size: usize, window_duration: Duration,
32}
33
34impl Replacer for WindowLFUReplacer {
37 fn new(capacity: usize) -> Self {
38 Self::with_window(capacity, DEFAULT_WINDOW_DURATION)
39 }
40
41 fn record_access(&mut self, frame_id: FrameId) -> QuillSQLResult<()> {
42 let now = Instant::now();
43 if let Some(node) = self.node_store.get_mut(&frame_id) {
44 node.frequency += 1;
45 node.last_access_time = now;
46 } else {
47 if self.node_store.len() >= self.capacity {
49 return Err(QuillSQLError::Internal(format!(
51 "WindowLFU capacity {} reached when accessing new frame {}",
52 self.capacity, frame_id
53 )));
54 }
55 self.node_store.insert(frame_id, WindowLFUNode::new());
56 }
58 Ok(())
59 }
60
61 fn evict(&mut self) -> Option<FrameId> {
62 if self.current_size == 0 {
63 return None;
64 }
65
66 let now = Instant::now();
67 let mut victim_frame = None;
68 let mut min_priority: Option<(u64, Instant)> = None;
71
72 for (&frame_id, node) in self.node_store.iter() {
73 if !node.is_evictable {
74 continue;
75 }
76
77 let effective_frequency =
78 if now.duration_since(node.last_access_time) <= self.window_duration {
79 node.frequency
81 } else {
82 0
84 };
85
86 let current_priority = (effective_frequency, node.last_access_time);
87
88 match min_priority {
89 None => {
90 min_priority = Some(current_priority);
91 victim_frame = Some(frame_id);
92 }
93 Some(min_p) => {
94 if effective_frequency < min_p.0
96 || (effective_frequency == min_p.0 && node.last_access_time < min_p.1)
97 {
98 min_priority = Some(current_priority);
99 victim_frame = Some(frame_id);
100 }
101 }
102 }
103 }
104
105 if let Some(victim_id) = victim_frame {
106 self.remove(victim_id); Some(victim_id)
108 } else {
109 None }
111 }
112
113 fn set_evictable(&mut self, frame_id: FrameId, set_evictable: bool) -> QuillSQLResult<()> {
114 if let Some(node) = self.node_store.get_mut(&frame_id) {
115 let was_evictable = node.is_evictable;
116 node.is_evictable = set_evictable;
117 if set_evictable && !was_evictable {
118 self.current_size += 1;
119 } else if !set_evictable && was_evictable {
120 if self.current_size > 0 {
122 self.current_size -= 1;
123 } else {
124 eprintln!("Warning: Attempted to decrease WindowLFUReplacer size below zero for frame {}", frame_id);
126 }
127 }
128 Ok(())
129 } else {
130 Err(QuillSQLError::Internal(format!(
131 "Frame {} not found in WindowLFUReplacer::set_evictable",
132 frame_id
133 )))
134 }
135 }
136
137 fn remove(&mut self, frame_id: FrameId) {
138 if let Some(node) = self.node_store.remove(&frame_id) {
139 if node.is_evictable {
140 if self.current_size > 0 {
141 self.current_size -= 1;
142 } else {
143 eprintln!("Warning: Attempted to decrease WindowLFUReplacer size below zero during remove for frame {}", frame_id);
144 }
145 }
146 }
147 }
148
149 fn size(&self) -> usize {
150 self.current_size
151 }
152}
153
154impl WindowLFUReplacer {
156 pub fn with_window(capacity: usize, window_duration: Duration) -> Self {
158 Self {
159 node_store: HashMap::with_capacity(capacity),
160 capacity,
161 current_size: 0,
162 window_duration,
163 }
164 }
165}
166
167#[cfg(test)]
169mod tests {
170 use super::*;
171 use std::thread::sleep;
172
173 #[test]
174 fn test_window_lfu_basic_eviction() {
175 let mut replacer = WindowLFUReplacer::new(3);
176
177 replacer.record_access(1).unwrap(); replacer.record_access(2).unwrap(); replacer.record_access(3).unwrap(); replacer.set_evictable(1, true).unwrap();
182 replacer.set_evictable(2, true).unwrap();
183 replacer.set_evictable(3, true).unwrap();
184 assert_eq!(replacer.size(), 3);
185
186 assert_eq!(replacer.evict(), Some(1));
188 assert_eq!(replacer.size(), 2);
189
190 assert_eq!(replacer.evict(), Some(2));
192 assert_eq!(replacer.size(), 1);
193
194 assert_eq!(replacer.evict(), Some(3));
196 assert_eq!(replacer.size(), 0);
197
198 assert_eq!(replacer.evict(), None);
199 }
200
201 #[test]
202 fn test_window_lfu_frequency_effect() {
203 let mut replacer = WindowLFUReplacer::new(3);
204
205 replacer.record_access(1).unwrap(); replacer.record_access(2).unwrap(); replacer.record_access(3).unwrap(); replacer.record_access(1).unwrap(); replacer.record_access(1).unwrap(); replacer.record_access(2).unwrap(); replacer.set_evictable(1, true).unwrap();
214 replacer.set_evictable(2, true).unwrap();
215 replacer.set_evictable(3, true).unwrap();
216
217 assert_eq!(replacer.evict(), Some(3));
219 assert_eq!(replacer.evict(), Some(2));
221 assert_eq!(replacer.evict(), Some(1));
223 }
224
225 #[test]
226 fn test_window_lfu_window_effect() {
227 let window = Duration::from_millis(50);
229 let mut replacer = WindowLFUReplacer::with_window(3, window);
230
231 replacer.record_access(1).unwrap(); sleep(window / 2);
233 replacer.record_access(2).unwrap(); replacer.record_access(2).unwrap(); replacer.set_evictable(1, true).unwrap();
237 replacer.set_evictable(2, true).unwrap();
238
239 sleep(window * 2);
241
242 replacer.record_access(3).unwrap(); replacer.set_evictable(3, true).unwrap();
244
245 assert_eq!(replacer.evict(), Some(1));
248
249 assert_eq!(replacer.evict(), Some(2));
251
252 assert_eq!(replacer.evict(), Some(3));
254 }
255
256 #[test]
257 fn test_window_lfu_set_evictable() {
258 let mut replacer = WindowLFUReplacer::new(3);
259
260 replacer.record_access(1).unwrap();
261 replacer.record_access(2).unwrap();
262 replacer.record_access(3).unwrap();
263
264 replacer.set_evictable(1, true).unwrap();
265 replacer.set_evictable(3, true).unwrap();
266 assert_eq!(replacer.size(), 2);
267
268 assert_eq!(replacer.evict(), Some(1));
270 assert_eq!(replacer.size(), 1);
271
272 replacer.set_evictable(2, true).unwrap();
274 replacer.set_evictable(3, false).unwrap();
275 assert_eq!(replacer.size(), 1);
276
277 assert_eq!(replacer.evict(), Some(2));
279 assert_eq!(replacer.size(), 0);
280 }
281
282 #[test]
283 fn test_window_lfu_remove() {
284 let mut replacer = WindowLFUReplacer::new(3);
285 replacer.record_access(1).unwrap();
286 replacer.record_access(2).unwrap();
287 replacer.set_evictable(1, true).unwrap();
288 replacer.set_evictable(2, true).unwrap();
289 assert_eq!(replacer.size(), 2);
290
291 replacer.remove(1); assert_eq!(replacer.size(), 1);
293 assert!(replacer.node_store.get(&1).is_none());
294
295 replacer.record_access(3).unwrap(); assert_eq!(replacer.size(), 1); replacer.remove(3); assert_eq!(replacer.size(), 1); assert!(replacer.node_store.get(&3).is_none());
301
302 assert_eq!(replacer.evict(), Some(2)); }
304
305 #[test]
306 fn test_window_lfu_capacity() {
307 let mut replacer = WindowLFUReplacer::new(2);
308 replacer.record_access(1).unwrap();
309 replacer.record_access(2).unwrap();
310 replacer.set_evictable(1, true).unwrap();
311 replacer.set_evictable(2, true).unwrap();
312
313 assert!(replacer.record_access(3).is_err());
316
317 assert_eq!(replacer.evict(), Some(1));
319
320 replacer.record_access(3).unwrap();
322 replacer.set_evictable(3, true).unwrap();
323 assert_eq!(replacer.size(), 2);
324 assert!(replacer.node_store.contains_key(&3));
325 }
326}