1use std::sync::atomic::{AtomicU64, Ordering};
8
9use ahash::AHashMap;
10use parking_lot::Mutex;
11
12use crate::page::{Page, PageId, PageManager};
13use mentedb_core::error::{MenteError, MenteResult};
14use tracing::{debug, trace};
15
16type FrameId = usize;
17
18#[derive(Debug, Clone, Copy, Default)]
21pub struct BufferStats {
22 pub hits: u64,
23 pub misses: u64,
24 pub evictions: u64,
25 pub resident_pages: u64,
27}
28
29struct Frame {
31 page: Box<Page>,
32 page_id: Option<PageId>,
33 pin_count: u32,
34 dirty: bool,
35 reference: bool,
37}
38
39impl Frame {
40 fn new() -> Self {
41 Self {
42 page: Box::new(Page::zeroed()),
43 page_id: None,
44 pin_count: 0,
45 dirty: false,
46 reference: false,
47 }
48 }
49}
50
51struct BufferPoolInner {
52 frames: Vec<Frame>,
53 page_table: AHashMap<PageId, FrameId>,
54 clock_hand: usize,
55 capacity: usize,
56}
57
58pub struct BufferPool {
60 inner: Mutex<BufferPoolInner>,
61 hits: AtomicU64,
62 misses: AtomicU64,
63 evictions: AtomicU64,
64}
65
66impl BufferPool {
67 pub fn new(capacity: usize) -> Self {
75 assert!(capacity > 0, "buffer pool capacity must be > 0");
76 Self {
77 inner: Mutex::new(BufferPoolInner {
78 frames: Vec::new(),
79 page_table: AHashMap::new(),
80 clock_hand: 0,
81 capacity,
82 }),
83 hits: AtomicU64::new(0),
84 misses: AtomicU64::new(0),
85 evictions: AtomicU64::new(0),
86 }
87 }
88
89 pub fn stats(&self) -> BufferStats {
91 let resident_pages = self.inner.lock().page_table.len() as u64;
92 BufferStats {
93 hits: self.hits.load(Ordering::Relaxed),
94 misses: self.misses.load(Ordering::Relaxed),
95 evictions: self.evictions.load(Ordering::Relaxed),
96 resident_pages,
97 }
98 }
99
100 pub fn fetch_page(&self, page_id: PageId, pm: &mut PageManager) -> MenteResult<Box<Page>> {
105 let mut inner = self.inner.lock();
106
107 if let Some(&frame_id) = inner.page_table.get(&page_id) {
109 let frame = &mut inner.frames[frame_id];
110 frame.pin_count += 1;
111 frame.reference = true;
112 self.hits.fetch_add(1, Ordering::Relaxed);
113 trace!(page_id = page_id.0, frame_id, "buffer pool hit");
114 return Ok(frame.page.clone());
115 }
116
117 self.misses.fetch_add(1, Ordering::Relaxed);
119 let frame_id = Self::find_victim(&mut inner)?;
120
121 if inner.frames[frame_id].dirty
123 && let Some(old_pid) = inner.frames[frame_id].page_id
124 {
125 pm.write_page(old_pid, &inner.frames[frame_id].page)?;
126 debug!(page_id = old_pid.0, frame_id, "flushed dirty victim");
127 }
128
129 if let Some(old_pid) = inner.frames[frame_id].page_id {
132 inner.page_table.remove(&old_pid);
133 self.evictions.fetch_add(1, Ordering::Relaxed);
134 }
135
136 let page = pm.read_page(page_id)?;
138 {
139 let frame = &mut inner.frames[frame_id];
140 *frame.page = *page;
141 frame.page_id = Some(page_id);
142 frame.pin_count = 1;
143 frame.dirty = false;
144 frame.reference = true;
145 }
146
147 inner.page_table.insert(page_id, frame_id);
148 trace!(
149 page_id = page_id.0,
150 frame_id, "loaded page into buffer pool"
151 );
152
153 Ok(inner.frames[frame_id].page.clone())
154 }
155
156 pub fn pin_page(&self, page_id: PageId) -> MenteResult<()> {
158 let mut inner = self.inner.lock();
159 match inner.page_table.get(&page_id) {
160 Some(&fid) => {
161 inner.frames[fid].pin_count += 1;
162 Ok(())
163 }
164 None => Err(MenteError::Storage(format!(
165 "page {} not in buffer pool",
166 page_id.0
167 ))),
168 }
169 }
170
171 pub fn unpin_page(&self, page_id: PageId, dirty: bool) -> MenteResult<()> {
173 let mut inner = self.inner.lock();
174 match inner.page_table.get(&page_id) {
175 Some(&fid) => {
176 let frame = &mut inner.frames[fid];
177 if frame.pin_count > 0 {
178 frame.pin_count -= 1;
179 }
180 if dirty {
181 frame.dirty = true;
182 }
183 Ok(())
184 }
185 None => Err(MenteError::Storage(format!(
186 "page {} not in buffer pool",
187 page_id.0
188 ))),
189 }
190 }
191
192 pub fn invalidate(&self, page_id: PageId) {
198 let mut inner = self.inner.lock();
199 if let Some(fid) = inner.page_table.remove(&page_id) {
200 let frame = &mut inner.frames[fid];
201 frame.page_id = None;
202 frame.pin_count = 0;
203 frame.dirty = false;
204 frame.reference = false;
205 debug!(page_id = page_id.0, "invalidated cached page");
206 }
207 }
208
209 pub fn update_page(&self, page_id: PageId, page: &Page) -> MenteResult<()> {
211 let mut inner = self.inner.lock();
212 match inner.page_table.get(&page_id) {
213 Some(&fid) => {
214 let frame = &mut inner.frames[fid];
215 *frame.page = page.clone();
216 frame.dirty = true;
217 Ok(())
218 }
219 None => Err(MenteError::Storage(format!(
220 "page {} not in buffer pool",
221 page_id.0
222 ))),
223 }
224 }
225
226 pub fn flush_page(&self, page_id: PageId, pm: &mut PageManager) -> MenteResult<()> {
228 let mut inner = self.inner.lock();
229 match inner.page_table.get(&page_id) {
230 Some(&fid) => {
231 let frame = &mut inner.frames[fid];
232 if frame.dirty {
233 pm.write_page(page_id, &frame.page)?;
234 frame.dirty = false;
235 debug!(page_id = page_id.0, "flushed page");
236 }
237 Ok(())
238 }
239 None => Err(MenteError::Storage(format!(
240 "page {} not in buffer pool",
241 page_id.0
242 ))),
243 }
244 }
245
246 pub fn flush_all(&self, pm: &mut PageManager) -> MenteResult<()> {
248 let mut inner = self.inner.lock();
249 for frame in &mut inner.frames {
250 if frame.dirty
251 && let Some(pid) = frame.page_id
252 {
253 pm.write_page(pid, &frame.page)?;
254 frame.dirty = false;
255 }
256 }
257 debug!("flushed all dirty pages");
258 Ok(())
259 }
260
261 fn find_victim(inner: &mut BufferPoolInner) -> MenteResult<FrameId> {
263 for i in 0..inner.frames.len() {
265 if inner.frames[i].page_id.is_none() {
266 return Ok(i);
267 }
268 }
269
270 if inner.frames.len() < inner.capacity {
272 inner.frames.push(Frame::new());
273 return Ok(inner.frames.len() - 1);
274 }
275
276 let cap = inner.capacity;
278 let max_sweeps = cap * 2;
279 for _ in 0..max_sweeps {
280 let idx = inner.clock_hand;
281 inner.clock_hand = (inner.clock_hand + 1) % cap;
282
283 let frame = &mut inner.frames[idx];
284 if frame.pin_count == 0 {
285 if !frame.reference {
286 return Ok(idx);
287 }
288 frame.reference = false;
289 }
290 }
291
292 Err(MenteError::Storage(
293 "buffer pool full: all pages are pinned".into(),
294 ))
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301 use crate::page::Page;
302
303 fn setup() -> (tempfile::TempDir, PageManager) {
304 let dir = tempfile::tempdir().unwrap();
305 let pm = PageManager::open(dir.path()).unwrap();
306 (dir, pm)
307 }
308
309 #[test]
310 fn test_fetch_and_cache_hit() {
311 let (_dir, mut pm) = setup();
312 let pool = BufferPool::new(4);
313
314 let pid = pm.allocate_page().unwrap();
315 let mut page = Page::zeroed();
316 page.header.page_id = pid.0;
317 page.data[0..3].copy_from_slice(b"abc");
318 pm.write_page(pid, &page).unwrap();
319
320 let p1 = pool.fetch_page(pid, &mut pm).unwrap();
322 assert_eq!(&p1.data[0..3], b"abc");
323
324 pool.unpin_page(pid, false).unwrap();
326
327 let p2 = pool.fetch_page(pid, &mut pm).unwrap();
329 assert_eq!(&p2.data[0..3], b"abc");
330 pool.unpin_page(pid, false).unwrap();
331 }
332
333 #[test]
334 fn test_dirty_flush() {
335 let (_dir, mut pm) = setup();
336 let pool = BufferPool::new(4);
337
338 let pid = pm.allocate_page().unwrap();
339
340 let mut page = Page::zeroed();
341 page.header.page_id = pid.0;
342 page.data[0] = 42;
343 pm.write_page(pid, &page).unwrap();
344
345 let _ = pool.fetch_page(pid, &mut pm).unwrap();
347 let mut modified = Page::zeroed();
348 modified.header.page_id = pid.0;
349 modified.data[0] = 99;
350 pool.update_page(pid, &modified).unwrap();
351 pool.unpin_page(pid, true).unwrap();
352
353 pool.flush_page(pid, &mut pm).unwrap();
355
356 let on_disk = pm.read_page(pid).unwrap();
358 assert_eq!(on_disk.data[0], 99);
359 }
360
361 #[test]
362 fn test_eviction() {
363 let (_dir, mut pm) = setup();
364 let pool = BufferPool::new(2); let p1 = pm.allocate_page().unwrap();
368 let p2 = pm.allocate_page().unwrap();
369 let p3 = pm.allocate_page().unwrap();
370
371 for pid in [p1, p2, p3] {
372 let mut page = Page::zeroed();
373 page.header.page_id = pid.0;
374 page.data[0] = pid.0 as u8;
375 pm.write_page(pid, &page).unwrap();
376 }
377
378 let _ = pool.fetch_page(p1, &mut pm).unwrap();
380 pool.unpin_page(p1, false).unwrap();
381 let _ = pool.fetch_page(p2, &mut pm).unwrap();
382 pool.unpin_page(p2, false).unwrap();
383
384 let page3 = pool.fetch_page(p3, &mut pm).unwrap();
386 assert_eq!(page3.data[0], p3.0 as u8);
387 pool.unpin_page(p3, false).unwrap();
388 }
389
390 #[test]
391 fn test_all_pinned_error() {
392 let (_dir, mut pm) = setup();
393 let pool = BufferPool::new(2);
394
395 let p1 = pm.allocate_page().unwrap();
396 let p2 = pm.allocate_page().unwrap();
397 let p3 = pm.allocate_page().unwrap();
398
399 for pid in [p1, p2, p3] {
400 let mut page = Page::zeroed();
401 page.header.page_id = pid.0;
402 pm.write_page(pid, &page).unwrap();
403 }
404
405 let _ = pool.fetch_page(p1, &mut pm).unwrap();
407 let _ = pool.fetch_page(p2, &mut pm).unwrap();
408
409 assert!(pool.fetch_page(p3, &mut pm).is_err());
411 }
412
413 #[test]
414 fn test_flush_all() {
415 let (_dir, mut pm) = setup();
416 let pool = BufferPool::new(4);
417
418 let p1 = pm.allocate_page().unwrap();
419 let p2 = pm.allocate_page().unwrap();
420
421 for pid in [p1, p2] {
422 let mut page = Page::zeroed();
423 page.header.page_id = pid.0;
424 pm.write_page(pid, &page).unwrap();
425 }
426
427 let _ = pool.fetch_page(p1, &mut pm).unwrap();
428 let _ = pool.fetch_page(p2, &mut pm).unwrap();
429
430 let mut mod1 = Page::zeroed();
431 mod1.data[0] = 0xAA;
432 pool.update_page(p1, &mod1).unwrap();
433
434 let mut mod2 = Page::zeroed();
435 mod2.data[0] = 0xBB;
436 pool.update_page(p2, &mod2).unwrap();
437
438 pool.unpin_page(p1, true).unwrap();
439 pool.unpin_page(p2, true).unwrap();
440
441 pool.flush_all(&mut pm).unwrap();
442
443 let d1 = pm.read_page(p1).unwrap();
444 let d2 = pm.read_page(p2).unwrap();
445 assert_eq!(d1.data[0], 0xAA);
446 assert_eq!(d2.data[0], 0xBB);
447 }
448}