From 33423fa914169cfe4fdd375b7ddfed8a68881b09 Mon Sep 17 00:00:00 2001 From: Aditya Mukhopadhyay Date: Sun, 23 Aug 2026 22:18:16 +0530 Subject: [PATCH] perf: skip page cache allocation for in-memory databases MemoryBackend serves every read and write directly from RAM and never consults the page cache, so honoring OpenOptions::page_cache_size for in_memory_with_options was pointless. Always construct the PageCache with zero capacity for MemoryBackend-backed storage, and document page_cache_size as file-backed-only. Adds pub(crate) capacity() / page_cache_capacity() test accessors on PageCache / PersistentFactStorage to verify the wiring. Closes #274 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01YMWSutMLJJ7Y5b8hs9ZaCU --- src/db.rs | 37 +++++++++++++++++++++++++++++++-- src/storage/persistent_facts.rs | 15 +++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/db.rs b/src/db.rs index 554f9c450..8d6f4e56f 100644 --- a/src/db.rs +++ b/src/db.rs @@ -56,6 +56,17 @@ fn is_write_tx_active() -> bool { WRITE_TX_ACTIVE.with(|f| f.get()) } +// ─── Page cache sizing ───────────────────────────────────────────────────────── + +/// Page cache capacity to use for an in-memory (`MemoryBackend`) database. +/// +/// `MemoryBackend` serves every read and write directly from RAM and never +/// consults the page cache, so `opts.page_cache_size` — which only affects +/// file-backed databases — is ignored here and zero capacity is always used. +fn memory_page_cache_capacity(_opts: &OpenOptions) -> usize { + 0 +} + // ─── OpenOptions ───────────────────────────────────────────────────────────── /// Configuration options for opening a `Minigraf` database. @@ -67,6 +78,10 @@ pub struct OpenOptions { /// more I/O). Higher values mean less frequent checkpoints (larger WAL, less I/O). pub wal_checkpoint_threshold: usize, /// Number of pages to hold in the LRU page cache. Default: 256 (= 1MB at 4KB pages). + /// + /// **File-backed databases only.** In-memory databases (`Minigraf::in_memory`, + /// `open_memory`) ignore this option — `MemoryBackend` serves every read and + /// write directly from RAM and never consults the page cache. pub page_cache_size: usize, /// Maximum facts that can be derived per recursive rule iteration. /// Defaults to 1_000_000. Use to prevent runaway recursive rules. @@ -106,6 +121,9 @@ impl OpenOptions { /// Set the number of pages to hold in the LRU page cache. /// /// Each page is 4KB, so the default of 256 pages uses ~1MB of memory. + /// + /// **File-backed databases only** — ignored when opening an in-memory + /// database (see [`OpenOptions::page_cache_size`]). pub fn page_cache_size(mut self, size: usize) -> Self { self.page_cache_size = size; self @@ -358,14 +376,15 @@ impl Minigraf { /// Create an in-memory database with custom options. /// - /// Note: WAL-related options are ignored for in-memory databases. + /// Note: WAL-related options are ignored for in-memory databases, as is + /// `page_cache_size` (see [`OpenOptions::page_cache_size`]). /// /// # Errors /// /// Returns an error if the in-memory storage backend fails to initialise. pub fn in_memory_with_options(opts: OpenOptions) -> Result { let backend = MemoryBackend::new(); - let pfs = PersistentFactStorage::new(backend, opts.page_cache_size)?; + let pfs = PersistentFactStorage::new(backend, memory_page_cache_capacity(&opts))?; let fact_storage = pfs.storage().clone(); // For in-memory databases we don't need the PFS beyond initialisation; @@ -1196,6 +1215,20 @@ mod tests { assert_eq!(facts.len(), 2, "expected 2 facts after 2 transacts"); } + #[test] + fn test_in_memory_page_cache_size_ignored() { + // In-memory databases never consult the page cache (`MemoryBackend` + // serves every read/write directly from RAM), so `page_cache_size` + // must be ignored and the underlying `PageCache` always constructed + // with zero capacity — regardless of what the caller requests. This + // mirrors exactly what `Minigraf::in_memory_with_options` does. + let opts = OpenOptions::default().page_cache_size(4096); + let pfs = + PersistentFactStorage::new(MemoryBackend::new(), memory_page_cache_capacity(&opts)) + .unwrap(); + assert_eq!(pfs.page_cache_capacity(), 0); + } + // ── begin_write / commit ───────────────────────────────────────────────── #[test] diff --git a/src/storage/persistent_facts.rs b/src/storage/persistent_facts.rs index 7e7cef678..7b7fd0dd8 100644 --- a/src/storage/persistent_facts.rs +++ b/src/storage/persistent_facts.rs @@ -196,6 +196,12 @@ impl PersistentFactStorage { Ok(persistent) } + /// The LRU page cache capacity this storage was constructed with (for testing). + #[allow(dead_code)] + pub(crate) fn page_cache_capacity(&self) -> usize { + self.page_cache.capacity() + } + /// Load all facts from the backend into memory. fn load(&mut self) -> Result<()> { let (header, raw_header_bytes) = { @@ -1268,6 +1274,15 @@ mod tests { use std::io::Write; use uuid::Uuid; + #[test] + fn test_page_cache_capacity_reflects_constructed_value() { + let zero_cap = PersistentFactStorage::new(MemoryBackend::new(), 0).unwrap(); + assert_eq!(zero_cap.page_cache_capacity(), 0); + + let nonzero_cap = PersistentFactStorage::new(MemoryBackend::new(), 64).unwrap(); + assert_eq!(nonzero_cap.page_cache_capacity(), 64); + } + #[test] fn test_persistent_fact_storage_new() { let backend = MemoryBackend::new();