armdb 0.12.0

sharded bitcask key-value storage optimized for NVMe
Documentation
//! Configuration for fjall-backed `LsmTree` collections.

/// Durability guarantee applied on each `flush` / `periodic_flush` call. Maps to fjall `PersistMode`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PersistPolicy {
    /// OS buffers only (fastest, weakest).
    Buffer,
    /// fdatasync (data only). Default.
    SyncData,
    /// fsync (data + metadata).
    SyncAll,
}

#[allow(clippy::derivable_impls)]
impl Default for PersistPolicy {
    fn default() -> Self {
        PersistPolicy::SyncData
    }
}

impl PersistPolicy {
    pub(crate) fn to_fjall(self) -> fjall::PersistMode {
        match self {
            PersistPolicy::Buffer => fjall::PersistMode::Buffer,
            PersistPolicy::SyncData => fjall::PersistMode::SyncData,
            PersistPolicy::SyncAll => fjall::PersistMode::SyncAll,
        }
    }
}

/// Database-scoped config, shared by ALL `LsmTree` collections in one `Db`.
/// Fixed by the FIRST `open_lsm_tree`; a later differing config is `DbError::Config`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LsmDbConfig {
    /// Shared read cache in bytes → `Database::builder().cache_size`.
    pub cache_size: u64,
    /// Persistence applied on flush / periodic_flush.
    pub persist: PersistPolicy,
}

impl Default for LsmDbConfig {
    fn default() -> Self {
        Self {
            cache_size: 64 * 1024 * 1024, // 64 MiB
            persist: PersistPolicy::SyncData,
        }
    }
}

/// Per-collection (one fjall keyspace) config.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LsmConfig {
    /// `true` ⇒ forward iteration yields DESC. In-memory only, tunable across reopen.
    pub reversed: bool,
    /// fjall memtable size (bytes) before flush → `KeyspaceCreateOptions::max_memtable_size`.
    pub memtable_size: u64,
    /// Striped locks for put/RMW/atomic.
    pub lock_stripes: usize,
}

impl Default for LsmConfig {
    fn default() -> Self {
        Self {
            reversed: true,
            memtable_size: 64 * 1024 * 1024, // 64 MiB
            lock_stripes: std::thread::available_parallelism()
                .map(|p| p.get())
                .unwrap_or(4)
                .clamp(1, 64),
        }
    }
}

impl LsmConfig {
    /// Per-collection persist policy is taken from the shared `LsmDbConfig`
    /// (`Db::open_lsm_tree` overrides this value via `open_with_persist`); this
    /// method only seeds a sane default when an `LsmTree` is opened standalone,
    /// bypassing `Db` (tests, direct `LsmTree::open`). `LsmConfig` deliberately
    /// carries no persist field of its own — persistence is a keyspace-wide fjall
    /// knob (`LsmDbConfig::persist`), not a per-collection one — so `&self` is
    /// never read. Kept as a method rather than an associated function so the
    /// `tree.rs` call site (`config.persist_or_default()`) is unaffected.
    #[allow(clippy::unused_self)]
    pub(crate) fn persist_or_default(&self) -> PersistPolicy {
        PersistPolicy::SyncData
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn lsm_config_defaults() {
        let c = LsmConfig::default();
        assert!(c.reversed, "reversed defaults to true (matches Bitcask)");
        assert!(c.lock_stripes >= 1);
        assert_eq!(LsmDbConfig::default().persist, PersistPolicy::SyncData);
    }
}