mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-10 11:44:22 +01:00
Merge 'Page Cache: optimize and use sieve/Gclock hybird algorithm in place of LRU' from Preston Thorpe
This PR replaces the existing LRU cache implementation with the sieve algo, providing better performance and simpler code. Same intrusive linked-list without pointer chasing and no per- page/insert heap allocations. Benchmarks look really good so far, although some did show some regression (I think it's more likely that they were flakey and its not representative on either end, but I do believe this to be considerably more performant) Best numbers I have seen yet on Mobibench locally, with 1473 tx/s. Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com> Reviewed-by: Nikita Sivukhin (@sivukhin) Closes #2885
This commit is contained in:
10
core/lib.rs
10
core/lib.rs
@@ -80,7 +80,7 @@ use std::{
|
||||
use storage::database::DatabaseFile;
|
||||
pub use storage::database::IOContext;
|
||||
pub use storage::encryption::{EncryptionContext, EncryptionKey};
|
||||
use storage::page_cache::DumbLruPageCache;
|
||||
use storage::page_cache::PageCache;
|
||||
use storage::pager::{AtomicDbState, DbState};
|
||||
use storage::sqlite3_ondisk::PageSize;
|
||||
pub use storage::{
|
||||
@@ -187,7 +187,7 @@ pub struct Database {
|
||||
buffer_pool: Arc<BufferPool>,
|
||||
// Shared structures of a Database are the parts that are common to multiple threads that might
|
||||
// create DB connections.
|
||||
_shared_page_cache: Arc<RwLock<DumbLruPageCache>>,
|
||||
_shared_page_cache: Arc<RwLock<PageCache>>,
|
||||
shared_wal: Arc<RwLock<WalFileShared>>,
|
||||
db_state: Arc<AtomicDbState>,
|
||||
init_lock: Arc<Mutex<()>>,
|
||||
@@ -385,7 +385,7 @@ impl Database {
|
||||
DbState::Initialized
|
||||
};
|
||||
|
||||
let shared_page_cache = Arc::new(RwLock::new(DumbLruPageCache::default()));
|
||||
let shared_page_cache = Arc::new(RwLock::new(PageCache::default()));
|
||||
let syms = SymbolTable::new();
|
||||
let arena_size = if std::env::var("TESTING").is_ok_and(|v| v.eq_ignore_ascii_case("true")) {
|
||||
BufferPool::TEST_ARENA_SIZE
|
||||
@@ -576,7 +576,7 @@ impl Database {
|
||||
self.db_file.clone(),
|
||||
Some(wal),
|
||||
self.io.clone(),
|
||||
Arc::new(RwLock::new(DumbLruPageCache::default())),
|
||||
Arc::new(RwLock::new(PageCache::default())),
|
||||
buffer_pool.clone(),
|
||||
db_state,
|
||||
self.init_lock.clone(),
|
||||
@@ -599,7 +599,7 @@ impl Database {
|
||||
self.db_file.clone(),
|
||||
None,
|
||||
self.io.clone(),
|
||||
Arc::new(RwLock::new(DumbLruPageCache::default())),
|
||||
Arc::new(RwLock::new(PageCache::default())),
|
||||
buffer_pool.clone(),
|
||||
db_state,
|
||||
Arc::new(Mutex::new(())),
|
||||
|
||||
@@ -7376,7 +7376,7 @@ mod tests {
|
||||
schema::IndexColumn,
|
||||
storage::{
|
||||
database::DatabaseFile,
|
||||
page_cache::DumbLruPageCache,
|
||||
page_cache::PageCache,
|
||||
pager::{AtomicDbState, DbState},
|
||||
sqlite3_ondisk::PageSize,
|
||||
},
|
||||
@@ -8631,7 +8631,7 @@ mod tests {
|
||||
db_file,
|
||||
Some(wal),
|
||||
io,
|
||||
Arc::new(parking_lot::RwLock::new(DumbLruPageCache::new(10))),
|
||||
Arc::new(parking_lot::RwLock::new(PageCache::new(10))),
|
||||
buffer_pool,
|
||||
Arc::new(AtomicDbState::new(DbState::Uninitialized)),
|
||||
Arc::new(Mutex::new(())),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,7 +25,7 @@ use std::sync::{Arc, Mutex};
|
||||
use tracing::{instrument, trace, Level};
|
||||
|
||||
use super::btree::btree_init_page;
|
||||
use super::page_cache::{CacheError, CacheResizeResult, DumbLruPageCache, PageCacheKey};
|
||||
use super::page_cache::{CacheError, CacheResizeResult, PageCache, PageCacheKey};
|
||||
use super::sqlite3_ondisk::begin_write_btree_page;
|
||||
use super::wal::CheckpointMode;
|
||||
use crate::storage::encryption::{CipherMode, EncryptionContext, EncryptionKey};
|
||||
@@ -129,7 +129,7 @@ pub struct PageInner {
|
||||
/// requests unpinning via [Page::unpin], the pin count will still be >0 if the outer
|
||||
/// code path has not yet requested to unpin the page as well.
|
||||
///
|
||||
/// Note that [DumbLruPageCache::clear] evicts the pages even if pinned, so as long as
|
||||
/// Note that [PageCache::clear] evicts the pages even if pinned, so as long as
|
||||
/// we clear the page cache on errors, pins will not 'leak'.
|
||||
pub pin_count: AtomicUsize,
|
||||
/// The WAL frame number this page was loaded from (0 if loaded from main DB file)
|
||||
@@ -464,7 +464,7 @@ pub struct Pager {
|
||||
/// in-memory databases, ephemeral tables and ephemeral indexes do not have a WAL.
|
||||
pub(crate) wal: Option<Rc<RefCell<dyn Wal>>>,
|
||||
/// A page cache for the database.
|
||||
page_cache: Arc<RwLock<DumbLruPageCache>>,
|
||||
page_cache: Arc<RwLock<PageCache>>,
|
||||
/// Buffer pool for temporary data storage.
|
||||
pub buffer_pool: Arc<BufferPool>,
|
||||
/// I/O interface for input/output operations.
|
||||
@@ -564,7 +564,7 @@ impl Pager {
|
||||
db_file: Arc<dyn DatabaseStorage>,
|
||||
wal: Option<Rc<RefCell<dyn Wal>>>,
|
||||
io: Arc<dyn crate::io::IO>,
|
||||
page_cache: Arc<RwLock<DumbLruPageCache>>,
|
||||
page_cache: Arc<RwLock<PageCache>>,
|
||||
buffer_pool: Arc<BufferPool>,
|
||||
db_state: Arc<AtomicDbState>,
|
||||
init_lock: Arc<Mutex<()>>,
|
||||
@@ -1153,7 +1153,7 @@ impl Pager {
|
||||
&self,
|
||||
page_idx: usize,
|
||||
page: PageRef,
|
||||
page_cache: &mut DumbLruPageCache,
|
||||
page_cache: &mut PageCache,
|
||||
) -> Result<()> {
|
||||
let page_key = PageCacheKey::new(page_idx);
|
||||
match page_cache.insert(page_key, page.clone()) {
|
||||
@@ -1171,7 +1171,7 @@ impl Pager {
|
||||
tracing::trace!("read_page(page_idx = {})", page_idx);
|
||||
let mut page_cache = self.page_cache.write();
|
||||
let page_key = PageCacheKey::new(page_idx);
|
||||
Ok(page_cache.get(&page_key)?)
|
||||
page_cache.get(&page_key)
|
||||
}
|
||||
|
||||
/// Get a page from cache only if it matches the target frame
|
||||
@@ -1981,7 +1981,7 @@ impl Pager {
|
||||
trunk_page.get_contents().as_ptr().fill(0);
|
||||
let page_key = PageCacheKey::new(trunk_page.get().id);
|
||||
{
|
||||
let mut page_cache = self.page_cache.write();
|
||||
let page_cache = self.page_cache.read();
|
||||
turso_assert!(
|
||||
page_cache.contains_key(&page_key),
|
||||
"page {} is not in cache",
|
||||
@@ -2013,7 +2013,7 @@ impl Pager {
|
||||
leaf_page.get_contents().as_ptr().fill(0);
|
||||
let page_key = PageCacheKey::new(leaf_page.get().id);
|
||||
{
|
||||
let mut page_cache = self.page_cache.write();
|
||||
let page_cache = self.page_cache.read();
|
||||
turso_assert!(
|
||||
page_cache.contains_key(&page_key),
|
||||
"page {} is not in cache",
|
||||
@@ -2090,13 +2090,11 @@ impl Pager {
|
||||
|
||||
// FIXME: use specific page key for writer instead of max frame, this will make readers not conflict
|
||||
assert!(page.is_dirty());
|
||||
cache
|
||||
.insert_ignore_existing(page_key, page.clone())
|
||||
.map_err(|e| {
|
||||
LimboError::InternalError(format!(
|
||||
"Failed to insert loaded page {id} into cache: {e:?}"
|
||||
))
|
||||
})?;
|
||||
cache.upsert_page(page_key, page.clone()).map_err(|e| {
|
||||
LimboError::InternalError(format!(
|
||||
"Failed to insert loaded page {id} into cache: {e:?}"
|
||||
))
|
||||
})?;
|
||||
page.set_loaded();
|
||||
Ok(())
|
||||
}
|
||||
@@ -2402,14 +2400,14 @@ mod tests {
|
||||
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use crate::storage::page_cache::{DumbLruPageCache, PageCacheKey};
|
||||
use crate::storage::page_cache::{PageCache, PageCacheKey};
|
||||
|
||||
use super::Page;
|
||||
|
||||
#[test]
|
||||
fn test_shared_cache() {
|
||||
// ensure cache can be shared between threads
|
||||
let cache = Arc::new(RwLock::new(DumbLruPageCache::new(10)));
|
||||
let cache = Arc::new(RwLock::new(PageCache::new(10)));
|
||||
|
||||
let thread = {
|
||||
let cache = cache.clone();
|
||||
@@ -2442,7 +2440,7 @@ mod ptrmap_tests {
|
||||
use crate::io::{MemoryIO, OpenFlags, IO};
|
||||
use crate::storage::buffer_pool::BufferPool;
|
||||
use crate::storage::database::{DatabaseFile, DatabaseStorage};
|
||||
use crate::storage::page_cache::DumbLruPageCache;
|
||||
use crate::storage::page_cache::PageCache;
|
||||
use crate::storage::pager::Pager;
|
||||
use crate::storage::sqlite3_ondisk::PageSize;
|
||||
use crate::storage::wal::{WalFile, WalFileShared};
|
||||
@@ -2471,7 +2469,7 @@ mod ptrmap_tests {
|
||||
let pages = initial_db_pages + 10;
|
||||
let sz = std::cmp::max(std::cmp::min(pages, 64), pages);
|
||||
let buffer_pool = BufferPool::begin_init(&io, (sz * page_size) as usize);
|
||||
let page_cache = Arc::new(RwLock::new(DumbLruPageCache::new(sz as usize)));
|
||||
let page_cache = Arc::new(RwLock::new(PageCache::new(sz as usize)));
|
||||
|
||||
let wal = Rc::new(RefCell::new(WalFile::new(
|
||||
io.clone(),
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::storage::btree::{
|
||||
integrity_check, IntegrityCheckError, IntegrityCheckState, PageCategory,
|
||||
};
|
||||
use crate::storage::database::DatabaseFile;
|
||||
use crate::storage::page_cache::DumbLruPageCache;
|
||||
use crate::storage::page_cache::PageCache;
|
||||
use crate::storage::pager::{AtomicDbState, CreateBTreeFlags, DbState};
|
||||
use crate::storage::sqlite3_ondisk::read_varint;
|
||||
use crate::translate::collate::CollationSeq;
|
||||
@@ -6996,7 +6996,7 @@ pub fn op_open_ephemeral(
|
||||
.get();
|
||||
|
||||
let buffer_pool = program.connection._db.buffer_pool.clone();
|
||||
let page_cache = Arc::new(RwLock::new(DumbLruPageCache::default()));
|
||||
let page_cache = Arc::new(RwLock::new(PageCache::default()));
|
||||
|
||||
let pager = Rc::new(Pager::new(
|
||||
db_file,
|
||||
|
||||
Reference in New Issue
Block a user