armdb 0.12.0

sharded bitcask key-value storage optimized for NVMe
Documentation
/// Common trait for armdb collections managed by [`Db`](super::tree_db::Db).
///
/// Implemented for `TypedTree`, `TypedMap`, `ZeroTree`, `ZeroMap`
/// where the value type implements [`CollectionMeta`](crate::CollectionMeta).
pub trait Collection: Send + Sync {
    /// Collection name (from `CollectionMeta::NAME`).
    fn name(&self) -> &str;
    /// Number of entries in the collection.
    fn len(&self) -> usize;
    fn is_empty(&self) -> bool {
        self.len() == 0
    }
    /// Run a compaction pass across all shards.
    fn compact(&self) -> crate::DbResult<usize>;
    /// Flush in-memory write buffers and pending hint data so an immediate
    /// process exit will not lose committed writes. No default — every impl
    /// must provide this to avoid silent durability gaps.
    fn flush(&self) -> crate::DbResult<()>;
    /// Lightest operation that bounds the periodic durability window for
    /// background use. Backend-specific:
    /// - Bitcask: flush userspace write buffers to the OS page cache (no fsync,
    ///   no hint regeneration) — bounds process-crash (panic/kill/OOM) loss.
    /// - FixedStore: fdatasync dirty pages (data is already in the page cache
    ///   via pwrite) — bounds power-loss loss for idle tail writes.
    ///
    /// Unlike [`flush`](Self::flush), this must NOT rebuild hint files or fsync
    /// the active data file on Bitcask, so it is cheap enough to run on a short
    /// interval. No default — every impl must provide it.
    fn periodic_flush(&self) -> crate::DbResult<()>;
    /// Make everything written so far durable on this collection: Bitcask —
    /// flush write buffers and fsync the active data file, without rebuilding
    /// hint files; FixedStore — `fdatasync`.
    ///
    /// Unlike [`periodic_flush`](Self::periodic_flush) (no Bitcask fsync at all)
    /// and [`flush`](Self::flush) (fsync only when hints are enabled), this is a
    /// real durability barrier. The transaction checkpoint uses it before
    /// retiring a log record — the record is the only durable copy until then.
    fn durable_flush(&self) -> crate::DbResult<()>;
    /// Full clean shutdown for this collection. Heavier than [`flush`](Self::flush):
    /// - Bitcask/Var: flush write buffers, write hint files, fsync data files.
    /// - FixedStore: fdatasync data, write the `fixed.versions` sidecar and set
    ///   the clean-shutdown flag so the NEXT open takes Clean recovery.
    ///
    /// # Contract
    ///
    /// The caller must stop all application writers first. Any write after this
    /// call — a local mutation *or* a FixedStore replication-apply from a leader —
    /// downgrades the affected shard back to Dirty recovery (safe, slow); it never
    /// corrupts. Background [`periodic_flush`](Self::periodic_flush) and
    /// [`compact`](Self::compact) are not writes and are harmless afterwards.
    /// [`Db::close`](super::db::Db::close) / [`Db::clean_shutdown`](super::db::Db::clean_shutdown)
    /// also gate TTL sweep (write source) before calling this; joining the sweeper
    /// thread remains [`Db::shutdown`](super::db::Db::shutdown).
    /// Idempotent; [`Db`](super::db::Db) drop after it keeps the clean state intact.
    ///
    /// # Comparison with other shutdown paths
    ///
    /// | Method | Bitcask hints | Fixed sidecar/clean-flag | Final fsync | Stops workers |
    /// |---|---|---|---|---|
    /// | [`periodic_flush`](Self::periodic_flush) (background) | no | no | no (Bitcask) / fdatasync (Fixed) | — |
    /// | [`Db::close`](super::db::Db::close) | writes hints | **no** | no / fdatasync | no (TTL gated) |
    /// | [`Db::clean_shutdown`](super::db::Db::clean_shutdown) | writes hints | **yes** | yes | no (TTL gated) |
    /// | [`Db::shutdown`](super::db::Db::shutdown) | via close | **no** | — | yes |
    fn clean_shutdown(&self) -> crate::DbResult<()>;
    /// Sweep expired rows up to `budget` candidates. `now_ms` is wall-clock millis.
    fn sweep_expired(
        &self,
        _now_ms: u64,
        _budget: usize,
    ) -> crate::DbResult<crate::ttl::SweepOutcome> {
        Ok(crate::ttl::SweepOutcome::default())
    }
    /// Enable or disable background TTL sweep for this collection.
    fn set_ttl_sweep(&self, _enabled: bool) -> crate::DbResult<()> {
        Ok(())
    }
    /// Rebuild the in-memory TTL index from live rows.
    fn rebuild_ttl_index(&self) -> crate::DbResult<()> {
        Err(crate::DbError::Client("collection has no TTL policy"))
    }
    /// Consume the `counters_dirty` marker (T9) and report whether it was set
    /// — i.e. whether **this session's open of this collection as a counted
    /// source** is what persisted the flag.
    ///
    /// `Db::close` / `Db::clean_shutdown` clear the durable flag only for a
    /// collection that answers `true`: the flag also carries a second,
    /// marker-less meaning set by `run_migration` ("my content moved under
    /// some counter"), which only a *source's* open consumes. Clearing that
    /// one at close would drop the sole evidence that a recount is owed;
    /// leaving it costs at most one redundant recount per open. (Once a
    /// source's open-time recount *has* consumed it, `Db` records that
    /// separately and the close retires the target's flag too.)
    ///
    /// Default `false` — a collection with no bound counter marker never
    /// marked anything.
    fn reset_counter_dirty_marker(&self) -> bool {
        false
    }
    /// Open-time / on-demand counter recount (T9 Task 8) — see
    /// `ManagedOpen::reconcile_counters` for the two-pass walk this reaches.
    /// [`Db::recount_counters`](super::db::Db::recount_counters) uses this
    /// by-name entry point since it only has a collection name, not a
    /// concrete type. Default no-op (`Ok(0)`) — only a collection with a
    /// bound counter carrier needs it.
    fn reconcile_counters(&self) -> crate::DbResult<u64> {
        Ok(0)
    }
    /// The collections this one declares as counter targets (T9 Task 8) —
    /// empty when this collection is not itself a counted source (it may be
    /// a plain counter *target*, or unrelated to the counter system).
    /// [`Db::recount_counters`](super::db::Db::recount_counters) uses this to
    /// refuse recounting — and, critically, clearing the `counters_dirty`
    /// flag of — a collection that isn't a source: for a plain target, that
    /// flag is the only evidence a source's own later open reads to know a
    /// recount is owed (see `Db::open_managed`), so clearing it here would
    /// silently destroy recovery state instead of recounting anything.
    /// Default empty; overridden by the six typed families.
    fn counter_target_names(&self) -> Vec<&'static str> {
        Vec::new()
    }
    /// Terminally close active realtime receivers after all database writers stop.
    fn close_watches(&self) {}
}