1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/// 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"))
}
}