1use std::path::PathBuf;
2
3use serde::Deserialize;
4use serde::Serialize;
5
6use crate::human::HumanDuration;
7use crate::human::HumanSize;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
11#[serde(rename_all = "lowercase")]
12pub enum ColdBackend {
13 #[default]
14 #[serde(alias = "disabled", alias = "off")]
15 None,
16 #[serde(alias = "mem", alias = "inmem")]
17 Memory,
18 S3,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
23#[serde(rename_all = "lowercase")]
24pub enum WalBackend {
25 #[default]
26 Memory,
27 Disk,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
32#[serde(rename_all = "lowercase")]
33pub enum RaftSnapshotBackend {
34 #[default]
35 #[serde(alias = "default", alias = "")]
36 Inline,
37 Local,
38 S3,
39}
40
41#[derive(Debug, Clone, Default, Deserialize, Serialize)]
46#[serde(default, deny_unknown_fields)]
47pub struct UrsulaConfig {
48 pub server: ServerConfig,
49 pub runtime: RuntimeConfig,
50 pub raft: RaftConfig,
51 pub storage: StorageConfig,
52 pub governance: GovernanceConfig,
53 pub observability: ObservabilityConfig,
54}
55
56#[derive(Debug, Clone, Deserialize, Serialize)]
58#[serde(default, deny_unknown_fields)]
59pub struct ServerConfig {
60 pub listen: String,
62 pub cluster_listen: Option<String>,
65 pub http_inflight_body_size: HumanSize,
67}
68
69impl Default for ServerConfig {
70 fn default() -> Self {
71 Self {
72 listen: "127.0.0.1:4437".to_string(),
73 cluster_listen: None,
74 http_inflight_body_size: HumanSize::mib(256),
75 }
76 }
77}
78
79#[derive(Debug, Clone, Deserialize, Serialize)]
81#[serde(default, deny_unknown_fields)]
82pub struct RuntimeConfig {
83 pub core_count: usize,
85 pub node_memory_abort_cap_size: Option<HumanSize>,
88 pub external_payload_min_size: Option<HumanSize>,
91 pub live_read_max_waiters_per_core: Option<usize>,
93}
94
95impl Default for RuntimeConfig {
96 fn default() -> Self {
97 Self {
98 core_count: std::thread::available_parallelism()
99 .map(|n| n.get())
100 .unwrap_or(4),
101 node_memory_abort_cap_size: None,
102 external_payload_min_size: None,
103 live_read_max_waiters_per_core: Some(65_536),
104 }
105 }
106}
107
108#[derive(Debug, Clone, Deserialize, Serialize)]
110#[serde(default, deny_unknown_fields)]
111pub struct RaftConfig {
112 pub node_id: u64,
115 pub group_count: usize,
117 pub max_uncommitted_size_per_group: Option<HumanSize>,
121 pub init_membership: bool,
123 pub init_membership_per_group: bool,
125 pub wal: WalConfig,
127 pub peers: Vec<RaftPeerConfig>,
129 #[serde(default)]
135 pub groups: Vec<RaftGroupConfig>,
136 pub rejoin_probe: HumanDuration,
140 pub bootstrap_peer_probe: HumanDuration,
143 pub bootstrap_peer_probe_interval: HumanDuration,
145 pub bootstrap_peer_connect: HumanDuration,
147 pub install_snapshot_timeout: HumanDuration,
152 pub memory_bootstrap_marker_dir: Option<PathBuf>,
156 pub grpc_reconnect_after_failures: usize,
158 pub snapshot_install_max_concurrency: usize,
160}
161
162impl Default for RaftConfig {
163 fn default() -> Self {
164 Self {
165 node_id: 0,
166 group_count: std::thread::available_parallelism()
167 .map(|n| n.get().saturating_mul(16).max(1))
168 .unwrap_or(16),
169 max_uncommitted_size_per_group: None,
170 init_membership: false,
171 init_membership_per_group: false,
172 wal: WalConfig::default(),
173 peers: Vec::new(),
174 groups: Vec::new(),
175 rejoin_probe: HumanDuration::sec(6),
176 bootstrap_peer_probe: HumanDuration::sec(60),
177 bootstrap_peer_probe_interval: HumanDuration::milli(250),
178 bootstrap_peer_connect: HumanDuration::milli(500),
179 install_snapshot_timeout: HumanDuration::sec(120),
180 memory_bootstrap_marker_dir: None,
181 grpc_reconnect_after_failures: 8,
182 snapshot_install_max_concurrency: 1,
183 }
184 }
185}
186
187#[derive(Debug, Clone, Deserialize, Serialize)]
189#[serde(default, deny_unknown_fields)]
190pub struct WalConfig {
191 pub backend: WalBackend,
193 pub path: Option<PathBuf>,
195}
196
197impl WalConfig {
198 pub fn resolved_path(&self) -> Option<PathBuf> {
204 match self.backend {
205 WalBackend::Memory => None,
206 WalBackend::Disk => self.path.as_ref().map(|p| p.join("raft-log")),
207 }
208 }
209}
210
211impl Default for WalConfig {
212 fn default() -> Self {
213 Self {
214 backend: WalBackend::Memory,
215 path: None,
216 }
217 }
218}
219
220#[derive(Debug, Clone, Deserialize, Serialize)]
222#[serde(deny_unknown_fields)]
223pub struct RaftPeerConfig {
224 pub node_id: u64,
226 pub url: String,
228}
229
230#[derive(Debug, Clone, Deserialize, Serialize)]
234#[serde(deny_unknown_fields)]
235pub struct RaftGroupConfig {
236 pub raft_group_id: u32,
238 pub voters: Vec<u64>,
240}
241
242#[derive(Debug, Clone, Default, Deserialize, Serialize)]
244#[serde(default, deny_unknown_fields)]
245pub struct StorageConfig {
246 pub cold: ColdConfig,
248 pub snapshot: RaftSnapshotConfig,
250}
251
252#[derive(Debug, Clone, Deserialize, Serialize)]
254#[serde(default, deny_unknown_fields)]
255pub struct ColdConfig {
256 pub backend: ColdBackend,
258 pub root: Option<String>,
260 pub s3: Option<S3Config>,
263 pub cache: Option<ColdCacheConfig>,
265 pub flush_interval: HumanDuration,
267 pub flush_size: HumanSize,
269 pub flush_min_hot_size: Option<HumanSize>,
272 pub flush_max_size: Option<HumanSize>,
275 pub flush_max_concurrency: usize,
277 pub max_hot_size_per_group: Option<HumanSize>,
280 pub gc_interval: HumanDuration,
282 pub gc_max_entries: usize,
284}
285
286impl ColdConfig {
287 pub fn flush_min_hot_size(&self) -> HumanSize {
292 self.flush_min_hot_size.unwrap_or(self.flush_size)
293 }
294
295 pub fn flush_max_size(&self) -> HumanSize {
300 self.flush_max_size.unwrap_or(self.flush_size)
301 }
302}
303
304impl Default for ColdConfig {
305 fn default() -> Self {
306 Self {
307 backend: ColdBackend::None,
308 root: None,
309 s3: None,
310 cache: None,
311 flush_interval: HumanDuration::sec(1),
312 flush_size: HumanSize::mib(8),
313 flush_min_hot_size: None,
314 flush_max_size: None,
315 flush_max_concurrency: 4,
316 max_hot_size_per_group: Some(HumanSize::mib(64)),
317 gc_interval: HumanDuration::sec(5),
318 gc_max_entries: 256,
319 }
320 }
321}
322
323#[derive(Debug, Clone, Deserialize, Serialize)]
325#[serde(default, deny_unknown_fields)]
326pub struct S3Config {
327 pub bucket: Option<String>,
329 pub region: Option<String>,
331 pub endpoint: Option<String>,
333 pub access_key_id: Option<String>,
335 pub secret_access_key: Option<String>,
337 pub session_token: Option<String>,
339 pub timeout: HumanDuration,
341 pub max_retries: usize,
343 pub probe_timeout: HumanDuration,
345 pub unhealthy_ticks: usize,
347 pub heal_ticks: usize,
349}
350
351impl Default for S3Config {
352 fn default() -> Self {
353 Self {
354 bucket: None,
355 region: None,
356 endpoint: None,
357 access_key_id: None,
358 secret_access_key: None,
359 session_token: None,
360 timeout: HumanDuration::sec(10),
361 max_retries: 3,
362 probe_timeout: HumanDuration::sec(2),
363 unhealthy_ticks: 1,
364 heal_ticks: 2,
365 }
366 }
367}
368
369#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
371#[serde(default, deny_unknown_fields)]
372pub struct ColdCacheConfig {
373 pub max_size: HumanSize,
375 pub block_size: HumanSize,
377 pub readahead_blocks: usize,
379}
380
381impl Default for ColdCacheConfig {
382 fn default() -> Self {
383 Self {
384 max_size: HumanSize::mib(256),
385 block_size: HumanSize::mib(1),
386 readahead_blocks: 4,
387 }
388 }
389}
390
391#[derive(Debug, Clone, Deserialize, Serialize)]
393#[serde(default, deny_unknown_fields)]
394pub struct RaftSnapshotConfig {
395 pub backend: RaftSnapshotBackend,
397 pub local_root: Option<PathBuf>,
400 pub s3_prefix: Option<String>,
402 pub drive_interval: Option<HumanDuration>,
408 pub drive_flush_concurrency: usize,
410}
411
412impl Default for RaftSnapshotConfig {
413 fn default() -> Self {
414 Self {
415 backend: RaftSnapshotBackend::Inline,
416 local_root: None,
417 s3_prefix: None,
418 drive_interval: None,
419 drive_flush_concurrency: 4,
420 }
421 }
422}
423
424#[derive(Debug, Clone, Default, Deserialize, Serialize)]
426#[serde(default, deny_unknown_fields)]
427pub struct GovernanceConfig {
428 pub leadership_balance: LeadershipBalanceConfig,
430 pub cluster_probe: ClusterProbeConfig,
432 pub commit_stall: CommitStallConfig,
434 pub cold_health: ColdHealthConfig,
436}
437
438#[derive(Debug, Clone, Deserialize, Serialize)]
440#[serde(default, deny_unknown_fields)]
441pub struct LeadershipBalanceConfig {
442 pub interval: HumanDuration,
444 pub max_per_tick: usize,
446 pub peer_timeout: HumanDuration,
448}
449
450impl Default for LeadershipBalanceConfig {
451 fn default() -> Self {
452 Self {
453 interval: HumanDuration::sec(5),
454 max_per_tick: 4,
455 peer_timeout: HumanDuration::milli(500),
456 }
457 }
458}
459
460#[derive(Debug, Clone, Deserialize, Serialize)]
462#[serde(default, deny_unknown_fields)]
463pub struct ClusterProbeConfig {
464 pub interval: HumanDuration,
466 pub probe_size: HumanSize,
468 pub timeout: HumanDuration,
470 pub unhealthy_ticks: usize,
472 pub heal_ticks: usize,
474}
475
476impl Default for ClusterProbeConfig {
477 fn default() -> Self {
478 Self {
479 interval: HumanDuration::milli(500),
480 probe_size: HumanSize::kib(64),
481 timeout: HumanDuration::milli(200),
482 unhealthy_ticks: 2,
483 heal_ticks: 6,
484 }
485 }
486}
487
488#[derive(Debug, Clone, Deserialize, Serialize)]
490#[serde(default, deny_unknown_fields)]
491pub struct CommitStallConfig {
492 pub interval: HumanDuration,
494 pub threshold: HumanDuration,
497}
498
499impl Default for CommitStallConfig {
500 fn default() -> Self {
501 Self {
502 interval: HumanDuration::sec(2),
503 threshold: HumanDuration::sec(15),
504 }
505 }
506}
507
508#[derive(Debug, Clone, Deserialize, Serialize)]
510#[serde(default, deny_unknown_fields)]
511pub struct ColdHealthConfig {
512 pub interval: HumanDuration,
514 pub unhealthy_ticks: usize,
516 pub heal_ticks: usize,
518 pub hot_size_high: HumanSize,
521 pub hot_size_low: HumanSize,
524 pub errors_per_tick_high: usize,
526}
527
528impl Default for ColdHealthConfig {
529 fn default() -> Self {
530 Self {
531 interval: HumanDuration::sec(2),
532 unhealthy_ticks: 3,
533 heal_ticks: 5,
534 hot_size_high: HumanSize::mib(7),
535 hot_size_low: HumanSize::mib(4),
536 errors_per_tick_high: 1,
537 }
538 }
539}
540
541#[derive(Debug, Clone, Default, Deserialize, Serialize)]
543#[serde(default, deny_unknown_fields)]
544pub struct ObservabilityConfig {
545 pub tokio_console: bool,
547}