Skip to main content

nodedb_types/config/tuning/
maintenance.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Background maintenance tuning — auto-ANALYZE triggering.
4//!
5//! Covers the Control-Plane maintenance work that runs off the write path.
6//! Per-database CPU budgets come from the quota record, not from here.
7
8use serde::{Deserialize, Serialize};
9
10fn default_auto_analyze_min_mutations() -> u64 {
11    // A collection re-analyzes once mutations reach 10% of its last row
12    // count. This floor keeps a small collection from re-scanning on a
13    // handful of writes.
14    1_000
15}
16
17fn default_clone_sweep_interval_ms() -> u64 {
18    30_000
19}
20
21fn default_constraint_reconcile_interval_ms() -> u64 {
22    1_000
23}
24
25fn default_scope_expiry_interval_secs() -> u64 {
26    60
27}
28
29/// Tuning knobs for background maintenance triggered by user writes.
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct MaintenanceTuning {
32    /// Smallest mutation count that can trigger an automatic ANALYZE.
33    ///
34    /// The trigger fires at `max(last_row_count / 10, this)`. Lowering it
35    /// refreshes a small collection's statistics sooner. Raising it trades
36    /// planner accuracy for fewer background scans.
37    #[serde(default = "default_auto_analyze_min_mutations")]
38    pub auto_analyze_min_mutations: u64,
39
40    /// Interval between clone materializer sweeps, in milliseconds.
41    ///
42    /// The sweep progresses cloned collections from Shadowed to Materialized
43    /// without explicit DDL. Lowering it materializes clones sooner, at the
44    /// cost of more scan passes.
45    #[serde(default = "default_clone_sweep_interval_ms")]
46    pub clone_sweep_interval_ms: u64,
47
48    /// Interval between CRDT constraint reconcile passes, in milliseconds.
49    ///
50    /// Each pass re-derives every collection's constraint set from the
51    /// catalog and replicates it to data-group replicas. Lowering it converges
52    /// an altered collection sooner, at the cost of catalog reads and Raft
53    /// proposals.
54    #[serde(default = "default_constraint_reconcile_interval_ms")]
55    pub constraint_reconcile_interval_ms: u64,
56
57    /// Interval between scope grant expiry sweeps, in seconds.
58    ///
59    /// Each sweep executes the `ON EXPIRE` action of every expired grant.
60    /// `ScopeGrant::is_effective` already enforces expiry on every read, so
61    /// this loop only makes the outcome durable. 10 is the floor. Below it the
62    /// sweep costs more than the resolution it buys.
63    #[serde(default = "default_scope_expiry_interval_secs")]
64    pub scope_expiry_interval_secs: u64,
65}
66
67impl Default for MaintenanceTuning {
68    fn default() -> Self {
69        Self {
70            auto_analyze_min_mutations: default_auto_analyze_min_mutations(),
71            clone_sweep_interval_ms: default_clone_sweep_interval_ms(),
72            constraint_reconcile_interval_ms: default_constraint_reconcile_interval_ms(),
73            scope_expiry_interval_secs: default_scope_expiry_interval_secs(),
74        }
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn default_floor_is_one_thousand() {
84        assert_eq!(
85            MaintenanceTuning::default().auto_analyze_min_mutations,
86            1000
87        );
88    }
89
90    #[test]
91    fn new_loop_interval_defaults() {
92        let tuning = MaintenanceTuning::default();
93        assert_eq!(tuning.clone_sweep_interval_ms, 30_000);
94        assert_eq!(tuning.constraint_reconcile_interval_ms, 1_000);
95        assert_eq!(tuning.scope_expiry_interval_secs, 60);
96    }
97
98    #[test]
99    fn override_via_toml() {
100        let parsed: MaintenanceTuning =
101            toml::from_str("auto_analyze_min_mutations = 20").expect("deserialize");
102        assert_eq!(parsed.auto_analyze_min_mutations, 20);
103    }
104
105    #[test]
106    fn empty_table_keeps_the_default() {
107        let parsed: MaintenanceTuning = toml::from_str("").expect("deserialize");
108        assert_eq!(parsed.auto_analyze_min_mutations, 1000);
109    }
110}