Skip to main content

typed_store/
database.rs

1// Copyright (c) 2026 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{
5    borrow::Borrow,
6    marker::PhantomData,
7    ops::{Bound, Deref, RangeBounds},
8    path::{Path, PathBuf},
9    sync::{Arc, Weak},
10    time::Duration,
11};
12
13use fastcrypto::hash::{Digest, HashFunction};
14use iota_common::debug_fatal;
15use iota_macros::{fail_point, nondeterministic};
16use prometheus_filtered::{Histogram, HistogramTimer};
17use rocksdb::{
18    DBPinnableSlice, DBWithThreadMode, Error, LiveFile, MultiThreaded, ReadOptions,
19    SnapshotWithThreadMode, WriteBatch,
20};
21use serde::{Serialize, de::DeserializeOwned};
22use tokio::{sync::oneshot, time::Instant};
23use tracing::{debug, error, instrument, warn};
24use typed_store_error::TypedStoreError;
25
26use crate::{
27    DbIterator,
28    memstore::{InMemoryBatch, InMemoryDB},
29    metrics::{DBMetrics, RocksDBPerfContext, SamplingInterval},
30    rocks::{
31        RocksDB,
32        errors::{typed_store_err_from_bcs_err, typed_store_err_from_rocks_err},
33        options::ReadWriteOptions,
34        rocks_cf, rocks_util,
35        safe_iter::{SafeIter, SafeRevIter},
36    },
37    traits::{Map, TableSummary},
38    util::{
39        be_fix_int_ser, iterator_bounds_with_range, prefix_iterator_bounds,
40        prefix_iterator_bounds_with_range,
41    },
42};
43
44#[derive(Clone)]
45pub(crate) enum ColumnFamily {
46    Rocks(String),
47    #[allow(dead_code)]
48    InMemory(String),
49}
50
51impl std::fmt::Debug for ColumnFamily {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        match self {
54            ColumnFamily::Rocks(name) => write!(f, "RocksDB cf: {name}"),
55            ColumnFamily::InMemory(name) => write!(f, "InMemory cf: {name}"),
56        }
57    }
58}
59
60impl ColumnFamily {
61    pub(crate) fn name(&self) -> &str {
62        match self {
63            ColumnFamily::Rocks(name) => name,
64            ColumnFamily::InMemory(name) => name,
65        }
66    }
67}
68
69pub(crate) enum Storage {
70    Rocks(RocksDB),
71    #[allow(dead_code)]
72    InMemory(InMemoryDB),
73}
74
75/// A point-in-time RocksDB snapshot of a database.
76///
77/// Reads through it ignore every write made after it was taken. It pins a
78/// sequence number rather than copying data, and does not survive a restart.
79///
80/// While it is alive, RocksDB keeps on disk every older version it may still
81/// read, so take it as late as possible and drop it as soon as the read is
82/// done.
83///
84/// A `CompactionFilter` ignores snapshots and may drop rows one is still
85/// reading, so never install one on a column family read through a snapshot.
86pub struct DbSnapshot<'db> {
87    snapshot: SnapshotWithThreadMode<'db, DBWithThreadMode<MultiThreaded>>,
88}
89
90impl std::fmt::Debug for Storage {
91    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92        match self {
93            Storage::Rocks(db) => write!(f, "RocksDB Storage {db:?}"),
94            Storage::InMemory(db) => write!(f, "InMemoryDB Storage {db:?}"),
95        }
96    }
97}
98
99pub(crate) enum GetResult<'a> {
100    Rocks(DBPinnableSlice<'a>),
101    InMemory(Vec<u8>),
102}
103
104impl Deref for GetResult<'_> {
105    type Target = [u8];
106    fn deref(&self) -> &[u8] {
107        match self {
108            GetResult::Rocks(d) => d.deref(),
109            GetResult::InMemory(d) => d.deref(),
110        }
111    }
112}
113
114pub enum StorageWriteBatch {
115    Rocks(rocksdb::WriteBatch),
116    InMemory(InMemoryBatch),
117}
118
119#[derive(Debug, Default)]
120pub struct MetricConf {
121    pub db_name: String,
122    pub read_sample_interval: SamplingInterval,
123    pub write_sample_interval: SamplingInterval,
124    pub iter_sample_interval: SamplingInterval,
125}
126
127impl MetricConf {
128    pub fn new(db_name: &str) -> Self {
129        if db_name.is_empty() {
130            error!("A meaningful db name should be used for metrics reporting.")
131        }
132        Self {
133            db_name: db_name.to_string(),
134            read_sample_interval: SamplingInterval::default(),
135            write_sample_interval: SamplingInterval::default(),
136            iter_sample_interval: SamplingInterval::default(),
137        }
138    }
139
140    pub fn with_sampling(self, read_interval: SamplingInterval) -> Self {
141        Self {
142            db_name: self.db_name,
143            read_sample_interval: read_interval,
144            write_sample_interval: SamplingInterval::default(),
145            iter_sample_interval: SamplingInterval::default(),
146        }
147    }
148}
149
150const CF_METRICS_REPORT_PERIOD_SECS: u64 = 30;
151
152#[derive(Debug)]
153pub struct Database {
154    pub(crate) storage: Storage,
155    pub(crate) metric_conf: MetricConf,
156    /// Declared after `storage`: fields are dropped in declaration order, so
157    /// this goes only once RocksDB has closed and released its `LOCK` file.
158    closed: Arc<()>,
159}
160
161/// What [`wait_for_database_close`] waits on; see [`Database::close_handle`].
162pub struct DatabaseClose(Weak<()>);
163
164impl Drop for Database {
165    fn drop(&mut self) {
166        DBMetrics::get().decrement_num_active_dbs(&self.metric_conf.db_name);
167    }
168}
169
170impl Database {
171    /// A handle for [`wait_for_database_close`]. Take it while the database
172    /// is still open, before its last handle is dropped.
173    pub fn close_handle(&self) -> DatabaseClose {
174        DatabaseClose(Arc::downgrade(&self.closed))
175    }
176
177    pub(crate) fn new(storage: Storage, metric_conf: MetricConf) -> Self {
178        DBMetrics::get().increment_num_active_dbs(&metric_conf.db_name);
179        Self {
180            storage,
181            metric_conf,
182            closed: Arc::new(()),
183        }
184    }
185
186    pub(crate) fn get<K: AsRef<[u8]>>(
187        &self,
188        cf: &ColumnFamily,
189        key: K,
190        readopts: &ReadOptions,
191    ) -> Result<Option<GetResult<'_>>, TypedStoreError> {
192        match (&self.storage, cf) {
193            (Storage::Rocks(db), ColumnFamily::Rocks(_)) => Ok(db
194                .underlying
195                .get_pinned_cf_opt(&rocks_cf(db, cf.name())?, key, readopts)
196                .map_err(typed_store_err_from_rocks_err)?
197                .map(GetResult::Rocks)),
198            (Storage::InMemory(db), ColumnFamily::InMemory(cf_name)) => {
199                Ok(db.get(cf_name, key)?.map(GetResult::InMemory))
200            }
201
202            _ => Err(TypedStoreError::RocksDB(
203                "typed store invariant violation".to_string(),
204            )),
205        }
206    }
207
208    pub(crate) fn multi_get<I, K>(
209        &self,
210        cf: &ColumnFamily,
211        keys: I,
212        readopts: &ReadOptions,
213    ) -> Vec<Result<Option<GetResult<'_>>, TypedStoreError>>
214    where
215        I: IntoIterator<Item = K>,
216        K: AsRef<[u8]>,
217    {
218        match (&self.storage, cf) {
219            (Storage::Rocks(db), ColumnFamily::Rocks(_)) => {
220                let keys_vec: Vec<K> = keys.into_iter().collect();
221                let handle = match rocks_cf(db, cf.name()) {
222                    Ok(handle) => handle,
223                    // Fail every key, so the result keeps one slot per key.
224                    Err(e) => return keys_vec.iter().map(|_| Err(e.clone())).collect(),
225                };
226                let res = db.underlying.batched_multi_get_cf_opt(
227                    &handle,
228                    keys_vec.iter(),
229                    // sorted_input
230                    false,
231                    readopts,
232                );
233                res.into_iter()
234                    .map(|r| {
235                        r.map_err(typed_store_err_from_rocks_err)
236                            .map(|item| item.map(GetResult::Rocks))
237                    })
238                    .collect()
239            }
240            (Storage::InMemory(db), ColumnFamily::InMemory(cf_name)) => db
241                .multi_get(cf_name, keys)
242                .into_iter()
243                .map(|r| r.map(|value| value.map(GetResult::InMemory)))
244                .collect(),
245            _ => unreachable!("typed store invariant violation"),
246        }
247    }
248
249    pub fn cf_handle(&self, name: &str) -> Option<()> {
250        match &self.storage {
251            Storage::Rocks(db) => db.underlying.cf_handle(name).map(|_| ()),
252            Storage::InMemory(db) => db.has_cf(name).then_some(()),
253        }
254    }
255
256    /// Creates a new column family at runtime. Fails if a column family with
257    /// this name already exists.
258    ///
259    /// `options` hold until the database is next opened, after which a
260    /// column family the caller does not declare is opened with the
261    /// crate's options instead. Declare it to keep them.
262    #[instrument(level = "debug", skip(self, options), err)]
263    pub fn create_cf(&self, name: &str, options: &rocksdb::Options) -> Result<(), TypedStoreError> {
264        match &self.storage {
265            Storage::Rocks(db) => nondeterministic!(db.underlying.create_cf(name, options))
266                .map_err(typed_store_err_from_rocks_err),
267            Storage::InMemory(db) => db.create_cf(name),
268        }
269    }
270
271    /// Maps still holding the column family keep working but report
272    /// [`TypedStoreError::UnregisteredColumn`] on every operation.
273    #[instrument(level = "debug", skip(self), err)]
274    pub fn drop_cf(&self, name: &str) -> Result<(), TypedStoreError> {
275        if name == rocksdb::DEFAULT_COLUMN_FAMILY_NAME {
276            // rocksdb refuses this, but not before its wrapper has taken the
277            // handle out of its own map, losing it for the rest of the process.
278            return Err(TypedStoreError::RocksDB(format!(
279                "the {name} column family cannot be dropped"
280            )));
281        }
282        match &self.storage {
283            Storage::Rocks(db) => {
284                // RocksDB deletes a dropped column family's files once its
285                // last handle is released. Holding one across the drop moves
286                // that past the drop, which blocks every column family lookup
287                // of this database while it runs.
288                let handle = db.underlying.cf_handle(name);
289                let dropped = nondeterministic!(db.underlying.drop_cf(name))
290                    .map_err(typed_store_err_from_rocks_err);
291                drop(handle);
292                if dropped.is_ok() {
293                    DBMetrics::get().remove_column_family(name);
294                }
295                dropped
296            }
297            Storage::InMemory(db) => {
298                db.drop_cf(name);
299                Ok(())
300            }
301        }
302    }
303
304    pub(crate) fn delete_cf<K: AsRef<[u8]>>(
305        &self,
306        cf: &ColumnFamily,
307        key: K,
308    ) -> Result<(), TypedStoreError> {
309        fail_point!("delete-cf-before");
310        let ret = match (&self.storage, cf) {
311            (Storage::Rocks(db), ColumnFamily::Rocks(_)) => {
312                rocks_cf(db, cf.name()).and_then(|handle| {
313                    db.underlying
314                        .delete_cf(&handle, key)
315                        .map_err(typed_store_err_from_rocks_err)
316                })
317            }
318            (Storage::InMemory(db), ColumnFamily::InMemory(cf_name)) => {
319                db.delete(cf_name, key.as_ref())
320            }
321            _ => Err(TypedStoreError::RocksDB(
322                "typed store invariant violation".to_string(),
323            )),
324        };
325        fail_point!("delete-cf-after");
326        #[allow(clippy::let_and_return)]
327        ret
328    }
329
330    pub fn path_for_pruning(&self) -> &Path {
331        match &self.storage {
332            Storage::Rocks(rocks) => rocks.underlying.path(),
333            _ => unimplemented!("method is only supported for rocksdb backend"),
334        }
335    }
336
337    pub(crate) fn put_cf(
338        &self,
339        cf: &ColumnFamily,
340        key: Vec<u8>,
341        value: Vec<u8>,
342    ) -> Result<(), TypedStoreError> {
343        fail_point!("put-cf-before");
344        let ret = match (&self.storage, cf) {
345            (Storage::Rocks(db), ColumnFamily::Rocks(_)) => {
346                rocks_cf(db, cf.name()).and_then(|handle| {
347                    db.underlying
348                        .put_cf(&handle, key, value)
349                        .map_err(typed_store_err_from_rocks_err)
350                })
351            }
352            (Storage::InMemory(db), ColumnFamily::InMemory(cf_name)) => db.put(cf_name, key, value),
353            _ => Err(TypedStoreError::RocksDB(
354                "typed store invariant violation".to_string(),
355            )),
356        };
357        fail_point!("put-cf-after");
358        #[allow(clippy::let_and_return)]
359        ret
360    }
361
362    pub(crate) fn key_may_exist_cf<K: AsRef<[u8]>>(
363        &self,
364        cf: &ColumnFamily,
365        key: K,
366        readopts: &ReadOptions,
367    ) -> bool {
368        match &self.storage {
369            // [`rocksdb::DBWithThreadMode::key_may_exist_cf`] can have false positives,
370            // but no false negatives. We use it to short-circuit the absent case
371            Storage::Rocks(rocks) => match rocks_cf(rocks, cf.name()) {
372                Ok(handle) => rocks
373                    .underlying
374                    .key_may_exist_cf_opt(&handle, key, readopts),
375                // Not a definitive absence: let the real read report it.
376                Err(_) => true,
377            },
378            _ => true,
379        }
380    }
381
382    /// Flush the memtable of a single column family to SST files on disk.
383    pub(crate) fn flush_cf(&self, cf: &ColumnFamily) -> Result<(), TypedStoreError> {
384        match &self.storage {
385            // A flush blocks on RocksDB background threads; under the simulator it
386            // must run off the test thread, like the opens in `rocks/mod.rs`, or
387            // real flush durations leak into the simulated schedule. No-op outside
388            // msim.
389            Storage::Rocks(rocks) => nondeterministic!({
390                let cf_name = cf.name();
391                // Deliberately not `rocks_cf`: flushing a column family
392                // dropped in the meantime is a no-op, not an error.
393                if let Some(handle) = rocks.underlying.cf_handle(cf_name) {
394                    rocks.underlying.flush_cf(&handle).map_err(|e| {
395                        TypedStoreError::RocksDB(format!(
396                            "Failed to flush column family {cf_name}: {e}"
397                        ))
398                    })?;
399                }
400                Ok(())
401            }),
402            // InMemory databases don't need flushing.
403            Storage::InMemory(_) => Ok(()),
404        }
405    }
406
407    /// Flush the memtables of every column family of the database to SST files
408    /// on disk.
409    pub fn flush_all(&self) -> Result<(), TypedStoreError> {
410        match &self.storage {
411            // See `flush_cf` for why the flushes run off the test thread under
412            // the simulator.
413            Storage::Rocks(rocks) => nondeterministic!({
414                for cf_name in rocks.cf_names().map_err(|e| {
415                    TypedStoreError::RocksDB(format!(
416                        "Failed to list column families of {}: {e}",
417                        rocks.underlying.path().display()
418                    ))
419                })? {
420                    if let Some(cf) = rocks.underlying.cf_handle(&cf_name) {
421                        rocks.underlying.flush_cf(&cf).map_err(|e| {
422                            TypedStoreError::RocksDB(format!(
423                                "Failed to flush column family {cf_name}: {e}"
424                            ))
425                        })?;
426                    }
427                }
428                Ok(())
429            }),
430            // InMemory databases don't need flushing.
431            Storage::InMemory(_) => Ok(()),
432        }
433    }
434
435    pub fn write_opt(
436        &self,
437        batch: StorageWriteBatch,
438        write_options: &rocksdb::WriteOptions,
439    ) -> Result<(), TypedStoreError> {
440        fail_point!("batch-write-before");
441        let ret = match (&self.storage, batch) {
442            (Storage::Rocks(rocks), StorageWriteBatch::Rocks(batch)) => rocks
443                .underlying
444                .write_opt(batch, write_options)
445                .map_err(typed_store_err_from_rocks_err),
446            (Storage::InMemory(db), StorageWriteBatch::InMemory(batch)) => {
447                // InMemory doesn't support write options.
448                db.write(batch)
449            }
450            _ => Err(TypedStoreError::RocksDB(
451                "using invalid batch type for the database".to_string(),
452            )),
453        };
454        fail_point!("batch-write-after");
455
456        #[allow(clippy::let_and_return)]
457        ret
458    }
459
460    pub(crate) fn compact_range_cf<K: AsRef<[u8]>>(
461        &self,
462        cf: &ColumnFamily,
463        start: Option<K>,
464        end: Option<K>,
465    ) -> Result<(), TypedStoreError> {
466        if let Storage::Rocks(rocksdb) = &self.storage {
467            let handle = rocks_cf(rocksdb, cf.name())?;
468            rocksdb.underlying.compact_range_cf(&handle, start, end);
469        }
470        Ok(())
471    }
472
473    /// How many sorted runs a point lookup in the column family `cf_name` may
474    /// probe: one per level-0 file and one per other level holding files. `0`
475    /// for a column family that does not exist and on the in-memory backend.
476    pub fn sorted_runs(&self, cf_name: &str) -> Result<u64, TypedStoreError> {
477        Ok(self
478            .files_per_level(cf_name)?
479            .iter()
480            .enumerate()
481            .map(|(level, &files)| {
482                if level == 0 {
483                    files
484                } else {
485                    u64::from(files > 0)
486                }
487            })
488            .sum())
489    }
490
491    /// How many SST files the column family `cf_name` has. `0` for a column
492    /// family that does not exist and on the in-memory backend.
493    pub fn sst_file_count(&self, cf_name: &str) -> Result<u64, TypedStoreError> {
494        Ok(self.files_per_level(cf_name)?.iter().sum())
495    }
496
497    /// The number of SST files on each level of the column family `cf_name`.
498    fn files_per_level(&self, cf_name: &str) -> Result<Vec<u64>, TypedStoreError> {
499        let Storage::Rocks(rocks) = &self.storage else {
500            return Ok(Vec::new());
501        };
502        let Some(handle) = rocks.underlying.cf_handle(cf_name) else {
503            return Ok(Vec::new());
504        };
505        let mut levels = Vec::new();
506        // The property is absent for the levels past the column family's
507        // last one.
508        for level in 0.. {
509            let Some(files) = rocks
510                .underlying
511                .property_value_cf(&handle, &format!("rocksdb.num-files-at-level{level}"))
512                .map_err(typed_store_err_from_rocks_err)?
513            else {
514                break;
515            };
516            levels.push(files.trim().parse().map_err(|e| {
517                TypedStoreError::RocksDB(format!(
518                    "the file count of level {level} of {cf_name} is not a number: {e}"
519                ))
520            })?);
521        }
522        Ok(levels)
523    }
524
525    /// The size of the SST files of the column family `cf_name`, which a
526    /// compaction of all of them needs again in free disk space while it runs.
527    /// `0` for a column family that does not exist and on the in-memory
528    /// backend.
529    pub fn sst_files_size(&self, cf_name: &str) -> Result<u64, TypedStoreError> {
530        let Storage::Rocks(rocks) = &self.storage else {
531            return Ok(0);
532        };
533        let Some(handle) = rocks.underlying.cf_handle(cf_name) else {
534            return Ok(0);
535        };
536        Ok(rocks
537            .underlying
538            .property_int_value_cf(&handle, rocksdb::properties::TOTAL_SST_FILES_SIZE)
539            .map_err(typed_store_err_from_rocks_err)?
540            .unwrap_or(0))
541    }
542
543    /// Compacts the whole column family `cf_name` into a single sorted run,
544    /// flushing its memtable first. Blocks until done. Other column families
545    /// keep compacting meanwhile. Does nothing on the in-memory backend.
546    pub fn compact_cf(&self, cf_name: &str) -> Result<(), TypedStoreError> {
547        if let Storage::Rocks(rocks) = &self.storage {
548            let handle = rocks_cf(rocks, cf_name)?;
549            let mut options = rocksdb::CompactOptions::default();
550            options.set_exclusive_manual_compaction(false);
551            options.set_bottommost_level_compaction(rocksdb::BottommostLevelCompaction::Force);
552            nondeterministic!(rocks.underlying.compact_range_cf_opt(
553                &handle,
554                None::<&[u8]>,
555                None::<&[u8]>,
556                &options
557            ));
558        }
559        Ok(())
560    }
561
562    /// A point-in-time snapshot of this database. See [`DbSnapshot`] for what
563    /// holding one costs.
564    ///
565    /// # Panics
566    ///
567    /// Panics on the in-memory backend, which has no point-in-time reads.
568    pub fn snapshot(&self) -> DbSnapshot<'_> {
569        match &self.storage {
570            Storage::Rocks(rocks) => DbSnapshot {
571                snapshot: rocks.underlying.snapshot(),
572            },
573            Storage::InMemory(_) => unimplemented!("method is only supported for rocksdb backend"),
574        }
575    }
576
577    pub fn get_sampling_interval(&self) -> SamplingInterval {
578        self.metric_conf.read_sample_interval.new_from_self()
579    }
580
581    pub fn multiget_sampling_interval(&self) -> SamplingInterval {
582        self.metric_conf.read_sample_interval.new_from_self()
583    }
584
585    pub fn write_sampling_interval(&self) -> SamplingInterval {
586        self.metric_conf.write_sample_interval.new_from_self()
587    }
588
589    pub fn iter_sampling_interval(&self) -> SamplingInterval {
590        self.metric_conf.iter_sample_interval.new_from_self()
591    }
592
593    pub(crate) fn db_name(&self) -> String {
594        let name = &self.metric_conf.db_name;
595        if name.is_empty() {
596            "default".to_string()
597        } else {
598            name.clone()
599        }
600    }
601
602    pub fn live_files(&self) -> Result<Vec<LiveFile>, Error> {
603        match &self.storage {
604            Storage::Rocks(rocks) => rocks.underlying.live_files(),
605            _ => Ok(vec![]),
606        }
607    }
608
609    pub(crate) fn try_catch_up_with_primary(&self) -> Result<(), TypedStoreError> {
610        if let Storage::Rocks(rocks) = &self.storage {
611            rocks
612                .underlying
613                .try_catch_up_with_primary()
614                .map_err(typed_store_err_from_rocks_err)?;
615        }
616        Ok(())
617    }
618}
619
620fn rocks_cf_from_db<'a>(
621    db: &'a Database,
622    cf_name: &str,
623) -> Result<Arc<rocksdb::BoundColumnFamily<'a>>, TypedStoreError> {
624    match &db.storage {
625        Storage::Rocks(rocksdb) => rocks_cf(rocksdb, cf_name),
626        _ => Err(TypedStoreError::RocksDB(
627            "using invalid batch type for the database".to_string(),
628        )),
629    }
630}
631
632/// An interface to a rocksDB database, keyed by a columnfamily
633#[derive(Clone, Debug)]
634pub struct DBMap<K, V> {
635    pub db: Arc<Database>,
636    _phantom: PhantomData<fn(K) -> V>,
637    column_family: ColumnFamily,
638    pub opts: ReadWriteOptions,
639    db_metrics: Arc<DBMetrics>,
640    get_sample_interval: SamplingInterval,
641    multiget_sample_interval: SamplingInterval,
642    write_sample_interval: SamplingInterval,
643    iter_sample_interval: SamplingInterval,
644    _metrics_task_cancel_handle: Arc<oneshot::Sender<()>>,
645    /// What the per-operation metrics are labelled with; the column family
646    /// name unless [`Self::with_metrics_label`] set another.
647    metrics_label: Arc<str>,
648}
649
650impl<K, V> DBMap<K, V> {
651    pub(crate) fn new(
652        db: Arc<Database>,
653        opts: &ReadWriteOptions,
654        column_family: ColumnFamily,
655        skip_metrics_reporting: bool,
656    ) -> Self {
657        let db_cloned = Arc::downgrade(&db);
658        let db_metrics = DBMetrics::get();
659        let db_metrics_cloned = db_metrics.clone();
660        let cf = column_family.name().to_string();
661
662        let (sender, mut recv) = tokio::sync::oneshot::channel();
663        if !skip_metrics_reporting && matches!(db.storage, Storage::Rocks(_)) {
664            tokio::task::spawn(async move {
665                let mut interval =
666                    tokio::time::interval(Duration::from_secs(CF_METRICS_REPORT_PERIOD_SECS));
667                loop {
668                    tokio::select! {
669                        _ = interval.tick() => {
670                            if let Some(db) = db_cloned.upgrade() {
671                                let cf = cf.clone();
672                                let db_metrics = db_metrics.clone();
673                                if let Err(e) = tokio::task::spawn_blocking(move || {
674                                    Self::report_rocksdb_metrics(&db, &cf, &db_metrics);
675                                }).await {
676                                    error!("Failed to log metrics with error: {}", e);
677                                }
678                            } else {
679                                break;
680                            }
681                        }
682                        _ = &mut recv => break,
683                    }
684                }
685                debug!("Returning the cf metric logging task for DBMap: {}", &cf);
686            });
687        }
688        let metrics_label = Arc::from(column_family.name());
689        DBMap {
690            db: db.clone(),
691            opts: opts.clone(),
692            _phantom: PhantomData,
693            column_family,
694            metrics_label,
695            db_metrics: db_metrics_cloned,
696            _metrics_task_cancel_handle: Arc::new(sender),
697            get_sample_interval: db.get_sampling_interval(),
698            multiget_sample_interval: db.multiget_sampling_interval(),
699            write_sample_interval: db.write_sampling_interval(),
700            iter_sample_interval: db.iter_sampling_interval(),
701        }
702    }
703
704    /// Reopens an open database as a typed map operating under a specific
705    /// column family. if no column family is passed, the default column
706    /// family is used.
707    ///
708    /// When `skip_metrics_reporting` is true, no periodic per-column-family
709    /// metrics task is spawned; use this for deprecated tables and for large
710    /// sets of rarely-touched column families.
711    #[instrument(level = "debug", skip(db), err)]
712    pub fn reopen(
713        db: &Arc<Database>,
714        opt_cf: Option<&str>,
715        rw_options: &ReadWriteOptions,
716        skip_metrics_reporting: bool,
717    ) -> Result<Self, TypedStoreError> {
718        let cf_key = opt_cf
719            .unwrap_or(rocksdb::DEFAULT_COLUMN_FAMILY_NAME)
720            .to_owned();
721        if db.cf_handle(&cf_key).is_none() {
722            return Err(TypedStoreError::UnregisteredColumn(cf_key));
723        }
724
725        let column_family = match &db.storage {
726            Storage::Rocks(_) => ColumnFamily::Rocks(cf_key),
727            Storage::InMemory(_) => ColumnFamily::InMemory(cf_key),
728        };
729        Ok(DBMap::new(
730            db.clone(),
731            rw_options,
732            column_family,
733            skip_metrics_reporting,
734        ))
735    }
736
737    /// Labels the per-operation metrics with `label` instead of the column
738    /// family name, so that the column families of one store, such as one per
739    /// epoch, share their series.
740    pub fn with_metrics_label(mut self, label: &str) -> Self {
741        self.metrics_label = Arc::from(label);
742        self
743    }
744
745    /// What the per-operation metrics are labelled with.
746    pub fn metrics_label(&self) -> &str {
747        &self.metrics_label
748    }
749
750    pub fn cf_name(&self) -> &str {
751        self.column_family.name()
752    }
753
754    pub fn batch(&self) -> DBBatch {
755        let batch = match &self.db.storage {
756            Storage::Rocks(_) => StorageWriteBatch::Rocks(WriteBatch::default()),
757            Storage::InMemory(_) => StorageWriteBatch::InMemory(InMemoryBatch::default()),
758        };
759        DBBatch::new(
760            &self.db,
761            batch,
762            &self.db_metrics,
763            &self.write_sample_interval,
764        )
765    }
766
767    /// Moves the SST files at `paths`, written by
768    /// [`SstFileBuffer::write_file`](crate::rocks::SstFileBuffer::write_file)
769    /// with this column family's options, into this table. The files may
770    /// overlap each other; a key in several of them keeps its value from the
771    /// last.
772    ///
773    /// # Panics
774    ///
775    /// Panics on the in-memory backend.
776    pub fn ingest_sst_files(&self, paths: &[PathBuf]) -> Result<(), TypedStoreError> {
777        if paths.is_empty() {
778            return Ok(());
779        }
780        match &self.db.storage {
781            // Ingestion can wait on a memtable flush; see `Database::flush_cf`
782            // for why that runs off the test thread under the simulator.
783            Storage::Rocks(rocks) => nondeterministic!({
784                let handle = rocks_cf(rocks, self.cf_name())?;
785                let mut options = rocksdb::IngestExternalFileOptions::default();
786                options.set_move_files(true);
787                rocks
788                    .underlying
789                    .ingest_external_file_cf_opts(&handle, &options, paths.to_vec())
790                    .map_err(typed_store_err_from_rocks_err)
791            }),
792            Storage::InMemory(_) => unimplemented!("method is only supported for rocksdb backend"),
793        }
794    }
795
796    /// Flush the memtable of this table's column family to SST files on disk.
797    pub fn flush(&self) -> Result<(), TypedStoreError> {
798        self.db.flush_cf(&self.column_family)
799    }
800
801    /// Flush the memtables of every column family of the database to SST files
802    /// on disk.
803    pub fn flush_all(&self) -> Result<(), TypedStoreError> {
804        self.db.flush_all()
805    }
806
807    pub fn compact_range<J: Serialize>(&self, start: &J, end: &J) -> Result<(), TypedStoreError> {
808        let from_buf = be_fix_int_ser(start);
809        let to_buf = be_fix_int_ser(end);
810        self.db
811            .compact_range_cf(&self.column_family, Some(from_buf), Some(to_buf))
812    }
813
814    pub fn compact_range_raw(
815        &self,
816        cf_name: &str,
817        start: Vec<u8>,
818        end: Vec<u8>,
819    ) -> Result<(), TypedStoreError> {
820        let cf = match &self.db.storage {
821            Storage::Rocks(_) => ColumnFamily::Rocks(cf_name.to_string()),
822            Storage::InMemory(_) => ColumnFamily::InMemory(cf_name.to_string()),
823        };
824        self.db.compact_range_cf(&cf, Some(start), Some(end))
825    }
826
827    /// Returns a vector of raw values corresponding to the keys provided.
828    fn multi_get_pinned(
829        &self,
830        keys_bytes: impl IntoIterator<Item = Vec<u8>>,
831    ) -> Result<Vec<Option<GetResult<'_>>>, TypedStoreError> {
832        let _timer = self
833            .db_metrics
834            .op_metrics
835            .rocksdb_multiget_latency_seconds
836            .with_label_values(&[self.metrics_label()])
837            .start_timer();
838        let perf_ctx = if self.multiget_sample_interval.sample() {
839            Some(RocksDBPerfContext)
840        } else {
841            None
842        };
843        let results: Result<Vec<_>, TypedStoreError> = self
844            .db
845            .multi_get(&self.column_family, keys_bytes, &self.opts.readopts())
846            .into_iter()
847            .collect();
848        let entries = results?;
849        let entry_size = entries
850            .iter()
851            .flatten()
852            .map(|entry| entry.len())
853            .sum::<usize>();
854        self.db_metrics
855            .op_metrics
856            .rocksdb_multiget_bytes
857            .with_label_values(&[self.metrics_label()])
858            .observe(entry_size as f64);
859        if perf_ctx.is_some() {
860            self.db_metrics
861                .read_perf_ctx_metrics
862                .report_metrics(self.metrics_label());
863        }
864        Ok(entries)
865    }
866
867    pub fn table_summary(&self) -> eyre::Result<TableSummary>
868    where
869        K: Serialize + DeserializeOwned,
870        V: Serialize + DeserializeOwned,
871    {
872        let mut num_keys = 0;
873        let mut key_bytes_total = 0;
874        let mut value_bytes_total = 0;
875        let mut key_hist = hdrhistogram::Histogram::<u64>::new_with_max(100000, 2).unwrap();
876        let mut value_hist = hdrhistogram::Histogram::<u64>::new_with_max(100000, 2).unwrap();
877        for item in self.safe_iter() {
878            let (key, value) = item?;
879            num_keys += 1;
880            let key_len = be_fix_int_ser(key.borrow()).len();
881            let value_len = bcs::to_bytes(value.borrow())?.len();
882            key_bytes_total += key_len;
883            value_bytes_total += value_len;
884            key_hist.record(key_len as u64)?;
885            value_hist.record(value_len as u64)?;
886        }
887        Ok(TableSummary {
888            num_keys,
889            key_bytes_total,
890            value_bytes_total,
891            key_hist,
892            value_hist,
893        })
894    }
895
896    // Creates metrics and context for tracking an iterator usage and performance.
897    fn create_iter_context(
898        &self,
899    ) -> (
900        Option<HistogramTimer>,
901        Option<Histogram>,
902        Option<Histogram>,
903        Option<RocksDBPerfContext>,
904    ) {
905        let timer = self
906            .db_metrics
907            .op_metrics
908            .rocksdb_iter_latency_seconds
909            .with_label_values(&[self.metrics_label()])
910            .start_timer();
911        let bytes_scanned = self
912            .db_metrics
913            .op_metrics
914            .rocksdb_iter_bytes
915            .with_label_values(&[self.metrics_label()]);
916        let keys_scanned = self
917            .db_metrics
918            .op_metrics
919            .rocksdb_iter_keys
920            .with_label_values(&[self.metrics_label()]);
921        let perf_ctx = if self.iter_sample_interval.sample() {
922            Some(RocksDBPerfContext)
923        } else {
924            None
925        };
926        (
927            Some(timer),
928            Some(bytes_scanned),
929            Some(keys_scanned),
930            perf_ctx,
931        )
932    }
933
934    /// Shared rocksdb `SafeIter` construction (raw iterator + scan metrics)
935    /// used by both the forward and reverse raw iterators.
936    fn rocks_safe_iter<'a>(
937        &self,
938        db: &'a RocksDB,
939        readopts: ReadOptions,
940    ) -> Result<SafeIter<'a, K, V>, TypedStoreError>
941    where
942        K: DeserializeOwned,
943        V: DeserializeOwned,
944    {
945        let db_iter = db
946            .underlying
947            .raw_iterator_cf_opt(&rocks_cf(db, self.column_family.name())?, readopts);
948        let (_timer, bytes_scanned, keys_scanned, _perf_ctx) = self.create_iter_context();
949        Ok(SafeIter::new(
950            self.metrics_label().to_string(),
951            db_iter,
952            _timer,
953            _perf_ctx,
954            bytes_scanned,
955            keys_scanned,
956            Some(self.db_metrics.clone()),
957        ))
958    }
959
960    /// Iterates the whole column family as of `db_snapshot` instead of the
961    /// current state of the database.
962    ///
963    /// The blocks it reads are not added to the block cache.
964    pub fn safe_iter_at_snapshot<'a>(
965        &'a self,
966        db_snapshot: &'a DbSnapshot<'a>,
967    ) -> DbIterator<'a, (K, V)>
968    where
969        K: DeserializeOwned,
970        V: DeserializeOwned,
971    {
972        match &self.db.storage {
973            Storage::Rocks(db) => {
974                let mut readopts = self.opts.readopts();
975                readopts.fill_cache(false);
976                readopts.set_snapshot(&db_snapshot.snapshot);
977                match self.rocks_safe_iter(db, readopts) {
978                    Ok(iter) => Box::new(iter),
979                    Err(e) => error_iterator(e),
980                }
981            }
982            Storage::InMemory(_) => {
983                unreachable!("a DbSnapshot is only created over the rocksdb backend")
984            }
985        }
986    }
987
988    /// Forward iterator over the raw byte bounds `[lower_bound, upper_bound)`;
989    /// both bounds (already serialized, `upper_bound` exclusive) are applied to
990    /// the read options.
991    fn iter_forward_raw(
992        &self,
993        lower_bound: Option<Vec<u8>>,
994        upper_bound: Option<Vec<u8>>,
995    ) -> DbIterator<'_, (K, V)>
996    where
997        K: DeserializeOwned,
998        V: DeserializeOwned,
999    {
1000        match &self.db.storage {
1001            Storage::Rocks(db) => {
1002                let readopts =
1003                    rocks_util::apply_range_bounds(self.opts.readopts(), lower_bound, upper_bound);
1004                match self.rocks_safe_iter(db, readopts) {
1005                    Ok(iter) => Box::new(iter),
1006                    Err(e) => error_iterator(e),
1007                }
1008            }
1009            Storage::InMemory(db) => {
1010                db.iterator(self.column_family.name(), lower_bound, upper_bound, false)
1011            }
1012        }
1013    }
1014
1015    /// Reverse counterpart of [`Self::iter_forward_raw`], yielding the same
1016    /// keys in descending order. Only the lower bound is applied to the
1017    /// read options; the (exclusive) `upper_bound` is enforced by
1018    /// [`SafeRevIter`]'s initial seek, because setting an
1019    /// iterate-upper-bound would stop `seek_for_prev` from landing on the
1020    /// boundary key.
1021    fn iter_reversed_raw(
1022        &self,
1023        lower_bound: Option<Vec<u8>>,
1024        upper_bound: Option<Vec<u8>>,
1025    ) -> DbIterator<'_, (K, V)>
1026    where
1027        K: DeserializeOwned,
1028        V: DeserializeOwned,
1029    {
1030        match &self.db.storage {
1031            Storage::Rocks(db) => {
1032                let readopts =
1033                    rocks_util::apply_range_bounds(self.opts.readopts(), lower_bound, None);
1034                match self.rocks_safe_iter(db, readopts) {
1035                    Ok(iter) => Box::new(SafeRevIter::new(iter, upper_bound)),
1036                    Err(e) => error_iterator(e),
1037                }
1038            }
1039            Storage::InMemory(db) => {
1040                db.iterator(self.column_family.name(), lower_bound, upper_bound, true)
1041            }
1042        }
1043    }
1044
1045    /// Forward iterator over every entry whose key begins with `prefix`.
1046    ///
1047    /// `prefix` is serialized with `be_fix_int_ser` and must form a prefix of
1048    /// the column family's key encoding — typically the leading field(s) of a
1049    /// tuple key. This avoids constructing artificial maximum keys to bound a
1050    /// composite-key scan.
1051    pub fn safe_iter_with_prefix<P>(&self, prefix: &P) -> DbIterator<'_, (K, V)>
1052    where
1053        P: ?Sized + Serialize,
1054        K: DeserializeOwned,
1055        V: DeserializeOwned,
1056    {
1057        let (lower_bound, upper_bound) = prefix_iterator_bounds(prefix);
1058        self.iter_forward_raw(lower_bound, upper_bound)
1059    }
1060
1061    /// Forward iterator over entries whose key begins with `prefix`, starting
1062    /// at the position `lower` gives within the prefix rather than at the
1063    /// first key under the prefix.
1064    ///
1065    /// The bound value is **not** a full key: it is the entire remainder of
1066    /// the key that follows `prefix` (all the trailing fields of a tuple key,
1067    /// not a partial suffix). Both `prefix` and the bound value are
1068    /// serialized with `be_fix_int_ser` and concatenated, mirroring how a
1069    /// composite key is encoded. `Included` starts the scan at
1070    /// `prefix ++ lower`, `Excluded` strictly after that position (also when
1071    /// no such key exists), and `Unbounded` at the first key under the
1072    /// prefix. The scan bounds never leave the prefix (excluding the
1073    /// maximum remainder yields an empty scan, not the next prefix's keys),
1074    /// so a paginated scan can resume from a cursor without an artificial
1075    /// maximum key for the upper bound.
1076    pub fn safe_iter_with_prefix_from<P, C>(
1077        &self,
1078        prefix: &P,
1079        lower: Bound<&C>,
1080    ) -> DbIterator<'_, (K, V)>
1081    where
1082        P: ?Sized + Serialize,
1083        C: ?Sized + Serialize,
1084        K: DeserializeOwned,
1085        V: DeserializeOwned,
1086    {
1087        let (lower_bound, upper_bound) =
1088            prefix_iterator_bounds_with_range::<P, C>(prefix, (lower, Bound::Unbounded));
1089        self.iter_forward_raw(lower_bound, upper_bound)
1090    }
1091
1092    /// Forward iterator over entries whose key begins with `prefix`, resuming
1093    /// strictly after `cursor`, or from the first key under the prefix when
1094    /// there is no cursor. `cursor` is the remainder of the key after
1095    /// `prefix`, as for [`Self::safe_iter_with_prefix_from`].
1096    pub fn safe_iter_with_prefix_after<P, C>(
1097        &self,
1098        prefix: &P,
1099        cursor: Option<&C>,
1100    ) -> DbIterator<'_, (K, V)>
1101    where
1102        P: ?Sized + Serialize,
1103        C: ?Sized + Serialize,
1104        K: DeserializeOwned,
1105        V: DeserializeOwned,
1106    {
1107        self.safe_iter_with_prefix_from(prefix, cursor.map_or(Bound::Unbounded, Bound::Excluded))
1108    }
1109
1110    /// Reverse counterpart of [`Self::safe_iter_with_prefix`]: the matching
1111    /// entries in descending key order (e.g. for "latest entry under a
1112    /// prefix").
1113    pub fn safe_iter_with_prefix_reversed<P>(&self, prefix: &P) -> DbIterator<'_, (K, V)>
1114    where
1115        P: ?Sized + Serialize,
1116        K: DeserializeOwned,
1117        V: DeserializeOwned,
1118    {
1119        let (lower_bound, upper_bound) = prefix_iterator_bounds(prefix);
1120        self.iter_reversed_raw(lower_bound, upper_bound)
1121    }
1122}
1123
1124/// Provides a mutable struct to form a collection of database write operations,
1125/// and execute them.
1126///
1127/// Batching write and delete operations is faster than performing them one by
1128/// one and ensures their atomicity,  ie. they are all written or none is.
1129/// This is also true of operations across column families in the same database.
1130///
1131/// Serializations / Deserialization, and naming of column families is performed
1132/// by passing a DBMap<K,V> with each operation.
1133///
1134/// ```
1135/// use core::fmt::Error;
1136/// use std::sync::Arc;
1137///
1138/// use prometheus_filtered::Registry;
1139/// use tempfile::tempdir;
1140/// use typed_store::{Map, metrics::DBMetrics, rocks::*};
1141///
1142/// #[tokio::main(flavor = "current_thread")]
1143/// async fn main() -> Result<(), Error> {
1144///     let rocks = open_cf_opts(
1145///         tempfile::tempdir().unwrap(),
1146///         None,
1147///         MetricConf::default(),
1148///         &[
1149///             ("First_CF", rocksdb::Options::default()),
1150///             ("Second_CF", rocksdb::Options::default()),
1151///         ],
1152///     )
1153///     .unwrap();
1154///
1155///     let db_cf_1 = DBMap::reopen(
1156///         &rocks,
1157///         Some("First_CF"),
1158///         &ReadWriteOptions::default(),
1159///         false,
1160///     )
1161///     .expect("Failed to open storage");
1162///     let keys_vals_1 = (1..100).map(|i| (i, i.to_string()));
1163///
1164///     let db_cf_2 = DBMap::reopen(
1165///         &rocks,
1166///         Some("Second_CF"),
1167///         &ReadWriteOptions::default(),
1168///         false,
1169///     )
1170///     .expect("Failed to open storage");
1171///     let keys_vals_2 = (1000..1100).map(|i| (i, i.to_string()));
1172///
1173///     let mut batch = db_cf_1.batch();
1174///     batch
1175///         .insert_batch(&db_cf_1, keys_vals_1.clone())
1176///         .expect("Failed to batch insert")
1177///         .insert_batch(&db_cf_2, keys_vals_2.clone())
1178///         .expect("Failed to batch insert");
1179///
1180///     let _ = batch.write().expect("Failed to execute batch");
1181///     for (k, v) in keys_vals_1 {
1182///         let val = db_cf_1.get(&k).expect("Failed to get inserted key");
1183///         assert_eq!(Some(v), val);
1184///     }
1185///
1186///     for (k, v) in keys_vals_2 {
1187///         let val = db_cf_2.get(&k).expect("Failed to get inserted key");
1188///         assert_eq!(Some(v), val);
1189///     }
1190///     Ok(())
1191/// }
1192/// ```
1193pub struct DBBatch {
1194    database: Arc<Database>,
1195    batch: StorageWriteBatch,
1196    db_metrics: Arc<DBMetrics>,
1197    write_sample_interval: SamplingInterval,
1198}
1199
1200impl DBBatch {
1201    /// Create a new batch associated with a DB reference.
1202    ///
1203    /// Use `open_cf` to get the DB reference or an existing open database.
1204    pub fn new(
1205        dbref: &Arc<Database>,
1206        batch: StorageWriteBatch,
1207        db_metrics: &Arc<DBMetrics>,
1208        write_sample_interval: &SamplingInterval,
1209    ) -> Self {
1210        DBBatch {
1211            database: dbref.clone(),
1212            batch,
1213            db_metrics: db_metrics.clone(),
1214            write_sample_interval: write_sample_interval.clone(),
1215        }
1216    }
1217
1218    /// Consume the batch and write its operations to the database
1219    #[instrument(level = "trace", skip_all, err)]
1220    pub fn write(self) -> Result<(), TypedStoreError> {
1221        self.write_opt(&rocksdb::WriteOptions::default())
1222    }
1223
1224    /// Consume the batch and write its operations to the database with custom
1225    /// write options.
1226    ///
1227    /// With default options, an entry whose column family was dropped
1228    /// between staging and this write fails the whole write with a fatal
1229    /// RocksDB background error. Stores that drop column families at
1230    /// runtime must pass [`drop_tolerant_write_options`].
1231    #[instrument(level = "trace", skip_all, err)]
1232    pub fn write_opt(self, write_options: &rocksdb::WriteOptions) -> Result<(), TypedStoreError> {
1233        let db_name = self.database.db_name();
1234        let timer = self
1235            .db_metrics
1236            .op_metrics
1237            .rocksdb_batch_commit_latency_seconds
1238            .with_label_values(&[&db_name])
1239            .start_timer();
1240        let batch_size_bytes = self.size_in_bytes();
1241
1242        let perf_ctx = if self.write_sample_interval.sample() {
1243            Some(RocksDBPerfContext)
1244        } else {
1245            None
1246        };
1247        self.database.write_opt(self.batch, write_options)?;
1248        self.db_metrics
1249            .op_metrics
1250            .rocksdb_batch_commit_bytes
1251            .with_label_values(&[&db_name])
1252            .observe(batch_size_bytes as f64);
1253
1254        if perf_ctx.is_some() {
1255            self.db_metrics
1256                .write_perf_ctx_metrics
1257                .report_metrics(&db_name);
1258        }
1259        let elapsed_secs = timer.stop_and_record();
1260        let threshold_secs = very_slow_batch_write_threshold_secs(batch_size_bytes);
1261        if elapsed_secs > threshold_secs {
1262            warn!(
1263                elapsed_secs,
1264                threshold_secs,
1265                batch_size_bytes,
1266                ?db_name,
1267                "very slow batch write"
1268            );
1269            self.db_metrics
1270                .op_metrics
1271                .rocksdb_very_slow_batch_writes_count
1272                .with_label_values(&[&db_name])
1273                .inc();
1274            self.db_metrics
1275                .op_metrics
1276                .rocksdb_very_slow_batch_writes_duration_ms
1277                .with_label_values(&[&db_name])
1278                .inc_by((elapsed_secs * 1000.0) as u64);
1279        }
1280        Ok(())
1281    }
1282
1283    pub fn size_in_bytes(&self) -> usize {
1284        match self.batch {
1285            StorageWriteBatch::Rocks(ref b) => b.size_in_bytes(),
1286            StorageWriteBatch::InMemory(_) => 0,
1287        }
1288    }
1289
1290    pub fn delete_batch<J: Borrow<K>, K: Serialize, V>(
1291        &mut self,
1292        db: &DBMap<K, V>,
1293        purged_vals: impl IntoIterator<Item = J>,
1294    ) -> Result<(), TypedStoreError> {
1295        self.delete_batch_raw_keys(
1296            db,
1297            purged_vals
1298                .into_iter()
1299                .map(|key| be_fix_int_ser(key.borrow())),
1300        )
1301    }
1302
1303    /// [`Self::delete_batch`] for keys already in the column family's
1304    /// `be_fix_int_ser` encoding.
1305    fn delete_batch_raw_keys<K, V>(
1306        &mut self,
1307        db: &DBMap<K, V>,
1308        purged_vals: impl IntoIterator<Item = Vec<u8>>,
1309    ) -> Result<(), TypedStoreError> {
1310        if !Arc::ptr_eq(&db.db, &self.database) {
1311            return Err(TypedStoreError::CrossDBBatch);
1312        }
1313
1314        purged_vals
1315            .into_iter()
1316            .try_for_each::<_, Result<_, TypedStoreError>>(|k_buf| {
1317                match (&mut self.batch, &db.column_family) {
1318                    (StorageWriteBatch::Rocks(b), ColumnFamily::Rocks(name)) => {
1319                        b.delete_cf(&rocks_cf_from_db(&self.database, name)?, k_buf)
1320                    }
1321                    (StorageWriteBatch::InMemory(b), ColumnFamily::InMemory(name)) => {
1322                        b.delete_cf(name, k_buf)
1323                    }
1324                    _ => Err(TypedStoreError::RocksDB(
1325                        "typed store invariant violation".to_string(),
1326                    ))?,
1327                }
1328                Ok(())
1329            })?;
1330        Ok(())
1331    }
1332
1333    /// Deletes a range of keys between `from` (inclusive) and `to`
1334    /// (non-inclusive) by writing a range delete tombstone in the db map.
1335    /// The effect of this write is visible immediately, i.e. you won't see
1336    /// old values when you do a lookup or scan.
1337    pub fn schedule_delete_range<K: Serialize, V>(
1338        &mut self,
1339        db: &DBMap<K, V>,
1340        from: &K,
1341        to: &K,
1342    ) -> Result<(), TypedStoreError> {
1343        self.schedule_delete_range_raw(db, be_fix_int_ser(from), be_fix_int_ser(to))
1344    }
1345
1346    /// [`Self::schedule_delete_range`] for a range whose bounds are not keys,
1347    /// such as a whole key prefix. The bounds are raw bytes ordered like the
1348    /// map's keys, i.e. in the `be_fix_int_ser` encoding; `from` is inclusive
1349    /// and `to` exclusive.
1350    fn schedule_delete_range_raw<K, V>(
1351        &mut self,
1352        db: &DBMap<K, V>,
1353        from: Vec<u8>,
1354        to: Vec<u8>,
1355    ) -> Result<(), TypedStoreError> {
1356        if !Arc::ptr_eq(&db.db, &self.database) {
1357            return Err(TypedStoreError::CrossDBBatch);
1358        }
1359
1360        if let StorageWriteBatch::Rocks(b) = &mut self.batch {
1361            b.delete_range_cf(&rocks_cf_from_db(&self.database, db.cf_name())?, from, to);
1362        }
1363        Ok(())
1364    }
1365
1366    /// inserts a range of (key, value) pairs given as an iterator
1367    pub fn insert_batch<J: Borrow<K>, K: Serialize, U: Borrow<V>, V: Serialize>(
1368        &mut self,
1369        db: &DBMap<K, V>,
1370        new_vals: impl IntoIterator<Item = (J, U)>,
1371    ) -> Result<&mut Self, TypedStoreError> {
1372        self.insert_batch_raw_keys(
1373            db,
1374            new_vals
1375                .into_iter()
1376                .map(|(key, value)| (be_fix_int_ser(key.borrow()), value)),
1377        )
1378    }
1379
1380    /// [`Self::insert_batch`] for keys already in the column family's
1381    /// `be_fix_int_ser` encoding.
1382    fn insert_batch_raw_keys<K, U: Borrow<V>, V: Serialize>(
1383        &mut self,
1384        db: &DBMap<K, V>,
1385        new_vals: impl IntoIterator<Item = (Vec<u8>, U)>,
1386    ) -> Result<&mut Self, TypedStoreError> {
1387        if !Arc::ptr_eq(&db.db, &self.database) {
1388            return Err(TypedStoreError::CrossDBBatch);
1389        }
1390        let mut total = 0usize;
1391        new_vals
1392            .into_iter()
1393            .try_for_each::<_, Result<_, TypedStoreError>>(|(k_buf, v)| {
1394                let v_buf = bcs::to_bytes(v.borrow()).map_err(typed_store_err_from_bcs_err)?;
1395                total += k_buf.len() + v_buf.len();
1396                if db.opts.log_value_hash {
1397                    let key_hash = default_hash(&k_buf);
1398                    let value_hash = default_hash(&v_buf);
1399                    debug!(
1400                        "Insert to DB table: {:?}, key_hash: {:?}, value_hash: {:?}",
1401                        db.cf_name(),
1402                        key_hash,
1403                        value_hash
1404                    );
1405                }
1406                match (&mut self.batch, &db.column_family) {
1407                    (StorageWriteBatch::Rocks(b), ColumnFamily::Rocks(name)) => {
1408                        b.put_cf(&rocks_cf_from_db(&self.database, name)?, k_buf, v_buf)
1409                    }
1410                    (StorageWriteBatch::InMemory(b), ColumnFamily::InMemory(name)) => {
1411                        b.put_cf(name, k_buf, v_buf)
1412                    }
1413                    _ => Err(TypedStoreError::RocksDB(
1414                        "typed store invariant violation".to_string(),
1415                    ))?,
1416                }
1417                Ok(())
1418            })?;
1419        self.db_metrics
1420            .op_metrics
1421            .rocksdb_batch_put_bytes
1422            .with_label_values(&[db.metrics_label()])
1423            .observe(total as f64);
1424        Ok(self)
1425    }
1426
1427    /// [`Self::insert_batch`] for a map sharing its column family by tag.
1428    pub fn insert_batch_tagged<J: Borrow<K>, K: Serialize, U: Borrow<V>, V: Serialize>(
1429        &mut self,
1430        map: &TaggedDBMap<K, V>,
1431        new_vals: impl IntoIterator<Item = (J, U)>,
1432    ) -> Result<&mut Self, TypedStoreError> {
1433        self.insert_batch_raw_keys(
1434            &map.map,
1435            new_vals
1436                .into_iter()
1437                .map(|(key, value)| (be_fix_int_ser(&(map.tag, key.borrow())), value)),
1438        )
1439    }
1440
1441    /// [`Self::delete_batch`] for a map sharing its column family by tag.
1442    pub fn delete_batch_tagged<J: Borrow<K>, K: Serialize, V>(
1443        &mut self,
1444        map: &TaggedDBMap<K, V>,
1445        purged_vals: impl IntoIterator<Item = J>,
1446    ) -> Result<(), TypedStoreError> {
1447        self.delete_batch_raw_keys(
1448            &map.map,
1449            purged_vals
1450                .into_iter()
1451                .map(|key| be_fix_int_ser(&(map.tag, key.borrow()))),
1452        )
1453    }
1454}
1455
1456impl<K, V> DBMap<K, V> {
1457    #[instrument(level = "trace", skip_all, err)]
1458    pub(crate) fn contains_raw_key(&self, key_buf: Vec<u8>) -> Result<bool, TypedStoreError> {
1459        let readopts = self.opts.readopts();
1460        Ok(self
1461            .db
1462            .key_may_exist_cf(&self.column_family, &key_buf, &readopts)
1463            && self
1464                .db
1465                .get(&self.column_family, &key_buf, &readopts)?
1466                .is_some())
1467    }
1468
1469    #[instrument(level = "trace", skip_all, err)]
1470    pub(crate) fn multi_contains_raw_keys(
1471        &self,
1472        keys: impl IntoIterator<Item = Vec<u8>>,
1473    ) -> Result<Vec<bool>, TypedStoreError> {
1474        let values = self.multi_get_pinned(keys)?;
1475        Ok(values.into_iter().map(|v| v.is_some()).collect())
1476    }
1477
1478    #[instrument(level = "trace", skip_all, err)]
1479    pub(crate) fn get_raw_key(&self, key_buf: Vec<u8>) -> Result<Option<V>, TypedStoreError>
1480    where
1481        V: DeserializeOwned,
1482    {
1483        let _timer = self
1484            .db_metrics
1485            .op_metrics
1486            .rocksdb_get_latency_seconds
1487            .with_label_values(&[self.metrics_label()])
1488            .start_timer();
1489        let perf_ctx = if self.get_sample_interval.sample() {
1490            Some(RocksDBPerfContext)
1491        } else {
1492            None
1493        };
1494        let res = self
1495            .db
1496            .get(&self.column_family, &key_buf, &self.opts.readopts())?;
1497        self.db_metrics
1498            .op_metrics
1499            .rocksdb_get_bytes
1500            .with_label_values(&[self.metrics_label()])
1501            .observe(res.as_ref().map_or(0.0, |v| v.len() as f64));
1502        if perf_ctx.is_some() {
1503            self.db_metrics
1504                .read_perf_ctx_metrics
1505                .report_metrics(self.metrics_label());
1506        }
1507        match res {
1508            Some(data) => {
1509                let value = bcs::from_bytes(&data).map_err(typed_store_err_from_bcs_err);
1510                if value.is_err() {
1511                    let key_hash = default_hash(&key_buf);
1512                    let value_hash = default_hash(&data);
1513                    debug_fatal!(
1514                        "Failed to deserialize value from DB table {:?}, key_hash: {:?}, value_hash: {:?}, error: {:?}",
1515                        self.cf_name(),
1516                        key_hash,
1517                        value_hash,
1518                        value.as_ref().err().unwrap()
1519                    );
1520                }
1521                Ok(Some(value?))
1522            }
1523            None => Ok(None),
1524        }
1525    }
1526
1527    #[instrument(level = "trace", skip_all, err)]
1528    pub(crate) fn insert_raw_key(&self, key_buf: Vec<u8>, value: &V) -> Result<(), TypedStoreError>
1529    where
1530        V: Serialize,
1531    {
1532        let timer = self
1533            .db_metrics
1534            .op_metrics
1535            .rocksdb_put_latency_seconds
1536            .with_label_values(&[self.metrics_label()])
1537            .start_timer();
1538        let perf_ctx = if self.write_sample_interval.sample() {
1539            Some(RocksDBPerfContext)
1540        } else {
1541            None
1542        };
1543        let value_buf = bcs::to_bytes(value).map_err(typed_store_err_from_bcs_err)?;
1544        self.db_metrics
1545            .op_metrics
1546            .rocksdb_put_bytes
1547            .with_label_values(&[self.metrics_label()])
1548            .observe((key_buf.len() + value_buf.len()) as f64);
1549        if perf_ctx.is_some() {
1550            self.db_metrics
1551                .write_perf_ctx_metrics
1552                .report_metrics(self.metrics_label());
1553        }
1554        self.db.put_cf(&self.column_family, key_buf, value_buf)?;
1555
1556        let elapsed_secs = timer.stop_and_record();
1557        if elapsed_secs > 1.0 {
1558            warn!(elapsed_secs, cf = ?self.cf_name(), "very slow insert");
1559            self.db_metrics
1560                .op_metrics
1561                .rocksdb_very_slow_puts_count
1562                .with_label_values(&[self.metrics_label()])
1563                .inc();
1564            self.db_metrics
1565                .op_metrics
1566                .rocksdb_very_slow_puts_duration_ms
1567                .with_label_values(&[self.metrics_label()])
1568                .inc_by((elapsed_secs * 1000.0) as u64);
1569        }
1570
1571        Ok(())
1572    }
1573
1574    #[instrument(level = "trace", skip_all, err)]
1575    pub(crate) fn remove_raw_key(&self, key_buf: Vec<u8>) -> Result<(), TypedStoreError> {
1576        let _timer = self
1577            .db_metrics
1578            .op_metrics
1579            .rocksdb_delete_latency_seconds
1580            .with_label_values(&[self.metrics_label()])
1581            .start_timer();
1582        let perf_ctx = if self.write_sample_interval.sample() {
1583            Some(RocksDBPerfContext)
1584        } else {
1585            None
1586        };
1587        self.db.delete_cf(&self.column_family, key_buf)?;
1588        self.db_metrics
1589            .op_metrics
1590            .rocksdb_deletes
1591            .with_label_values(&[self.metrics_label()])
1592            .inc();
1593        if perf_ctx.is_some() {
1594            self.db_metrics
1595                .write_perf_ctx_metrics
1596                .report_metrics(self.metrics_label());
1597        }
1598        Ok(())
1599    }
1600
1601    #[instrument(level = "trace", skip_all, err)]
1602    pub(crate) fn multi_get_raw_keys(
1603        &self,
1604        keys: impl IntoIterator<Item = Vec<u8>>,
1605    ) -> Result<Vec<Option<V>>, TypedStoreError>
1606    where
1607        V: DeserializeOwned,
1608    {
1609        let results = self.multi_get_pinned(keys)?;
1610        let values_parsed: Result<Vec<_>, TypedStoreError> = results
1611            .into_iter()
1612            .map(|value_byte| match value_byte {
1613                Some(data) => Ok(Some(
1614                    bcs::from_bytes(&data).map_err(typed_store_err_from_bcs_err)?,
1615                )),
1616                None => Ok(None),
1617            })
1618            .collect();
1619
1620        values_parsed
1621    }
1622}
1623
1624impl<'a, K, V> Map<'a, K, V> for DBMap<K, V>
1625where
1626    K: Serialize + DeserializeOwned,
1627    V: Serialize + DeserializeOwned,
1628{
1629    type Error = TypedStoreError;
1630
1631    fn contains_key(&self, key: &K) -> Result<bool, TypedStoreError> {
1632        self.contains_raw_key(be_fix_int_ser(key))
1633    }
1634
1635    fn multi_contains_keys<J>(
1636        &self,
1637        keys: impl IntoIterator<Item = J>,
1638    ) -> Result<Vec<bool>, Self::Error>
1639    where
1640        J: Borrow<K>,
1641    {
1642        self.multi_contains_raw_keys(keys.into_iter().map(|k| be_fix_int_ser(k.borrow())))
1643    }
1644
1645    fn get(&self, key: &K) -> Result<Option<V>, TypedStoreError> {
1646        self.get_raw_key(be_fix_int_ser(key))
1647    }
1648
1649    fn insert(&self, key: &K, value: &V) -> Result<(), TypedStoreError> {
1650        self.insert_raw_key(be_fix_int_ser(key), value)
1651    }
1652
1653    fn remove(&self, key: &K) -> Result<(), TypedStoreError> {
1654        self.remove_raw_key(be_fix_int_ser(key))
1655    }
1656
1657    /// Writes a range delete tombstone to delete all entries in the db map.
1658    /// The effect of this write is visible immediately, i.e. you won't see
1659    /// old values when you do a lookup or scan.
1660    #[instrument(level = "trace", skip_all, err)]
1661    fn schedule_delete_all(&self) -> Result<(), TypedStoreError> {
1662        let Some(last_key) = self
1663            .safe_range_iter_reversed(..)
1664            .next()
1665            .transpose()?
1666            .map(|(k, _v)| k)
1667        else {
1668            return Ok(());
1669        };
1670        // The tombstone excludes its upper bound, so it has to end past the last key:
1671        // appending a zero byte gives the first key sorting above it.
1672        let mut to = be_fix_int_ser(&last_key);
1673        to.push(0);
1674        // The empty key sorts below every key, so the range starts before the first
1675        // entry.
1676        let mut batch = self.batch();
1677        batch.schedule_delete_range_raw(self, Vec::new(), to)?;
1678        batch.write()
1679    }
1680
1681    fn is_empty(&self) -> bool {
1682        self.safe_iter().next().is_none()
1683    }
1684
1685    fn safe_iter(&'a self) -> DbIterator<'a, (K, V)> {
1686        match &self.db.storage {
1687            Storage::Rocks(db) => match self.rocks_safe_iter(db, self.opts.readopts()) {
1688                Ok(iter) => Box::new(iter),
1689                Err(e) => error_iterator(e),
1690            },
1691            Storage::InMemory(db) => db.iterator(self.column_family.name(), None, None, false),
1692        }
1693    }
1694
1695    fn safe_iter_with_bounds(
1696        &'a self,
1697        lower_bound: Option<K>,
1698        upper_bound: Option<K>,
1699    ) -> DbIterator<'a, (K, V)> {
1700        let range = (
1701            lower_bound.map(Bound::Included).unwrap_or(Bound::Unbounded),
1702            upper_bound.map(Bound::Excluded).unwrap_or(Bound::Unbounded),
1703        );
1704        self.safe_range_iter(range)
1705    }
1706
1707    fn safe_range_iter(&'a self, range: impl RangeBounds<K>) -> DbIterator<'a, (K, V)> {
1708        let (lower_bound, upper_bound) = iterator_bounds_with_range(range);
1709        self.iter_forward_raw(lower_bound, upper_bound)
1710    }
1711
1712    /// Both directions derive their bounds from the same
1713    /// `iterator_bounds_with_range`, so they are guaranteed to cover the
1714    /// identical set of keys regardless of the bound inclusivity.
1715    fn safe_range_iter_reversed(&'a self, range: impl RangeBounds<K>) -> DbIterator<'a, (K, V)> {
1716        let (lower_bound, upper_bound) = iterator_bounds_with_range(range);
1717        self.iter_reversed_raw(lower_bound, upper_bound)
1718    }
1719
1720    /// Returns a vector of values corresponding to the keys provided.
1721    fn multi_get<J>(
1722        &self,
1723        keys: impl IntoIterator<Item = J>,
1724    ) -> Result<Vec<Option<V>>, TypedStoreError>
1725    where
1726        J: Borrow<K>,
1727    {
1728        self.multi_get_raw_keys(keys.into_iter().map(|k| be_fix_int_ser(k.borrow())))
1729    }
1730
1731    /// Convenience method for batch insertion
1732    #[instrument(level = "trace", skip_all, err)]
1733    fn multi_insert<J, U>(
1734        &self,
1735        key_val_pairs: impl IntoIterator<Item = (J, U)>,
1736    ) -> Result<(), Self::Error>
1737    where
1738        J: Borrow<K>,
1739        U: Borrow<V>,
1740    {
1741        let mut batch = self.batch();
1742        batch.insert_batch(self, key_val_pairs)?;
1743        batch.write()
1744    }
1745
1746    /// Convenience method for batch removal
1747    #[instrument(level = "trace", skip_all, err)]
1748    fn multi_remove<J>(&self, keys: impl IntoIterator<Item = J>) -> Result<(), Self::Error>
1749    where
1750        J: Borrow<K>,
1751    {
1752        let mut batch = self.batch();
1753        batch.delete_batch(self, keys)?;
1754        batch.write()
1755    }
1756
1757    /// Try to catch up with primary when running as secondary
1758    #[instrument(level = "trace", skip_all, err)]
1759    fn try_catch_up_with_primary(&self) -> Result<(), Self::Error> {
1760        self.db.try_catch_up_with_primary()
1761    }
1762}
1763
1764/// A typed map sharing one column family with other typed maps,
1765/// distinguished by a tag byte prefixed to every key: the big-endian fixint
1766/// serialization of `(tag, key)` keeps each map a contiguous key range of
1767/// the column family. Useful for sets of tables that are always created and
1768/// dropped together — one column family instead of one per table keeps the
1769/// column-family and SST-file counts low.
1770///
1771/// Every read is scoped to the map's tag: full scans iterate the tag's key
1772/// prefix, and ranges are two-sided within it, so rows of other tags never
1773/// surface and their (differently typed) values are never deserialized.
1774///
1775/// Tags of existing maps must never change or be reused, or old rows would
1776/// be read under the wrong types.
1777///
1778/// Metrics are labelled by column family, so all the maps of one column
1779/// family report together, with no way to tell the tags apart.
1780///
1781/// # Examples
1782///
1783/// ```
1784/// use tempfile::tempdir;
1785/// use typed_store::{Map, rocks::*};
1786///
1787/// #[tokio::main(flavor = "current_thread")]
1788/// async fn main() {
1789///     let db = open_cf_opts(
1790///         tempdir().unwrap(),
1791///         None,
1792///         MetricConf::default(),
1793///         &[("shared", rocksdb::Options::default())],
1794///     )
1795///     .unwrap();
1796///
1797///     let numbers: TaggedDBMap<u32, String> =
1798///         TaggedDBMap::reopen(&db, "shared", 0, &ReadWriteOptions::default(), false).unwrap();
1799///     let words: TaggedDBMap<String, u64> =
1800///         TaggedDBMap::reopen(&db, "shared", 1, &ReadWriteOptions::default(), false).unwrap();
1801///
1802///     let mut batch = numbers.batch();
1803///     batch
1804///         .insert_batch_tagged(&numbers, [(1, "one".to_string())])
1805///         .unwrap()
1806///         .insert_batch_tagged(&words, [("one".to_string(), 1)])
1807///         .unwrap();
1808///     batch.write().unwrap();
1809///
1810///     assert_eq!(numbers.get(&1).unwrap(), Some("one".to_string()));
1811///     assert_eq!(words.get(&"one".to_string()).unwrap(), Some(1));
1812///     // Each map sees only its own row.
1813///     assert_eq!(numbers.safe_iter().count(), 1);
1814/// }
1815/// ```
1816pub struct TaggedDBMap<K, V> {
1817    tag: u8,
1818    map: DBMap<(u8, K), V>,
1819}
1820
1821impl<K, V> TaggedDBMap<K, V> {
1822    /// Drops the tag prefixed to a key of the underlying map, turning its row
1823    /// into a row of this map.
1824    fn strip_tag(row: Result<((u8, K), V), TypedStoreError>) -> Result<(K, V), TypedStoreError> {
1825        row.map(|((_, key), value)| (key, value))
1826    }
1827
1828    pub(crate) fn tag(&self) -> u8 {
1829        self.tag
1830    }
1831
1832    /// [`DBMap::ingest_sst_files`] for this map's column family, which the
1833    /// files may hold rows of any table sharing it for.
1834    pub fn ingest_sst_files(&self, paths: &[PathBuf]) -> Result<(), TypedStoreError> {
1835        self.map.ingest_sst_files(paths)
1836    }
1837
1838    /// Reopens an open database as a tagged map operating under `cf_name`.
1839    /// See [`DBMap::reopen`] for the remaining parameters.
1840    pub fn reopen(
1841        db: &Arc<Database>,
1842        cf_name: &str,
1843        tag: u8,
1844        rw_options: &ReadWriteOptions,
1845        skip_metrics_reporting: bool,
1846    ) -> Result<Self, TypedStoreError> {
1847        Ok(Self {
1848            tag,
1849            map: DBMap::reopen(db, Some(cf_name), rw_options, skip_metrics_reporting)?,
1850        })
1851    }
1852
1853    /// Create a batch associated with the map's database.
1854    pub fn batch(&self) -> DBBatch {
1855        self.map.batch()
1856    }
1857
1858    /// See [`DBMap::with_metrics_label`].
1859    pub fn with_metrics_label(mut self, label: &str) -> Self {
1860        self.map = self.map.with_metrics_label(label);
1861        self
1862    }
1863}
1864
1865impl<'a, K, V> Map<'a, K, V> for TaggedDBMap<K, V>
1866where
1867    K: Serialize + DeserializeOwned,
1868    V: Serialize + DeserializeOwned,
1869{
1870    type Error = TypedStoreError;
1871
1872    fn contains_key(&self, key: &K) -> Result<bool, TypedStoreError> {
1873        self.map.contains_raw_key(be_fix_int_ser(&(self.tag, key)))
1874    }
1875
1876    fn multi_contains_keys<J>(
1877        &self,
1878        keys: impl IntoIterator<Item = J>,
1879    ) -> Result<Vec<bool>, TypedStoreError>
1880    where
1881        J: Borrow<K>,
1882    {
1883        self.map.multi_contains_raw_keys(
1884            keys.into_iter()
1885                .map(|key| be_fix_int_ser(&(self.tag, key.borrow()))),
1886        )
1887    }
1888
1889    fn get(&self, key: &K) -> Result<Option<V>, TypedStoreError> {
1890        self.map.get_raw_key(be_fix_int_ser(&(self.tag, key)))
1891    }
1892
1893    fn multi_get<J>(
1894        &self,
1895        keys: impl IntoIterator<Item = J>,
1896    ) -> Result<Vec<Option<V>>, TypedStoreError>
1897    where
1898        J: Borrow<K>,
1899    {
1900        self.map.multi_get_raw_keys(
1901            keys.into_iter()
1902                .map(|key| be_fix_int_ser(&(self.tag, key.borrow()))),
1903        )
1904    }
1905
1906    fn insert(&self, key: &K, value: &V) -> Result<(), TypedStoreError> {
1907        self.map
1908            .insert_raw_key(be_fix_int_ser(&(self.tag, key)), value)
1909    }
1910
1911    fn remove(&self, key: &K) -> Result<(), TypedStoreError> {
1912        self.map.remove_raw_key(be_fix_int_ser(&(self.tag, key)))
1913    }
1914
1915    #[instrument(level = "trace", skip_all, err)]
1916    fn multi_insert<J, U>(
1917        &self,
1918        key_val_pairs: impl IntoIterator<Item = (J, U)>,
1919    ) -> Result<(), TypedStoreError>
1920    where
1921        J: Borrow<K>,
1922        U: Borrow<V>,
1923    {
1924        let mut batch = self.batch();
1925        batch.insert_batch_tagged(self, key_val_pairs)?;
1926        batch.write()
1927    }
1928
1929    #[instrument(level = "trace", skip_all, err)]
1930    fn multi_remove<J>(&self, keys: impl IntoIterator<Item = J>) -> Result<(), TypedStoreError>
1931    where
1932        J: Borrow<K>,
1933    {
1934        let mut batch = self.batch();
1935        batch.delete_batch_tagged(self, keys)?;
1936        batch.write()
1937    }
1938
1939    /// Deletes every entry of the map with a single range tombstone over the
1940    /// tag's key range, leaving the other tags of the column family untouched.
1941    ///
1942    /// The entries stay on disk until compaction, and until then iteration
1943    /// is slowed because of them.
1944    #[instrument(level = "trace", skip_all, err)]
1945    fn schedule_delete_all(&self) -> Result<(), TypedStoreError> {
1946        let from = be_fix_int_ser(&self.tag);
1947        let to = match prefix_iterator_bounds(&self.tag).1 {
1948            Some(to) => {
1949                if self.is_empty() {
1950                    return Ok(());
1951                }
1952                to
1953            }
1954            // The maximum tag has no following tag to bound the range
1955            // against, so end it just past the last entry. That read is why a
1956            // row written in between survives the clear, and why a value that
1957            // does not deserialize fails it.
1958            None => match self.safe_range_iter_reversed(..).next().transpose()? {
1959                Some((last_key, _)) => {
1960                    let mut to = be_fix_int_ser(&(self.tag, last_key));
1961                    to.push(0);
1962                    to
1963                }
1964                None => return Ok(()),
1965            },
1966        };
1967
1968        let mut batch = self.batch();
1969        batch.schedule_delete_range_raw(&self.map, from, to)?;
1970        batch.write()
1971    }
1972
1973    fn is_empty(&self) -> bool {
1974        self.safe_iter().next().is_none()
1975    }
1976
1977    fn safe_iter(&'a self) -> DbIterator<'a, (K, V)> {
1978        Box::new(
1979            self.map
1980                .safe_iter_with_prefix(&self.tag)
1981                .map(Self::strip_tag),
1982        )
1983    }
1984
1985    fn safe_iter_with_bounds(
1986        &'a self,
1987        lower_bound: Option<K>,
1988        upper_bound: Option<K>,
1989    ) -> DbIterator<'a, (K, V)> {
1990        let range = (
1991            lower_bound.map(Bound::Included).unwrap_or(Bound::Unbounded),
1992            upper_bound.map(Bound::Excluded).unwrap_or(Bound::Unbounded),
1993        );
1994        self.safe_range_iter(range)
1995    }
1996
1997    fn safe_range_iter(&'a self, range: impl RangeBounds<K>) -> DbIterator<'a, (K, V)> {
1998        let (lower_bound, upper_bound) = prefix_iterator_bounds_with_range(&self.tag, range);
1999        Box::new(
2000            self.map
2001                .iter_forward_raw(lower_bound, upper_bound)
2002                .map(Self::strip_tag),
2003        )
2004    }
2005
2006    fn safe_range_iter_reversed(&'a self, range: impl RangeBounds<K>) -> DbIterator<'a, (K, V)> {
2007        let (lower_bound, upper_bound) = prefix_iterator_bounds_with_range(&self.tag, range);
2008        Box::new(
2009            self.map
2010                .iter_reversed_raw(lower_bound, upper_bound)
2011                .map(Self::strip_tag),
2012        )
2013    }
2014
2015    fn try_catch_up_with_primary(&self) -> Result<(), TypedStoreError> {
2016        self.map.try_catch_up_with_primary()
2017    }
2018}
2019
2020/// Waits until the database of `db` has been closed, every handle to it
2021/// dropped and RocksDB's files released, so its directory can be reopened,
2022/// moved, or deleted. Returns `false` if it is still open after 30 seconds.
2023pub async fn wait_for_database_close(db: DatabaseClose) -> bool {
2024    // The deadline is read off the same clock the sleep advances, so a test
2025    // pausing time does not turn the wait into a busy loop.
2026    let deadline = Instant::now() + Duration::from_secs(30);
2027    while db.0.strong_count() != 0 {
2028        if Instant::now() > deadline {
2029            return false;
2030        }
2031        tokio::time::sleep(Duration::from_millis(100)).await;
2032    }
2033    true
2034}
2035
2036/// Returns an iterator whose only item is `Err(error)`.
2037pub(crate) fn error_iterator<'a, T: 'a>(error: TypedStoreError) -> DbIterator<'a, T> {
2038    Box::new(std::iter::once(Err(error)))
2039}
2040
2041/// Write options that discard the batch entries of a column family dropped
2042/// after staging, instead of failing the write; see [`DBBatch::write_opt`].
2043pub fn drop_tolerant_write_options() -> rocksdb::WriteOptions {
2044    let mut options = rocksdb::WriteOptions::default();
2045    options.set_ignore_missing_column_families(true);
2046    options
2047}
2048
2049fn default_hash(value: &[u8]) -> Digest<32> {
2050    let mut hasher = fastcrypto::hash::Blake2b256::default();
2051    hasher.update(value);
2052    hasher.finalize()
2053}
2054
2055// The throughput floor is used to compute the threshold for when a batch write
2056// is considered "very slow".
2057const THROUGHPUT_FLOOR_BYTES_PER_SEC: f64 = 32.0 * 1024.0 * 1024.0; // 32 MiB/s
2058
2059// A batch write that took less than this is never reported as very slow.
2060const MIN_VERY_SLOW_BATCH_WRITE_SECS: f64 = 1.0;
2061
2062/// Returns the elapsed time above which a batch write of `batch_size_bytes` is
2063/// reported as very slow: what it would take at
2064/// [`THROUGHPUT_FLOOR_BYTES_PER_SEC`], and never less than
2065/// [`MIN_VERY_SLOW_BATCH_WRITE_SECS`].
2066fn very_slow_batch_write_threshold_secs(batch_size_bytes: usize) -> f64 {
2067    (batch_size_bytes as f64 / THROUGHPUT_FLOOR_BYTES_PER_SEC).max(MIN_VERY_SLOW_BATCH_WRITE_SECS)
2068}
2069
2070#[cfg(test)]
2071mod tests {
2072    use super::very_slow_batch_write_threshold_secs;
2073
2074    const MIB: usize = 1024 * 1024;
2075
2076    #[test]
2077    fn very_slow_batch_write_threshold_never_below_one_second() {
2078        assert_eq!(very_slow_batch_write_threshold_secs(0), 1.0);
2079        assert_eq!(very_slow_batch_write_threshold_secs(1), 1.0);
2080        assert_eq!(very_slow_batch_write_threshold_secs(MIB), 1.0);
2081        assert_eq!(very_slow_batch_write_threshold_secs(32 * MIB), 1.0);
2082    }
2083
2084    #[test]
2085    fn very_slow_batch_write_threshold_grows_with_batch_size() {
2086        assert_eq!(very_slow_batch_write_threshold_secs(48 * MIB), 1.5);
2087        assert_eq!(very_slow_batch_write_threshold_secs(64 * MIB), 2.0);
2088        assert_eq!(very_slow_batch_write_threshold_secs(320 * MIB), 10.0);
2089    }
2090}