1pub mod errors;
6pub(crate) mod options;
7pub(crate) mod rocks_util;
8pub(crate) mod safe_iter;
9
10use std::{
11 collections::HashSet,
12 ffi::CStr,
13 path::{Path, PathBuf},
14 sync::Arc,
15 time::Duration,
16};
17
18use backoff::backoff::Backoff;
19use iota_macros::nondeterministic;
20use rocksdb::{
21 AsColumnFamilyRef, ColumnFamilyDescriptor, Error, MultiThreaded, properties,
22 properties::num_files_at_level,
23};
24use tracing::warn;
25use typed_store_error::TypedStoreError;
26
27pub use crate::{
28 database::{DBBatch, DBMap, MetricConf},
29 rocks::options::{
30 BulkIngestionOptions, DBMapTableConfigMap, DBOptions, ReadWriteOptions,
31 bulk_ingestion_options, bulk_ingestion_write_options, default_db_options, list_tables,
32 read_size_from_env,
33 },
34};
35use crate::{
36 database::{Database, Storage},
37 metrics::DBMetrics,
38 rocks::errors::typed_store_err_from_rocks_err,
39};
40
41const ROCKSDB_PROPERTY_TOTAL_BLOB_FILES_SIZE: &CStr =
44 unsafe { CStr::from_bytes_with_nul_unchecked("rocksdb.total-blob-file-size\0".as_bytes()) };
45
46const METRICS_ERROR: i64 = -1;
47
48const DB_CORRUPTED_KEY: &[u8] = b"db_corrupted";
49
50#[cfg(test)]
51mod tests;
52
53#[derive(Debug)]
54pub(crate) struct RocksDB {
55 pub(crate) underlying: rocksdb::DBWithThreadMode<MultiThreaded>,
56 pub(crate) cf_names: Vec<String>,
58}
59
60impl Drop for RocksDB {
61 fn drop(&mut self) {
62 self.underlying.cancel_all_background_work(true);
63 }
64}
65
66pub(crate) fn rocks_cf<'a>(
67 rocks_db: &'a RocksDB,
68 cf_name: &str,
69) -> Arc<rocksdb::BoundColumnFamily<'a>> {
70 rocks_db
71 .underlying
72 .cf_handle(cf_name)
73 .expect("Map-keying column family should have been checked at DB creation")
74}
75
76pub fn check_and_mark_db_corruption(path: &Path) -> Result<(), String> {
79 let db = nondeterministic!(rocksdb::DB::open_default(path)).map_err(|e| e.to_string())?;
85
86 db.get(DB_CORRUPTED_KEY)
87 .map_err(|e| format!("Failed to open database: {e}"))
88 .and_then(|value| match value {
89 Some(v) if v[0] == 1 => Err(
90 "Database is corrupted, please remove the current database and start clean!"
91 .to_string(),
92 ),
93 Some(_) => Ok(()),
94 None => db
95 .put(DB_CORRUPTED_KEY, [1])
96 .map_err(|e| format!("Failed to set corrupted key in database: {e}")),
97 })?;
98
99 Ok(())
100}
101
102pub fn unmark_db_corruption(path: &Path) -> Result<(), Error> {
103 nondeterministic!(rocksdb::DB::open_default(path))?.put(DB_CORRUPTED_KEY, [0])
105}
106
107#[tracing::instrument(level="debug", skip_all, fields(path = ?path.as_ref()), err)]
110pub fn open_cf_opts<P: AsRef<Path>>(
111 path: P,
112 db_options: Option<rocksdb::Options>,
113 metric_conf: MetricConf,
114 opt_cfs: &[(&str, rocksdb::Options)],
115) -> Result<Arc<Database>, TypedStoreError> {
116 let path = path.as_ref();
117 let cfs = populate_missing_cfs(opt_cfs, path).map_err(typed_store_err_from_rocks_err)?;
127 nondeterministic!({
128 let mut options = db_options.unwrap_or_else(|| default_db_options().options);
129 options.create_if_missing(true);
130 options.create_missing_column_families(true);
131 let cf_names: Vec<String> = cfs.iter().map(|(name, _)| name.clone()).collect();
132 let rocksdb = {
133 rocksdb::DBWithThreadMode::<MultiThreaded>::open_cf_descriptors(
134 &options,
135 path,
136 cfs.into_iter()
137 .map(|(name, opts)| ColumnFamilyDescriptor::new(name, opts)),
138 )
139 .map_err(typed_store_err_from_rocks_err)?
140 };
141 Ok(Arc::new(Database::new(
142 Storage::Rocks(RocksDB {
143 underlying: rocksdb,
144 cf_names,
145 }),
146 metric_conf,
147 )))
148 })
149}
150
151pub fn open_cf_opts_secondary<P: AsRef<Path>>(
154 primary_path: P,
155 secondary_path: Option<P>,
156 db_options: Option<rocksdb::Options>,
157 metric_conf: MetricConf,
158 opt_cfs: &[(&str, rocksdb::Options)],
159) -> Result<Arc<Database>, TypedStoreError> {
160 let primary_path = primary_path.as_ref();
161 let secondary_path = secondary_path.as_ref().map(|p| p.as_ref());
162 nondeterministic!({
164 let mut options = db_options.unwrap_or_else(|| default_db_options().options);
166
167 fdlimit::raise_fd_limit();
168 options.set_max_open_files(-1);
170
171 let mut opt_cfs: std::collections::HashMap<_, _> = opt_cfs.iter().cloned().collect();
172 let cfs = rocksdb::DBWithThreadMode::<MultiThreaded>::list_cf(&options, primary_path)
173 .ok()
174 .unwrap_or_default();
175
176 let default_db_options = default_db_options();
177 for cf_key in cfs.iter() {
179 if !opt_cfs.contains_key(&cf_key[..]) {
180 opt_cfs.insert(cf_key, default_db_options.options.clone());
181 }
182 }
183
184 let primary_path = primary_path.to_path_buf();
185 let secondary_path = secondary_path.map(|q| q.to_path_buf()).unwrap_or_else(|| {
186 let mut s = primary_path.clone();
187 s.pop();
188 s.push("SECONDARY");
189 s.as_path().to_path_buf()
190 });
191
192 let rocksdb = {
193 options.create_if_missing(true);
194 options.create_missing_column_families(true);
195 let db = rocksdb::DBWithThreadMode::<MultiThreaded>::open_cf_descriptors_as_secondary(
196 &options,
197 &primary_path,
198 &secondary_path,
199 opt_cfs
200 .iter()
201 .map(|(name, opts)| ColumnFamilyDescriptor::new(*name, (*opts).clone())),
202 )
203 .map_err(typed_store_err_from_rocks_err)?;
204 db.try_catch_up_with_primary()
205 .map_err(typed_store_err_from_rocks_err)?;
206 db
207 };
208 let cf_names: Vec<String> = opt_cfs.keys().map(|name| name.to_string()).collect();
209 Ok(Arc::new(Database::new(
210 Storage::Rocks(RocksDB {
211 underlying: rocksdb,
212 cf_names,
213 }),
214 metric_conf,
215 )))
216 })
217}
218
219#[cfg(msim)]
221pub async fn safe_drop_db(path: PathBuf, timeout: Duration) -> Result<(), rocksdb::Error> {
222 nondeterministic!({
228 let deadline = std::time::Instant::now() + timeout;
229 loop {
230 match rocksdb::DB::destroy(&rocksdb::Options::default(), path.clone()) {
231 Ok(()) => return Ok(()),
232 Err(err) => {
233 if std::time::Instant::now() >= deadline {
234 return Err(err);
235 }
236 std::thread::sleep(Duration::from_millis(100));
237 }
238 }
239 }
240 })
241}
242
243#[cfg(not(msim))]
245pub async fn safe_drop_db(path: PathBuf, timeout: Duration) -> Result<(), rocksdb::Error> {
246 let mut backoff = backoff::ExponentialBackoff {
247 max_elapsed_time: Some(timeout),
248 ..Default::default()
249 };
250 loop {
251 match rocksdb::DB::destroy(&rocksdb::Options::default(), path.clone()) {
252 Ok(()) => return Ok(()),
253 Err(err) => match backoff.next_backoff() {
254 Some(duration) => tokio::time::sleep(duration).await,
255 None => return Err(err),
256 },
257 }
258 }
259}
260
261fn populate_missing_cfs(
262 input_cfs: &[(&str, rocksdb::Options)],
263 path: &Path,
264) -> Result<Vec<(String, rocksdb::Options)>, rocksdb::Error> {
265 let mut cfs = vec![];
266 let input_cf_index: HashSet<_> = input_cfs.iter().map(|(name, _)| *name).collect();
267 let existing_cfs =
268 rocksdb::DBWithThreadMode::<MultiThreaded>::list_cf(&rocksdb::Options::default(), path)
269 .ok()
270 .unwrap_or_default();
271
272 for cf_name in existing_cfs {
273 if !input_cf_index.contains(&cf_name[..]) {
274 cfs.push((cf_name, rocksdb::Options::default()));
275 }
276 }
277 cfs.extend(
278 input_cfs
279 .iter()
280 .map(|(name, opts)| (name.to_string(), (*opts).clone())),
281 );
282 Ok(cfs)
283}
284
285impl<K, V> DBMap<K, V> {
289 fn get_rocksdb_int_property(
290 rocksdb: &RocksDB,
291 cf: &impl AsColumnFamilyRef,
292 property_name: &CStr,
293 ) -> Result<i64, TypedStoreError> {
294 match rocksdb.underlying.property_int_value_cf(cf, property_name) {
295 Ok(Some(value)) => Ok(value.min(i64::MAX as u64).try_into().unwrap_or_default()),
296 Ok(None) => Ok(0),
297 Err(e) => Err(TypedStoreError::RocksDB(e.into_string())),
298 }
299 }
300
301 pub(crate) fn report_rocksdb_metrics(
302 database: &Arc<Database>,
303 cf_name: &str,
304 db_metrics: &Arc<DBMetrics>,
305 ) {
306 let Storage::Rocks(rocksdb) = &database.storage else {
307 return;
308 };
309
310 let Some(cf) = rocksdb.underlying.cf_handle(cf_name) else {
311 warn!(
312 "unable to report metrics for cf {cf_name:?} in db {:?}",
313 database.db_name()
314 );
315 return;
316 };
317
318 db_metrics
319 .cf_metrics
320 .rocksdb_total_sst_files_size
321 .with_label_values(&[cf_name])
322 .set(
323 Self::get_rocksdb_int_property(rocksdb, &cf, properties::TOTAL_SST_FILES_SIZE)
324 .unwrap_or(METRICS_ERROR),
325 );
326 db_metrics
327 .cf_metrics
328 .rocksdb_total_blob_files_size
329 .with_label_values(&[cf_name])
330 .set(
331 Self::get_rocksdb_int_property(
332 rocksdb,
333 &cf,
334 ROCKSDB_PROPERTY_TOTAL_BLOB_FILES_SIZE,
335 )
336 .unwrap_or(METRICS_ERROR),
337 );
338 let total_num_files: i64 = (0..=6)
342 .map(|level| {
343 Self::get_rocksdb_int_property(rocksdb, &cf, &num_files_at_level(level))
344 .unwrap_or(METRICS_ERROR)
345 })
346 .sum();
347 db_metrics
348 .cf_metrics
349 .rocksdb_total_num_files
350 .with_label_values(&[cf_name])
351 .set(total_num_files);
352 db_metrics
353 .cf_metrics
354 .rocksdb_num_level0_files
355 .with_label_values(&[cf_name])
356 .set(
357 Self::get_rocksdb_int_property(rocksdb, &cf, &num_files_at_level(0))
358 .unwrap_or(METRICS_ERROR),
359 );
360 db_metrics
361 .cf_metrics
362 .rocksdb_current_size_active_mem_tables
363 .with_label_values(&[cf_name])
364 .set(
365 Self::get_rocksdb_int_property(rocksdb, &cf, properties::CUR_SIZE_ACTIVE_MEM_TABLE)
366 .unwrap_or(METRICS_ERROR),
367 );
368 db_metrics
369 .cf_metrics
370 .rocksdb_size_all_mem_tables
371 .with_label_values(&[cf_name])
372 .set(
373 Self::get_rocksdb_int_property(rocksdb, &cf, properties::SIZE_ALL_MEM_TABLES)
374 .unwrap_or(METRICS_ERROR),
375 );
376 db_metrics
377 .cf_metrics
378 .rocksdb_num_snapshots
379 .with_label_values(&[cf_name])
380 .set(
381 Self::get_rocksdb_int_property(rocksdb, &cf, properties::NUM_SNAPSHOTS)
382 .unwrap_or(METRICS_ERROR),
383 );
384 db_metrics
385 .cf_metrics
386 .rocksdb_oldest_snapshot_time
387 .with_label_values(&[cf_name])
388 .set(
389 Self::get_rocksdb_int_property(rocksdb, &cf, properties::OLDEST_SNAPSHOT_TIME)
390 .unwrap_or(METRICS_ERROR),
391 );
392 db_metrics
393 .cf_metrics
394 .rocksdb_actual_delayed_write_rate
395 .with_label_values(&[cf_name])
396 .set(
397 Self::get_rocksdb_int_property(rocksdb, &cf, properties::ACTUAL_DELAYED_WRITE_RATE)
398 .unwrap_or(METRICS_ERROR),
399 );
400 db_metrics
401 .cf_metrics
402 .rocksdb_is_write_stopped
403 .with_label_values(&[cf_name])
404 .set(
405 Self::get_rocksdb_int_property(rocksdb, &cf, properties::IS_WRITE_STOPPED)
406 .unwrap_or(METRICS_ERROR),
407 );
408 db_metrics
409 .cf_metrics
410 .rocksdb_block_cache_capacity
411 .with_label_values(&[cf_name])
412 .set(
413 Self::get_rocksdb_int_property(rocksdb, &cf, properties::BLOCK_CACHE_CAPACITY)
414 .unwrap_or(METRICS_ERROR),
415 );
416 db_metrics
417 .cf_metrics
418 .rocksdb_block_cache_usage
419 .with_label_values(&[cf_name])
420 .set(
421 Self::get_rocksdb_int_property(rocksdb, &cf, properties::BLOCK_CACHE_USAGE)
422 .unwrap_or(METRICS_ERROR),
423 );
424 db_metrics
425 .cf_metrics
426 .rocksdb_block_cache_pinned_usage
427 .with_label_values(&[cf_name])
428 .set(
429 Self::get_rocksdb_int_property(rocksdb, &cf, properties::BLOCK_CACHE_PINNED_USAGE)
430 .unwrap_or(METRICS_ERROR),
431 );
432 db_metrics
433 .cf_metrics
434 .rocksdb_estimate_table_readers_mem
435 .with_label_values(&[cf_name])
436 .set(
437 Self::get_rocksdb_int_property(
438 rocksdb,
439 &cf,
440 properties::ESTIMATE_TABLE_READERS_MEM,
441 )
442 .unwrap_or(METRICS_ERROR),
443 );
444 db_metrics
445 .cf_metrics
446 .rocksdb_estimated_num_keys
447 .with_label_values(&[cf_name])
448 .set(
449 Self::get_rocksdb_int_property(rocksdb, &cf, properties::ESTIMATE_NUM_KEYS)
450 .unwrap_or(METRICS_ERROR),
451 );
452 db_metrics
453 .cf_metrics
454 .rocksdb_num_immutable_mem_tables
455 .with_label_values(&[cf_name])
456 .set(
457 Self::get_rocksdb_int_property(rocksdb, &cf, properties::NUM_IMMUTABLE_MEM_TABLE)
458 .unwrap_or(METRICS_ERROR),
459 );
460 db_metrics
461 .cf_metrics
462 .rocksdb_mem_table_flush_pending
463 .with_label_values(&[cf_name])
464 .set(
465 Self::get_rocksdb_int_property(rocksdb, &cf, properties::MEM_TABLE_FLUSH_PENDING)
466 .unwrap_or(METRICS_ERROR),
467 );
468 db_metrics
469 .cf_metrics
470 .rocksdb_compaction_pending
471 .with_label_values(&[cf_name])
472 .set(
473 Self::get_rocksdb_int_property(rocksdb, &cf, properties::COMPACTION_PENDING)
474 .unwrap_or(METRICS_ERROR),
475 );
476 db_metrics
477 .cf_metrics
478 .rocksdb_estimate_pending_compaction_bytes
479 .with_label_values(&[cf_name])
480 .set(
481 Self::get_rocksdb_int_property(
482 rocksdb,
483 &cf,
484 properties::ESTIMATE_PENDING_COMPACTION_BYTES,
485 )
486 .unwrap_or(METRICS_ERROR),
487 );
488 db_metrics
489 .cf_metrics
490 .rocksdb_num_running_compactions
491 .with_label_values(&[cf_name])
492 .set(
493 Self::get_rocksdb_int_property(rocksdb, &cf, properties::NUM_RUNNING_COMPACTIONS)
494 .unwrap_or(METRICS_ERROR),
495 );
496 db_metrics
497 .cf_metrics
498 .rocksdb_num_running_flushes
499 .with_label_values(&[cf_name])
500 .set(
501 Self::get_rocksdb_int_property(rocksdb, &cf, properties::NUM_RUNNING_FLUSHES)
502 .unwrap_or(METRICS_ERROR),
503 );
504 db_metrics
505 .cf_metrics
506 .rocksdb_estimate_oldest_key_time
507 .with_label_values(&[cf_name])
508 .set(
509 Self::get_rocksdb_int_property(rocksdb, &cf, properties::ESTIMATE_OLDEST_KEY_TIME)
510 .unwrap_or(METRICS_ERROR),
511 );
512 db_metrics
513 .cf_metrics
514 .rocksdb_background_errors
515 .with_label_values(&[cf_name])
516 .set(
517 Self::get_rocksdb_int_property(rocksdb, &cf, properties::BACKGROUND_ERRORS)
518 .unwrap_or(METRICS_ERROR),
519 );
520 db_metrics
521 .cf_metrics
522 .rocksdb_base_level
523 .with_label_values(&[cf_name])
524 .set(
525 Self::get_rocksdb_int_property(rocksdb, &cf, properties::BASE_LEVEL)
526 .unwrap_or(METRICS_ERROR),
527 );
528 }
529}