Skip to content

Commit 152fd5f

Browse files
authored
Configurable RocksDB Write Options (#844)
1 parent e9cfa65 commit 152fd5f

File tree

11 files changed

+136
-42
lines changed

11 files changed

+136
-42
lines changed

madara/crates/client/db/src/rocksdb/blocks.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ impl RocksDBStorageInner {
148148
&super::serialize_to_smallvec::<[u8; 16]>(&block_n)?,
149149
);
150150

151-
self.db.write_opt(batch, &self.writeopts_no_wal)?;
151+
self.db.write_opt(batch, &self.writeopts)?;
152152
Ok(())
153153
}
154154

@@ -191,7 +191,7 @@ impl RocksDBStorageInner {
191191
value.iter().map(|tx_with_receipt| *tx_with_receipt.receipt.transaction_hash()).collect();
192192
batch.put_cf(&block_info_col, block_n_u32.to_be_bytes(), super::serialize(&block_info)?);
193193

194-
self.db.write_opt(batch, &self.writeopts_no_wal)?;
194+
self.db.write_opt(batch, &self.writeopts)?;
195195
Ok(())
196196
}
197197

@@ -202,7 +202,7 @@ impl RocksDBStorageInner {
202202

203203
let block_n_to_state_diff = self.get_column(BLOCK_STATE_DIFF_COLUMN);
204204
batch.put_cf(&block_n_to_state_diff, block_n_u32.to_be_bytes(), &super::serialize(value)?);
205-
self.db.write_opt(batch, &self.writeopts_no_wal)?;
205+
self.db.write_opt(batch, &self.writeopts)?;
206206

207207
Ok(())
208208
}
@@ -214,7 +214,7 @@ impl RocksDBStorageInner {
214214

215215
let block_n_to_bouncer_weights = self.get_column(BLOCK_BOUNCER_WEIGHT_COLUMN);
216216
batch.put_cf(&block_n_to_bouncer_weights, block_n_u32.to_be_bytes(), &super::serialize(value)?);
217-
self.db.write_opt(batch, &self.writeopts_no_wal)?;
217+
self.db.write_opt(batch, &self.writeopts)?;
218218

219219
Ok(())
220220
}
@@ -248,7 +248,7 @@ impl RocksDBStorageInner {
248248
);
249249
}
250250

251-
self.db.write_opt(batch, &self.writeopts_no_wal)?;
251+
self.db.write_opt(batch, &self.writeopts)?;
252252
Ok(())
253253
}
254254

@@ -380,7 +380,7 @@ impl RocksDBStorageInner {
380380
batch.delete_cf(&block_info_col, block_n_u32.to_be_bytes());
381381
batch.delete_cf(&block_hash_to_block_n_col, block_info.block_hash.to_bytes_be());
382382

383-
self.db.write_opt(batch, &self.writeopts_no_wal)?;
383+
self.db.write_opt(batch, &self.writeopts)?;
384384
tracing::debug!("📦 REORG [block_db_revert]: Block {} successfully removed from database", block_n);
385385
}
386386

madara/crates/client/db/src/rocksdb/classes.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ impl RocksDBStorageInner {
5656
);
5757
}
5858
}
59-
self.db.write_opt(batch, &self.writeopts_no_wal)?;
59+
self.db.write_opt(batch, &self.writeopts)?;
6060
anyhow::Ok(())
6161
},
6262
)?;
@@ -83,7 +83,7 @@ impl RocksDBStorageInner {
8383
})?,
8484
);
8585
}
86-
self.db.write_opt(batch, &self.writeopts_no_wal)?;
86+
self.db.write_opt(batch, &self.writeopts)?;
8787
anyhow::Ok(())
8888
},
8989
)?;
@@ -156,7 +156,7 @@ impl RocksDBStorageInner {
156156
total_classes
157157
);
158158

159-
self.db.write_opt(batch, &self.writeopts_no_wal)?;
159+
self.db.write_opt(batch, &self.writeopts)?;
160160

161161
tracing::info!("✅ REORG [class_db_revert]: Successfully removed all declared classes");
162162

madara/crates/client/db/src/rocksdb/events.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ impl RocksDBStorageInner {
139139
&self.get_column(EVENTS_BLOOM_COLUMN),
140140
block_n.to_be_bytes(),
141141
&super::serialize(&writer)?,
142-
&self.writeopts_no_wal,
142+
&self.writeopts,
143143
)?;
144144
Ok(())
145145
}

madara/crates/client/db/src/rocksdb/l1_to_l2_messages.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,21 @@ impl RocksDBStorageInner {
2929
batch.put_cf(&on_l2_cf, key, receipt.transaction_hash.to_bytes_be());
3030
}
3131

