Skip to content

Commit a767fe3

Browse files
eabzshekhirin
andauthored
feat: allow using SafeNoSync for MDBX (#18945)
Co-authored-by: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com>
1 parent 4a24cb3 commit a767fe3

File tree

18 files changed

+134
-4
lines changed

18 files changed

+134
-4
lines changed

crates/node/core/src/args/database.rs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ use crate::version::default_client_version;
66
use clap::{
77
builder::{PossibleValue, TypedValueParser},
88
error::ErrorKind,
9-
Arg, Args, Command, Error,
9+
value_parser, Arg, Args, Command, Error,
10+
};
11+
use reth_db::{
12+
mdbx::{MaxReadTransactionDuration, SyncMode},
13+
ClientVersion,
1014
};
11-
use reth_db::{mdbx::MaxReadTransactionDuration, ClientVersion};
1215
use reth_storage_errors::db::LogLevel;
1316

1417
/// Parameters for database configuration
@@ -34,6 +37,12 @@ pub struct DatabaseArgs {
3437
/// Maximum number of readers allowed to access the database concurrently.
3538
#[arg(long = "db.max-readers")]
3639
pub max_readers: Option<u64>,
40+
/// Controls how aggressively the database synchronizes data to disk.
41+
#[arg(
42+
long = "db.sync-mode",
43+
value_parser = value_parser!(SyncMode),
44+
)]
45+
pub sync_mode: Option<SyncMode>,
3746
}
3847

3948
impl DatabaseArgs {
@@ -61,6 +70,7 @@ impl DatabaseArgs {
6170
.with_geometry_max_size(self.max_size)
6271
.with_growth_step(self.growth_step)
6372
.with_max_readers(self.max_readers)
73+
.with_sync_mode(self.sync_mode)
6474
}
6575
}
6676

@@ -340,4 +350,36 @@ mod tests {
340350
let cmd = CommandParser::<DatabaseArgs>::try_parse_from(["reth"]).unwrap();
341351
assert_eq!(cmd.args.log_level, None);
342352
}
353+
354+
#[test]
355+
fn test_command_parser_with_valid_default_sync_mode() {
356+
let cmd = CommandParser::<DatabaseArgs>::try_parse_from(["reth"]).unwrap();
357+
assert!(cmd.args.sync_mode.is_none());
358+
}
359+
360+
#[test]
361+
fn test_command_parser_with_valid_sync_mode_durable() {
362+
let cmd =
363+
CommandParser::<DatabaseArgs>::try_parse_from(["reth", "--db.sync-mode", "durable"])
364+
.unwrap();
365+
assert!(matches!(cmd.args.sync_mode, Some(SyncMode::Durable)));
366+
}
367+
368+
#[test]
369+
fn test_command_parser_with_valid_sync_mode_safe_no_sync() {
370+
let cmd = CommandParser::<DatabaseArgs>::try_parse_from([
371+
"reth",
372+
"--db.sync-mode",
373+
"safe-no-sync",
374+
])
375+
.unwrap();
376+
assert!(matches!(cmd.args.sync_mode, Some(SyncMode::SafeNoSync)));
377+
}
378+
379+
#[test]
380+
fn test_command_parser_with_invalid_sync_mode() {
381+
let result =
382+
CommandParser::<DatabaseArgs>::try_parse_from(["reth", "--db.sync-mode", "ultra-fast"]);
383+
assert!(result.is_err());
384+
}
343385
}

crates/storage/db/src/implementation/mdbx/mod.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,22 @@ pub struct DatabaseArguments {
103103
/// MDBX allows up to 32767 readers (`MDBX_READERS_LIMIT`). This arg is to configure the max
104104
/// readers.
105105
max_readers: Option<u64>,
106+
/// Defines the synchronization strategy used by the MDBX database when writing data to disk.
107+
///
108+
/// This determines how aggressively MDBX ensures data durability versus prioritizing
109+
/// performance. The available modes are:
110+
///
111+
/// - [`SyncMode::Durable`]: Ensures all transactions are fully flushed to disk before they are
112+
/// considered committed. This provides the highest level of durability and crash safety
113+
/// but may have a performance cost.
114+
/// - [`SyncMode::SafeNoSync`]: Skips certain fsync operations to improve write performance.
115+
/// This mode still maintains database integrity but may lose the most recent transactions if
116+
/// the system crashes unexpectedly.
117+
///
118+
/// Choose `Durable` if consistency and crash safety are critical (e.g., production
119+
/// environments). Choose `SafeNoSync` if performance is more important and occasional data
120+
/// loss is acceptable (e.g., testing or ephemeral data).
121+
sync_mode: SyncMode,
106122
}
107123

108124
impl Default for DatabaseArguments {
@@ -126,6 +142,7 @@ impl DatabaseArguments {
126142
max_read_transaction_duration: None,
127143
exclusive: None,
128144
max_readers: None,
145+
sync_mode: SyncMode::Durable,
129146
}
130147
}
131148

@@ -137,6 +154,15 @@ impl DatabaseArguments {
137154
self
138155
}
139156

