From 37b5db0d475963395ae669992de2e210b90e2afd Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Wed, 21 Jan 2026 00:45:17 -0800 Subject: [PATCH] feat(cli): add RocksDB table stats to `reth db stats` command (#21221) Co-authored-by: joshieDo <93316087+joshieDo@users.noreply.github.com> --- crates/cli/commands/src/db/stats.rs | 49 ++++++++++- crates/storage/provider/src/providers/mod.rs | 4 +- .../provider/src/providers/rocksdb/mod.rs | 4 +- .../src/providers/rocksdb/provider.rs | 83 +++++++++++++++++++ .../provider/src/providers/rocksdb_stub.rs | 20 +++++ 5 files changed, 157 insertions(+), 3 deletions(-) diff --git a/crates/cli/commands/src/db/stats.rs b/crates/cli/commands/src/db/stats.rs index d84091c2d6..0e46bfdbb3 100644 --- a/crates/cli/commands/src/db/stats.rs +++ b/crates/cli/commands/src/db/stats.rs @@ -11,7 +11,10 @@ use reth_db_common::DbTool; use reth_fs_util as fs; use reth_node_builder::{NodePrimitives, NodeTypesWithDB, NodeTypesWithDBAdapter}; use reth_node_core::dirs::{ChainPath, DataDirPath}; -use reth_provider::providers::{ProviderNodeTypes, StaticFileProvider}; +use reth_provider::{ + providers::{ProviderNodeTypes, StaticFileProvider}, + RocksDBProviderFactory, +}; use reth_static_file_types::SegmentRangeInclusive; use std::{sync::Arc, time::Duration}; @@ -61,6 +64,11 @@ impl Command { let db_stats_table = self.db_stats_table(tool)?; println!("{db_stats_table}"); + println!("\n"); + + let rocksdb_stats_table = self.rocksdb_stats_table(tool); + println!("{rocksdb_stats_table}"); + Ok(()) } @@ -148,6 +156,45 @@ impl Command { Ok(table) } + fn rocksdb_stats_table(&self, tool: &DbTool) -> ComfyTable { + let mut table = ComfyTable::new(); + table.load_preset(comfy_table::presets::ASCII_MARKDOWN); + table.set_header(["RocksDB Table Name", "# Entries", "Total Size", "Pending Compaction"]); + + let stats = tool.provider_factory.rocksdb_provider().table_stats(); + let mut total_size: u64 = 0; + let mut total_pending: u64 = 0; + + for stat in &stats { + total_size += stat.estimated_size_bytes; + total_pending += stat.pending_compaction_bytes; + let mut row = Row::new(); + row.add_cell(Cell::new(&stat.name)) + .add_cell(Cell::new(stat.estimated_num_keys)) + .add_cell(Cell::new(human_bytes(stat.estimated_size_bytes as f64))) + .add_cell(Cell::new(human_bytes(stat.pending_compaction_bytes as f64))); + table.add_row(row); + } + + if !stats.is_empty() { + let max_widths = table.column_max_content_widths(); + let mut separator = Row::new(); + for width in max_widths { + separator.add_cell(Cell::new("-".repeat(width as usize))); + } + table.add_row(separator); + + let mut row = Row::new(); + row.add_cell(Cell::new("RocksDB Total")) + .add_cell(Cell::new("")) + .add_cell(Cell::new(human_bytes(total_size as f64))) + .add_cell(Cell::new(human_bytes(total_pending as f64))); + table.add_row(row); + } + + table + } + fn static_files_stats_table( &self, data_dir: ChainPath, diff --git a/crates/storage/provider/src/providers/mod.rs b/crates/storage/provider/src/providers/mod.rs index 7cdf32a8ad..c477ccbb98 100644 --- a/crates/storage/provider/src/providers/mod.rs +++ b/crates/storage/provider/src/providers/mod.rs @@ -38,7 +38,9 @@ pub use consistent::ConsistentProvider; #[cfg_attr(not(all(unix, feature = "rocksdb")), path = "rocksdb_stub.rs")] pub(crate) mod rocksdb; -pub use rocksdb::{RocksDBBatch, RocksDBBuilder, RocksDBProvider, RocksDBRawIter, RocksTx}; +pub use rocksdb::{ + RocksDBBatch, RocksDBBuilder, RocksDBProvider, RocksDBRawIter, RocksDBTableStats, RocksTx, +}; /// Helper trait to bound [`NodeTypes`] so that combined with database they satisfy /// [`ProviderNodeTypes`]. diff --git a/crates/storage/provider/src/providers/rocksdb/mod.rs b/crates/storage/provider/src/providers/rocksdb/mod.rs index 49a332ccce..efab03e2af 100644 --- a/crates/storage/provider/src/providers/rocksdb/mod.rs +++ b/crates/storage/provider/src/providers/rocksdb/mod.rs @@ -5,4 +5,6 @@ mod metrics; mod provider; pub(crate) use provider::{PendingRocksDBBatches, RocksDBWriteCtx}; -pub use provider::{RocksDBBatch, RocksDBBuilder, RocksDBProvider, RocksDBRawIter, RocksTx}; +pub use provider::{ + RocksDBBatch, RocksDBBuilder, RocksDBProvider, RocksDBRawIter, RocksDBTableStats, RocksTx, +}; diff --git a/crates/storage/provider/src/providers/rocksdb/provider.rs b/crates/storage/provider/src/providers/rocksdb/provider.rs index a673fa6291..d1423ba171 100644 --- a/crates/storage/provider/src/providers/rocksdb/provider.rs +++ b/crates/storage/provider/src/providers/rocksdb/provider.rs @@ -38,6 +38,19 @@ use tracing::instrument; /// Pending `RocksDB` batches type alias. pub(crate) type PendingRocksDBBatches = Arc>>>; +/// Statistics for a single `RocksDB` table (column family). +#[derive(Debug, Clone)] +pub struct RocksDBTableStats { + /// Name of the table/column family. + pub name: String, + /// Estimated number of keys in the table. + pub estimated_num_keys: u64, + /// Estimated size of live data in bytes (SST files + memtables). + pub estimated_size_bytes: u64, + /// Estimated bytes pending compaction (reclaimable space). + pub pending_compaction_bytes: u64, +} + /// Context for `RocksDB` block writes. #[derive(Clone)] pub(crate) struct RocksDBWriteCtx { @@ -405,6 +418,69 @@ impl RocksDBProviderInner { Self::ReadOnly { db, .. } => RocksDBIterEnum::ReadOnly(db.iterator_cf(cf, mode)), } } + + /// Returns statistics for all column families in the database. + fn table_stats(&self) -> Vec { + let cf_names = [ + tables::TransactionHashNumbers::NAME, + tables::AccountsHistory::NAME, + tables::StoragesHistory::NAME, + ]; + + let mut stats = Vec::new(); + + macro_rules! collect_stats { + ($db:expr) => { + for cf_name in cf_names { + if let Some(cf) = $db.cf_handle(cf_name) { + let estimated_num_keys = $db + .property_int_value_cf(cf, rocksdb::properties::ESTIMATE_NUM_KEYS) + .ok() + .flatten() + .unwrap_or(0); + + // SST files size (on-disk) + memtable size (in-memory) + let sst_size = $db + .property_int_value_cf(cf, rocksdb::properties::LIVE_SST_FILES_SIZE) + .ok() + .flatten() + .unwrap_or(0); + + let memtable_size = $db + .property_int_value_cf(cf, rocksdb::properties::SIZE_ALL_MEM_TABLES) + .ok() + .flatten() + .unwrap_or(0); + + let estimated_size_bytes = sst_size + memtable_size; + + let pending_compaction_bytes = $db + .property_int_value_cf( + cf, + rocksdb::properties::ESTIMATE_PENDING_COMPACTION_BYTES, + ) + .ok() + .flatten() + .unwrap_or(0); + + stats.push(RocksDBTableStats { + name: cf_name.to_string(), + estimated_num_keys, + estimated_size_bytes, + pending_compaction_bytes, + }); + } + } + }; + } + + match self { + Self::ReadWrite { db, .. } => collect_stats!(db), + Self::ReadOnly { db, .. } => collect_stats!(db), + } + + stats + } } impl fmt::Debug for RocksDBProviderInner { @@ -666,6 +742,13 @@ impl RocksDBProvider { Ok(RocksDBIter { inner: iter, _marker: std::marker::PhantomData }) } + /// Returns statistics for all column families in the database. + /// + /// Returns a vector of (`table_name`, `estimated_keys`, `estimated_size_bytes`) tuples. + pub fn table_stats(&self) -> Vec { + self.0.table_stats() + } + /// Creates a raw iterator over all entries in the specified table. /// /// Returns raw `(key_bytes, value_bytes)` pairs without decoding. diff --git a/crates/storage/provider/src/providers/rocksdb_stub.rs b/crates/storage/provider/src/providers/rocksdb_stub.rs index d46cd15e2f..ff12113167 100644 --- a/crates/storage/provider/src/providers/rocksdb_stub.rs +++ b/crates/storage/provider/src/providers/rocksdb_stub.rs @@ -14,6 +14,19 @@ use std::{path::Path, sync::Arc}; /// Pending `RocksDB` batches type alias (stub - uses unit type). pub(crate) type PendingRocksDBBatches = Arc>>; +/// Statistics for a single `RocksDB` table (column family) - stub. +#[derive(Debug, Clone)] +pub struct RocksDBTableStats { + /// Name of the table/column family. + pub name: String, + /// Estimated number of keys in the table. + pub estimated_num_keys: u64, + /// Estimated size of live data in bytes (SST files + memtables). + pub estimated_size_bytes: u64, + /// Estimated bytes pending compaction (reclaimable space). + pub pending_compaction_bytes: u64, +} + /// Context for `RocksDB` block writes (stub). #[derive(Debug, Clone)] #[allow(dead_code)] @@ -56,6 +69,13 @@ impl RocksDBProvider { ) -> ProviderResult> { Ok(None) } + + /// Returns statistics for all column families in the database (stub implementation). + /// + /// Returns an empty vector since there is no `RocksDB` when the feature is disabled. + pub const fn table_stats(&self) -> Vec { + Vec::new() + } } /// A stub batch writer for `RocksDB`.