32-
self.db.write_opt(batch, &self.writeopts_no_wal)?;
32+
self.db.write_opt(batch, &self.writeopts)?;
3333
Ok(())
3434
}
3535

3636
/// If the message is already pending, this will overwrite it.
3737
pub(super) fn write_pending_message_to_l2(&self, msg: &L1HandlerTransactionWithFee) -> Result<()> {
3838
let pending_cf = self.get_column(L1_TO_L2_PENDING_MESSAGE_BY_NONCE);
39-
self.db.put_cf_opt(&pending_cf, msg.tx.nonce.to_be_bytes(), super::serialize(&msg)?, &self.writeopts_no_wal)?;
39+
self.db.put_cf_opt(&pending_cf, msg.tx.nonce.to_be_bytes(), super::serialize(&msg)?, &self.writeopts)?;
4040
Ok(())
4141
}
4242

4343
/// If the message does not exist, this does nothing.
4444
pub(super) fn remove_pending_message_to_l2(&self, core_contract_nonce: u64) -> Result<()> {
4545
let pending_cf = self.get_column(L1_TO_L2_PENDING_MESSAGE_BY_NONCE);
46-
self.db.delete_cf_opt(&pending_cf, core_contract_nonce.to_be_bytes(), &self.writeopts_no_wal)?;
46+
self.db.delete_cf_opt(&pending_cf, core_contract_nonce.to_be_bytes(), &self.writeopts)?;
4747
Ok(())
4848
}
4949

@@ -82,7 +82,7 @@ impl RocksDBStorageInner {
8282
&on_l2_cf,
8383
core_contract_nonce.to_be_bytes(),
8484
txn_hash.to_bytes_be(),
85-
&self.writeopts_no_wal,
85+
&self.writeopts,
8686
)?;
8787
Ok(())
8888
}

madara/crates/client/db/src/rocksdb/mempool.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ impl RocksDBStorageInner {
3232
batch.delete_cf(&col, super::serialize(&tx_hash)?);
3333
}
3434

35-
self.db.write_opt(batch, &self.writeopts_no_wal)?;
35+
self.db.write_opt(batch, &self.writeopts)?;
3636
Ok(())
3737
}
3838

madara/crates/client/db/src/rocksdb/meta.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@ impl RocksDBStorageInner {
3232
&self.get_column(META_COLUMN),
3333
META_LAST_SYNCED_L1_EVENT_BLOCK_KEY,
3434
block_n.to_be_bytes(),
35-
&self.writeopts_no_wal,
35+
&self.writeopts,
3636
)?;
3737
} else {
3838
self.db.delete_cf_opt(
3939
&self.get_column(META_COLUMN),
4040
META_LAST_SYNCED_L1_EVENT_BLOCK_KEY,
41-
&self.writeopts_no_wal,
41+
&self.writeopts,
4242
)?;
4343
}
4444
Ok(())
@@ -61,13 +61,13 @@ impl RocksDBStorageInner {
6161
&self.get_column(META_COLUMN),
6262
META_CONFIRMED_ON_L1_TIP_KEY,
6363
block_n.to_be_bytes(),
64-
&self.writeopts_no_wal,
64+
&self.writeopts,
6565
)?;
6666
} else {
6767
self.db.delete_cf_opt(
6868
&self.get_column(META_COLUMN),
6969
META_CONFIRMED_ON_L1_TIP_KEY,
70-
&self.writeopts_no_wal,
70+
&self.writeopts,
7171
)?;
7272
}
7373
Ok(())
@@ -97,7 +97,7 @@ impl RocksDBStorageInner {
9797
&self.get_column(META_COLUMN),
9898
META_DEVNET_KEYS_KEY,
9999
super::serialize(&devnet_keys)?,
100-
&self.writeopts_no_wal,
100+
&self.writeopts,
101101
)?;
102102
Ok(())
103103
}
@@ -142,7 +142,7 @@ impl RocksDBStorageInner {
142142
// Write chain tip atomically
143143
// Note: Using regular write opts (no fsync) for performance
144144
// The chain tip will be synced on next flush or graceful shutdown
145-
self.db.write_opt(batch, &self.writeopts_no_wal)?;
145+
self.db.write_opt(batch, &self.writeopts)?;
146146
Ok(())
147147
}
148148

@@ -168,7 +168,7 @@ impl RocksDBStorageInner {
168168
let tx_index = u16::try_from(tx_index).context("Converting tx_index to u16")?;
169169
batch.put_cf(&col, tx_index.to_be_bytes(), super::serialize(&value)?);
170170
}
171-
self.db.write_opt(batch, &self.writeopts_no_wal)?;
171+
self.db.write_opt(batch, &self.writeopts)?;
172172
Ok(())
173173
}
174174

