Skip to main content

nodedb_types/config/tuning/
startup.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Startup tuning: the stall bounds of the boot readiness waits.
4
5use std::time::Duration;
6
7use serde::{Deserialize, Serialize};
8
9/// Default stall bound of the metadata group's replay wait.
10pub const DEFAULT_RAFT_READY_TIMEOUT_MS: u64 = 30_000;
11
12/// Default stall bound of the data groups' replay wait and the Calvin
13/// scheduler catch-up wait.
14pub const DEFAULT_DATA_GROUP_RECOVERY_TIMEOUT_MS: u64 = 60_000;
15
16/// Stall bound of the metadata group's replay wait.
17///
18/// A distinct type from [`DataGroupRecoveryTimeout`], so the two bounds
19/// cannot swap places as adjacent arguments.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct RaftReadyTimeout(pub Duration);
22
23/// Stall bound of the data groups' replay wait and the Calvin scheduler
24/// catch-up wait.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub struct DataGroupRecoveryTimeout(pub Duration);
27
28impl From<Duration> for RaftReadyTimeout {
29    fn from(value: Duration) -> Self {
30        Self(value)
31    }
32}
33
34impl From<Duration> for DataGroupRecoveryTimeout {
35    fn from(value: Duration) -> Self {
36        Self(value)
37    }
38}
39
40fn default_raft_ready_timeout_ms() -> u64 {
41    DEFAULT_RAFT_READY_TIMEOUT_MS
42}
43
44fn default_data_group_recovery_timeout_ms() -> u64 {
45    DEFAULT_DATA_GROUP_RECOVERY_TIMEOUT_MS
46}
47
48/// `[tuning.startup]`: how long each boot wait can go without progress.
49///
50/// A wait that keeps progressing never fails, however long the replay takes.
51/// The server range-checks both values at load. An unknown key is refused, so
52/// a misspelt bound never leaves the default in force silently.
53#[derive(Debug, Clone, Serialize, Deserialize)]
54#[serde(deny_unknown_fields)]
55pub struct StartupTuning {
56    /// Stall bound of the metadata group's replay. Every applied entry resets
57    /// it. Default: 30_000.
58    #[serde(default = "default_raft_ready_timeout_ms")]
59    pub raft_ready_timeout_ms: u64,
60
61    /// Stall bound of the data groups' replay, then of the Calvin scheduler
62    /// catch-up. An election, a commit or apply advance, a scheduler epoch, or
63    /// a finished group or scheduler resets it. Default: 60_000.
64    #[serde(default = "default_data_group_recovery_timeout_ms")]
65    pub data_group_recovery_timeout_ms: u64,
66}
67
68impl Default for StartupTuning {
69    fn default() -> Self {
70        Self {
71            raft_ready_timeout_ms: default_raft_ready_timeout_ms(),
72            data_group_recovery_timeout_ms: default_data_group_recovery_timeout_ms(),
73        }
74    }
75}
76
77impl StartupTuning {
78    /// Stall bound of the metadata group's replay wait.
79    pub fn raft_ready_timeout(&self) -> RaftReadyTimeout {
80        RaftReadyTimeout(Duration::from_millis(self.raft_ready_timeout_ms))
81    }
82
83    /// Stall bound of the data groups' replay and Calvin catch-up waits.
84    pub fn data_group_recovery_timeout(&self) -> DataGroupRecoveryTimeout {
85        DataGroupRecoveryTimeout(Duration::from_millis(self.data_group_recovery_timeout_ms))
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn defaults_are_thirty_and_sixty_seconds() {
95        let cfg = StartupTuning::default();
96        assert_eq!(
97            cfg.raft_ready_timeout(),
98            RaftReadyTimeout(Duration::from_secs(30))
99        );
100        assert_eq!(
101            cfg.data_group_recovery_timeout(),
102            DataGroupRecoveryTimeout(Duration::from_secs(60))
103        );
104    }
105
106    #[test]
107    fn one_bound_set_leaves_the_other_at_its_default() {
108        let cfg: StartupTuning =
109            toml::from_str("raft_ready_timeout_ms = 1234").expect("deserialize");
110        assert_eq!(cfg.raft_ready_timeout_ms, 1234);
111        assert_eq!(
112            cfg.data_group_recovery_timeout_ms,
113            DEFAULT_DATA_GROUP_RECOVERY_TIMEOUT_MS
114        );
115    }
116
117    #[test]
118    fn an_unknown_key_is_refused() {
119        let error = toml::from_str::<StartupTuning>("data_group_recovery_timeout = 1800000")
120            .expect_err("a key without the _ms suffix is not a bound");
121        let message = error.to_string();
122        assert!(message.contains("data_group_recovery_timeout"), "{message}");
123        assert!(
124            message.contains("data_group_recovery_timeout_ms"),
125            "the message must list the keys that exist: {message}"
126        );
127    }
128}