//! Module that interacts with MDBX. use crate::{ lockfile::StorageLock, metrics::DatabaseEnvMetrics, tables::{self, TableType, Tables}, utils::default_page_size, DatabaseError, }; use eyre::Context; use metrics::{gauge, Label}; use reth_db_api::{ cursor::{DbCursorRO, DbCursorRW}, database::Database, database_metrics::{DatabaseMetadata, DatabaseMetadataValue, DatabaseMetrics}, models::client_version::ClientVersion, transaction::{DbTx, DbTxMut}, }; use reth_libmdbx::{ ffi, DatabaseFlags, Environment, EnvironmentFlags, Geometry, HandleSlowReadersReturnCode, MaxReadTransactionDuration, Mode, PageSize, SyncMode, RO, RW, }; use reth_storage_errors::db::LogLevel; use reth_tracing::tracing::error; use std::{ ops::Deref, path::Path, sync::Arc, time::{SystemTime, UNIX_EPOCH}, }; use tx::Tx; pub mod cursor; pub mod tx; const GIGABYTE: usize = 1024 * 1024 * 1024; const TERABYTE: usize = GIGABYTE * 1024; /// MDBX allows up to 32767 readers (`MDBX_READERS_LIMIT`), but we limit it to slightly below that const DEFAULT_MAX_READERS: u64 = 32_000; /// Space that a read-only transaction can occupy until the warning is emitted. /// See [`reth_libmdbx::EnvironmentBuilder::set_handle_slow_readers`] for more information. const MAX_SAFE_READER_SPACE: usize = 10 * GIGABYTE; /// Environment used when opening a MDBX environment. RO/RW. #[derive(Debug)] pub enum DatabaseEnvKind { /// Read-only MDBX environment. RO, /// Read-write MDBX environment. RW, } impl DatabaseEnvKind { /// Returns `true` if the environment is read-write. pub const fn is_rw(&self) -> bool { matches!(self, Self::RW) } } /// Arguments for database initialization. #[derive(Clone, Debug, Default)] pub struct DatabaseArguments { /// Client version that accesses the database. client_version: ClientVersion, /// Database log level. If [None], the default value is used. log_level: Option, /// Maximum duration of a read transaction. If [None], the default value is used. max_read_transaction_duration: Option, /// Open environment in exclusive/monopolistic mode. If [None], the default value is used. /// /// This can be used as a replacement for `MDB_NOLOCK`, which don't supported by MDBX. In this /// way, you can get the minimal overhead, but with the correct multi-process and multi-thread /// locking. /// /// If `true` = open environment in exclusive/monopolistic mode or return `MDBX_BUSY` if /// environment already used by other process. The main feature of the exclusive mode is the /// ability to open the environment placed on a network share. /// /// If `false` = open environment in cooperative mode, i.e. for multi-process /// access/interaction/cooperation. The main requirements of the cooperative mode are: /// - Data files MUST be placed in the LOCAL file system, but NOT on a network share. /// - Environment MUST be opened only by LOCAL processes, but NOT over a network. /// - OS kernel (i.e. file system and memory mapping implementation) and all processes that /// open the given environment MUST be running in the physically single RAM with /// cache-coherency. The only exception for cache-consistency requirement is Linux on MIPS /// architecture, but this case has not been tested for a long time). /// /// This flag affects only at environment opening but can't be changed after. exclusive: Option, } impl DatabaseArguments { /// Create new database arguments with given client version. pub const fn new(client_version: ClientVersion) -> Self { Self { client_version, log_level: None, max_read_transaction_duration: None, exclusive: None, } } /// Set the log level. pub const fn with_log_level(mut self, log_level: Option) -> Self { self.log_level = log_level; self } /// Set the maximum duration of a read transaction. pub const fn with_max_read_transaction_duration( mut self, max_read_transaction_duration: Option, ) -> Self { self.max_read_transaction_duration = max_read_transaction_duration; self } /// Set the mdbx exclusive flag. pub const fn with_exclusive(mut self, exclusive: Option) -> Self { self.exclusive = exclusive; self } /// Returns the client version if any. pub const fn client_version(&self) -> &ClientVersion { &self.client_version } } /// Wrapper for the libmdbx environment: [Environment] #[derive(Debug)] pub struct DatabaseEnv { /// Libmdbx-sys environment. inner: Environment, /// Cache for metric handles. If `None`, metrics are not recorded. metrics: Option>, /// Write lock for when dealing with a read-write environment. _lock_file: Option, } impl Database for DatabaseEnv { type TX = tx::Tx; type TXMut = tx::Tx; fn tx(&self) -> Result { Tx::new_with_metrics( self.inner.begin_ro_txn().map_err(|e| DatabaseError::InitTx(e.into()))?, self.metrics.as_ref().cloned(), ) .map_err(|e| DatabaseError::InitTx(e.into())) } fn tx_mut(&self) -> Result { Tx::new_with_metrics( self.inner.begin_rw_txn().map_err(|e| DatabaseError::InitTx(e.into()))?, self.metrics.as_ref().cloned(), ) .map_err(|e| DatabaseError::InitTx(e.into())) } } impl DatabaseMetrics for DatabaseEnv { fn report_metrics(&self) { for (name, value, labels) in self.gauge_metrics() { gauge!(name, labels).set(value); } } fn gauge_metrics(&self) -> Vec<(&'static str, f64, Vec