@@ -208,7 +208,7 @@ impl RocksDBStorageInner {
208208
&self.get_column(META_COLUMN),
209209
META_CHAIN_INFO_KEY,
210210
super::serialize_to_smallvec::<[u8; 128]>(info)?,
211-
&self.writeopts_no_wal,
211+
&self.writeopts,
212212
)?;
213213
Ok(())
214214
}
@@ -228,13 +228,13 @@ impl RocksDBStorageInner {
228228
&self.get_column(META_COLUMN),
229229
META_LATEST_APPLIED_TRIE_UPDATE,
230230
super::serialize_to_smallvec::<[u8; 128]>(block_n)?,
231-
&self.writeopts_no_wal,
231+
&self.writeopts,
232232
)?;
233233
} else {
234234
self.db.delete_cf_opt(
235235
&self.get_column(META_COLUMN),
236236
META_LATEST_APPLIED_TRIE_UPDATE,
237-
&self.writeopts_no_wal,
237+
&self.writeopts,
238238
)?;
239239
}
240240
Ok(())

madara/crates/client/db/src/rocksdb/mod.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ pub mod update_global_trie;
5252
type WriteBatchWithTransaction = rocksdb::WriteBatchWithTransaction<false>;
5353
type DB = DBWithThreadMode<MultiThreaded>;
5454

55-
pub use options::{RocksDBConfig, StatsLevel};
55+
pub use options::{DbWriteMode, RocksDBConfig, StatsLevel};
5656

5757
const DB_UPDATES_BATCH_SIZE: usize = 1024;
5858

@@ -80,7 +80,7 @@ fn deserialize<T: serde::de::DeserializeOwned>(bytes: impl AsRef<[u8]>) -> Resul
8080

8181
struct RocksDBStorageInner {
8282
db: DB,
83-
writeopts_no_wal: WriteOptions,
83+
writeopts: WriteOptions,
8484
config: RocksDBConfig,
8585
}
8686

@@ -191,9 +191,9 @@ impl RocksDBStorage {
191191
ALL_COLUMNS.iter().map(|col| ColumnFamilyDescriptor::new(col.rocksdb_name, col.rocksdb_options(&config))),
192192
)?;
193193

194-
let mut writeopts_no_wal = WriteOptions::new();
195-
writeopts_no_wal.disable_wal(true);
196-
let inner = Arc::new(RocksDBStorageInner { writeopts_no_wal, db, config: config.clone() });
194+
let writeopts = config.write_mode.to_write_options();
195+
tracing::info!("📝 Database write mode: {}", config.write_mode);
196+
let inner = Arc::new(RocksDBStorageInner { writeopts, db, config: config.clone() });
197197

