From 26145ef8d9cc6398f824fab9ddbd1ee1588e0490 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Mon, 20 Apr 2026 13:53:53 +0000 Subject: [PATCH 1/7] perf(db): prebind cursor operation metrics Amp-Thread-ID: https://ampcode.com/threads/T-019dab1a-a4a3-734a-9122-f91ce331ff8d Co-authored-by: Amp --- .../db/src/implementation/mdbx/cursor.rs | 16 +++-- .../storage/db/src/implementation/mdbx/tx.rs | 4 +- crates/storage/db/src/metrics.rs | 65 +++++++++++++++++++ 3 files changed, 77 insertions(+), 8 deletions(-) diff --git a/crates/storage/db/src/implementation/mdbx/cursor.rs b/crates/storage/db/src/implementation/mdbx/cursor.rs index a8f71b40c78..b409d33a035 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 } } @@ -53,8 +53,10 @@ impl Cursor { value_size: Option, f: impl FnOnce(&mut Self) -> R, ) -> R { - if let Some(metrics) = self.metrics.clone() { - metrics.record_operation(T::NAME, operation, value_size, || f(self)) + if let Some(metrics) = + self.metrics.as_ref().map(|metrics| metrics.operation_metric(operation)) + { + metrics.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..0354f519869 100644 --- a/crates/storage/db/src/implementation/mdbx/tx.rs +++ b/crates/storage/db/src/implementation/mdbx/tx.rs @@ -100,7 +100,9 @@ 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..c9e56ab0eed 100644 --- a/crates/storage/db/src/metrics.rs +++ b/crates/storage/db/src/metrics.rs @@ -26,6 +26,66 @@ pub(crate) struct DatabaseEnvMetrics { FxHashMap<(TransactionMode, TransactionOutcome), TransactionOutcomeMetrics>, } +/// Per-table operation metric handles cached for hot cursor paths. +#[derive(Debug, Clone)] +pub(crate) struct TableOperationMetrics { + get: OperationMetrics, + put_upsert: OperationMetrics, + put_append: OperationMetrics, + delete: OperationMetrics, + cursor_upsert: OperationMetrics, + cursor_insert: OperationMetrics, + cursor_append: OperationMetrics, + cursor_append_dup: OperationMetrics, + cursor_delete_current: OperationMetrics, + cursor_delete_current_duplicates: OperationMetrics, +} + +impl TableOperationMetrics { + fn new(env_metrics: &DatabaseEnvMetrics, table: &'static str) -> Self { + let metric = |operation| { + env_metrics + .operations + .get(&(table, operation)) + .expect("operation metric handle not found") + .clone() + }; + + Self { + get: metric(Operation::Get), + put_upsert: metric(Operation::PutUpsert), + put_append: metric(Operation::PutAppend), + delete: metric(Operation::Delete), + cursor_upsert: metric(Operation::CursorUpsert), + cursor_insert: metric(Operation::CursorInsert), + cursor_append: metric(Operation::CursorAppend), + cursor_append_dup: metric(Operation::CursorAppendDup), + cursor_delete_current: metric(Operation::CursorDeleteCurrent), + cursor_delete_current_duplicates: metric(Operation::CursorDeleteCurrentDuplicates), + } + } + + const fn metric(&self, operation: Operation) -> &OperationMetrics { + match operation { + Operation::Get => &self.get, + Operation::PutUpsert => &self.put_upsert, + Operation::PutAppend => &self.put_append, + Operation::Delete => &self.delete, + Operation::CursorUpsert => &self.cursor_upsert, + Operation::CursorInsert => &self.cursor_insert, + Operation::CursorAppend => &self.cursor_append, + Operation::CursorAppendDup => &self.cursor_append_dup, + Operation::CursorDeleteCurrent => &self.cursor_delete_current, + Operation::CursorDeleteCurrentDuplicates => &self.cursor_delete_current_duplicates, + } + } + + /// Returns the cached metric handle for the given operation. + pub(crate) fn operation_metric(&self, operation: Operation) -> OperationMetrics { + self.metric(operation).clone() + } +} + impl DatabaseEnvMetrics { pub(crate) fn new() -> Self { // Pre-populate metric handle maps with all possible combinations of labels @@ -112,6 +172,11 @@ impl DatabaseEnvMetrics { } } + /// Returns pre-bound operation metric handles for a single table. + pub(crate) fn table_operation_metrics(&self, table: &'static str) -> TableOperationMetrics { + TableOperationMetrics::new(self, table) + } + /// Record metrics for opening a database transaction. pub(crate) fn record_opened_transaction(&self, mode: TransactionMode) { self.transactions From 6e3b3793714e1d9a42f7101ab1391807a52a38c6 Mon Sep 17 00:00:00 2001 From: Brian Picciano <933154+mediocregopher@users.noreply.github.com> Date: Mon, 27 Apr 2026 13:33:42 +0000 Subject: [PATCH 2/7] style(db): format mdbx tx metrics wiring Co-authored-by: Brian Picciano <933154+mediocregopher@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019dce13-c0cb-7700-8208-d890370e38ed Co-authored-by: Amp --- crates/storage/db/src/implementation/mdbx/tx.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/storage/db/src/implementation/mdbx/tx.rs b/crates/storage/db/src/implementation/mdbx/tx.rs index 0354f519869..10b4ae3039c 100644 --- a/crates/storage/db/src/implementation/mdbx/tx.rs +++ b/crates/storage/db/src/implementation/mdbx/tx.rs @@ -100,9 +100,7 @@ impl Tx { Ok(Cursor::new_with_metrics( inner, - self.metrics_handler - .as_ref() - .map(|h| h.env_metrics.table_operation_metrics(T::NAME)), + self.metrics_handler.as_ref().map(|h| h.env_metrics.table_operation_metrics(T::NAME)), )) } From 72ea6b0eb89a164c9a36d0ed4bfacbfd8b0d0971 Mon Sep 17 00:00:00 2001 From: Brian Picciano <933154+mediocregopher@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:37:46 +0000 Subject: [PATCH 3/7] refactor(db): use Arc table metric cache Co-Authored-By: Brian Picciano <933154+mediocregopher@users.noreply.github.com> --- .../db/src/implementation/mdbx/cursor.rs | 2 +- crates/storage/db/src/metrics.rs | 125 +++++++++--------- 2 files changed, 65 insertions(+), 62 deletions(-) diff --git a/crates/storage/db/src/implementation/mdbx/cursor.rs b/crates/storage/db/src/implementation/mdbx/cursor.rs index b409d33a035..ffea4ab549d 100644 --- a/crates/storage/db/src/implementation/mdbx/cursor.rs +++ b/crates/storage/db/src/implementation/mdbx/cursor.rs @@ -54,7 +54,7 @@ impl Cursor { f: impl FnOnce(&mut Self) -> R, ) -> R { if let Some(metrics) = - self.metrics.as_ref().map(|metrics| metrics.operation_metric(operation)) + self.metrics.as_ref().map(|metrics| metrics[operation.index()].clone()) { metrics.record(value_size, || f(self)) } else { diff --git a/crates/storage/db/src/metrics.rs b/crates/storage/db/src/metrics.rs index c9e56ab0eed..ed5930ec0e8 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; @@ -17,6 +17,8 @@ const LARGE_VALUE_THRESHOLD_BYTES: usize = 4096; 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 hot cursor paths. + table_operations: FxHashMap<&'static str, TableOperationMetrics>, /// Caches `TransactionMetrics` handles for counters grouped by only transaction mode. /// Updated both at tx open and close. transactions: FxHashMap, @@ -27,71 +29,16 @@ pub(crate) struct DatabaseEnvMetrics { } /// Per-table operation metric handles cached for hot cursor paths. -#[derive(Debug, Clone)] -pub(crate) struct TableOperationMetrics { - get: OperationMetrics, - put_upsert: OperationMetrics, - put_append: OperationMetrics, - delete: OperationMetrics, - cursor_upsert: OperationMetrics, - cursor_insert: OperationMetrics, - cursor_append: OperationMetrics, - cursor_append_dup: OperationMetrics, - cursor_delete_current: OperationMetrics, - cursor_delete_current_duplicates: OperationMetrics, -} - -impl TableOperationMetrics { - fn new(env_metrics: &DatabaseEnvMetrics, table: &'static str) -> Self { - let metric = |operation| { - env_metrics - .operations - .get(&(table, operation)) - .expect("operation metric handle not found") - .clone() - }; - - Self { - get: metric(Operation::Get), - put_upsert: metric(Operation::PutUpsert), - put_append: metric(Operation::PutAppend), - delete: metric(Operation::Delete), - cursor_upsert: metric(Operation::CursorUpsert), - cursor_insert: metric(Operation::CursorInsert), - cursor_append: metric(Operation::CursorAppend), - cursor_append_dup: metric(Operation::CursorAppendDup), - cursor_delete_current: metric(Operation::CursorDeleteCurrent), - cursor_delete_current_duplicates: metric(Operation::CursorDeleteCurrentDuplicates), - } - } - - const fn metric(&self, operation: Operation) -> &OperationMetrics { - match operation { - Operation::Get => &self.get, - Operation::PutUpsert => &self.put_upsert, - Operation::PutAppend => &self.put_append, - Operation::Delete => &self.delete, - Operation::CursorUpsert => &self.cursor_upsert, - Operation::CursorInsert => &self.cursor_insert, - Operation::CursorAppend => &self.cursor_append, - Operation::CursorAppendDup => &self.cursor_append_dup, - Operation::CursorDeleteCurrent => &self.cursor_delete_current, - Operation::CursorDeleteCurrentDuplicates => &self.cursor_delete_current_duplicates, - } - } - - /// Returns the cached metric handle for the given operation. - pub(crate) fn operation_metric(&self, operation: Operation) -> OperationMetrics { - self.metric(operation).clone() - } -} +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 // to avoid runtime locks on the map when recording metrics. + let operations = Self::generate_operation_handles(); Self { - operations: Self::generate_operation_handles(), + table_operations: Self::generate_table_operation_handles(&operations), + operations, transactions: Self::generate_transaction_handles(), transaction_outcomes: Self::generate_transaction_outcome_handles(), } @@ -118,6 +65,29 @@ impl DatabaseEnvMetrics { operations } + /// Generate a map of pre-bound operation handles for each table. + fn generate_table_operation_handles( + operations: &FxHashMap<(&'static str, Operation), OperationMetrics>, + ) -> FxHashMap<&'static str, TableOperationMetrics> { + let mut table_operations = + FxHashMap::with_capacity_and_hasher(Tables::COUNT, Default::default()); + + for table in Tables::ALL { + let table_name = table.name(); + let metrics = array::from_fn(|index| { + let operation = Operation::from_index(index); + operations + .get(&(table_name, operation)) + .expect("operation metric handle not found") + .clone() + }); + + table_operations.insert(table_name, Arc::new(metrics)); + } + + table_operations + } + /// Generate a map of all possible transaction modes to metric handles. /// Used for tracking a counter of open transactions. fn generate_transaction_handles() -> FxHashMap { @@ -174,7 +144,7 @@ impl DatabaseEnvMetrics { /// Returns pre-bound operation metric handles for a single table. pub(crate) fn table_operation_metrics(&self, table: &'static str) -> TableOperationMetrics { - TableOperationMetrics::new(self, table) + self.table_operations.get(table).expect("table operation metric handles not found").clone() } /// Record metrics for opening a database transaction. @@ -284,6 +254,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 { From df5ad778c982b382ed77a5fd40fca31d77b5345b Mon Sep 17 00:00:00 2001 From: Brian Picciano <933154+mediocregopher@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:40:03 +0000 Subject: [PATCH 4/7] perf(db): reuse Arc cursor metric cache Co-authored-by: Brian Picciano <933154+mediocregopher@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019dd44d-3253-710b-bc57-9795eb2dfb6a Co-authored-by: Amp --- crates/storage/db/src/implementation/mdbx/cursor.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/storage/db/src/implementation/mdbx/cursor.rs b/crates/storage/db/src/implementation/mdbx/cursor.rs index ffea4ab549d..cd642980576 100644 --- a/crates/storage/db/src/implementation/mdbx/cursor.rs +++ b/crates/storage/db/src/implementation/mdbx/cursor.rs @@ -53,10 +53,8 @@ impl Cursor { value_size: Option, f: impl FnOnce(&mut Self) -> R, ) -> R { - if let Some(metrics) = - self.metrics.as_ref().map(|metrics| metrics[operation.index()].clone()) - { - metrics.record(value_size, || f(self)) + if let Some(metrics) = self.metrics.clone() { + metrics[operation.index()].record(value_size, || f(self)) } else { f(self) } From c5dfa557847bbe294123557dd2f537e034bbbde6 Mon Sep 17 00:00:00 2001 From: Brian Picciano <933154+mediocregopher@users.noreply.github.com> Date: Tue, 28 Apr 2026 14:49:19 +0000 Subject: [PATCH 5/7] refactor(db): collapse operation metric caches Co-authored-by: Brian Picciano <933154+mediocregopher@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019dd44d-3253-710b-bc57-9795eb2dfb6a Co-authored-by: Amp --- crates/storage/db/src/metrics.rs | 56 ++++++++------------------------ 1 file changed, 14 insertions(+), 42 deletions(-) diff --git a/crates/storage/db/src/metrics.rs b/crates/storage/db/src/metrics.rs index ed5930ec0e8..84f7b52d518 100644 --- a/crates/storage/db/src/metrics.rs +++ b/crates/storage/db/src/metrics.rs @@ -15,10 +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 hot cursor paths. - table_operations: FxHashMap<&'static str, TableOperationMetrics>, + /// 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, @@ -35,57 +33,31 @@ impl DatabaseEnvMetrics { pub(crate) fn new() -> Self { // Pre-populate metric handle maps with all possible combinations of labels // to avoid runtime locks on the map when recording metrics. - let operations = Self::generate_operation_handles(); Self { - table_operations: Self::generate_table_operation_handles(&operations), - operations, + operations: Self::generate_operation_handles(), transactions: Self::generate_transaction_handles(), transaction_outcomes: Self::generate_transaction_outcome_handles(), } } - /// 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(), - ); - 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()), - ]), - ); - } - } - operations - } - /// Generate a map of pre-bound operation handles for each table. - fn generate_table_operation_handles( - operations: &FxHashMap<(&'static str, Operation), OperationMetrics>, - ) -> FxHashMap<&'static str, TableOperationMetrics> { - let mut table_operations = - FxHashMap::with_capacity_and_hasher(Tables::COUNT, Default::default()); + 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 { let table_name = table.name(); let metrics = array::from_fn(|index| { let operation = Operation::from_index(index); - operations - .get(&(table_name, operation)) - .expect("operation metric handle not found") - .clone() + OperationMetrics::new_with_labels(&[ + (Labels::Table.as_str(), table_name), + (Labels::Operation.as_str(), operation.as_str()), + ]) }); - table_operations.insert(table_name, Arc::new(metrics)); + operations.insert(table_name, Arc::new(metrics)); } - table_operations + operations } /// Generate a map of all possible transaction modes to metric handles. @@ -135,8 +107,8 @@ 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() } @@ -144,7 +116,7 @@ impl DatabaseEnvMetrics { /// Returns pre-bound operation metric handles for a single table. pub(crate) fn table_operation_metrics(&self, table: &'static str) -> TableOperationMetrics { - self.table_operations.get(table).expect("table operation metric handles not found").clone() + self.operations.get(table).expect("table operation metric handles not found").clone() } /// Record metrics for opening a database transaction. From 99c6bf62fbe1bc28a3cf623db58531ad339696ef Mon Sep 17 00:00:00 2001 From: Brian Picciano <933154+mediocregopher@users.noreply.github.com> Date: Tue, 28 Apr 2026 14:52:52 +0000 Subject: [PATCH 6/7] refactor(db): use repr for operation indexing Co-authored-by: Brian Picciano <933154+mediocregopher@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019dd44d-3253-710b-bc57-9795eb2dfb6a Co-authored-by: Amp --- .../db/src/implementation/mdbx/cursor.rs | 2 +- crates/storage/db/src/metrics.rs | 49 ++++++------------- 2 files changed, 16 insertions(+), 35 deletions(-) diff --git a/crates/storage/db/src/implementation/mdbx/cursor.rs b/crates/storage/db/src/implementation/mdbx/cursor.rs index cd642980576..56276f39abe 100644 --- a/crates/storage/db/src/implementation/mdbx/cursor.rs +++ b/crates/storage/db/src/implementation/mdbx/cursor.rs @@ -54,7 +54,7 @@ impl Cursor { f: impl FnOnce(&mut Self) -> R, ) -> R { if let Some(metrics) = self.metrics.clone() { - metrics[operation.index()].record(value_size, || f(self)) + metrics[operation as usize].record(value_size, || f(self)) } else { f(self) } diff --git a/crates/storage/db/src/metrics.rs b/crates/storage/db/src/metrics.rs index 84f7b52d518..796d843afa4 100644 --- a/crates/storage/db/src/metrics.rs +++ b/crates/storage/db/src/metrics.rs @@ -47,7 +47,7 @@ impl DatabaseEnvMetrics { for table in Tables::ALL { let table_name = table.name(); let metrics = array::from_fn(|index| { - let operation = Operation::from_index(index); + let operation = Operation::ALL[index]; OperationMetrics::new_with_labels(&[ (Labels::Table.as_str(), table_name), (Labels::Operation.as_str(), operation.as_str()), @@ -108,7 +108,7 @@ impl DatabaseEnvMetrics { f: impl FnOnce() -> R, ) -> R { if let Some(metrics) = self.operations.get(table) { - metrics[operation.index()].record(value_size, f) + metrics[operation as usize].record(value_size, f) } else { f() } @@ -201,6 +201,7 @@ impl TransactionOutcome { } /// Types of operations conducted on the database: get, put, delete, and various cursor operations. +#[repr(usize)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, EnumCount, EnumIter)] pub(crate) enum Operation { /// Database get operation. @@ -226,38 +227,18 @@ 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"), - } - } + const ALL: [Self; Self::COUNT] = [ + Self::Get, + Self::PutUpsert, + Self::PutAppend, + Self::Delete, + Self::CursorUpsert, + Self::CursorInsert, + Self::CursorAppend, + Self::CursorAppendDup, + Self::CursorDeleteCurrent, + Self::CursorDeleteCurrentDuplicates, + ]; /// Returns the operation as a string. pub(crate) const fn as_str(&self) -> &'static str { From b01c960891aced99db72d49bebc7ff48e38937a1 Mon Sep 17 00:00:00 2001 From: Brian Picciano <933154+mediocregopher@users.noreply.github.com> Date: Tue, 28 Apr 2026 14:55:23 +0000 Subject: [PATCH 7/7] revert(db): restore explicit operation index mapping Co-authored-by: Brian Picciano <933154+mediocregopher@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019dd44d-3253-710b-bc57-9795eb2dfb6a Co-authored-by: Amp --- .../db/src/implementation/mdbx/cursor.rs | 2 +- crates/storage/db/src/metrics.rs | 49 +++++++++++++------ 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/crates/storage/db/src/implementation/mdbx/cursor.rs b/crates/storage/db/src/implementation/mdbx/cursor.rs index 56276f39abe..cd642980576 100644 --- a/crates/storage/db/src/implementation/mdbx/cursor.rs +++ b/crates/storage/db/src/implementation/mdbx/cursor.rs @@ -54,7 +54,7 @@ impl Cursor { f: impl FnOnce(&mut Self) -> R, ) -> R { if let Some(metrics) = self.metrics.clone() { - metrics[operation as usize].record(value_size, || f(self)) + metrics[operation.index()].record(value_size, || f(self)) } else { f(self) } diff --git a/crates/storage/db/src/metrics.rs b/crates/storage/db/src/metrics.rs index 796d843afa4..84f7b52d518 100644 --- a/crates/storage/db/src/metrics.rs +++ b/crates/storage/db/src/metrics.rs @@ -47,7 +47,7 @@ impl DatabaseEnvMetrics { for table in Tables::ALL { let table_name = table.name(); let metrics = array::from_fn(|index| { - let operation = Operation::ALL[index]; + let operation = Operation::from_index(index); OperationMetrics::new_with_labels(&[ (Labels::Table.as_str(), table_name), (Labels::Operation.as_str(), operation.as_str()), @@ -108,7 +108,7 @@ impl DatabaseEnvMetrics { f: impl FnOnce() -> R, ) -> R { if let Some(metrics) = self.operations.get(table) { - metrics[operation as usize].record(value_size, f) + metrics[operation.index()].record(value_size, f) } else { f() } @@ -201,7 +201,6 @@ impl TransactionOutcome { } /// Types of operations conducted on the database: get, put, delete, and various cursor operations. -#[repr(usize)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, EnumCount, EnumIter)] pub(crate) enum Operation { /// Database get operation. @@ -227,18 +226,38 @@ pub(crate) enum Operation { } impl Operation { - const ALL: [Self; Self::COUNT] = [ - Self::Get, - Self::PutUpsert, - Self::PutAppend, - Self::Delete, - Self::CursorUpsert, - Self::CursorInsert, - Self::CursorAppend, - Self::CursorAppendDup, - Self::CursorDeleteCurrent, - Self::CursorDeleteCurrentDuplicates, - ]; + /// 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 {