diff --git a/crates/storage/db/src/implementation/mdbx/cursor.rs b/crates/storage/db/src/implementation/mdbx/cursor.rs index a8f71b40c78..cd642980576 100644 --- a/crates/storage/db/src/implementation/mdbx/cursor.rs +++ b/crates/storage/db/src/implementation/mdbx/cursor.rs @@ -2,7 +2,7 @@ use super::utils::*; use crate::{ - metrics::{DatabaseEnvMetrics, Operation}, + metrics::{Operation, TableOperationMetrics}, DatabaseError, }; use reth_db_api::{ @@ -15,7 +15,7 @@ use reth_db_api::{ }; use reth_libmdbx::{Error as MDBXError, TransactionKind, WriteFlags, RO, RW}; use reth_storage_errors::db::{DatabaseErrorInfo, DatabaseWriteError, DatabaseWriteOperation}; -use std::{borrow::Cow, collections::Bound, marker::PhantomData, ops::RangeBounds, sync::Arc}; +use std::{borrow::Cow, collections::Bound, marker::PhantomData, ops::RangeBounds}; /// Read only Cursor. pub type CursorRO = Cursor; @@ -29,8 +29,8 @@ pub struct Cursor { pub(crate) inner: reth_libmdbx::Cursor, /// Cache buffer that receives compressed values. buf: Vec, - /// Reference to metric handles in the DB environment. If `None`, metrics are not recorded. - metrics: Option>, + /// Per-table operation metrics. If `None`, metrics are not recorded. + metrics: Option, /// Phantom data to enforce encoding/decoding. _dbi: PhantomData, } @@ -38,7 +38,7 @@ pub struct Cursor { impl Cursor { pub(crate) const fn new_with_metrics( inner: reth_libmdbx::Cursor, - metrics: Option>, + metrics: Option, ) -> Self { Self { inner, buf: Vec::new(), metrics, _dbi: PhantomData } } @@ -54,7 +54,7 @@ impl Cursor { f: impl FnOnce(&mut Self) -> R, ) -> R { if let Some(metrics) = self.metrics.clone() { - metrics.record_operation(T::NAME, operation, value_size, || f(self)) + metrics[operation.index()].record(value_size, || f(self)) } else { f(self) } diff --git a/crates/storage/db/src/implementation/mdbx/tx.rs b/crates/storage/db/src/implementation/mdbx/tx.rs index 166cfeaa43c..10b4ae3039c 100644 --- a/crates/storage/db/src/implementation/mdbx/tx.rs +++ b/crates/storage/db/src/implementation/mdbx/tx.rs @@ -100,7 +100,7 @@ impl Tx { Ok(Cursor::new_with_metrics( inner, - self.metrics_handler.as_ref().map(|h| h.env_metrics.clone()), + self.metrics_handler.as_ref().map(|h| h.env_metrics.table_operation_metrics(T::NAME)), )) } diff --git a/crates/storage/db/src/metrics.rs b/crates/storage/db/src/metrics.rs index 1b41ce52e72..84f7b52d518 100644 --- a/crates/storage/db/src/metrics.rs +++ b/crates/storage/db/src/metrics.rs @@ -3,7 +3,7 @@ use metrics::Histogram; use quanta::Instant; use reth_metrics::{metrics::Counter, Metrics}; use rustc_hash::FxHashMap; -use std::time::Duration; +use std::{array, sync::Arc, time::Duration}; use strum::{EnumCount, EnumIter, IntoEnumIterator}; const LARGE_VALUE_THRESHOLD_BYTES: usize = 4096; @@ -15,8 +15,8 @@ const LARGE_VALUE_THRESHOLD_BYTES: usize = 4096; /// Otherwise, metric recording will no-op. #[derive(Debug)] pub(crate) struct DatabaseEnvMetrics { - /// Caches `OperationMetrics` handles for each table and operation tuple. - operations: FxHashMap<(&'static str, Operation), OperationMetrics>, + /// Caches per-table operation metric handles for all database operation metrics. + operations: FxHashMap<&'static str, TableOperationMetrics>, /// Caches `TransactionMetrics` handles for counters grouped by only transaction mode. /// Updated both at tx open and close. transactions: FxHashMap, @@ -26,6 +26,9 @@ pub(crate) struct DatabaseEnvMetrics { FxHashMap<(TransactionMode, TransactionOutcome), TransactionOutcomeMetrics>, } +/// Per-table operation metric handles cached for hot cursor paths. +pub(crate) type TableOperationMetrics = Arc<[OperationMetrics; Operation::COUNT]>; + impl DatabaseEnvMetrics { pub(crate) fn new() -> Self { // Pre-populate metric handle maps with all possible combinations of labels @@ -37,24 +40,23 @@ impl DatabaseEnvMetrics { } } - /// Generate a map of all possible operation handles for each table and operation tuple. - /// Used for tracking all operation metrics. - fn generate_operation_handles() -> FxHashMap<(&'static str, Operation), OperationMetrics> { - let mut operations = FxHashMap::with_capacity_and_hasher( - Tables::COUNT * Operation::COUNT, - Default::default(), - ); + /// Generate a map of pre-bound operation handles for each table. + fn generate_operation_handles() -> FxHashMap<&'static str, TableOperationMetrics> { + let mut operations = FxHashMap::with_capacity_and_hasher(Tables::COUNT, Default::default()); + for table in Tables::ALL { - for operation in Operation::iter() { - operations.insert( - (table.name(), operation), - OperationMetrics::new_with_labels(&[ - (Labels::Table.as_str(), table.name()), - (Labels::Operation.as_str(), operation.as_str()), - ]), - ); - } + let table_name = table.name(); + let metrics = array::from_fn(|index| { + let operation = Operation::from_index(index); + OperationMetrics::new_with_labels(&[ + (Labels::Table.as_str(), table_name), + (Labels::Operation.as_str(), operation.as_str()), + ]) + }); + + operations.insert(table_name, Arc::new(metrics)); } + operations } @@ -105,13 +107,18 @@ impl DatabaseEnvMetrics { value_size: Option, f: impl FnOnce() -> R, ) -> R { - if let Some(metrics) = self.operations.get(&(table, operation)) { - metrics.record(value_size, f) + if let Some(metrics) = self.operations.get(table) { + metrics[operation.index()].record(value_size, f) } else { f() } } + /// Returns pre-bound operation metric handles for a single table. + pub(crate) fn table_operation_metrics(&self, table: &'static str) -> TableOperationMetrics { + self.operations.get(table).expect("table operation metric handles not found").clone() + } + /// Record metrics for opening a database transaction. pub(crate) fn record_opened_transaction(&self, mode: TransactionMode) { self.transactions @@ -219,6 +226,39 @@ pub(crate) enum Operation { } impl Operation { + /// Returns the index of the operation in the cached per-table operation array. + pub(crate) const fn index(&self) -> usize { + match self { + Self::Get => 0, + Self::PutUpsert => 1, + Self::PutAppend => 2, + Self::Delete => 3, + Self::CursorUpsert => 4, + Self::CursorInsert => 5, + Self::CursorAppend => 6, + Self::CursorAppendDup => 7, + Self::CursorDeleteCurrent => 8, + Self::CursorDeleteCurrentDuplicates => 9, + } + } + + /// Returns the operation for the given index in the cached per-table operation array. + const fn from_index(index: usize) -> Self { + match index { + 0 => Self::Get, + 1 => Self::PutUpsert, + 2 => Self::PutAppend, + 3 => Self::Delete, + 4 => Self::CursorUpsert, + 5 => Self::CursorInsert, + 6 => Self::CursorAppend, + 7 => Self::CursorAppendDup, + 8 => Self::CursorDeleteCurrent, + 9 => Self::CursorDeleteCurrentDuplicates, + _ => panic!("invalid operation index"), + } + } + /// Returns the operation as a string. pub(crate) const fn as_str(&self) -> &'static str { match self {