diff --git a/Cargo.lock b/Cargo.lock index 8d64b1cbc05..ba5ce775365 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2746,6 +2746,7 @@ version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ + "arbitrary", "cfg-if", "crossbeam-utils", "hashbrown 0.14.5", @@ -7607,6 +7608,7 @@ dependencies = [ "arbitrary", "assert_matches", "codspeed-criterion-compat", + "dashmap 6.1.0", "derive_more", "eyre", "metrics", diff --git a/crates/storage/db/Cargo.toml b/crates/storage/db/Cargo.toml index 5e2c2f31b9b..264a1f1f628 100644 --- a/crates/storage/db/Cargo.toml +++ b/crates/storage/db/Cargo.toml @@ -39,6 +39,7 @@ derive_more.workspace = true rustc-hash = { workspace = true, optional = true, features = ["std"] } sysinfo = { workspace = true, features = ["system"] } parking_lot = { workspace = true, optional = true } +dashmap.workspace = true # arbitrary utils strum = { workspace = true, features = ["derive"], optional = true } @@ -91,6 +92,7 @@ arbitrary = [ "alloy-consensus/arbitrary", "reth-primitives-traits/arbitrary", "reth-prune-types/arbitrary", + "dashmap/arbitrary", ] op = [ "reth-db-api/op", diff --git a/crates/storage/db/src/implementation/mdbx/tx.rs b/crates/storage/db/src/implementation/mdbx/tx.rs index d2b20f5ae38..04aa4f8f85c 100644 --- a/crates/storage/db/src/implementation/mdbx/tx.rs +++ b/crates/storage/db/src/implementation/mdbx/tx.rs @@ -5,6 +5,7 @@ use crate::{ metrics::{DatabaseEnvMetrics, Operation, TransactionMode, TransactionOutcome}, DatabaseError, }; +use dashmap::DashMap; use reth_db_api::{ table::{Compress, DupSort, Encode, Table, TableImporter}, transaction::{DbTx, DbTxMut}, @@ -31,6 +32,10 @@ pub struct Tx { /// Libmdbx-sys transaction. pub inner: Transaction, + /// Cached MDBX DBIs for reuse. + /// TODO: Reuse DBIs even among transactions, ideally with no synchronization overhead. + dbis: DashMap<&'static str, MDBX_dbi>, + /// Handler for metrics with its own [Drop] implementation for cases when the transaction isn't /// closed by [`Tx::commit`] or [`Tx::abort`], but we still need to report it in the metrics. /// @@ -41,7 +46,7 @@ pub struct Tx { impl Tx { /// Creates new `Tx` object with a `RO` or `RW` transaction. #[inline] - pub const fn new(inner: Transaction) -> Self { + pub fn new(inner: Transaction) -> Self { Self::new_inner(inner, None) } @@ -64,8 +69,8 @@ impl Tx { } #[inline] - const fn new_inner(inner: Transaction, metrics_handler: Option>) -> Self { - Self { inner, metrics_handler } + fn new_inner(inner: Transaction, metrics_handler: Option>) -> Self { + Self { inner, metrics_handler, dbis: DashMap::new() } } /// Gets this transaction ID. @@ -75,10 +80,18 @@ impl Tx { /// Gets a table database handle if it exists, otherwise creates it. pub fn get_dbi(&self) -> Result { - self.inner - .open_db(Some(T::NAME)) - .map(|db| db.dbi()) - .map_err(|e| DatabaseError::Open(e.into())) + match self.dbis.entry(T::NAME) { + dashmap::Entry::Occupied(occ) => Ok(*occ.get()), + dashmap::Entry::Vacant(vac) => { + let dbi = self + .inner + .open_db(Some(T::NAME)) + .map(|db| db.dbi()) + .map_err(|e| DatabaseError::Open(e.into()))?; + vac.insert(dbi); + Ok(dbi) + } + } } /// Create db Cursor