From e7af52b694bfe3c51287920aaffeb4b34a4d782e Mon Sep 17 00:00:00 2001 From: Tonguechaude Date: Wed, 27 May 2026 16:24:13 +0200 Subject: [PATCH 1/9] fix: remove double I/O useless code --- src/storage/src/lmdb.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/storage/src/lmdb.rs b/src/storage/src/lmdb.rs index 09047e60..5539f52e 100644 --- a/src/storage/src/lmdb.rs +++ b/src/storage/src/lmdb.rs @@ -57,10 +57,9 @@ impl StorageBackend { let mut rw_txn = env.write_txn()?; let db: Database, Bytes> = env.create_database(&mut rw_txn, Some(&table))?; - if db.get(&rw_txn, &key)?.is_some() { + if db.get_or_put(&mut rw_txn, &key, &value)?.is_some() { return Err(StorageError::KeyExists(key as u64)); } - db.put(&mut rw_txn, &key, &value)?; rw_txn.commit()?; Ok(()) } @@ -71,12 +70,7 @@ impl StorageBackend { let db: Database, Bytes> = env .open_database(&ro_txn, Some(&table))? .ok_or(StorageError::TableError("Table not found".to_string()))?; - let value = db.get(&ro_txn, &key)?; - if let Some(v) = value { - Ok(Some(v.to_vec())) - } else { - Ok(None) - } + Ok(db.get(&ro_txn, &key)?.map(|v| v.to_vec())) } pub fn delete(&self, table: String, key: u128) -> Result<(), StorageError> { @@ -85,10 +79,9 @@ impl StorageBackend { let db: Database, Bytes> = env .open_database(&rw_txn, Some(&table))? .ok_or(StorageError::TableError("Table not found".to_string()))?; - if db.get(&rw_txn, &key)?.is_none() { + if !db.delete(&mut rw_txn, &key)? { return Err(StorageError::KeyNotFound(key as u64)); } - db.delete(&mut rw_txn, &key)?; rw_txn.commit()?; Ok(()) } From 83385977f3c51e812bedd8d53c2ffb2da60d75d7 Mon Sep 17 00:00:00 2001 From: Tonguechaude Date: Wed, 27 May 2026 16:55:18 +0200 Subject: [PATCH 2/9] fix: replace all String call by &str to avoid unncessary allocations --- src/app/runtime/src/setup.rs | 4 ++-- src/storage/src/benches/db.rs | 22 ++++++++++---------- src/storage/src/lmdb.rs | 38 +++++++++++++++-------------------- src/world/db/src/chunks.rs | 14 ++++++------- src/world/src/importing.rs | 2 +- src/world/src/player.rs | 20 +++++------------- 6 files changed, 42 insertions(+), 58 deletions(-) diff --git a/src/app/runtime/src/setup.rs b/src/app/runtime/src/setup.rs index 7bd2c8e9..f7e82f7c 100644 --- a/src/app/runtime/src/setup.rs +++ b/src/app/runtime/src/setup.rs @@ -59,14 +59,14 @@ pub fn setup_db(state: GlobalState) -> Result<(), BinaryError> { let chunk_key = string_to_u128("chunk-format-hash"); state.world.storage_backend.insert( - "metadata".to_string(), + "metadata", chunk_key, Chunk::type_hash().to_be_bytes().to_vec(), )?; let player_key = string_to_u128("player-format-hash"); state.world.storage_backend.insert( - "metadata".to_string(), + "metadata", player_key, OfflinePlayerData::type_hash().to_be_bytes().to_vec(), )?; diff --git a/src/storage/src/benches/db.rs b/src/storage/src/benches/db.rs index 02425aad..7cf5e525 100644 --- a/src/storage/src/benches/db.rs +++ b/src/storage/src/benches/db.rs @@ -28,14 +28,14 @@ pub(crate) fn db_benches(c: &mut criterion::Criterion) { let db = StorageBackend::initialize(Some(tempdir), 100 * 1024 * 1024 * 1024).unwrap(); - db.create_table("insert_test".to_string()).unwrap(); + db.create_table("insert_test").unwrap(); let mut insert_group = c.benchmark_group("Insert"); insert_group.bench_function("512b".to_string(), |b| { b.iter(|| { db.insert( - "insert_test".to_string(), + "insert_test", generate_random_key(&mut used_keys), generate_random_data(512), ) @@ -46,7 +46,7 @@ pub(crate) fn db_benches(c: &mut criterion::Criterion) { insert_group.bench_function("1kb".to_string(), |b| { b.iter(|| { db.insert( - "insert_test".to_string(), + "insert_test", generate_random_key(&mut used_keys), generate_random_data(1024), ) @@ -57,7 +57,7 @@ pub(crate) fn db_benches(c: &mut criterion::Criterion) { insert_group.bench_function("4kb".to_string(), |b| { b.iter(|| { db.insert( - "insert_test".to_string(), + "insert_test", generate_random_key(&mut used_keys), generate_random_data(4096), ) @@ -69,20 +69,20 @@ pub(crate) fn db_benches(c: &mut criterion::Criterion) { let mut read_group = c.benchmark_group("Read"); - db.create_table("read_test".to_string()).unwrap(); + db.create_table("read_test").unwrap(); let keys_512b = (0..1000) .map(|_| generate_random_key(&mut used_keys)) .collect::>(); for key in keys_512b.iter() { - db.insert("read_test".to_string(), *key, generate_random_data(512)) + db.insert("read_test", *key, generate_random_data(512)) .unwrap(); } read_group.bench_function("512b".to_string(), |b| { b.iter(|| { - db.get("read_test".to_string(), select_random(keys_512b.clone())) + db.get("read_test", select_random(keys_512b.clone())) .unwrap(); }) }); @@ -92,13 +92,13 @@ pub(crate) fn db_benches(c: &mut criterion::Criterion) { .collect::>(); for key in keys_1kb.iter() { - db.insert("read_test".to_string(), *key, generate_random_data(1024)) + db.insert("read_test", *key, generate_random_data(1024)) .unwrap(); } read_group.bench_function("1kb".to_string(), |b| { b.iter(|| { - db.get("read_test".to_string(), select_random(keys_1kb.clone())) + db.get("read_test", select_random(keys_1kb.clone())) .unwrap(); }) }); @@ -108,13 +108,13 @@ pub(crate) fn db_benches(c: &mut criterion::Criterion) { .collect::>(); for key in keys_4kb.iter() { - db.insert("read_test".to_string(), *key, generate_random_data(4096)) + db.insert("read_test", *key, generate_random_data(4096)) .unwrap(); } read_group.bench_function("4kb".to_string(), |b| { b.iter(|| { - db.get("read_test".to_string(), select_random(keys_4kb.clone())) + db.get("read_test", select_random(keys_4kb.clone())) .unwrap(); }) }); diff --git a/src/storage/src/lmdb.rs b/src/storage/src/lmdb.rs index 5539f52e..7a3a3ece 100644 --- a/src/storage/src/lmdb.rs +++ b/src/storage/src/lmdb.rs @@ -52,7 +52,7 @@ impl StorageBackend { } } - pub fn insert(&self, table: String, key: u128, value: Vec) -> Result<(), StorageError> { + pub fn insert(&self, table: &str, key: u128, value: Vec) -> Result<(), StorageError> { let env = self.env.lock(); let mut rw_txn = env.write_txn()?; let db: Database, Bytes> = @@ -64,7 +64,7 @@ impl StorageBackend { Ok(()) } - pub fn get(&self, table: String, key: u128) -> Result>, StorageError> { + pub fn get(&self, table: &str, key: u128) -> Result>, StorageError> { let env = self.env.lock(); let ro_txn = env.read_txn()?; let db: Database, Bytes> = env @@ -73,7 +73,7 @@ impl StorageBackend { Ok(db.get(&ro_txn, &key)?.map(|v| v.to_vec())) } - pub fn delete(&self, table: String, key: u128) -> Result<(), StorageError> { + pub fn delete(&self, table: &str, key: u128) -> Result<(), StorageError> { let env = self.env.lock(); let mut rw_txn = env.write_txn()?; let db: Database, Bytes> = env @@ -86,7 +86,7 @@ impl StorageBackend { Ok(()) } - pub fn update(&self, table: String, key: u128, value: Vec) -> Result<(), StorageError> { + pub fn update(&self, table: &str, key: u128, value: Vec) -> Result<(), StorageError> { let env = self.env.lock(); let mut rw_txn = env.write_txn()?; let db: Database, Bytes> = env @@ -100,7 +100,7 @@ impl StorageBackend { Ok(()) } - pub fn upsert(&self, table: String, key: u128, value: Vec) -> Result { + pub fn upsert(&self, table: &str, key: u128, value: Vec) -> Result { let env = self.env.lock(); let mut rw_txn = env.write_txn()?; let db: Database, Bytes> = env @@ -111,7 +111,7 @@ impl StorageBackend { Ok(true) } - pub fn exists(&self, table: String, key: u128) -> Result { + pub fn exists(&self, table: &str, key: u128) -> Result { let env = self.env.lock(); let ro_txn = env.read_txn()?; let db: Database, Bytes> = env @@ -120,7 +120,7 @@ impl StorageBackend { Ok(db.get(&ro_txn, &key)?.is_some()) } - pub fn table_exists(&self, table: String) -> Result { + pub fn table_exists(&self, table: &str) -> Result { let env = self.env.lock(); let ro_txn = env.read_txn()?; let db = env.open_database::, Bytes>(&ro_txn, Some(&table))?; @@ -138,7 +138,7 @@ impl StorageBackend { Ok(()) } - pub fn create_table(&self, table: String) -> Result<(), StorageError> { + pub fn create_table(&self, table: &str) -> Result<(), StorageError> { let env = self.env.lock(); let mut rw_txn = env.write_txn()?; env.create_database::, Bytes>(&mut rw_txn, Some(&table))?; @@ -176,13 +176,11 @@ mod tests { { let backend = StorageBackend::initialize(Some(path.clone()), 10 * 1024 * 1024 * 1024).unwrap(); - backend.create_table("test_table".to_string()).unwrap(); + backend.create_table("test_table").unwrap(); let key = 12345678901234567890u128; let value = vec![1, 2, 3, 4, 5]; - backend - .insert("test_table".to_string(), key, value.clone()) - .unwrap(); - let retrieved_value = backend.get("test_table".to_string(), key).unwrap(); + backend.insert("test_table", key, value.clone()).unwrap(); + let retrieved_value = backend.get("test_table", key).unwrap(); assert_eq!(retrieved_value, Some(value)); } remove_dir_all(path).unwrap(); @@ -194,7 +192,7 @@ mod tests { { let backend = StorageBackend::initialize(Some(path.clone()), 10 * 1024 * 1024 * 1024).unwrap(); - backend.create_table("test_table".to_string()).unwrap(); + backend.create_table("test_table").unwrap(); let mut threads = vec![]; for thread_iter in 0..10 { let handle = std::thread::spawn({ @@ -203,9 +201,7 @@ mod tests { for iter in 0..100 { let key = hash_2_to_u128(iter, thread_iter); let value = vec![rand::random::(); 10]; - backend - .insert("test_table".to_string(), key, value) - .unwrap(); + backend.insert("test_table", key, value).unwrap(); } } }); @@ -224,14 +220,12 @@ mod tests { { let backend = StorageBackend::initialize(Some(path.clone()), 10 * 1024 * 1024 * 1024).unwrap(); - backend.create_table("test_table".to_string()).unwrap(); + backend.create_table("test_table").unwrap(); for thread_iter in 0..10 { for iter in 0..100 { let value = vec![rand::random::(); 10]; let key = hash_2_to_u128(iter, thread_iter); - backend - .insert("test_table".to_string(), key, value) - .unwrap(); + backend.insert("test_table", key, value).unwrap(); } } let mut threads = vec![]; @@ -241,7 +235,7 @@ mod tests { move || { for iter in 0..100 { let key = hash_2_to_u128(iter, thread_iter); - let _ = backend.get("test_table".to_string(), key).unwrap(); + let _ = backend.get("test_table", key).unwrap(); } } }); diff --git a/src/world/db/src/chunks.rs b/src/world/db/src/chunks.rs index 8fce3cde..5b38e60c 100644 --- a/src/world/db/src/chunks.rs +++ b/src/world/db/src/chunks.rs @@ -14,8 +14,8 @@ pub fn save_chunk_internal( dimension: Dimension, chunk: &Chunk, ) -> Result<(), WorldError> { - if !storage.table_exists("chunks".to_string())? { - storage.create_table("chunks".to_string())?; + if !storage.table_exists("chunks")? { + storage.create_table("chunks")?; } let as_bytes = yazi::compress( &bitcode::serialize(chunk).expect("Unable to serialize chunk"), @@ -23,7 +23,7 @@ pub fn save_chunk_internal( CompressionLevel::BestSpeed, )?; let digest = create_key(dimension, pos); - storage.upsert("chunks".to_string(), digest, as_bytes)?; + storage.upsert("chunks", digest, as_bytes)?; Ok(()) } @@ -34,7 +34,7 @@ pub fn load_chunk_internal( verify: bool, ) -> Result { let digest = create_key(dimension, pos); - match storage.get("chunks".to_string(), digest)? { + match storage.get("chunks", digest)? { Some(compressed) => { let (data, checksum) = yazi::decompress(compressed.as_slice(), yazi::Format::Zlib)?; if verify { @@ -60,11 +60,11 @@ pub fn chunk_exists_internal( pos: ChunkPos, dimension: Dimension, ) -> Result { - if !storage.table_exists("chunks".to_string())? { + if !storage.table_exists("chunks")? { return Ok(false); } let digest = create_key(dimension, pos); - Ok(storage.exists("chunks".to_string(), digest)?) + Ok(storage.exists("chunks", digest)?) } pub fn delete_chunk_internal( @@ -73,7 +73,7 @@ pub fn delete_chunk_internal( dimension: Dimension, ) -> Result<(), WorldError> { let digest = create_key(dimension, pos); - storage.delete("chunks".to_string(), digest)?; + storage.delete("chunks", digest)?; Ok(()) } diff --git a/src/world/src/importing.rs b/src/world/src/importing.rs index df563be2..f7c83272 100644 --- a/src/world/src/importing.rs +++ b/src/world/src/importing.rs @@ -54,7 +54,7 @@ impl World { progress.set_message("Setting up database and preparing import..."); - self.storage_backend.create_table("chunks".to_string())?; + self.storage_backend.create_table("chunks")?; let start = std::time::Instant::now(); diff --git a/src/world/src/player.rs b/src/world/src/player.rs index c18eeddc..5913a533 100644 --- a/src/world/src/player.rs +++ b/src/world/src/player.rs @@ -22,10 +22,7 @@ impl World { &self, uuid: uuid::Uuid, ) -> Result, WorldError> { - if !self - .storage_backend - .table_exists("player_data".to_string())? - { + if !self.storage_backend.table_exists("player_data")? { trace!( "Player data table does not exist. Returning None for player {}", uuid @@ -34,7 +31,7 @@ impl World { } let data = self .storage_backend - .get("player_data".to_string(), uuid.as_u128()) + .get("player_data", uuid.as_u128()) .map_err(WorldError::DatabaseError); data.map(|opt_bytes| opt_bytes.and_then(|bytes| bitcode::decode(&bytes).ok())) } @@ -60,20 +57,13 @@ impl World { uuid: uuid::Uuid, data: &T, ) -> Result { - if !self - .storage_backend - .table_exists("player_data".to_string())? - { + if !self.storage_backend.table_exists("player_data")? { self.storage_backend - .create_table("player_data".to_string()) + .create_table("player_data") .map_err(WorldError::DatabaseError)?; } self.storage_backend - .upsert( - "player_data".to_string(), - uuid.as_u128(), - bitcode::encode(data), - ) + .upsert("player_data", uuid.as_u128(), bitcode::encode(data)) .map_err(WorldError::DatabaseError) } } From 50b3a5376b6c42252e86d043b114578a31fa7e9b Mon Sep 17 00:00:00 2001 From: Tonguechaude Date: Wed, 27 May 2026 17:03:09 +0200 Subject: [PATCH 3/9] fix: clippy warning --- src/storage/src/lmdb.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/storage/src/lmdb.rs b/src/storage/src/lmdb.rs index 7a3a3ece..b7a927d8 100644 --- a/src/storage/src/lmdb.rs +++ b/src/storage/src/lmdb.rs @@ -55,8 +55,7 @@ impl StorageBackend { pub fn insert(&self, table: &str, key: u128, value: Vec) -> Result<(), StorageError> { let env = self.env.lock(); let mut rw_txn = env.write_txn()?; - let db: Database, Bytes> = - env.create_database(&mut rw_txn, Some(&table))?; + let db: Database, Bytes> = env.create_database(&mut rw_txn, Some(table))?; if db.get_or_put(&mut rw_txn, &key, &value)?.is_some() { return Err(StorageError::KeyExists(key as u64)); } @@ -68,7 +67,7 @@ impl StorageBackend { let env = self.env.lock(); let ro_txn = env.read_txn()?; let db: Database, Bytes> = env - .open_database(&ro_txn, Some(&table))? + .open_database(&ro_txn, Some(table))? .ok_or(StorageError::TableError("Table not found".to_string()))?; Ok(db.get(&ro_txn, &key)?.map(|v| v.to_vec())) } @@ -77,7 +76,7 @@ impl StorageBackend { let env = self.env.lock(); let mut rw_txn = env.write_txn()?; let db: Database, Bytes> = env - .open_database(&rw_txn, Some(&table))? + .open_database(&rw_txn, Some(table))? .ok_or(StorageError::TableError("Table not found".to_string()))?; if !db.delete(&mut rw_txn, &key)? { return Err(StorageError::KeyNotFound(key as u64)); @@ -90,7 +89,7 @@ impl StorageBackend { let env = self.env.lock(); let mut rw_txn = env.write_txn()?; let db: Database, Bytes> = env - .open_database(&rw_txn, Some(&table))? + .open_database(&rw_txn, Some(table))? .ok_or(StorageError::TableError("Table not found".to_string()))?; if db.get(&rw_txn, &key)?.is_none() { return Err(StorageError::KeyNotFound(key as u64)); @@ -104,7 +103,7 @@ impl StorageBackend { let env = self.env.lock(); let mut rw_txn = env.write_txn()?; let db: Database, Bytes> = env - .open_database(&rw_txn, Some(&table))? + .open_database(&rw_txn, Some(table))? .ok_or(StorageError::TableError("Table not found".to_string()))?; db.put(&mut rw_txn, &key, &value)?; rw_txn.commit()?; @@ -115,7 +114,7 @@ impl StorageBackend { let env = self.env.lock(); let ro_txn = env.read_txn()?; let db: Database, Bytes> = env - .open_database(&ro_txn, Some(&table))? + .open_database(&ro_txn, Some(table))? .ok_or(StorageError::TableError("Table not found".to_string()))?; Ok(db.get(&ro_txn, &key)?.is_some()) } @@ -123,7 +122,7 @@ impl StorageBackend { pub fn table_exists(&self, table: &str) -> Result { let env = self.env.lock(); let ro_txn = env.read_txn()?; - let db = env.open_database::, Bytes>(&ro_txn, Some(&table))?; + let db = env.open_database::, Bytes>(&ro_txn, Some(table))?; Ok(db.is_some()) } @@ -141,7 +140,7 @@ impl StorageBackend { pub fn create_table(&self, table: &str) -> Result<(), StorageError> { let env = self.env.lock(); let mut rw_txn = env.write_txn()?; - env.create_database::, Bytes>(&mut rw_txn, Some(&table))?; + env.create_database::, Bytes>(&mut rw_txn, Some(table))?; rw_txn.commit()?; Ok(()) } From 85a9b3763218391fd3bd842fba89cd02333ff992 Mon Sep 17 00:00:00 2001 From: Tonguechaude Date: Thu, 28 May 2026 16:03:40 +0200 Subject: [PATCH 4/9] feat: remove mutex in lmdb.rs --- src/app/validate/src/check_chunks.rs | 2 +- src/app/validate/src/check_players.rs | 2 +- src/storage/src/lmdb.rs | 65 +++++++++++++-------------- 3 files changed, 34 insertions(+), 35 deletions(-) diff --git a/src/app/validate/src/check_chunks.rs b/src/app/validate/src/check_chunks.rs index 03b01921..8c3f4e06 100644 --- a/src/app/validate/src/check_chunks.rs +++ b/src/app/validate/src/check_chunks.rs @@ -10,7 +10,7 @@ use tracing::{error, warn}; use type_hash::TypeHash; pub fn check_chunks(state: &ServerState) -> Result<(), String> { - let env = state.world.storage_backend.env.lock(); + let env = &state.world.storage_backend.env; let txn = env .read_txn() .map_err(|e| format!("Failed to create read transaction: {}", e))?; diff --git a/src/app/validate/src/check_players.rs b/src/app/validate/src/check_players.rs index 39c220cb..53db71f3 100644 --- a/src/app/validate/src/check_players.rs +++ b/src/app/validate/src/check_players.rs @@ -10,7 +10,7 @@ use type_hash::TypeHash; use uuid::Uuid; pub fn check_players(state: &ServerState) -> Result<(), String> { - let env = state.world.storage_backend.env.lock(); + let env = &state.world.storage_backend.env; let txn = env .read_txn() .map_err(|e| format!("Failed to create read transaction: {}", e))?; diff --git a/src/storage/src/lmdb.rs b/src/storage/src/lmdb.rs index b7a927d8..62e03da3 100644 --- a/src/storage/src/lmdb.rs +++ b/src/storage/src/lmdb.rs @@ -3,13 +3,12 @@ use heed; use heed::byteorder::BigEndian; use heed::types::{Bytes, U128}; use heed::{Database, Env, EnvOpenOptions, WithoutTls}; -use parking_lot::Mutex; use std::path::PathBuf; use std::sync::Arc; #[derive(Debug, Clone)] pub struct StorageBackend { - pub env: Arc>>, + pub env: Arc>, } impl From for StorageError { @@ -38,7 +37,7 @@ impl StorageBackend { * page_size::get() as f64) as usize; unsafe { let backend = StorageBackend { - env: Arc::new(Mutex::new( + env: Arc::new( EnvOpenOptions::new() .read_txn_without_tls() // Change this as more tables are needed. @@ -46,16 +45,16 @@ impl StorageBackend { .map_size(rounded_map_size) .open(checked_path) .map_err(|e| StorageError::DatabaseInitError(e.to_string()))?, - )), + ), }; Ok(backend) } } pub fn insert(&self, table: &str, key: u128, value: Vec) -> Result<(), StorageError> { - let env = self.env.lock(); - let mut rw_txn = env.write_txn()?; - let db: Database, Bytes> = env.create_database(&mut rw_txn, Some(table))?; + let mut rw_txn = self.env.write_txn()?; + let db: Database, Bytes> = + self.env.create_database(&mut rw_txn, Some(table))?; if db.get_or_put(&mut rw_txn, &key, &value)?.is_some() { return Err(StorageError::KeyExists(key as u64)); } @@ -64,18 +63,18 @@ impl StorageBackend { } pub fn get(&self, table: &str, key: u128) -> Result>, StorageError> { - let env = self.env.lock(); - let ro_txn = env.read_txn()?; - let db: Database, Bytes> = env + let ro_txn = self.env.read_txn()?; + let db: Database, Bytes> = self + .env .open_database(&ro_txn, Some(table))? .ok_or(StorageError::TableError("Table not found".to_string()))?; Ok(db.get(&ro_txn, &key)?.map(|v| v.to_vec())) } pub fn delete(&self, table: &str, key: u128) -> Result<(), StorageError> { - let env = self.env.lock(); - let mut rw_txn = env.write_txn()?; - let db: Database, Bytes> = env + let mut rw_txn = self.env.write_txn()?; + let db: Database, Bytes> = self + .env .open_database(&rw_txn, Some(table))? .ok_or(StorageError::TableError("Table not found".to_string()))?; if !db.delete(&mut rw_txn, &key)? { @@ -86,9 +85,9 @@ impl StorageBackend { } pub fn update(&self, table: &str, key: u128, value: Vec) -> Result<(), StorageError> { - let env = self.env.lock(); - let mut rw_txn = env.write_txn()?; - let db: Database, Bytes> = env + let mut rw_txn = self.env.write_txn()?; + let db: Database, Bytes> = self + .env .open_database(&rw_txn, Some(table))? .ok_or(StorageError::TableError("Table not found".to_string()))?; if db.get(&rw_txn, &key)?.is_none() { @@ -100,9 +99,9 @@ impl StorageBackend { } pub fn upsert(&self, table: &str, key: u128, value: Vec) -> Result { - let env = self.env.lock(); - let mut rw_txn = env.write_txn()?; - let db: Database, Bytes> = env + let mut rw_txn = self.env.write_txn()?; + let db: Database, Bytes> = self + .env .open_database(&rw_txn, Some(table))? .ok_or(StorageError::TableError("Table not found".to_string()))?; db.put(&mut rw_txn, &key, &value)?; @@ -111,36 +110,36 @@ impl StorageBackend { } pub fn exists(&self, table: &str, key: u128) -> Result { - let env = self.env.lock(); - let ro_txn = env.read_txn()?; - let db: Database, Bytes> = env + let ro_txn = self.env.read_txn()?; + let db: Database, Bytes> = self + .env .open_database(&ro_txn, Some(table))? .ok_or(StorageError::TableError("Table not found".to_string()))?; Ok(db.get(&ro_txn, &key)?.is_some()) } pub fn table_exists(&self, table: &str) -> Result { - let env = self.env.lock(); - let ro_txn = env.read_txn()?; - let db = env.open_database::, Bytes>(&ro_txn, Some(table))?; + let ro_txn = self.env.read_txn()?; + let db = self + .env + .open_database::, Bytes>(&ro_txn, Some(table))?; Ok(db.is_some()) } pub fn details(&self) -> String { - format!("LMDB (heed 0.20.5): {:?}", self.env.lock().info()) + format!("LMDB (heed 0.20.5): {:?}", self.env.info()) } pub fn flush(&self) -> Result<(), StorageError> { - let env = self.env.lock(); - env.clear_stale_readers()?; - env.force_sync()?; + self.env.clear_stale_readers()?; + self.env.force_sync()?; Ok(()) } pub fn create_table(&self, table: &str) -> Result<(), StorageError> { - let env = self.env.lock(); - let mut rw_txn = env.write_txn()?; - env.create_database::, Bytes>(&mut rw_txn, Some(table))?; + let mut rw_txn = self.env.write_txn()?; + self.env + .create_database::, Bytes>(&mut rw_txn, Some(table))?; rw_txn.commit()?; Ok(()) } @@ -150,7 +149,7 @@ impl StorageBackend { Ok(()) } - pub fn get_env(&self) -> Arc>> { + pub fn get_env(&self) -> Arc> { self.env.clone() } } From 24d4ce7e640e72fd1253b57d49b53e4e2b0bd28b Mon Sep 17 00:00:00 2001 From: Tonguechaude Date: Mon, 1 Jun 2026 16:52:48 +0200 Subject: [PATCH 5/9] fix: add a lock for the first run + refacto --- src/app/runtime/src/setup.rs | 6 ++-- src/storage/Cargo.toml | 1 + src/storage/src/lmdb.rs | 64 ++++++++++++++++++++---------------- 3 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/app/runtime/src/setup.rs b/src/app/runtime/src/setup.rs index f7e82f7c..1ce014fc 100644 --- a/src/app/runtime/src/setup.rs +++ b/src/app/runtime/src/setup.rs @@ -57,15 +57,17 @@ pub fn generate_spawn_chunks(state: GlobalState) -> Result<(), BinaryError> { pub fn setup_db(state: GlobalState) -> Result<(), BinaryError> { info!("Setting up database..."); + state.world.storage_backend.create_table("metadata")?; + let chunk_key = string_to_u128("chunk-format-hash"); - state.world.storage_backend.insert( + state.world.storage_backend.upsert( "metadata", chunk_key, Chunk::type_hash().to_be_bytes().to_vec(), )?; let player_key = string_to_u128("player-format-hash"); - state.world.storage_backend.insert( + state.world.storage_backend.upsert( "metadata", player_key, OfflinePlayerData::type_hash().to_be_bytes().to_vec(), diff --git a/src/storage/Cargo.toml b/src/storage/Cargo.toml index 42066e58..4cd34811 100644 --- a/src/storage/Cargo.toml +++ b/src/storage/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" thiserror = { workspace = true } heed = { workspace = true } page_size = { workspace = true } +dashmap = { workspace = true } parking_lot = { workspace = true } diff --git a/src/storage/src/lmdb.rs b/src/storage/src/lmdb.rs index 62e03da3..432f218d 100644 --- a/src/storage/src/lmdb.rs +++ b/src/storage/src/lmdb.rs @@ -1,14 +1,18 @@ use crate::errors::StorageError; +use dashmap::DashMap; use heed; use heed::byteorder::BigEndian; use heed::types::{Bytes, U128}; use heed::{Database, Env, EnvOpenOptions, WithoutTls}; +use parking_lot::Mutex; use std::path::PathBuf; use std::sync::Arc; #[derive(Debug, Clone)] pub struct StorageBackend { pub env: Arc>, + dbs: Arc, Bytes>>>, + dbi_open_lock: Arc>, } impl From for StorageError { @@ -46,6 +50,8 @@ impl StorageBackend { .open(checked_path) .map_err(|e| StorageError::DatabaseInitError(e.to_string()))?, ), + dbs: Arc::new(DashMap::new()), + dbi_open_lock: Arc::new(parking_lot::Mutex::new(())), }; Ok(backend) } @@ -53,8 +59,7 @@ impl StorageBackend { pub fn insert(&self, table: &str, key: u128, value: Vec) -> Result<(), StorageError> { let mut rw_txn = self.env.write_txn()?; - let db: Database, Bytes> = - self.env.create_database(&mut rw_txn, Some(table))?; + let db: Database, Bytes> = self.get_db(table)?; if db.get_or_put(&mut rw_txn, &key, &value)?.is_some() { return Err(StorageError::KeyExists(key as u64)); } @@ -64,19 +69,13 @@ impl StorageBackend { pub fn get(&self, table: &str, key: u128) -> Result>, StorageError> { let ro_txn = self.env.read_txn()?; - let db: Database, Bytes> = self - .env - .open_database(&ro_txn, Some(table))? - .ok_or(StorageError::TableError("Table not found".to_string()))?; + let db: Database, Bytes> = self.get_db(table)?; Ok(db.get(&ro_txn, &key)?.map(|v| v.to_vec())) } pub fn delete(&self, table: &str, key: u128) -> Result<(), StorageError> { let mut rw_txn = self.env.write_txn()?; - let db: Database, Bytes> = self - .env - .open_database(&rw_txn, Some(table))? - .ok_or(StorageError::TableError("Table not found".to_string()))?; + let db: Database, Bytes> = self.get_db(table)?; if !db.delete(&mut rw_txn, &key)? { return Err(StorageError::KeyNotFound(key as u64)); } @@ -86,10 +85,7 @@ impl StorageBackend { pub fn update(&self, table: &str, key: u128, value: Vec) -> Result<(), StorageError> { let mut rw_txn = self.env.write_txn()?; - let db: Database, Bytes> = self - .env - .open_database(&rw_txn, Some(table))? - .ok_or(StorageError::TableError("Table not found".to_string()))?; + let db: Database, Bytes> = self.get_db(table)?; if db.get(&rw_txn, &key)?.is_none() { return Err(StorageError::KeyNotFound(key as u64)); } @@ -100,10 +96,7 @@ impl StorageBackend { pub fn upsert(&self, table: &str, key: u128, value: Vec) -> Result { let mut rw_txn = self.env.write_txn()?; - let db: Database, Bytes> = self - .env - .open_database(&rw_txn, Some(table))? - .ok_or(StorageError::TableError("Table not found".to_string()))?; + let db: Database, Bytes> = self.get_db(table)?; db.put(&mut rw_txn, &key, &value)?; rw_txn.commit()?; Ok(true) @@ -111,19 +104,12 @@ impl StorageBackend { pub fn exists(&self, table: &str, key: u128) -> Result { let ro_txn = self.env.read_txn()?; - let db: Database, Bytes> = self - .env - .open_database(&ro_txn, Some(table))? - .ok_or(StorageError::TableError("Table not found".to_string()))?; + let db: Database, Bytes> = self.get_db(table)?; Ok(db.get(&ro_txn, &key)?.is_some()) } pub fn table_exists(&self, table: &str) -> Result { - let ro_txn = self.env.read_txn()?; - let db = self - .env - .open_database::, Bytes>(&ro_txn, Some(table))?; - Ok(db.is_some()) + Ok(self.dbs.contains_key(table)) } pub fn details(&self) -> String { @@ -138,9 +124,11 @@ impl StorageBackend { pub fn create_table(&self, table: &str) -> Result<(), StorageError> { let mut rw_txn = self.env.write_txn()?; - self.env + let db = self + .env .create_database::, Bytes>(&mut rw_txn, Some(table))?; rw_txn.commit()?; + self.dbs.insert(table.to_string(), db); Ok(()) } @@ -152,6 +140,26 @@ impl StorageBackend { pub fn get_env(&self) -> Arc> { self.env.clone() } + + fn get_db(&self, table: &str) -> Result, Bytes>, StorageError> { + // Already in cache + if let Some(db) = self.dbs.get(table) { + return Ok(*db); + } + // First time + let _lock = self.dbi_open_lock.lock(); + // Double-check + if let Some(db) = self.dbs.get(table) { + return Ok(*db); + } + let ro_txn = self.env.read_txn()?; + let db = self + .env + .open_database::, Bytes>(&ro_txn, Some(table))? + .ok_or_else(|| StorageError::TableError("Table not found".to_string()))?; + self.dbs.insert(table.to_string(), db); + Ok(db) + } } #[cfg(test)] From 573a03df2778da4f704987072e708f8190d4cdc7 Mon Sep 17 00:00:00 2001 From: Tonguechaude Date: Tue, 2 Jun 2026 07:48:43 +0200 Subject: [PATCH 6/9] fix: table_exist works now --- src/storage/src/lmdb.rs | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/storage/src/lmdb.rs b/src/storage/src/lmdb.rs index 432f218d..82e28c00 100644 --- a/src/storage/src/lmdb.rs +++ b/src/storage/src/lmdb.rs @@ -12,7 +12,7 @@ use std::sync::Arc; pub struct StorageBackend { pub env: Arc>, dbs: Arc, Bytes>>>, - dbi_open_lock: Arc>, + dbi_open_lock: Arc>, } impl From for StorageError { @@ -51,7 +51,7 @@ impl StorageBackend { .map_err(|e| StorageError::DatabaseInitError(e.to_string()))?, ), dbs: Arc::new(DashMap::new()), - dbi_open_lock: Arc::new(parking_lot::Mutex::new(())), + dbi_open_lock: Arc::new(Mutex::new(())), }; Ok(backend) } @@ -109,7 +109,25 @@ impl StorageBackend { } pub fn table_exists(&self, table: &str) -> Result { - Ok(self.dbs.contains_key(table)) + if self.dbs.contains_key(table) { + return Ok(true); + } + let _lock = self.dbi_open_lock.lock(); + if self.dbs.contains_key(table) { + return Ok(true); + } + let rw_txn = self.env.write_txn()?; + match self + .env + .open_database::, Bytes>(&rw_txn, Some(table))? + { + Some(db) => { + rw_txn.commit()?; + self.dbs.insert(table.to_string(), db); + Ok(true) + } + None => Ok(false), + } } pub fn details(&self) -> String { @@ -152,11 +170,12 @@ impl StorageBackend { if let Some(db) = self.dbs.get(table) { return Ok(*db); } - let ro_txn = self.env.read_txn()?; + let rw_txn = self.env.write_txn()?; let db = self .env - .open_database::, Bytes>(&ro_txn, Some(table))? + .open_database::, Bytes>(&rw_txn, Some(table))? .ok_or_else(|| StorageError::TableError("Table not found".to_string()))?; + rw_txn.commit()?; self.dbs.insert(table.to_string(), db); Ok(db) } From a7db592217aeeef34802961df706322ac4d28baf Mon Sep 17 00:00:00 2001 From: Tonguechaude Date: Tue, 2 Jun 2026 08:33:19 +0200 Subject: [PATCH 7/9] fix: details heed version --- src/storage/src/lmdb.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/storage/src/lmdb.rs b/src/storage/src/lmdb.rs index 82e28c00..b172dac1 100644 --- a/src/storage/src/lmdb.rs +++ b/src/storage/src/lmdb.rs @@ -131,7 +131,7 @@ impl StorageBackend { } pub fn details(&self) -> String { - format!("LMDB (heed 0.20.5): {:?}", self.env.info()) + format!("LMDB (heed 0.22.1): {:?}", self.env.info()) } pub fn flush(&self) -> Result<(), StorageError> { From f364587c92c64db1d4bd8ac152016afdb65c8d25 Mon Sep 17 00:00:00 2001 From: Tonguechaude Date: Tue, 2 Jun 2026 09:15:06 +0200 Subject: [PATCH 8/9] fix: benchmark + add a concurrent group --- src/storage/src/benches/db.rs | 88 ++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 32 deletions(-) diff --git a/src/storage/src/benches/db.rs b/src/storage/src/benches/db.rs index 7cf5e525..3523cd72 100644 --- a/src/storage/src/benches/db.rs +++ b/src/storage/src/benches/db.rs @@ -1,4 +1,5 @@ use std::collections::HashSet; +use std::sync::Arc; use temper_storage::lmdb::StorageBackend; fn generate_random_data(size: usize) -> Vec { @@ -17,9 +18,9 @@ fn generate_random_key(used: &mut HashSet) -> u128 { key } -fn select_random(choices: Vec) -> T { +fn select_random(choices: &[T]) -> T { let index = rand::random::() as usize % choices.len(); - *choices.get(index).unwrap() + choices[index] } pub(crate) fn db_benches(c: &mut criterion::Criterion) { @@ -32,7 +33,7 @@ pub(crate) fn db_benches(c: &mut criterion::Criterion) { let mut insert_group = c.benchmark_group("Insert"); - insert_group.bench_function("512b".to_string(), |b| { + insert_group.bench_function("512b", |b| { b.iter(|| { db.insert( "insert_test", @@ -43,7 +44,7 @@ pub(crate) fn db_benches(c: &mut criterion::Criterion) { }) }); - insert_group.bench_function("1kb".to_string(), |b| { + insert_group.bench_function("1kb", |b| { b.iter(|| { db.insert( "insert_test", @@ -54,7 +55,7 @@ pub(crate) fn db_benches(c: &mut criterion::Criterion) { }) }); - insert_group.bench_function("4kb".to_string(), |b| { + insert_group.bench_function("4kb", |b| { b.iter(|| { db.insert( "insert_test", @@ -71,53 +72,76 @@ pub(crate) fn db_benches(c: &mut criterion::Criterion) { db.create_table("read_test").unwrap(); - let keys_512b = (0..1000) + let keys_512b: Vec = (0..1000) .map(|_| generate_random_key(&mut used_keys)) - .collect::>(); - + .collect(); for key in keys_512b.iter() { db.insert("read_test", *key, generate_random_data(512)) .unwrap(); } - - read_group.bench_function("512b".to_string(), |b| { - b.iter(|| { - db.get("read_test", select_random(keys_512b.clone())) - .unwrap(); - }) + read_group.bench_function("512b", |b| { + b.iter(|| db.get("read_test", select_random(&keys_512b)).unwrap()) }); - let keys_1kb = (0..1000) + let keys_1kb: Vec = (0..1000) .map(|_| generate_random_key(&mut used_keys)) - .collect::>(); - + .collect(); for key in keys_1kb.iter() { db.insert("read_test", *key, generate_random_data(1024)) .unwrap(); } - - read_group.bench_function("1kb".to_string(), |b| { - b.iter(|| { - db.get("read_test", select_random(keys_1kb.clone())) - .unwrap(); - }) + read_group.bench_function("1kb", |b| { + b.iter(|| db.get("read_test", select_random(&keys_1kb)).unwrap()) }); - let keys_4kb = (0..1000) + let keys_4kb: Vec = (0..1000) .map(|_| generate_random_key(&mut used_keys)) - .collect::>(); - + .collect(); for key in keys_4kb.iter() { db.insert("read_test", *key, generate_random_data(4096)) .unwrap(); } - - read_group.bench_function("4kb".to_string(), |b| { - b.iter(|| { - db.get("read_test", select_random(keys_4kb.clone())) - .unwrap(); - }) + read_group.bench_function("4kb", |b| { + b.iter(|| db.get("read_test", select_random(&keys_4kb)).unwrap()) }); read_group.finish(); + + let mut concurrent_group = c.benchmark_group("ConcurrentRead"); + + db.create_table("concurrent_test").unwrap(); + + let concurrent_keys: Vec = (0..1000) + .map(|_| generate_random_key(&mut used_keys)) + .collect(); + for key in concurrent_keys.iter() { + db.insert("concurrent_test", *key, generate_random_data(512)) + .unwrap(); + } + + let db = Arc::new(db); + + for thread_count in [2usize, 4, 8, 16] { + let db = Arc::clone(&db); + let keys = concurrent_keys.clone(); + + concurrent_group.bench_function(format!("{thread_count}_threads_512b"), |b| { + b.iter(|| { + let handles: Vec<_> = (0..thread_count) + .map(|_| { + let db = Arc::clone(&db); + let keys = keys.clone(); + std::thread::spawn(move || { + db.get("concurrent_test", select_random(&keys)).unwrap(); + }) + }) + .collect(); + for h in handles { + h.join().unwrap(); + } + }) + }); + } + + concurrent_group.finish(); } From 01a9e81b3885e9102da14bdabc7d7f97956134b8 Mon Sep 17 00:00:00 2001 From: Tonguechaude Date: Tue, 2 Jun 2026 15:43:34 +0200 Subject: [PATCH 9/9] fix: benchmark now use rayon to avoid spawning 8 thread every run --- src/storage/Cargo.toml | 1 + src/storage/src/benches/db.rs | 28 +++++++++++++++------------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/storage/Cargo.toml b/src/storage/Cargo.toml index 4cd34811..ced5f659 100644 --- a/src/storage/Cargo.toml +++ b/src/storage/Cargo.toml @@ -17,6 +17,7 @@ criterion = { workspace = true } tempfile = { workspace = true } wyhash = { workspace = true } rand = { workspace = true } +rayon = { workspace = true } [[bench]] name = "storage_bench" diff --git a/src/storage/src/benches/db.rs b/src/storage/src/benches/db.rs index 3523cd72..938251ff 100644 --- a/src/storage/src/benches/db.rs +++ b/src/storage/src/benches/db.rs @@ -107,8 +107,6 @@ pub(crate) fn db_benches(c: &mut criterion::Criterion) { read_group.finish(); - let mut concurrent_group = c.benchmark_group("ConcurrentRead"); - db.create_table("concurrent_test").unwrap(); let concurrent_keys: Vec = (0..1000) @@ -121,27 +119,31 @@ pub(crate) fn db_benches(c: &mut criterion::Criterion) { let db = Arc::new(db); + let mut pool_group = c.benchmark_group("ConcurrentReadPool"); + for thread_count in [2usize, 4, 8, 16] { let db = Arc::clone(&db); let keys = concurrent_keys.clone(); - concurrent_group.bench_function(format!("{thread_count}_threads_512b"), |b| { + let pool = rayon::ThreadPoolBuilder::new() + .num_threads(thread_count) + .build() + .unwrap(); + + pool_group.bench_function(format!("{thread_count}_threads_512b"), |b| { b.iter(|| { - let handles: Vec<_> = (0..thread_count) - .map(|_| { + pool.scope(|s| { + for _ in 0..thread_count { let db = Arc::clone(&db); let keys = keys.clone(); - std::thread::spawn(move || { + s.spawn(move |_| { db.get("concurrent_test", select_random(&keys)).unwrap(); - }) - }) - .collect(); - for h in handles { - h.join().unwrap(); - } + }); + } + }); }) }); } - concurrent_group.finish(); + pool_group.finish(); }