1#![allow(
9 clippy::cast_possible_truncation,
10 clippy::cast_sign_loss,
11 clippy::cast_possible_wrap
12)]
13
14use std::{
15 ffi::OsString,
16 fmt::Display,
17 path::PathBuf,
18 sync::{
19 atomic::{AtomicBool, Ordering},
20 Arc,
21 },
22};
23
24use linera_base::ensure;
25use rocksdb::{BlockBasedOptions, Cache, DBCompactionStyle, SliceTransform, WriteBufferManager};
26use serde::{Deserialize, Serialize};
27use sysinfo::{MemoryRefreshKind, RefreshKind, System};
28use tempfile::TempDir;
29use thiserror::Error;
30
31#[cfg(with_metrics)]
32use crate::metering::MeteredDatabase;
33#[cfg(with_testing)]
34use crate::store::TestKeyValueDatabase;
35use crate::{
36 batch::{Batch, WriteOperation},
37 common::get_upper_bound_option,
38 lru_caching::{LruCachingConfig, LruCachingDatabase},
39 store::{
40 KeyValueDatabase, KeyValueStoreError, ReadableKeyValueStore, WithError,
41 WritableKeyValueStore,
42 },
43 value_splitting::{ValueSplittingDatabase, ValueSplittingError},
44};
45
46static ROOT_KEY_DOMAIN: [u8; 1] = [0];
48static STORED_ROOT_KEYS_PREFIX: u8 = 1;
49
50const MAX_VALUE_SIZE: usize = 3 * 1024 * 1024 * 1024 - 400;
53
54const MAX_KEY_SIZE: usize = 8 * 1024 * 1024 - 400;
57
58const WRITE_BUFFER_SIZE: usize = 16 * 1024 * 1024; const MAX_WRITE_BUFFER_NUMBER: i32 = 6;
64
65fn get_available_memory(sys: &System) -> usize {
66 sys.cgroup_limits()
67 .map_or_else(|| sys.total_memory() as usize, |c| c.total_memory as usize)
68}
69
70fn get_available_cpus() -> i32 {
71 std::thread::available_parallelism().map_or(1, |p| p.get() as i32)
72}
73
74const HYPER_CLOCK_CACHE_BLOCK_SIZE: usize = 8 * 1024; type DB = rocksdb::DBWithThreadMode<rocksdb::MultiThreaded>;
78
79#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
86pub enum RocksDbSpawnMode {
87 SpawnBlocking,
89 BlockInPlace,
91}
92
93impl RocksDbSpawnMode {
94 pub fn get_spawn_mode_from_runtime() -> Self {
96 if tokio::runtime::Handle::current().metrics().num_workers() > 1 {
97 RocksDbSpawnMode::BlockInPlace
98 } else {
99 RocksDbSpawnMode::SpawnBlocking
100 }
101 }
102
103 #[inline]
105 async fn spawn<F, I, O>(&self, f: F, input: I) -> Result<O, RocksDbStoreInternalError>
106 where
107 F: FnOnce(I) -> Result<O, RocksDbStoreInternalError> + Send + 'static,
108 I: Send + 'static,
109 O: Send + 'static,
110 {
111 Ok(match self {
112 RocksDbSpawnMode::BlockInPlace => tokio::task::block_in_place(move || f(input))?,
113 RocksDbSpawnMode::SpawnBlocking => {
114 tokio::task::spawn_blocking(move || f(input)).await??
115 }
116 })
117 }
118}
119
120impl Display for RocksDbSpawnMode {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 match &self {
123 RocksDbSpawnMode::SpawnBlocking => write!(f, "spawn_blocking"),
124 RocksDbSpawnMode::BlockInPlace => write!(f, "block_in_place"),
125 }
126 }
127}
128
129fn check_key_size(key: &[u8]) -> Result<(), RocksDbStoreInternalError> {
130 ensure!(
131 key.len() <= MAX_KEY_SIZE,
132 RocksDbStoreInternalError::KeyTooLong
133 );
134 Ok(())
135}
136
137#[derive(Clone)]
138struct RocksDbStoreExecutor {
139 db: Arc<DB>,
140 start_key: Vec<u8>,
141}
142
143impl RocksDbStoreExecutor {
144 fn contains_keys_internal(
145 &self,
146 keys: Vec<Vec<u8>>,
147 ) -> Result<Vec<bool>, RocksDbStoreInternalError> {
148 let size = keys.len();
149 let mut results = vec![false; size];
150 let mut indices = Vec::new();
151 let mut keys_red = Vec::new();
152 for (i, key) in keys.into_iter().enumerate() {
153 check_key_size(&key)?;
154 let mut full_key = self.start_key.to_vec();
155 full_key.extend(key);
156 if self.db.key_may_exist(&full_key) {
157 indices.push(i);
158 keys_red.push(full_key);
159 }
160 }
161 let values_red = self.db.multi_get(keys_red);
162 for (index, value) in indices.into_iter().zip(values_red) {
163 results[index] = value?.is_some();
164 }
165 Ok(results)
166 }
167
168 fn read_multi_values_bytes_internal(
169 &self,
170 keys: Vec<Vec<u8>>,
171 ) -> Result<Vec<Option<Vec<u8>>>, RocksDbStoreInternalError> {
172 for key in &keys {
173 check_key_size(key)?;
174 }
175 let full_keys = keys
176 .into_iter()
177 .map(|key| {
178 let mut full_key = self.start_key.to_vec();
179 full_key.extend(key);
180 full_key
181 })
182 .collect::<Vec<_>>();
183 let entries = self.db.multi_get(&full_keys);
184 Ok(entries.into_iter().collect::<Result<_, _>>()?)
185 }
186
187 fn get_find_prefix_iterator(
188 &self,
189 prefix: &[u8],
190 ) -> rocksdb::DBRawIteratorWithThreadMode<'_, DB> {
191 let mut read_opts = rocksdb::ReadOptions::default();
193 read_opts.set_async_io(true);
195
196 let upper_bound = get_upper_bound_option(prefix);
198 if let Some(upper_bound) = upper_bound {
199 read_opts.set_iterate_upper_bound(upper_bound);
200 }
201
202 let mut iter = self.db.raw_iterator_opt(read_opts);
203 iter.seek(prefix);
204 iter
205 }
206
207 fn find_keys_by_prefix_internal(
208 &self,
209 key_prefix: Vec<u8>,
210 ) -> Result<Vec<Vec<u8>>, RocksDbStoreInternalError> {
211 check_key_size(&key_prefix)?;
212
213 let mut prefix = self.start_key.clone();
214 prefix.extend(key_prefix);
215 let len = prefix.len();
216
217 let mut iter = self.get_find_prefix_iterator(&prefix);
218 let mut keys = Vec::new();
219 while let Some(key) = iter.key() {
220 keys.push(key[len..].to_vec());
221 iter.next();
222 }
223 Ok(keys)
224 }
225
226 #[expect(clippy::type_complexity)]
227 fn find_key_values_by_prefix_internal(
228 &self,
229 key_prefix: Vec<u8>,
230 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>, RocksDbStoreInternalError> {
231 check_key_size(&key_prefix)?;
232 let mut prefix = self.start_key.clone();
233 prefix.extend(key_prefix);
234 let len = prefix.len();
235
236 let mut iter = self.get_find_prefix_iterator(&prefix);
237 let mut key_values = Vec::new();
238 while let Some((key, value)) = iter.item() {
239 let key_value = (key[len..].to_vec(), value.to_vec());
240 key_values.push(key_value);
241 iter.next();
242 }
243 Ok(key_values)
244 }
245
246 fn write_batch_internal(
247 &self,
248 batch: Batch,
249 write_root_key: bool,
250 ) -> Result<(), RocksDbStoreInternalError> {
251 let mut inner_batch = rocksdb::WriteBatchWithTransaction::default();
252 for operation in batch.operations {
253 match operation {
254 WriteOperation::Delete { key } => {
255 check_key_size(&key)?;
256 let mut full_key = self.start_key.to_vec();
257 full_key.extend(key);
258 inner_batch.delete(&full_key)
259 }
260 WriteOperation::Put { key, value } => {
261 check_key_size(&key)?;
262 let mut full_key = self.start_key.to_vec();
263 full_key.extend(key);
264 inner_batch.put(&full_key, value)
265 }
266 WriteOperation::DeletePrefix { key_prefix } => {
267 check_key_size(&key_prefix)?;
268 let mut full_key1 = self.start_key.to_vec();
269 full_key1.extend(&key_prefix);
270 let full_key2 =
271 get_upper_bound_option(&full_key1).expect("the first entry cannot be 255");
272 inner_batch.delete_range(&full_key1, &full_key2);
273 }
274 }
275 }
276 if write_root_key {
277 let mut full_key = self.start_key.to_vec();
278 full_key[0] = STORED_ROOT_KEYS_PREFIX;
279 inner_batch.put(&full_key, vec![]);
280 }
281 self.db.write(inner_batch)?;
282 Ok(())
283 }
284}
285
286#[derive(Clone)]
288pub struct RocksDbStoreInternal {
289 executor: RocksDbStoreExecutor,
290 path_with_guard: PathWithGuard,
291 spawn_mode: RocksDbSpawnMode,
292 root_key_written: Arc<AtomicBool>,
293}
294
295#[derive(Clone)]
297pub struct RocksDbDatabaseInternal {
298 executor: RocksDbStoreExecutor,
299 path_with_guard: PathWithGuard,
300 spawn_mode: RocksDbSpawnMode,
301}
302
303impl WithError for RocksDbDatabaseInternal {
304 type Error = RocksDbStoreInternalError;
305}
306
307#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Deserialize, Serialize, strum::EnumString)]
312#[serde(rename_all = "kebab-case")]
313#[strum(serialize_all = "kebab-case")]
314pub enum RocksDbStatisticsLevel {
315 DisableAll,
317 #[default]
319 ExceptHistogramOrTimers,
320 ExceptTimers,
322 ExceptDetailedTimers,
324 ExceptTimeForMutex,
326 All,
328}
329
330impl RocksDbStatisticsLevel {
331 fn to_rocksdb(self) -> rocksdb::statistics::StatsLevel {
332 use rocksdb::statistics::StatsLevel;
333 match self {
334 Self::DisableAll => StatsLevel::DisableAll,
335 Self::ExceptHistogramOrTimers => StatsLevel::ExceptHistogramOrTimers,
336 Self::ExceptTimers => StatsLevel::ExceptTimers,
337 Self::ExceptDetailedTimers => StatsLevel::ExceptDetailedTimers,
338 Self::ExceptTimeForMutex => StatsLevel::ExceptTimeForMutex,
339 Self::All => StatsLevel::All,
340 }
341 }
342}
343
344#[cfg(test)]
345mod statistics_level_tests {
346 use std::str::FromStr as _;
347
348 use super::RocksDbStatisticsLevel;
349
350 #[test]
351 fn parses_kebab_case_names() {
352 let cases = [
353 ("disable-all", RocksDbStatisticsLevel::DisableAll),
354 (
355 "except-histogram-or-timers",
356 RocksDbStatisticsLevel::ExceptHistogramOrTimers,
357 ),
358 ("except-timers", RocksDbStatisticsLevel::ExceptTimers),
359 (
360 "except-detailed-timers",
361 RocksDbStatisticsLevel::ExceptDetailedTimers,
362 ),
363 (
364 "except-time-for-mutex",
365 RocksDbStatisticsLevel::ExceptTimeForMutex,
366 ),
367 ("all", RocksDbStatisticsLevel::All),
368 ];
369 for (name, expected) in cases {
370 assert_eq!(RocksDbStatisticsLevel::from_str(name), Ok(expected));
371 }
372 assert!(RocksDbStatisticsLevel::from_str("not-a-level").is_err());
373 }
374}
375
376#[derive(Clone, Debug, Deserialize, Serialize)]
378pub struct RocksDbStoreInternalConfig {
379 pub path_with_guard: PathWithGuard,
381 pub spawn_mode: RocksDbSpawnMode,
383 #[serde(default)]
387 pub enable_statistics: bool,
388 #[serde(default)]
390 pub statistics_level: RocksDbStatisticsLevel,
391}
392
393impl RocksDbDatabaseInternal {
394 fn check_namespace(namespace: &str) -> Result<(), RocksDbStoreInternalError> {
395 if !namespace
396 .chars()
397 .all(|character| character.is_ascii_alphanumeric() || character == '_')
398 {
399 return Err(RocksDbStoreInternalError::InvalidNamespace);
400 }
401 Ok(())
402 }
403
404 fn build(
405 config: &RocksDbStoreInternalConfig,
406 namespace: &str,
407 ) -> Result<RocksDbDatabaseInternal, RocksDbStoreInternalError> {
408 let start_key = ROOT_KEY_DOMAIN.to_vec();
409 let temp_store = RocksDbStoreInternal::build(config, namespace, start_key)?;
411 Ok(RocksDbDatabaseInternal {
412 executor: temp_store.executor,
413 path_with_guard: temp_store.path_with_guard,
414 spawn_mode: temp_store.spawn_mode,
415 })
416 }
417}
418
419impl RocksDbStoreInternal {
420 fn build(
421 config: &RocksDbStoreInternalConfig,
422 namespace: &str,
423 start_key: Vec<u8>,
424 ) -> Result<RocksDbStoreInternal, RocksDbStoreInternalError> {
425 RocksDbDatabaseInternal::check_namespace(namespace)?;
426 let mut path_buf = config.path_with_guard.path_buf.clone();
427 let mut path_with_guard = config.path_with_guard.clone();
428 path_buf.push(namespace);
429 path_with_guard.path_buf = path_buf.clone();
430 let spawn_mode = config.spawn_mode;
431 if !std::path::Path::exists(&path_buf) {
432 std::fs::create_dir_all(path_buf.clone())?;
433 }
434 let sys = System::new_with_specifics(
435 RefreshKind::nothing().with_memory(MemoryRefreshKind::nothing().with_ram()),
436 );
437 let num_cpus = get_available_cpus();
438 let total_ram = get_available_memory(&sys);
439
440 let mut options = rocksdb::Options::default();
441 options.create_if_missing(true);
442 options.create_missing_column_families(true);
443
444 options.set_write_buffer_size(WRITE_BUFFER_SIZE);
446 options.set_max_write_buffer_number(MAX_WRITE_BUFFER_NUMBER);
447 options.set_compression_type(rocksdb::DBCompressionType::Lz4);
448 options.set_level_zero_slowdown_writes_trigger(8);
449 options.set_level_zero_stop_writes_trigger(12);
450 options.set_level_zero_file_num_compaction_trigger(2);
451 options.increase_parallelism(num_cpus);
455 options.set_max_background_jobs(num_cpus);
456 options.set_max_subcompactions(num_cpus as u32);
457 options.set_level_compaction_dynamic_level_bytes(true);
458
459 options.set_compaction_style(DBCompactionStyle::Level);
460 options.set_target_file_size_base(2 * WRITE_BUFFER_SIZE as u64);
461
462 let mut block_options = BlockBasedOptions::default();
463 block_options.set_pin_l0_filter_and_index_blocks_in_cache(true);
464 block_options.set_cache_index_and_filter_blocks(true);
465 block_options.set_block_cache(&Cache::new_hyper_clock_cache(
471 total_ram / 4,
472 HYPER_CLOCK_CACHE_BLOCK_SIZE,
473 ));
474
475 let write_buffer_manager =
478 WriteBufferManager::new_write_buffer_manager(total_ram / 4, true);
479 options.set_write_buffer_manager(&write_buffer_manager);
480
481 block_options.set_bloom_filter(10.0, false);
483 block_options.set_whole_key_filtering(false);
484
485 block_options.set_block_size(32 * 1024);
487 block_options.set_format_version(5);
489
490 options.set_block_based_table_factory(&block_options);
491
492 let prefix_extractor = SliceTransform::create_fixed_prefix(8);
495 options.set_prefix_extractor(prefix_extractor);
496
497 options.set_memtable_prefix_bloom_ratio(0.125);
499 options.set_optimize_filters_for_hits(true);
501 options.set_allow_mmap_reads(true);
503 options.set_advise_random_on_open(false);
505
506 if config.enable_statistics {
507 options.enable_statistics();
508 options.set_statistics_level(config.statistics_level.to_rocksdb());
509 }
510
511 let db = Arc::new(DB::open(&options, path_buf)?);
512 #[cfg(with_metrics)]
513 if config.enable_statistics {
514 statistics_metrics::register(Arc::new(options), db.clone());
515 }
516 let executor = RocksDbStoreExecutor { db, start_key };
517 Ok(RocksDbStoreInternal {
518 executor,
519 path_with_guard,
520 spawn_mode,
521 root_key_written: Arc::new(AtomicBool::new(false)),
522 })
523 }
524}
525
526#[cfg(with_metrics)]
532mod statistics_metrics {
533 use std::sync::{Arc, OnceLock};
534
535 use prometheus::{
536 core::{Collector, Desc},
537 proto::MetricFamily,
538 IntGauge,
539 };
540 use rocksdb::{statistics::Ticker, Options};
541
542 use super::DB;
543
544 enum Source {
545 Ticker(Ticker),
546 Property(&'static str),
547 }
548
549 struct Entry {
550 source: Source,
551 gauge: IntGauge,
552 }
553
554 fn definitions() -> Vec<(&'static str, &'static str, Source)> {
555 vec![
556 (
557 "linera_rocksdb_block_cache_hit",
558 "Cumulative RocksDB block cache hits since open",
559 Source::Ticker(Ticker::BlockCacheHit),
560 ),
561 (
562 "linera_rocksdb_block_cache_miss",
563 "Cumulative RocksDB block cache misses since open",
564 Source::Ticker(Ticker::BlockCacheMiss),
565 ),
566 (
567 "linera_rocksdb_compact_read_bytes",
568 "Cumulative bytes read during compaction since open",
569 Source::Ticker(Ticker::CompactReadBytes),
570 ),
571 (
572 "linera_rocksdb_compact_write_bytes",
573 "Cumulative bytes written during compaction since open",
574 Source::Ticker(Ticker::CompactWriteBytes),
575 ),
576 (
577 "linera_rocksdb_flush_write_bytes",
578 "Cumulative bytes written during flushes since open",
579 Source::Ticker(Ticker::FlushWriteBytes),
580 ),
581 (
582 "linera_rocksdb_stall_micros",
583 "Cumulative write-stall time in microseconds since open",
584 Source::Ticker(Ticker::StallMicros),
585 ),
586 (
587 "linera_rocksdb_bytes_written",
588 "Cumulative user bytes written since open",
589 Source::Ticker(Ticker::BytesWritten),
590 ),
591 (
592 "linera_rocksdb_bytes_read",
593 "Cumulative user bytes read since open",
594 Source::Ticker(Ticker::BytesRead),
595 ),
596 (
597 "linera_rocksdb_wal_bytes",
598 "Cumulative bytes written to the write-ahead log since open",
599 Source::Ticker(Ticker::WalFileBytes),
600 ),
601 (
602 "linera_rocksdb_bloom_filter_useful",
603 "Cumulative count of reads avoided by the bloom filter since open",
604 Source::Ticker(Ticker::BloomFilterUseful),
605 ),
606 (
607 "linera_rocksdb_memtable_hit",
608 "Cumulative memtable hits since open",
609 Source::Ticker(Ticker::MemtableHit),
610 ),
611 (
612 "linera_rocksdb_memtable_miss",
613 "Cumulative memtable misses since open",
614 Source::Ticker(Ticker::MemtableMiss),
615 ),
616 (
617 "linera_rocksdb_number_keys_written",
618 "Cumulative number of keys written since open",
619 Source::Ticker(Ticker::NumberKeysWritten),
620 ),
621 (
622 "linera_rocksdb_num_files_at_level0",
623 "Number of files at level 0",
624 Source::Property("rocksdb.num-files-at-level0"),
625 ),
626 (
627 "linera_rocksdb_estimate_pending_compaction_bytes",
628 "Estimated bytes pending compaction",
629 Source::Property("rocksdb.estimate-pending-compaction-bytes"),
630 ),
631 (
632 "linera_rocksdb_num_running_compactions",
633 "Number of currently running compactions",
634 Source::Property("rocksdb.num-running-compactions"),
635 ),
636 (
637 "linera_rocksdb_num_running_flushes",
638 "Number of currently running flushes",
639 Source::Property("rocksdb.num-running-flushes"),
640 ),
641 (
642 "linera_rocksdb_is_write_stopped",
643 "Whether writes are currently stopped (1) or not (0)",
644 Source::Property("rocksdb.is-write-stopped"),
645 ),
646 (
647 "linera_rocksdb_actual_delayed_write_rate",
648 "Current delayed write rate in bytes/s (0 when not delayed)",
649 Source::Property("rocksdb.actual-delayed-write-rate"),
650 ),
651 (
652 "linera_rocksdb_cur_size_all_mem_tables",
653 "Approximate size in bytes of all active and unflushed memtables",
654 Source::Property("rocksdb.cur-size-all-mem-tables"),
655 ),
656 (
657 "linera_rocksdb_num_immutable_mem_table",
658 "Number of immutable memtables not yet flushed",
659 Source::Property("rocksdb.num-immutable-mem-table"),
660 ),
661 (
662 "linera_rocksdb_live_sst_files_size",
663 "Total size in bytes of all live SST files",
664 Source::Property("rocksdb.live-sst-files-size"),
665 ),
666 (
667 "linera_rocksdb_total_sst_files_size",
668 "Total size in bytes of all SST files including obsolete ones",
669 Source::Property("rocksdb.total-sst-files-size"),
670 ),
671 (
672 "linera_rocksdb_estimate_num_keys",
673 "Estimated number of keys in the database",
674 Source::Property("rocksdb.estimate-num-keys"),
675 ),
676 (
677 "linera_rocksdb_block_cache_usage",
678 "Memory in bytes used by the block cache",
679 Source::Property("rocksdb.block-cache-usage"),
680 ),
681 (
682 "linera_rocksdb_block_cache_capacity",
683 "Capacity in bytes of the block cache",
684 Source::Property("rocksdb.block-cache-capacity"),
685 ),
686 ]
687 }
688
689 struct RocksDbStatisticsCollector {
690 options: Arc<Options>,
691 db: Arc<DB>,
692 entries: Vec<Entry>,
693 }
694
695 impl RocksDbStatisticsCollector {
696 fn new(options: Arc<Options>, db: Arc<DB>) -> Self {
697 let entries = definitions()
698 .into_iter()
699 .map(|(name, help, source)| Entry {
700 source,
701 gauge: IntGauge::new(name, help)
702 .expect("RocksDB statistics metric name is valid"),
703 })
704 .collect();
705 Self {
706 options,
707 db,
708 entries,
709 }
710 }
711 }
712
713 impl Collector for RocksDbStatisticsCollector {
714 fn desc(&self) -> Vec<&Desc> {
715 self.entries
716 .iter()
717 .flat_map(|entry| entry.gauge.desc())
718 .collect()
719 }
720
721 fn collect(&self) -> Vec<MetricFamily> {
722 self.entries
723 .iter()
724 .flat_map(|entry| {
725 let value = match &entry.source {
726 Source::Ticker(ticker) => self.options.get_ticker_count(*ticker) as i64,
727 Source::Property(property) => {
728 self.db
729 .property_int_value(*property)
730 .ok()
731 .flatten()
732 .unwrap_or(0) as i64
733 }
734 };
735 entry.gauge.set(value);
736 entry.gauge.collect()
737 })
738 .collect()
739 }
740 }
741
742 pub(super) fn register(options: Arc<Options>, db: Arc<DB>) {
743 static REGISTERED: OnceLock<()> = OnceLock::new();
744 if REGISTERED.set(()).is_err() {
745 tracing::warn!(
746 "RocksDB statistics collector is already registered; skipping additional store"
747 );
748 return;
749 }
750 let collector = RocksDbStatisticsCollector::new(options, db);
751 if let Err(error) = prometheus::register(Box::new(collector)) {
752 tracing::warn!("failed to register the RocksDB statistics collector: {error}");
753 }
754 }
755
756 #[cfg(test)]
757 mod tests {
758 use std::collections::HashSet;
759
760 use super::{definitions, IntGauge};
761
762 #[test]
763 fn definitions_build_unique_valid_gauges() {
764 let definitions = definitions();
765 assert!(!definitions.is_empty());
766 let mut names = HashSet::new();
767 for (name, help, _source) in &definitions {
768 assert!(!help.is_empty(), "metric {name} has empty help text");
769 assert!(names.insert(*name), "duplicate metric name: {name}");
770 IntGauge::new(*name, *help).expect("metric definition should be valid");
771 }
772 }
773 }
774}
775
776impl WithError for RocksDbStoreInternal {
777 type Error = RocksDbStoreInternalError;
778}
779
780impl ReadableKeyValueStore for RocksDbStoreInternal {
781 const MAX_KEY_SIZE: usize = MAX_KEY_SIZE;
782
783 fn root_key(&self) -> Result<Vec<u8>, RocksDbStoreInternalError> {
784 assert!(self.executor.start_key.starts_with(&ROOT_KEY_DOMAIN));
785 let root_key = bcs::from_bytes(&self.executor.start_key[ROOT_KEY_DOMAIN.len()..])?;
786 Ok(root_key)
787 }
788
789 async fn read_value_bytes(
790 &self,
791 key: &[u8],
792 ) -> Result<Option<Vec<u8>>, RocksDbStoreInternalError> {
793 check_key_size(key)?;
794 let db = self.executor.db.clone();
795 let mut full_key = self.executor.start_key.to_vec();
796 full_key.extend(key);
797 self.spawn_mode
798 .spawn(move |x| Ok(db.get(&x)?), full_key)
799 .await
800 }
801
802 async fn contains_key(&self, key: &[u8]) -> Result<bool, RocksDbStoreInternalError> {
803 check_key_size(key)?;
804 let db = self.executor.db.clone();
805 let mut full_key = self.executor.start_key.to_vec();
806 full_key.extend(key);
807 self.spawn_mode
808 .spawn(
809 move |x| {
810 if !db.key_may_exist(&x) {
811 return Ok(false);
812 }
813 Ok(db.get(&x)?.is_some())
814 },
815 full_key,
816 )
817 .await
818 }
819
820 async fn contains_keys(
821 &self,
822 keys: &[Vec<u8>],
823 ) -> Result<Vec<bool>, RocksDbStoreInternalError> {
824 let executor = self.executor.clone();
825 self.spawn_mode
826 .spawn(move |x| executor.contains_keys_internal(x), keys.to_vec())
827 .await
828 }
829
830 async fn read_multi_values_bytes(
831 &self,
832 keys: &[Vec<u8>],
833 ) -> Result<Vec<Option<Vec<u8>>>, RocksDbStoreInternalError> {
834 let executor = self.executor.clone();
835 self.spawn_mode
836 .spawn(
837 move |x| executor.read_multi_values_bytes_internal(x),
838 keys.to_vec(),
839 )
840 .await
841 }
842
843 async fn find_keys_by_prefix(
844 &self,
845 key_prefix: &[u8],
846 ) -> Result<Vec<Vec<u8>>, RocksDbStoreInternalError> {
847 let executor = self.executor.clone();
848 let key_prefix = key_prefix.to_vec();
849 self.spawn_mode
850 .spawn(
851 move |x| executor.find_keys_by_prefix_internal(x),
852 key_prefix,
853 )
854 .await
855 }
856
857 async fn find_key_values_by_prefix(
858 &self,
859 key_prefix: &[u8],
860 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>, RocksDbStoreInternalError> {
861 let executor = self.executor.clone();
862 let key_prefix = key_prefix.to_vec();
863 self.spawn_mode
864 .spawn(
865 move |x| executor.find_key_values_by_prefix_internal(x),
866 key_prefix,
867 )
868 .await
869 }
870}
871
872impl WritableKeyValueStore for RocksDbStoreInternal {
873 const MAX_VALUE_SIZE: usize = MAX_VALUE_SIZE;
874
875 async fn write_batch(&self, batch: Batch) -> Result<(), RocksDbStoreInternalError> {
876 let write_root_key = !self.root_key_written.fetch_or(true, Ordering::SeqCst);
877 let executor = self.executor.clone();
878 self.spawn_mode
879 .spawn(
880 move |x| executor.write_batch_internal(x, write_root_key),
881 batch,
882 )
883 .await
884 }
885
886 async fn clear_journal(&self) -> Result<(), RocksDbStoreInternalError> {
887 Ok(())
888 }
889}
890
891impl KeyValueDatabase for RocksDbDatabaseInternal {
892 type Config = RocksDbStoreInternalConfig;
893 type Store = RocksDbStoreInternal;
894
895 fn get_name() -> String {
896 "rocksdb internal".to_string()
897 }
898
899 async fn connect(
900 config: &Self::Config,
901 namespace: &str,
902 ) -> Result<Self, RocksDbStoreInternalError> {
903 Self::build(config, namespace)
904 }
905
906 fn open_shared(&self, root_key: &[u8]) -> Result<Self::Store, RocksDbStoreInternalError> {
907 let mut start_key = ROOT_KEY_DOMAIN.to_vec();
908 start_key.extend(bcs::to_bytes(root_key)?);
909 let mut executor = self.executor.clone();
910 executor.start_key = start_key;
911 Ok(RocksDbStoreInternal {
912 executor,
913 path_with_guard: self.path_with_guard.clone(),
914 spawn_mode: self.spawn_mode,
915 root_key_written: Arc::new(AtomicBool::new(false)),
916 })
917 }
918
919 fn open_exclusive(&self, root_key: &[u8]) -> Result<Self::Store, RocksDbStoreInternalError> {
920 self.open_shared(root_key)
921 }
922
923 async fn list_all(config: &Self::Config) -> Result<Vec<String>, RocksDbStoreInternalError> {
924 let entries = std::fs::read_dir(config.path_with_guard.path_buf.clone())?;
925 let mut namespaces = Vec::new();
926 for entry in entries {
927 let entry = entry?;
928 if !entry.file_type()?.is_dir() {
929 return Err(RocksDbStoreInternalError::NonDirectoryNamespace);
930 }
931 let namespace = match entry.file_name().into_string() {
932 Err(error) => {
933 return Err(RocksDbStoreInternalError::IntoStringError(error));
934 }
935 Ok(namespace) => namespace,
936 };
937 namespaces.push(namespace);
938 }
939 Ok(namespaces)
940 }
941
942 async fn list_root_keys(&self) -> Result<Vec<Vec<u8>>, RocksDbStoreInternalError> {
943 let mut store = self.open_shared(&[])?;
944 store.executor.start_key = vec![STORED_ROOT_KEYS_PREFIX];
945 let bcs_root_keys = store.find_keys_by_prefix(&[]).await?;
946 let mut root_keys = Vec::new();
947 for bcs_root_key in bcs_root_keys {
948 let root_key = bcs::from_bytes::<Vec<u8>>(&bcs_root_key)?;
949 root_keys.push(root_key);
950 }
951 Ok(root_keys)
952 }
953
954 async fn delete_all(config: &Self::Config) -> Result<(), RocksDbStoreInternalError> {
955 let namespaces = Self::list_all(config).await?;
956 for namespace in namespaces {
957 let mut path_buf = config.path_with_guard.path_buf.clone();
958 path_buf.push(&namespace);
959 std::fs::remove_dir_all(path_buf.as_path())?;
960 }
961 Ok(())
962 }
963
964 async fn exists(
965 config: &Self::Config,
966 namespace: &str,
967 ) -> Result<bool, RocksDbStoreInternalError> {
968 Self::check_namespace(namespace)?;
969 let mut path_buf = config.path_with_guard.path_buf.clone();
970 path_buf.push(namespace);
971 let test = std::path::Path::exists(&path_buf);
972 Ok(test)
973 }
974
975 async fn create(
976 config: &Self::Config,
977 namespace: &str,
978 ) -> Result<(), RocksDbStoreInternalError> {
979 Self::check_namespace(namespace)?;
980 let mut path_buf = config.path_with_guard.path_buf.clone();
981 path_buf.push(namespace);
982 if std::path::Path::exists(&path_buf) {
983 return Err(RocksDbStoreInternalError::StoreAlreadyExists);
984 }
985 std::fs::create_dir_all(path_buf)?;
986 Ok(())
987 }
988
989 async fn delete(
990 config: &Self::Config,
991 namespace: &str,
992 ) -> Result<(), RocksDbStoreInternalError> {
993 Self::check_namespace(namespace)?;
994 let mut path_buf = config.path_with_guard.path_buf.clone();
995 path_buf.push(namespace);
996 let path = path_buf.as_path();
997 std::fs::remove_dir_all(path)?;
998 Ok(())
999 }
1000}
1001
1002#[cfg(with_testing)]
1003impl TestKeyValueDatabase for RocksDbDatabaseInternal {
1004 async fn new_test_config() -> Result<RocksDbStoreInternalConfig, RocksDbStoreInternalError> {
1005 let path_with_guard = PathWithGuard::new_testing();
1006 let spawn_mode = RocksDbSpawnMode::get_spawn_mode_from_runtime();
1007 Ok(RocksDbStoreInternalConfig {
1008 path_with_guard,
1009 spawn_mode,
1010 enable_statistics: false,
1011 statistics_level: RocksDbStatisticsLevel::default(),
1012 })
1013 }
1014}
1015
1016#[derive(Error, Debug)]
1018pub enum RocksDbStoreInternalError {
1019 #[error("Store already exists")]
1021 StoreAlreadyExists,
1022
1023 #[error("tokio join error: {0}")]
1025 TokioJoinError(#[from] tokio::task::JoinError),
1026
1027 #[error("RocksDB error: {0}")]
1029 RocksDb(#[from] rocksdb::Error),
1030
1031 #[error("Namespaces should be directories")]
1033 NonDirectoryNamespace,
1034
1035 #[error("error in the conversion from OsString: {0:?}")]
1037 IntoStringError(OsString),
1038
1039 #[error("The key must have at most 8 MiB")]
1041 KeyTooLong,
1042
1043 #[error("Namespace contains forbidden characters")]
1045 InvalidNamespace,
1046
1047 #[error("Filesystem error: {0}")]
1049 FsError(#[from] std::io::Error),
1050
1051 #[error(transparent)]
1053 BcsError(#[from] bcs::Error),
1054}
1055
1056#[derive(Clone, Debug, Deserialize, Serialize)]
1058pub struct PathWithGuard {
1059 pub path_buf: PathBuf,
1061 #[serde(skip)]
1063 _dir_guard: Option<Arc<TempDir>>,
1064}
1065
1066impl PathWithGuard {
1067 pub fn new(path_buf: PathBuf) -> Self {
1069 Self {
1070 path_buf,
1071 _dir_guard: None,
1072 }
1073 }
1074
1075 #[cfg(with_testing)]
1077 fn new_testing() -> PathWithGuard {
1078 let dir = TempDir::new().unwrap();
1079 let path_buf = dir.path().to_path_buf();
1080 let dir_guard = Some(Arc::new(dir));
1081 PathWithGuard {
1082 path_buf,
1083 _dir_guard: dir_guard,
1084 }
1085 }
1086}
1087
1088impl PartialEq for PathWithGuard {
1089 fn eq(&self, other: &Self) -> bool {
1090 self.path_buf == other.path_buf
1091 }
1092}
1093impl Eq for PathWithGuard {}
1094
1095impl KeyValueStoreError for RocksDbStoreInternalError {
1096 const BACKEND: &'static str = "rocks_db";
1097}
1098
1099pub type RocksDbStoreError = ValueSplittingError<RocksDbStoreInternalError>;
1101
1102pub type RocksDbStoreConfig = LruCachingConfig<RocksDbStoreInternalConfig>;
1104
1105#[cfg(with_metrics)]
1107pub type RocksDbDatabase = MeteredDatabase<
1108 LruCachingDatabase<
1109 MeteredDatabase<ValueSplittingDatabase<MeteredDatabase<RocksDbDatabaseInternal>>>,
1110 >,
1111>;
1112#[cfg(not(with_metrics))]
1114pub type RocksDbDatabase = LruCachingDatabase<ValueSplittingDatabase<RocksDbDatabaseInternal>>;
1115
1116#[cfg(with_testing)]
1117impl crate::backends::DatabaseBackup for RocksDbDatabaseInternal {
1118 fn backup_to(&self, dir: &std::path::Path) -> anyhow::Result<()> {
1119 use rocksdb::{
1120 backup::{BackupEngine, BackupEngineOptions},
1121 Env,
1122 };
1123 let opts = BackupEngineOptions::new(dir)?;
1124 let env = Env::new()?;
1125 let mut engine = BackupEngine::open(&opts, &env)?;
1126 engine.create_new_backup_flush(&*self.executor.db, true)?;
1127 Ok(())
1128 }
1129}