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