198198
let head_block_n = inner.get_chain_tip_without_content()?.and_then(|c| match c {
199199
StoredChainTipWithoutContent::Confirmed(block_n) => Some(block_n),

madara/crates/client/db/src/rocksdb/options.rs

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,85 @@ use std::path::PathBuf;
55

66
use crate::rocksdb::column::{Column, ColumnMemoryBudget};
77
use anyhow::{Context, Result};
8-
use rocksdb::{DBCompressionType, Env, Options, SliceTransform};
8+
use rocksdb::{DBCompressionType, Env, Options, SliceTransform, WriteOptions};
9+
use serde::{Deserialize, Serialize};
910

1011
const KiB: usize = 1024;
1112
const MiB: usize = 1024 * KiB;
1213
const GiB: usize = 1024 * MiB;
1314

1415
pub use rocksdb::statistics::StatsLevel;
1516

17+
/// RocksDB write durability configuration. Controls WAL (Write-Ahead Log) and fsync behavior.
18+
///
19+
/// # Performance & Safety Trade-offs
20+
///
21+
/// | WAL | Fsync | Performance | Safety | Use Case |
22+
/// |---------|---------|-------------|---------|---------------------------------------|
23+
/// | Enabled | Enabled | Slowest | Highest | Production critical data |
24+
/// | Enabled | Disable | Medium | High | Production (safe on crash) - **RECOMMENDED** |
25+
/// | Disable | Enabled | Fast | Medium | Testing, can tolerate data loss |
26+
/// | Disable | Disable | Fastest | Lowest | Devnet, testing |
27+
///
28+
/// # Safety Considerations
29+
///
30+
/// - **WAL enabled**: Write-Ahead Log records changes before applying them. Enables crash recovery.
31+
/// Disabling WAL is faster but may lose recent uncommitted data on crash.
32+
///
33+
/// - **Fsync enabled**: Forces data to be flushed to disk before acknowledging writes.
34+
/// Survives power failures but slower. Disabling fsync relies on OS buffering (faster, survives crashes but not power loss).
35+
///
36+
/// **Recommended settings:**
37+
/// - Production: `wal=true, fsync=false` (safe on crash, good performance)
38+
/// - Maximum durability: `wal=true, fsync=true` (survives power failures)
39+
/// - Testing/Development: `wal=false, fsync=false` (fastest)
40+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
41+
pub struct DbWriteMode {
42+
/// Enable Write-Ahead Log (WAL). Provides crash recovery but adds overhead.
43+
/// Default: true (recommended for production)
44+
pub wal: bool,
45+
/// Enable fsync after writes. Ensures data reaches disk before acknowledging.
46+
/// Default: false (recommended for production - survives crashes, faster than fsync)
47+
pub fsync: bool,
48+
}
49+
50+
impl Default for DbWriteMode {
51+
fn default() -> Self {
52+
Self {
53+
wal: true,
54+
fsync: false,
55+
}
56+
}
57+
}
58+
59+
impl DbWriteMode {
60+
/// Convert the write mode to RocksDB WriteOptions
61+
pub fn to_write_options(&self) -> WriteOptions {
62+
let mut opts = WriteOptions::default();
63+
64+
if !self.wal {
65+
opts.disable_wal(true);
66+
}
67+
68+
if !self.fsync {
69+
opts.set_sync(false);
70+
}
71+
72+
opts
73+
}
74+
}
75+
76+
impl std::fmt::Display for DbWriteMode {
77+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78+
match (self.wal, self.fsync) {
79+
(true, true) => write!(f, "WAL enabled, fsync enabled (safest)"),
80+
(true, false) => write!(f, "WAL enabled, fsync disabled (recommended)"),
81+
(false, true) => write!(f, "WAL disabled, fsync enabled (fast)"),
82+
(false, false) => write!(f, "WAL disabled, fsync disabled (fastest, least safe)"),
83+
}
84+
}
85+
}
86+
1687
#[derive(Debug, Clone)]
1788
pub struct RocksDBConfig {
1889
/// Enable statistics. Statistics will be put in the `LOG` file in the db folder. This can have an effect on performance.
@@ -41,6 +112,9 @@ pub struct RocksDBConfig {
41112
pub backup_dir: Option<PathBuf>,
42113
/// When true, the latest backup will be restored on startup.
43114
pub restore_from_latest_backup: bool,
115+
116+
/// Write durability mode (WAL and fsync settings)
117+
pub write_mode: DbWriteMode,
44118
}
45119

46120
impl Default for RocksDBConfig {
@@ -59,6 +133,7 @@ impl Default for RocksDBConfig {
59133
snapshot_interval: 5,
60134
backup_dir: None,
61135
restore_from_latest_backup: false,
136+
write_mode: DbWriteMode::default(),
62137
}
63138
}
64139
}

madara/crates/client/db/src/rocksdb/state.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ impl RocksDBStorageInner {
153153
super::serialize_to_smallvec::<[u8; 64]>(&value)?,
154154
);
155155
}
156-
self.db.write_opt(batch, &self.writeopts_no_wal)?;
156+
self.db.write_opt(batch, &self.writeopts)?;
157157
anyhow::Ok(())
158158
},
159159
)?;
@@ -168,7 +168,7 @@ impl RocksDBStorageInner {
168168
super::serialize_to_smallvec::<[u8; 64]>(&value)?,
169169
);
170170
}
171-
self.db.write_opt(batch, &self.writeopts_no_wal)?;
171+
self.db.write_opt(batch, &self.writeopts)?;
172172
anyhow::Ok(())
173173
},
174174
)?;
@@ -183,7 +183,7 @@ impl RocksDBStorageInner {
183183
super::serialize_to_smallvec::<[u8; 64]>(&value)?,
184184
);
185185
}
186-
self.db.write_opt(batch, &self.writeopts_no_wal)?;
186+
self.db.write_opt(batch, &self.writeopts)?;
187187
anyhow::Ok(())
188188
},
189189
)?;
@@ -277,7 +277,7 @@ impl RocksDBStorageInner {
277277
total_storage_entries
278278
);
279279

280-
self.db.write_opt(batch, &self.writeopts_no_wal)?;
280+
self.db.write_opt(batch, &self.writeopts)?;
281281

282282
tracing::info!("✅ REORG [contract_db_revert]: Successfully removed all contract state");
283283

0 commit comments

Comments
 (0)