157+
/// Sets the database sync mode.
158+
pub const fn with_sync_mode(mut self, sync_mode: Option<SyncMode>) -> Self {
159+
if let Some(sync_mode) = sync_mode {
160+
self.sync_mode = sync_mode;
161+
}
162+
163+
self
164+
}
165+
140166
/// Configures the database growth step in bytes.
141167
pub const fn with_growth_step(mut self, growth_step: Option<usize>) -> Self {
142168
if let Some(growth_step) = growth_step {
@@ -329,7 +355,7 @@ impl DatabaseEnv {
329355
DatabaseEnvKind::RW => {
330356
// enable writemap mode in RW mode
331357
inner_env.write_map();
332-
Mode::ReadWrite { sync_mode: SyncMode::Durable }
358+
Mode::ReadWrite { sync_mode: args.sync_mode }
333359
}
334360
};
335361

crates/storage/libmdbx-rs/src/flags.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
use std::str::FromStr;
2+
13
use bitflags::bitflags;
24
use ffi::*;
35

46
/// MDBX sync mode
5-
#[derive(Clone, Copy, Debug, Default)]
7+
#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)]
68
pub enum SyncMode {
79
/// Default robust and durable sync mode.
810
/// Metadata is written and flushed to disk after a data is written and flushed, which
@@ -119,6 +121,21 @@ impl From<Mode> for EnvironmentFlags {
119121
}
120122
}
121123

124+
impl FromStr for SyncMode {
125+
type Err = String;
126+
127+
fn from_str(s: &str) -> Result<Self, Self::Err> {
128+
let val = s.trim().to_ascii_lowercase();
129+
match val.as_str() {
130+
"durable" => Ok(Self::Durable),
131+
"safe-no-sync" | "safenosync" | "safe_no_sync" => Ok(Self::SafeNoSync),
132+
_ => Err(format!(
133+
"invalid value '{s}' for sync mode. valid values: durable, safe-no-sync"
134+
)),
135+
}
136+
}
137+
}
138+
122139
#[derive(Clone, Copy, Debug, Default)]
123140
pub struct EnvironmentFlags {
124141
pub no_sub_dir: bool,

docs/vocs/docs/pages/cli/reth/db.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ Database:
8383
--db.max-readers <MAX_READERS>
8484
Maximum number of readers allowed to access the database concurrently
8585
86+
--db.sync-mode <SYNC_MODE>
87+
Controls how aggressively the database synchronizes data to disk
88+
8689
Logging:
8790
--log.stdout.format <FORMAT>
8891
The format to use for logs written to stdout

docs/vocs/docs/pages/cli/reth/db/diff.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ Database:
4646
--db.max-readers <MAX_READERS>
4747
Maximum number of readers allowed to access the database concurrently
4848
49+
--db.sync-mode <SYNC_MODE>
50+
Controls how aggressively the database synchronizes data to disk
51+
4952
--table <TABLE>
5053
The table name to diff. If not specified, all tables are diffed.
5154

docs/vocs/docs/pages/cli/reth/download.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ Database:
7070
--db.max-readers <MAX_READERS>
7171
Maximum number of readers allowed to access the database concurrently
7272
73+
--db.sync-mode <SYNC_MODE>
74+
Controls how aggressively the database synchronizes data to disk
75+
7376
-u, --url <URL>
7477
Specify a snapshot URL or let the command propose a default one.
7578

docs/vocs/docs/pages/cli/reth/export-era.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ Database:
7070
--db.max-readers <MAX_READERS>
7171
Maximum number of readers allowed to access the database concurrently
7272
73+
--db.sync-mode <SYNC_MODE>
74+
Controls how aggressively the database synchronizes data to disk
75+
7376
--first-block-number <first-block-number>
7477
Optional first block number to export from the db.
7578
It is by default 0.

docs/vocs/docs/pages/cli/reth/import-era.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ Database:
7070
--db.max-readers <MAX_READERS>
7171
Maximum number of readers allowed to access the database concurrently
7272
73+
--db.sync-mode <SYNC_MODE>
74+
Controls how aggressively the database synchronizes data to disk
75+
7376
--path <IMPORT_ERA_PATH>
7477
The path to a directory for import.
7578

docs/vocs/docs/pages/cli/reth/import.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ Database:
7070
--db.max-readers <MAX_READERS>
7171
Maximum number of readers allowed to access the database concurrently
7272
73+
--db.sync-mode <SYNC_MODE>
74+
Controls how aggressively the database synchronizes data to disk
75+
7376
--no-state
7477
Disables stages that require state.
7578

docs/vocs/docs/pages/cli/reth/init-state.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ Database:
7070
--db.max-readers <MAX_READERS>
7171
Maximum number of readers allowed to access the database concurrently
7272
73+
--db.sync-mode <SYNC_MODE>
74+
Controls how aggressively the database synchronizes data to disk
75+
7376
--without-evm
7477
Specifies whether to initialize the state without relying on EVM historical data.
7578

0 commit comments

Comments
 (0)