mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
perf(db): introduce environment-level cache for metric handles (#6550)
This commit is contained in:
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -88,9 +88,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
version = "0.8.7"
|
version = "0.8.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01"
|
checksum = "42cd52102d3df161c77a887b608d7a4897d7cc112886a9537b738a887a03aaff"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"getrandom 0.2.12",
|
"getrandom 0.2.12",
|
||||||
@ -5946,6 +5946,7 @@ dependencies = [
|
|||||||
"assert_matches",
|
"assert_matches",
|
||||||
"bytes",
|
"bytes",
|
||||||
"criterion",
|
"criterion",
|
||||||
|
"dashmap",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"eyre",
|
"eyre",
|
||||||
"iai",
|
"iai",
|
||||||
@ -5967,8 +5968,10 @@ dependencies = [
|
|||||||
"reth-nippy-jar",
|
"reth-nippy-jar",
|
||||||
"reth-primitives",
|
"reth-primitives",
|
||||||
"reth-tracing",
|
"reth-tracing",
|
||||||
|
"rustc-hash",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"strum 0.26.1",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"test-fuzz 5.0.0",
|
"test-fuzz 5.0.0",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
|||||||
@ -38,11 +38,14 @@ parking_lot.workspace = true
|
|||||||
derive_more.workspace = true
|
derive_more.workspace = true
|
||||||
eyre.workspace = true
|
eyre.workspace = true
|
||||||
paste = "1.0"
|
paste = "1.0"
|
||||||
|
dashmap = "5.5.3"
|
||||||
|
rustc-hash = "1.1.0"
|
||||||
|
|
||||||
# arbitrary utils
|
# arbitrary utils
|
||||||
arbitrary = { workspace = true, features = ["derive"], optional = true }
|
arbitrary = { workspace = true, features = ["derive"], optional = true }
|
||||||
proptest = { workspace = true, optional = true }
|
proptest = { workspace = true, optional = true }
|
||||||
proptest-derive = { workspace = true, optional = true }
|
proptest-derive = { workspace = true, optional = true }
|
||||||
|
strum = { workspace = true, features = ["derive"] }
|
||||||
once_cell.workspace = true
|
once_cell.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
@ -6,14 +6,14 @@ use crate::{
|
|||||||
DbCursorRO, DbCursorRW, DbDupCursorRO, DbDupCursorRW, DupWalker, RangeWalker,
|
DbCursorRO, DbCursorRW, DbDupCursorRO, DbDupCursorRW, DupWalker, RangeWalker,
|
||||||
ReverseWalker, Walker,
|
ReverseWalker, Walker,
|
||||||
},
|
},
|
||||||
metrics::{Operation, OperationMetrics},
|
metrics::{DatabaseEnvMetrics, Operation},
|
||||||
table::{Compress, Decode, Decompress, DupSort, Encode, Table},
|
table::{Compress, Decode, Decompress, DupSort, Encode, Table},
|
||||||
tables::utils::*,
|
tables::utils::*,
|
||||||
DatabaseError,
|
DatabaseError,
|
||||||
};
|
};
|
||||||
use reth_interfaces::db::{DatabaseErrorInfo, DatabaseWriteError, DatabaseWriteOperation};
|
use reth_interfaces::db::{DatabaseErrorInfo, DatabaseWriteError, DatabaseWriteOperation};
|
||||||
use reth_libmdbx::{self, Error as MDBXError, TransactionKind, WriteFlags, RO, RW};
|
use reth_libmdbx::{self, Error as MDBXError, TransactionKind, WriteFlags, RO, RW};
|
||||||
use std::{borrow::Cow, collections::Bound, marker::PhantomData, ops::RangeBounds};
|
use std::{borrow::Cow, collections::Bound, marker::PhantomData, ops::RangeBounds, sync::Arc};
|
||||||
|
|
||||||
/// Read only Cursor.
|
/// Read only Cursor.
|
||||||
pub type CursorRO<T> = Cursor<RO, T>;
|
pub type CursorRO<T> = Cursor<RO, T>;
|
||||||
@ -27,18 +27,22 @@ pub struct Cursor<K: TransactionKind, T: Table> {
|
|||||||
pub(crate) inner: reth_libmdbx::Cursor<K>,
|
pub(crate) inner: reth_libmdbx::Cursor<K>,
|
||||||
/// Cache buffer that receives compressed values.
|
/// Cache buffer that receives compressed values.
|
||||||
buf: Vec<u8>,
|
buf: Vec<u8>,
|
||||||
/// Whether to record metrics or not.
|
/// Reference to metric handles in the DB environment. If `None`, metrics are not recorded.
|
||||||
with_metrics: bool,
|
metrics: Option<Arc<DatabaseEnvMetrics>>,
|
||||||
/// Phantom data to enforce encoding/decoding.
|
/// Phantom data to enforce encoding/decoding.
|
||||||
_dbi: PhantomData<T>,
|
_dbi: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: TransactionKind, T: Table> Cursor<K, T> {
|
impl<K: TransactionKind, T: Table> Cursor<K, T> {
|
||||||
pub(crate) fn new_with_metrics(inner: reth_libmdbx::Cursor<K>, with_metrics: bool) -> Self {
|
pub(crate) fn new_with_metrics(
|
||||||
Self { inner, buf: Vec::new(), with_metrics, _dbi: PhantomData }
|
inner: reth_libmdbx::Cursor<K>,
|
||||||
|
metrics: Option<Arc<DatabaseEnvMetrics>>,
|
||||||
|
) -> Self {
|
||||||
|
Self { inner, buf: Vec::new(), metrics, _dbi: PhantomData }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If `self.with_metrics == true`, record a metric with the provided operation and value size.
|
/// If `self.metrics` is `Some(...)`, record a metric with the provided operation and value
|
||||||
|
/// size.
|
||||||
///
|
///
|
||||||
/// Otherwise, just execute the closure.
|
/// Otherwise, just execute the closure.
|
||||||
fn execute_with_operation_metric<R>(
|
fn execute_with_operation_metric<R>(
|
||||||
@ -47,8 +51,8 @@ impl<K: TransactionKind, T: Table> Cursor<K, T> {
|
|||||||
value_size: Option<usize>,
|
value_size: Option<usize>,
|
||||||
f: impl FnOnce(&mut Self) -> R,
|
f: impl FnOnce(&mut Self) -> R,
|
||||||
) -> R {
|
) -> R {
|
||||||
if self.with_metrics {
|
if let Some(metrics) = self.metrics.as_ref().cloned() {
|
||||||
OperationMetrics::record(T::NAME, operation, value_size, || f(self))
|
metrics.record_operation(T::NAME, operation, value_size, || f(self))
|
||||||
} else {
|
} else {
|
||||||
f(self)
|
f(self)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
database::Database,
|
database::Database,
|
||||||
database_metrics::{DatabaseMetadata, DatabaseMetadataValue, DatabaseMetrics},
|
database_metrics::{DatabaseMetadata, DatabaseMetadataValue, DatabaseMetrics},
|
||||||
|
metrics::DatabaseEnvMetrics,
|
||||||
tables::{TableType, Tables},
|
tables::{TableType, Tables},
|
||||||
utils::default_page_size,
|
utils::default_page_size,
|
||||||
DatabaseError,
|
DatabaseError,
|
||||||
@ -16,7 +17,7 @@ use reth_libmdbx::{
|
|||||||
PageSize, SyncMode, RO, RW,
|
PageSize, SyncMode, RO, RW,
|
||||||
};
|
};
|
||||||
use reth_tracing::tracing::error;
|
use reth_tracing::tracing::error;
|
||||||
use std::{ops::Deref, path::Path};
|
use std::{ops::Deref, path::Path, sync::Arc};
|
||||||
use tx::Tx;
|
use tx::Tx;
|
||||||
|
|
||||||
pub mod cursor;
|
pub mod cursor;
|
||||||
@ -86,8 +87,8 @@ impl DatabaseArguments {
|
|||||||
pub struct DatabaseEnv {
|
pub struct DatabaseEnv {
|
||||||
/// Libmdbx-sys environment.
|
/// Libmdbx-sys environment.
|
||||||
inner: Environment,
|
inner: Environment,
|
||||||
/// Whether to record metrics or not.
|
/// Cache for metric handles. If `None`, metrics are not recorded.
|
||||||
with_metrics: bool,
|
metrics: Option<Arc<DatabaseEnvMetrics>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Database for DatabaseEnv {
|
impl Database for DatabaseEnv {
|
||||||
@ -97,14 +98,14 @@ impl Database for DatabaseEnv {
|
|||||||
fn tx(&self) -> Result<Self::TX, DatabaseError> {
|
fn tx(&self) -> Result<Self::TX, DatabaseError> {
|
||||||
Ok(Tx::new_with_metrics(
|
Ok(Tx::new_with_metrics(
|
||||||
self.inner.begin_ro_txn().map_err(|e| DatabaseError::InitTx(e.into()))?,
|
self.inner.begin_ro_txn().map_err(|e| DatabaseError::InitTx(e.into()))?,
|
||||||
self.with_metrics,
|
self.metrics.as_ref().cloned(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tx_mut(&self) -> Result<Self::TXMut, DatabaseError> {
|
fn tx_mut(&self) -> Result<Self::TXMut, DatabaseError> {
|
||||||
Ok(Tx::new_with_metrics(
|
Ok(Tx::new_with_metrics(
|
||||||
self.inner.begin_rw_txn().map_err(|e| DatabaseError::InitTx(e.into()))?,
|
self.inner.begin_rw_txn().map_err(|e| DatabaseError::InitTx(e.into()))?,
|
||||||
self.with_metrics,
|
self.metrics.as_ref().cloned(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -309,7 +310,7 @@ impl DatabaseEnv {
|
|||||||
|
|
||||||
let env = DatabaseEnv {
|
let env = DatabaseEnv {
|
||||||
inner: inner_env.open(path).map_err(|e| DatabaseError::Open(e.into()))?,
|
inner: inner_env.open(path).map_err(|e| DatabaseError::Open(e.into()))?,
|
||||||
with_metrics: false,
|
metrics: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(env)
|
Ok(env)
|
||||||
@ -317,7 +318,7 @@ impl DatabaseEnv {
|
|||||||
|
|
||||||
/// Enables metrics on the database.
|
/// Enables metrics on the database.
|
||||||
pub fn with_metrics(mut self) -> Self {
|
pub fn with_metrics(mut self) -> Self {
|
||||||
self.with_metrics = true;
|
self.metrics = Some(DatabaseEnvMetrics::new().into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
use super::cursor::Cursor;
|
use super::cursor::Cursor;
|
||||||
use crate::{
|
use crate::{
|
||||||
metrics::{
|
metrics::{
|
||||||
Operation, OperationMetrics, TransactionMetrics, TransactionMode, TransactionOutcome,
|
DatabaseEnvMetrics, Operation, TransactionMetrics, TransactionMode, TransactionOutcome,
|
||||||
},
|
},
|
||||||
table::{Compress, DupSort, Encode, Table, TableImporter},
|
table::{Compress, DupSort, Encode, Table, TableImporter},
|
||||||
tables::{utils::decode_one, Tables, NUM_TABLES},
|
tables::{utils::decode_one, Tables, NUM_TABLES},
|
||||||
@ -50,9 +50,12 @@ impl<K: TransactionKind> Tx<K> {
|
|||||||
|
|
||||||
/// Creates new `Tx` object with a `RO` or `RW` transaction and optionally enables metrics.
|
/// Creates new `Tx` object with a `RO` or `RW` transaction and optionally enables metrics.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn new_with_metrics(inner: Transaction<K>, with_metrics: bool) -> Self {
|
pub fn new_with_metrics(
|
||||||
let metrics_handler = if with_metrics {
|
inner: Transaction<K>,
|
||||||
let handler = MetricsHandler::<K>::new(inner.id());
|
metrics: Option<Arc<DatabaseEnvMetrics>>,
|
||||||
|
) -> Self {
|
||||||
|
let metrics_handler = if let Some(metrics) = metrics {
|
||||||
|
let handler = MetricsHandler::<K>::new(inner.id(), metrics);
|
||||||
TransactionMetrics::record_open(handler.transaction_mode());
|
TransactionMetrics::record_open(handler.transaction_mode());
|
||||||
handler.log_transaction_opened();
|
handler.log_transaction_opened();
|
||||||
Some(handler)
|
Some(handler)
|
||||||
@ -90,7 +93,10 @@ impl<K: TransactionKind> Tx<K> {
|
|||||||
.cursor_with_dbi(self.get_dbi::<T>()?)
|
.cursor_with_dbi(self.get_dbi::<T>()?)
|
||||||
.map_err(|e| DatabaseError::InitCursor(e.into()))?;
|
.map_err(|e| DatabaseError::InitCursor(e.into()))?;
|
||||||
|
|
||||||
Ok(Cursor::new_with_metrics(inner, self.metrics_handler.is_some()))
|
Ok(Cursor::new_with_metrics(
|
||||||
|
inner,
|
||||||
|
self.metrics_handler.as_ref().map(|h| h.env_metrics.clone()),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If `self.metrics_handler == Some(_)`, measure the time it takes to execute the closure and
|
/// If `self.metrics_handler == Some(_)`, measure the time it takes to execute the closure and
|
||||||
@ -137,7 +143,9 @@ impl<K: TransactionKind> Tx<K> {
|
|||||||
) -> R {
|
) -> R {
|
||||||
if let Some(metrics_handler) = &self.metrics_handler {
|
if let Some(metrics_handler) = &self.metrics_handler {
|
||||||
metrics_handler.log_backtrace_on_long_read_transaction();
|
metrics_handler.log_backtrace_on_long_read_transaction();
|
||||||
OperationMetrics::record(T::NAME, operation, value_size, || f(&self.inner))
|
metrics_handler
|
||||||
|
.env_metrics
|
||||||
|
.record_operation(T::NAME, operation, value_size, || f(&self.inner))
|
||||||
} else {
|
} else {
|
||||||
f(&self.inner)
|
f(&self.inner)
|
||||||
}
|
}
|
||||||
@ -159,17 +167,19 @@ struct MetricsHandler<K: TransactionKind> {
|
|||||||
/// If `true`, the backtrace of transaction has already been recorded and logged.
|
/// If `true`, the backtrace of transaction has already been recorded and logged.
|
||||||
/// See [MetricsHandler::log_backtrace_on_long_read_transaction].
|
/// See [MetricsHandler::log_backtrace_on_long_read_transaction].
|
||||||
backtrace_recorded: AtomicBool,
|
backtrace_recorded: AtomicBool,
|
||||||
|
env_metrics: Arc<DatabaseEnvMetrics>,
|
||||||
_marker: PhantomData<K>,
|
_marker: PhantomData<K>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: TransactionKind> MetricsHandler<K> {
|
impl<K: TransactionKind> MetricsHandler<K> {
|
||||||
fn new(txn_id: u64) -> Self {
|
fn new(txn_id: u64, env_metrics: Arc<DatabaseEnvMetrics>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
txn_id,
|
txn_id,
|
||||||
start: Instant::now(),
|
start: Instant::now(),
|
||||||
close_recorded: false,
|
close_recorded: false,
|
||||||
record_backtrace: true,
|
record_backtrace: true,
|
||||||
backtrace_recorded: AtomicBool::new(false),
|
backtrace_recorded: AtomicBool::new(false),
|
||||||
|
env_metrics,
|
||||||
_marker: PhantomData,
|
_marker: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,57 @@
|
|||||||
|
use crate::{Tables, NUM_TABLES};
|
||||||
|
use dashmap::DashMap;
|
||||||
use metrics::{Gauge, Histogram};
|
use metrics::{Gauge, Histogram};
|
||||||
use reth_libmdbx::CommitLatency;
|
use reth_libmdbx::CommitLatency;
|
||||||
use reth_metrics::{metrics::Counter, Metrics};
|
use reth_metrics::{metrics::Counter, Metrics};
|
||||||
use std::time::{Duration, Instant};
|
use rustc_hash::FxHasher;
|
||||||
|
use std::{
|
||||||
|
hash::BuildHasherDefault,
|
||||||
|
str::FromStr,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
use strum::EnumCount;
|
||||||
|
|
||||||
const LARGE_VALUE_THRESHOLD_BYTES: usize = 4096;
|
const LARGE_VALUE_THRESHOLD_BYTES: usize = 4096;
|
||||||
|
|
||||||
|
/// Caches metric handles for database environment to make sure handles are not re-created
|
||||||
|
/// on every operation.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DatabaseEnvMetrics {
|
||||||
|
/// Caches OperationMetrics handles for each table and operation tuple.
|
||||||
|
operations: DashMap<(Tables, Operation), OperationMetrics, BuildHasherDefault<FxHasher>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DatabaseEnvMetrics {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
operations: DashMap::with_capacity_and_hasher(
|
||||||
|
NUM_TABLES * Operation::COUNT,
|
||||||
|
BuildHasherDefault::<FxHasher>::default(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Record a metric for database operation executed in `f`. Panics if the table name is unknown.
|
||||||
|
pub(crate) fn record_operation<R>(
|
||||||
|
&self,
|
||||||
|
table: &'static str,
|
||||||
|
operation: Operation,
|
||||||
|
value_size: Option<usize>,
|
||||||
|
f: impl FnOnce() -> R,
|
||||||
|
) -> R {
|
||||||
|
let handle = self
|
||||||
|
.operations
|
||||||
|
.entry((Tables::from_str(table).expect("unknown table name"), operation))
|
||||||
|
.or_insert_with(|| {
|
||||||
|
OperationMetrics::new_with_labels(&[
|
||||||
|
(Labels::Table.as_str(), table),
|
||||||
|
(Labels::Operation.as_str(), operation.as_str()),
|
||||||
|
])
|
||||||
|
});
|
||||||
|
handle.record(value_size, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Transaction mode for the database, either read-only or read-write.
|
/// Transaction mode for the database, either read-only or read-write.
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||||
pub(crate) enum TransactionMode {
|
pub(crate) enum TransactionMode {
|
||||||
@ -51,7 +98,7 @@ impl TransactionOutcome {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Types of operations conducted on the database: get, put, delete, and various cursor operations.
|
/// Types of operations conducted on the database: get, put, delete, and various cursor operations.
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, EnumCount)]
|
||||||
pub(crate) enum Operation {
|
pub(crate) enum Operation {
|
||||||
/// Database get operation.
|
/// Database get operation.
|
||||||
Get,
|
Get,
|
||||||
@ -199,24 +246,15 @@ impl OperationMetrics {
|
|||||||
///
|
///
|
||||||
/// The duration it took to execute the closure is recorded only if the provided `value_size` is
|
/// The duration it took to execute the closure is recorded only if the provided `value_size` is
|
||||||
/// larger than [LARGE_VALUE_THRESHOLD_BYTES].
|
/// larger than [LARGE_VALUE_THRESHOLD_BYTES].
|
||||||
pub(crate) fn record<T>(
|
pub(crate) fn record<R>(&self, value_size: Option<usize>, f: impl FnOnce() -> R) -> R {
|
||||||
table: &'static str,
|
self.calls_total.increment(1);
|
||||||
operation: Operation,
|
|
||||||
value_size: Option<usize>,
|
|
||||||
f: impl FnOnce() -> T,
|
|
||||||
) -> T {
|
|
||||||
let metrics = Self::new_with_labels(&[
|
|
||||||
(Labels::Table.as_str(), table),
|
|
||||||
(Labels::Operation.as_str(), operation.as_str()),
|
|
||||||
]);
|
|
||||||
metrics.calls_total.increment(1);
|
|
||||||
|
|
||||||
// Record duration only for large values to prevent the performance hit of clock syscall
|
// Record duration only for large values to prevent the performance hit of clock syscall
|
||||||
// on small operations
|
// on small operations
|
||||||
if value_size.map_or(false, |size| size > LARGE_VALUE_THRESHOLD_BYTES) {
|
if value_size.map_or(false, |size| size > LARGE_VALUE_THRESHOLD_BYTES) {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let result = f();
|
let result = f();
|
||||||
metrics.large_value_duration_seconds.record(start.elapsed());
|
self.large_value_duration_seconds.record(start.elapsed());
|
||||||
result
|
result
|
||||||
} else {
|
} else {
|
||||||
f()
|
f()
|
||||||
|
|||||||
@ -108,7 +108,7 @@ macro_rules! tables {
|
|||||||
(TableType::Table, [$($table:ident),*]),
|
(TableType::Table, [$($table:ident),*]),
|
||||||
(TableType::DupSort, [$($dupsort:ident),*])
|
(TableType::DupSort, [$($dupsort:ident),*])
|
||||||
]) => {
|
]) => {
|
||||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Copy, Clone, Hash, Eq)]
|
||||||
/// Default tables that should be present inside database.
|
/// Default tables that should be present inside database.
|
||||||
pub enum Tables {
|
pub enum Tables {
|
||||||
$(
|
$(
|
||||||
|
|||||||
Reference in New Issue
Block a user