From f7471a22c0012f939c49497d97220f7fdd806324 Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Tue, 9 Sep 2025 13:22:56 -0400 Subject: [PATCH 1/3] Fix clear_page_cache method and stop iterating over every entry --- core/storage/pager.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/core/storage/pager.rs b/core/storage/pager.rs index 31eb980cd..551467f22 100644 --- a/core/storage/pager.rs +++ b/core/storage/pager.rs @@ -1563,12 +1563,16 @@ impl Pager { /// of a rollback or in case we want to invalidate page cache after starting a read transaction /// right after new writes happened which would invalidate current page cache. pub fn clear_page_cache(&self) { - self.dirty_pages.borrow_mut().clear(); - self.page_cache.write().unset_dirty_all_pages(); - self.page_cache - .write() - .clear() - .expect("Failed to clear page cache"); + let mut dirty_pages = self.dirty_pages.borrow_mut(); + let mut cache = self.page_cache.write(); + for page_id in dirty_pages.iter() { + let page_key = PageCacheKey::new(*page_id); + if let Some(page) = cache.get(&page_key).unwrap_or(None) { + page.clear_dirty(); + } + } + dirty_pages.clear(); + cache.clear().expect("Failed to clear page cache"); } /// Checkpoint in Truncate mode and delete the WAL file. This method is _only_ to be called From 8cc4e7f7a03d4515717467af6fe29649bc982656 Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Tue, 9 Sep 2025 13:28:17 -0400 Subject: [PATCH 2/3] Fix rollback method to stop using highly inefficient cache::clear_dirty --- core/storage/page_cache.rs | 10 ---------- core/storage/pager.rs | 10 ++-------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/core/storage/page_cache.rs b/core/storage/page_cache.rs index c125f44c8..90aa9ab9e 100644 --- a/core/storage/page_cache.rs +++ b/core/storage/page_cache.rs @@ -631,16 +631,6 @@ impl PageCache { self.capacity } - pub fn unset_dirty_all_pages(&mut self) { - let entries = &self.entries; - for entry in entries.iter() { - if entry.page.is_none() { - continue; - } - entry.page.as_ref().unwrap().clear_dirty(); - } - } - #[cfg(test)] fn verify_cache_integrity(&self) { let map = &self.map; diff --git a/core/storage/pager.rs b/core/storage/pager.rs index 551467f22..6e42d17aa 100644 --- a/core/storage/pager.rs +++ b/core/storage/pager.rs @@ -2122,20 +2122,14 @@ impl Pager { is_write: bool, ) -> Result<(), LimboError> { tracing::debug!(schema_did_change); - if is_write { - self.dirty_pages.borrow_mut().clear(); - } else { + if !is_write { turso_assert!( self.dirty_pages.borrow().is_empty(), "dirty pages should be empty for read txn" ); } - let mut cache = self.page_cache.write(); - + self.clear_page_cache(); self.reset_internal_states(); - - cache.unset_dirty_all_pages(); - cache.clear().expect("failed to clear page cache"); if schema_did_change { connection.schema.replace(connection._db.clone_schema()?); } From cb12a1319d09f095298a5a3da07fea34417058c4 Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Tue, 9 Sep 2025 14:57:55 -0400 Subject: [PATCH 3/3] Fix page cache `clear` method to not re-initialize every slot --- core/storage/page_cache.rs | 26 +++++++++++++++++++++----- core/storage/pager.rs | 9 +++++---- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/core/storage/page_cache.rs b/core/storage/page_cache.rs index 90aa9ab9e..c5a70d614 100644 --- a/core/storage/page_cache.rs +++ b/core/storage/page_cache.rs @@ -528,20 +528,36 @@ impl PageCache { } pub fn clear(&mut self) -> Result<(), CacheError> { - for e in self.entries.iter() { + if self.map.len() == 0 { + // Fast path: nothing to do. + self.clock_hand = NULL; + return Ok(()); + } + + for node in self.map.iter() { + let e = &self.entries[node.slot_index]; if let Some(ref p) = e.page { if p.is_dirty() { return Err(CacheError::Dirty { pgno: p.get().id }); } + } + } + let mut used_slots = Vec::with_capacity(self.map.len()); + for node in self.map.iter() { + used_slots.push(node.slot_index); + } + // don't touch already-free slots at all. + for &i in &used_slots { + if let Some(p) = self.entries[i].page.take() { p.clear_loaded(); let _ = p.get().contents.take(); } + self.entries[i].clear_ref(); + self.entries[i].reset_links(); } - self.entries.fill(PageCacheEntry::empty()); - self.map.clear(); self.clock_hand = NULL; - self.freelist.clear(); - for i in (0..self.capacity).rev() { + self.map = PageHashMap::new(self.capacity); + for &i in used_slots.iter().rev() { self.freelist.push(i); } Ok(()) diff --git a/core/storage/pager.rs b/core/storage/pager.rs index 6e42d17aa..f2fb9d160 100644 --- a/core/storage/pager.rs +++ b/core/storage/pager.rs @@ -1563,7 +1563,7 @@ impl Pager { /// of a rollback or in case we want to invalidate page cache after starting a read transaction /// right after new writes happened which would invalidate current page cache. pub fn clear_page_cache(&self) { - let mut dirty_pages = self.dirty_pages.borrow_mut(); + let dirty_pages = self.dirty_pages.borrow(); let mut cache = self.page_cache.write(); for page_id in dirty_pages.iter() { let page_key = PageCacheKey::new(*page_id); @@ -1571,7 +1571,6 @@ impl Pager { page.clear_dirty(); } } - dirty_pages.clear(); cache.clear().expect("Failed to clear page cache"); } @@ -2122,13 +2121,15 @@ impl Pager { is_write: bool, ) -> Result<(), LimboError> { tracing::debug!(schema_did_change); - if !is_write { + self.clear_page_cache(); + if is_write { + self.dirty_pages.borrow_mut().clear(); + } else { turso_assert!( self.dirty_pages.borrow().is_empty(), "dirty pages should be empty for read txn" ); } - self.clear_page_cache(); self.reset_internal_states(); if schema_did_change { connection.schema.replace(connection._db.clone_schema()?);