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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/// 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) {}
}