Skip to main content

nodedb_types/config/tuning/
calvin.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Calvin scheduler tuning — channel sizes, deadlines, and the in-flight
4//! bounds of each per-vShard scheduler.
5//!
6//! The epoch duration belongs to the sequencer, so it is not set here. The
7//! scheduler reads it from the sequencer config it runs beside.
8
9use serde::{Deserialize, Serialize};
10
11fn default_channel_capacity() -> usize {
12    512
13}
14
15fn default_txn_deadline_multiplier() -> u32 {
16    3
17}
18
19fn default_dependent_read_passive_timeout_ms() -> u64 {
20    // A passive read crosses two data groups: its read result is proposed to
21    // each active vShard's group, often led by another node. The timeout only
22    // bounds how long a lost result holds the active vShard's locks, so it
23    // leaves room for a leader change on the way. 250 sequencer epochs.
24    5_000
25}
26
27fn default_verdict_stall_warn_ms() -> u64 {
28    // 250 sequencer epochs of 20 ms.
29    5_000
30}
31
32fn default_max_inflight_backlog() -> usize {
33    // The per-core request queue capacity of the bridge dispatcher. Every
34    // dispatch of one vShard goes to one Data Plane core.
35    1_024
36}
37
38fn default_catch_up_window() -> u64 {
39    // One channel of missed entries per drain.
40    512
41}
42
43fn default_max_redo_entry_bytes() -> usize {
44    // Well below the 64 MiB RPC frame limit, so one entry and the frame
45    // around it always fit.
46    8 * 1024 * 1024
47}
48
49fn default_max_open_redo_bytes() -> u64 {
50    // Sixty-four streams of the default entry size.
51    512 * 1024 * 1024
52}
53
54fn default_restage_attempts() -> u32 {
55    5
56}
57
58fn default_restage_backoff_ms() -> u64 {
59    // Five sequencer epochs of 20 ms. Five doubling waits span about 3 s.
60    100
61}
62
63/// Tuning knobs for the Calvin scheduler that runs per hosted vShard.
64#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
65pub struct CalvinTuning {
66    /// Capacity of each scheduler's bounded input, completion, and verdict
67    /// channels.
68    #[serde(default = "default_channel_capacity")]
69    pub channel_capacity: usize,
70
71    /// Transaction deadline, in sequencer epochs.
72    #[serde(default = "default_txn_deadline_multiplier")]
73    pub txn_deadline_multiplier: u32,
74
75    /// Wait in milliseconds of an active vShard's dependent-read barrier for
76    /// the read results of its passive vShards. Past it, the vShard's
77    /// data-group leader proposes the barrier's timeout entry, and every
78    /// replica aborts a barrier that entry finds incomplete.
79    #[serde(default = "default_dependent_read_passive_timeout_ms")]
80    pub dependent_read_passive_timeout_ms: u64,
81
82    /// Interval in milliseconds between stall warnings for a transaction
83    /// parked on its global verdict. The transaction keeps waiting and never
84    /// aborts. The idle sweep runs at a quarter of this interval.
85    #[serde(default = "default_verdict_stall_warn_ms")]
86    pub verdict_stall_warn_ms: u64,
87
88    /// In-flight backlog at which a scheduler stops taking new sequenced
89    /// input. The backlog counts pending, blocked, and dependent-barrier
90    /// transactions. It also bounds the dependent-read barrier entries a
91    /// vShard holds in memory for transactions its scheduler has not taken
92    /// yet. Entries past it wait in the system catalog alone.
93    #[serde(default = "default_max_inflight_backlog")]
94    pub max_inflight_backlog: usize,
95
96    /// Most sequencer log entries one catch-up drain reads and replays.
97    #[serde(default = "default_catch_up_window")]
98    pub catch_up_window: u64,
99
100    /// Largest encoded redo entry a proposer puts in a data-group log. A
101    /// committed redo past it travels as chunk entries and a final entry.
102    #[serde(default = "default_max_redo_entry_bytes")]
103    pub max_redo_entry_bytes: usize,
104
105    /// Most bytes of chunked redo streams a node holds open. A group leader
106    /// refuses a new stream that would pass it on the leader's node. A node
107    /// holds at most this much per data group it hosts.
108    #[serde(default = "default_max_open_redo_bytes")]
109    pub max_open_redo_bytes: u64,
110
111    /// Most times a leader stages a committed transaction again after its
112    /// own stage failed. An earlier leader's COMMIT vote decided the
113    /// transaction, so it cannot drop. The scheduler halts once the stages
114    /// run out.
115    #[serde(default = "default_restage_attempts")]
116    pub restage_attempts: u32,
117
118    /// Wait in milliseconds before the first restage of a committed
119    /// transaction. Each later restage waits twice as long as the one
120    /// before, at most 60 seconds.
121    #[serde(default = "default_restage_backoff_ms")]
122    pub restage_backoff_ms: u64,
123}
124
125impl Default for CalvinTuning {
126    fn default() -> Self {
127        Self {
128            channel_capacity: default_channel_capacity(),
129            txn_deadline_multiplier: default_txn_deadline_multiplier(),
130            dependent_read_passive_timeout_ms: default_dependent_read_passive_timeout_ms(),
131            verdict_stall_warn_ms: default_verdict_stall_warn_ms(),
132            max_inflight_backlog: default_max_inflight_backlog(),
133            catch_up_window: default_catch_up_window(),
134            max_redo_entry_bytes: default_max_redo_entry_bytes(),
135            max_open_redo_bytes: default_max_open_redo_bytes(),
136            restage_attempts: default_restage_attempts(),
137            restage_backoff_ms: default_restage_backoff_ms(),
138        }
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn defaults() {
148        let t = CalvinTuning::default();
149        assert_eq!(t.channel_capacity, 512);
150        assert_eq!(t.txn_deadline_multiplier, 3);
151        assert_eq!(t.dependent_read_passive_timeout_ms, 5_000);
152        assert_eq!(t.verdict_stall_warn_ms, 5_000);
153        assert_eq!(t.max_inflight_backlog, 1_024);
154        assert_eq!(t.catch_up_window, 512);
155        assert_eq!(t.max_redo_entry_bytes, 8 * 1024 * 1024);
156        assert_eq!(t.max_open_redo_bytes, 512 * 1024 * 1024);
157        assert_eq!(t.restage_attempts, 5);
158        assert_eq!(t.restage_backoff_ms, 100);
159    }
160
161    #[test]
162    fn partial_override() {
163        let toml_str = r#"
164max_inflight_backlog = 256
165verdict_stall_warn_ms = 10000
166"#;
167        let t: CalvinTuning = toml::from_str(toml_str).expect("deserialize");
168        assert_eq!(t.max_inflight_backlog, 256);
169        assert_eq!(t.verdict_stall_warn_ms, 10_000);
170        assert_eq!(t.channel_capacity, 512);
171        assert_eq!(t.catch_up_window, 512);
172    }
173
174    #[test]
175    fn empty_table_keeps_the_defaults() {
176        let t: CalvinTuning = toml::from_str("").expect("deserialize");
177        assert_eq!(t, CalvinTuning::default());
178    }
179}