Skip to main content

reddb_file/
physical_metadata_policy.rs

1//! Process-wide policy knobs for physical metadata sidecars.
2//!
3//! Runtime crates decide the active storage tier; this module owns how that
4//! tier maps to file-artifact emission policy.
5
6use std::sync::atomic::{AtomicU8, AtomicUsize, Ordering};
7
8/// Retention applied when the seq-N catalog journal is enabled at the `Max`
9/// tier. See [`seqn_journal_retention`].
10pub const DEFAULT_METADATA_JOURNAL_RETENTION: usize = 32;
11/// Retention applied when the seq-N catalog journal is opt-in enabled outside
12/// of the `Max` tier, keeping forensics surface minimal on lower tiers.
13pub const OPT_IN_METADATA_JOURNAL_RETENTION: usize = 4;
14
15// JSON sidecar policy. 0 = unset (consult env, default off), 1 = enabled,
16// 2 = disabled. Threaded as a process-global because metadata saves are reached
17// from many call sites that do not currently carry a layout handle.
18static META_JSON_SIDECAR_POLICY: AtomicU8 = AtomicU8::new(0);
19
20/// Process-wide opt-in for the legacy `<data>.meta.json` sidecar.
21pub fn set_meta_json_sidecar_enabled(enabled: bool) {
22    META_JSON_SIDECAR_POLICY.store(if enabled { 1 } else { 2 }, Ordering::Relaxed);
23}
24
25/// Whether new metadata writes should additionally emit the JSON sidecar.
26/// Defaults to `false`; opt-in via [`set_meta_json_sidecar_enabled`] or the
27/// `REDDB_META_JSON_SIDECAR=1` env var. Reads always tolerate either JSON or
28/// binary.
29pub fn meta_json_sidecar_enabled() -> bool {
30    match META_JSON_SIDECAR_POLICY.load(Ordering::Relaxed) {
31        1 => true,
32        2 => false,
33        _ => env_flag("REDDB_META_JSON_SIDECAR"),
34    }
35}
36
37// Seq-N catalog journal policy. 0 = unset (consult env, default off), 1 =
38// enabled, 2 = disabled. Mirrors the meta-json sidecar toggle but governs the
39// `<data>.meta.rdbx.seq-{N}` forensic trail emitted on every metadata save.
40static SEQN_JOURNAL_POLICY: AtomicU8 = AtomicU8::new(0);
41// Retention override. 0 = unset (consult env, default off-tier retention).
42static SEQN_JOURNAL_RETENTION: AtomicUsize = AtomicUsize::new(0);
43
44/// Process-wide opt-in for the seq-N catalog journal.
45pub fn set_seqn_journal_enabled(enabled: bool) {
46    SEQN_JOURNAL_POLICY.store(if enabled { 1 } else { 2 }, Ordering::Relaxed);
47}
48
49/// Whether new metadata saves should also emit a seq-N journal entry.
50pub fn seqn_journal_enabled() -> bool {
51    match SEQN_JOURNAL_POLICY.load(Ordering::Relaxed) {
52        1 => true,
53        2 => false,
54        _ => env_flag("REDDB_SEQN_JOURNAL"),
55    }
56}
57
58// Pager-meta sidecar policy (#477). 0 = unset (consult env, default off: keep
59// `<data>-meta` shadow), 1 = enabled (fold meta into page 1 + overflow chain;
60// no `-meta` sidecar), 2 = disabled (current behavior).
61static FOLD_PAGER_META_POLICY: AtomicU8 = AtomicU8::new(0);
62
63/// Process-wide opt-in for folding pager metadata (page 1) into the datafile
64/// without an adjacent `<data>-meta` shadow.
65pub fn set_fold_pager_meta_enabled(enabled: bool) {
66    FOLD_PAGER_META_POLICY.store(if enabled { 1 } else { 2 }, Ordering::Relaxed);
67}
68
69/// Whether the pager should fold metadata into page 1 only and skip the
70/// `<data>-meta` sidecar shadow. Reads still tolerate the sidecar so existing
71/// databases keep working through the flag flip.
72pub fn fold_pager_meta_enabled() -> bool {
73    match FOLD_PAGER_META_POLICY.load(Ordering::Relaxed) {
74        1 => true,
75        2 => false,
76        _ => env_flag("REDDB_FOLD_PAGER_META"),
77    }
78}
79
80// Fold-DWB-into-WAL policy (#478). 0 = unset (consult env, default off: keep
81// `-dwb` sidecar), 1 = enabled (emit FullPageImage WAL records before first
82// page modification per checkpoint cycle; no `-dwb` sidecar), 2 = disabled.
83static FOLD_DWB_INTO_WAL_POLICY: AtomicU8 = AtomicU8::new(0);
84
85/// Process-wide opt-in for folding the double-write buffer into the WAL via
86/// full-page-image records.
87pub fn set_fold_dwb_into_wal_enabled(enabled: bool) {
88    FOLD_DWB_INTO_WAL_POLICY.store(if enabled { 1 } else { 2 }, Ordering::Relaxed);
89}
90
91/// Whether the pager should fold DWB into WAL (no `<data>-dwb` sidecar).
92/// Reads still tolerate the legacy sidecar so existing databases keep working
93/// through the flag flip.
94pub fn fold_dwb_into_wal_enabled() -> bool {
95    match FOLD_DWB_INTO_WAL_POLICY.load(Ordering::Relaxed) {
96        1 => true,
97        2 => false,
98        _ => env_flag("REDDB_FOLD_DWB_INTO_WAL"),
99    }
100}
101
102/// Process-wide retention for the seq-N journal. `0` resets to defaults (env
103/// or off-tier baseline).
104pub fn set_seqn_journal_retention(retention: usize) {
105    SEQN_JOURNAL_RETENTION.store(retention, Ordering::Relaxed);
106}
107
108/// Resolved retention bound for the seq-N journal. Falls back to env
109/// `REDDB_SEQN_JOURNAL_RETENTION`, then to
110/// [`OPT_IN_METADATA_JOURNAL_RETENTION`].
111pub fn seqn_journal_retention() -> usize {
112    let stored = SEQN_JOURNAL_RETENTION.load(Ordering::Relaxed);
113    if stored > 0 {
114        return stored;
115    }
116    std::env::var("REDDB_SEQN_JOURNAL_RETENTION")
117        .ok()
118        .and_then(|v| v.parse::<usize>().ok())
119        .filter(|v| *v > 0)
120        .unwrap_or(OPT_IN_METADATA_JOURNAL_RETENTION)
121}
122
123fn env_flag(name: &str) -> bool {
124    std::env::var(name)
125        .ok()
126        .map(|v| matches!(v.as_str(), "1" | "true" | "TRUE" | "yes" | "on"))
127        .unwrap_or(false)
128}
129
130#[cfg(test)]
131fn reset_physical_metadata_policy_for_test() {
132    META_JSON_SIDECAR_POLICY.store(0, Ordering::Relaxed);
133    SEQN_JOURNAL_POLICY.store(0, Ordering::Relaxed);
134    FOLD_PAGER_META_POLICY.store(0, Ordering::Relaxed);
135    FOLD_DWB_INTO_WAL_POLICY.store(0, Ordering::Relaxed);
136    SEQN_JOURNAL_RETENTION.store(0, Ordering::Relaxed);
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142    use std::sync::Mutex;
143
144    static POLICY_TEST_LOCK: Mutex<()> = Mutex::new(());
145
146    fn set_env(name: &str, value: &str) {
147        unsafe {
148            std::env::set_var(name, value);
149        }
150    }
151
152    fn remove_env(name: &str) {
153        unsafe {
154            std::env::remove_var(name);
155        }
156    }
157
158    #[test]
159    fn env_flags_and_explicit_overrides_drive_sidecar_policies() {
160        let _guard = POLICY_TEST_LOCK.lock().unwrap();
161        reset_physical_metadata_policy_for_test();
162
163        for value in ["1", "true", "TRUE", "yes", "on"] {
164            set_env("REDDB_META_JSON_SIDECAR", value);
165            assert!(meta_json_sidecar_enabled(), "{value}");
166        }
167        set_env("REDDB_META_JSON_SIDECAR", "false");
168        assert!(!meta_json_sidecar_enabled());
169
170        set_meta_json_sidecar_enabled(true);
171        set_env("REDDB_META_JSON_SIDECAR", "false");
172        assert!(meta_json_sidecar_enabled());
173        set_meta_json_sidecar_enabled(false);
174        set_env("REDDB_META_JSON_SIDECAR", "1");
175        assert!(!meta_json_sidecar_enabled());
176        remove_env("REDDB_META_JSON_SIDECAR");
177    }
178
179    #[test]
180    fn journal_and_fold_policy_overrides_are_independent() {
181        let _guard = POLICY_TEST_LOCK.lock().unwrap();
182        reset_physical_metadata_policy_for_test();
183
184        set_env("REDDB_SEQN_JOURNAL", "1");
185        assert!(seqn_journal_enabled());
186        set_seqn_journal_enabled(false);
187        assert!(!seqn_journal_enabled());
188        set_seqn_journal_enabled(true);
189        assert!(seqn_journal_enabled());
190        remove_env("REDDB_SEQN_JOURNAL");
191
192        set_env("REDDB_FOLD_PAGER_META", "yes");
193        assert!(fold_pager_meta_enabled());
194        set_fold_pager_meta_enabled(false);
195        assert!(!fold_pager_meta_enabled());
196        set_fold_pager_meta_enabled(true);
197        assert!(fold_pager_meta_enabled());
198        remove_env("REDDB_FOLD_PAGER_META");
199
200        set_env("REDDB_FOLD_DWB_INTO_WAL", "on");
201        assert!(fold_dwb_into_wal_enabled());
202        set_fold_dwb_into_wal_enabled(false);
203        assert!(!fold_dwb_into_wal_enabled());
204        set_fold_dwb_into_wal_enabled(true);
205        assert!(fold_dwb_into_wal_enabled());
206        remove_env("REDDB_FOLD_DWB_INTO_WAL");
207    }
208
209    #[test]
210    fn seqn_journal_retention_prefers_override_then_env_then_default() {
211        let _guard = POLICY_TEST_LOCK.lock().unwrap();
212        reset_physical_metadata_policy_for_test();
213        remove_env("REDDB_SEQN_JOURNAL_RETENTION");
214
215        assert_eq!(seqn_journal_retention(), OPT_IN_METADATA_JOURNAL_RETENTION);
216
217        set_env("REDDB_SEQN_JOURNAL_RETENTION", "12");
218        assert_eq!(seqn_journal_retention(), 12);
219        set_env("REDDB_SEQN_JOURNAL_RETENTION", "0");
220        assert_eq!(seqn_journal_retention(), OPT_IN_METADATA_JOURNAL_RETENTION);
221        set_env("REDDB_SEQN_JOURNAL_RETENTION", "bad");
222        assert_eq!(seqn_journal_retention(), OPT_IN_METADATA_JOURNAL_RETENTION);
223
224        set_seqn_journal_retention(99);
225        assert_eq!(seqn_journal_retention(), 99);
226        set_seqn_journal_retention(0);
227        assert_eq!(seqn_journal_retention(), OPT_IN_METADATA_JOURNAL_RETENTION);
228
229        remove_env("REDDB_SEQN_JOURNAL_RETENTION");
230    }
231}