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 S3,
38}
39
40#[derive(Debug, Clone, Default, Deserialize, Serialize)]
44#[serde(default, deny_unknown_fields)]
45pub struct UrsulaConfig {
46 pub server: ServerConfig,
47 pub runtime: RuntimeConfig,
48 pub raft: RaftConfig,
49 pub storage: StorageConfig,
50 pub governance: GovernanceConfig,
51 pub observability: ObservabilityConfig,
52}
53
54#[derive(Debug, Clone, Deserialize, Serialize)]
56#[serde(default, deny_unknown_fields)]
57pub struct ServerConfig {
58 pub listen: String,
60 pub cluster_listen: Option<String>,
63 pub admin_listen: String,
68 pub http_inflight_body_size: HumanSize,
70}
71
72impl Default for ServerConfig {
73 fn default() -> Self {
74 Self {
75 listen: "127.0.0.1:4437".to_string(),
76 cluster_listen: None,
77 admin_listen: "127.0.0.1:4438".to_string(),
78 http_inflight_body_size: HumanSize::mib(256),
79 }
80 }
81}
82
83#[derive(Debug, Clone, Deserialize, Serialize)]
85#[serde(default, deny_unknown_fields)]
86pub struct RuntimeConfig {
87 pub core_count: usize,
89 pub node_memory_abort_cap_size: Option<HumanSize>,
92 pub external_payload_min_size: Option<HumanSize>,
95 pub live_read_max_waiters_per_core: Option<usize>,
97}
98
99impl Default for RuntimeConfig {
100 fn default() -> Self {
101 Self {
102 core_count: std::thread::available_parallelism()
103 .map(|n| n.get())
104 .unwrap_or(4),
105 node_memory_abort_cap_size: None,
106 external_payload_min_size: None,
107 live_read_max_waiters_per_core: Some(65_536),
108 }
109 }
110}
111
112#[derive(Debug, Clone, Deserialize, Serialize)]
114#[serde(default, deny_unknown_fields)]
115pub struct RaftConfig {
116 pub node_id: u64,
119 pub group_count: usize,
121 pub max_uncommitted_size_per_group: Option<HumanSize>,
125 pub init_membership: bool,
127 pub init_membership_per_group: bool,
129 pub wal: WalConfig,
131 pub peers: Vec<RaftPeerConfig>,
133 #[serde(default)]
139 pub groups: Vec<RaftGroupConfig>,
140 pub rejoin_probe: HumanDuration,
144 pub bootstrap_peer_probe: HumanDuration,
147 pub bootstrap_peer_probe_interval: HumanDuration,
149 pub bootstrap_peer_connect: HumanDuration,
151 pub install_snapshot_timeout: HumanDuration,
156 pub memory_bootstrap_marker_dir: Option<PathBuf>,
161 pub grpc_reconnect_after_failures: usize,
163 pub snapshot_build_max_concurrency: usize,
165 pub snapshot_install_max_concurrency: usize,
167}
168
169impl Default for RaftConfig {
170 fn default() -> Self {
171 Self {
172 node_id: 0,
173 group_count: std::thread::available_parallelism()
174 .map(|n| n.get().saturating_mul(16).max(1))
175 .unwrap_or(16),
176 max_uncommitted_size_per_group: None,
177 init_membership: false,
178 init_membership_per_group: false,
179 wal: WalConfig::default(),
180 peers: Vec::new(),
181 groups: Vec::new(),
182 rejoin_probe: HumanDuration::sec(6),
183 bootstrap_peer_probe: HumanDuration::sec(60),
184 bootstrap_peer_probe_interval: HumanDuration::milli(250),
185 bootstrap_peer_connect: HumanDuration::milli(500),
186 install_snapshot_timeout: HumanDuration::sec(120),
187 memory_bootstrap_marker_dir: None,
188 grpc_reconnect_after_failures: 8,
189 snapshot_build_max_concurrency: 1,
190 snapshot_install_max_concurrency: 1,
191 }
192 }
193}
194
195#[derive(Debug, Clone, Deserialize, Serialize)]
197#[serde(default, deny_unknown_fields)]
198pub struct WalConfig {
199 pub backend: WalBackend,
201 pub path: Option<PathBuf>,
203}
204
205impl WalConfig {
206 pub fn resolved_path(&self) -> Option<PathBuf> {
212 match self.backend {
213 WalBackend::Memory => None,
214 WalBackend::Disk => self.path.as_ref().map(|p| p.join("raft-log")),
215 }
216 }
217}
218
219impl Default for WalConfig {
220 fn default() -> Self {
221 Self {
222 backend: WalBackend::Memory,
223 path: None,
224 }
225 }
226}
227
228#[derive(Debug, Clone, Deserialize, Serialize)]
230#[serde(deny_unknown_fields)]
231pub struct RaftPeerConfig {
232 pub node_id: u64,
234 pub url: String,
236}
237
238#[derive(Debug, Clone, Deserialize, Serialize)]
242#[serde(deny_unknown_fields)]
243pub struct RaftGroupConfig {
244 pub raft_group_id: u32,
246 pub voters: Vec<u64>,
248}
249
250#[derive(Debug, Clone, Default, Deserialize, Serialize)]
252#[serde(default, deny_unknown_fields)]
253pub struct StorageConfig {
254 pub cold: ColdConfig,
256 pub snapshot: RaftSnapshotConfig,
258}
259
260#[derive(Debug, Clone, Deserialize, Serialize)]
262#[serde(default, deny_unknown_fields)]
263pub struct ColdConfig {
264 pub backend: ColdBackend,
266 pub root: Option<String>,
268 pub s3: Option<S3Config>,
271 pub cache: Option<ColdCacheConfig>,
273 pub flush_interval: HumanDuration,
275 pub flush_size: HumanSize,
277 pub flush_min_hot_size: Option<HumanSize>,
280 pub flush_max_size: Option<HumanSize>,
283 pub flush_max_concurrency: usize,
285 pub max_hot_size_per_group: Option<HumanSize>,
288 pub gc_interval: HumanDuration,
290 pub gc_max_entries: usize,
292}
293
294impl ColdConfig {
295 pub fn flush_min_hot_size(&self) -> HumanSize {
300 self.flush_min_hot_size.unwrap_or(self.flush_size)
301 }
302
303 pub fn flush_max_size(&self) -> HumanSize {
308 self.flush_max_size.unwrap_or(self.flush_size)
309 }
310}
311
312impl Default for ColdConfig {
313 fn default() -> Self {
314 Self {
315 backend: ColdBackend::None,
316 root: None,
317 s3: None,
318 cache: None,
319 flush_interval: HumanDuration::sec(1),
320 flush_size: HumanSize::mib(8),
321 flush_min_hot_size: None,
322 flush_max_size: None,
323 flush_max_concurrency: 4,
324 max_hot_size_per_group: Some(HumanSize::mib(64)),
325 gc_interval: HumanDuration::sec(5),
326 gc_max_entries: 256,
327 }
328 }
329}
330
331#[derive(Debug, Clone, Deserialize, Serialize)]
333#[serde(default, deny_unknown_fields)]
334pub struct S3Config {
335 pub bucket: Option<String>,
337 pub region: Option<String>,
339 pub endpoint: Option<String>,
341 pub access_key_id: Option<String>,
343 pub secret_access_key: Option<String>,
345 pub session_token: Option<String>,
347 pub timeout: HumanDuration,
349 pub max_retries: usize,
351 pub probe_timeout: HumanDuration,
353 pub unhealthy_ticks: usize,
355 pub heal_ticks: usize,
357}
358
359impl Default for S3Config {
360 fn default() -> Self {
361 Self {
362 bucket: None,
363 region: None,
364 endpoint: None,
365 access_key_id: None,
366 secret_access_key: None,
367 session_token: None,
368 timeout: HumanDuration::sec(10),
369 max_retries: 3,
370 probe_timeout: HumanDuration::sec(2),
371 unhealthy_ticks: 1,
372 heal_ticks: 2,
373 }
374 }
375}
376
377#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
379#[serde(default, deny_unknown_fields)]
380pub struct ColdCacheConfig {
381 pub max_size: HumanSize,
383 pub block_size: HumanSize,
385 pub readahead_blocks: usize,
387}
388
389impl Default for ColdCacheConfig {
390 fn default() -> Self {
391 Self {
392 max_size: HumanSize::mib(256),
393 block_size: HumanSize::mib(1),
394 readahead_blocks: 4,
395 }
396 }
397}
398
399#[derive(Debug, Clone, Deserialize, Serialize)]
401#[serde(default, deny_unknown_fields)]
402pub struct RaftSnapshotConfig {
403 pub backend: RaftSnapshotBackend,
405 pub s3_prefix: Option<String>,
407 pub drive_interval: Option<HumanDuration>,
413 pub drive_flush_concurrency: usize,
415}
416
417impl Default for RaftSnapshotConfig {
418 fn default() -> Self {
419 Self {
420 backend: RaftSnapshotBackend::Inline,
421 s3_prefix: None,
422 drive_interval: None,
423 drive_flush_concurrency: 4,
424 }
425 }
426}
427
428#[derive(Debug, Clone, Default, Deserialize, Serialize)]
430#[serde(default, deny_unknown_fields)]
431pub struct GovernanceConfig {
432 pub leadership_balance: LeadershipBalanceConfig,
434 pub cluster_probe: ClusterProbeConfig,
436 pub commit_stall: CommitStallConfig,
438 pub cold_health: ColdHealthConfig,
440}
441
442#[derive(Debug, Clone, Deserialize, Serialize)]
444#[serde(default, deny_unknown_fields)]
445pub struct LeadershipBalanceConfig {
446 pub interval: HumanDuration,
448 pub max_per_tick: usize,
450 pub peer_timeout: HumanDuration,
452}
453
454impl Default for LeadershipBalanceConfig {
455 fn default() -> Self {
456 Self {
457 interval: HumanDuration::sec(5),
458 max_per_tick: 4,
459 peer_timeout: HumanDuration::milli(500),
460 }
461 }
462}
463
464#[derive(Debug, Clone, Deserialize, Serialize)]
466#[serde(default, deny_unknown_fields)]
467pub struct ClusterProbeConfig {
468 pub interval: HumanDuration,
470 pub probe_size: HumanSize,
472 pub timeout: HumanDuration,
474 pub unhealthy_ticks: usize,
476 pub heal_ticks: usize,
478}
479
480impl Default for ClusterProbeConfig {
481 fn default() -> Self {
482 Self {
483 interval: HumanDuration::milli(500),
484 probe_size: HumanSize::kib(64),
485 timeout: HumanDuration::milli(200),
486 unhealthy_ticks: 2,
487 heal_ticks: 6,
488 }
489 }
490}
491
492#[derive(Debug, Clone, Deserialize, Serialize)]
494#[serde(default, deny_unknown_fields)]
495pub struct CommitStallConfig {
496 pub interval: HumanDuration,
498 pub threshold: HumanDuration,
501}
502
503impl Default for CommitStallConfig {
504 fn default() -> Self {
505 Self {
506 interval: HumanDuration::sec(2),
507 threshold: HumanDuration::sec(15),
508 }
509 }
510}
511
512#[derive(Debug, Clone, Deserialize, Serialize)]
514#[serde(default, deny_unknown_fields)]
515pub struct ColdHealthConfig {
516 pub interval: HumanDuration,
518 pub unhealthy_ticks: usize,
520 pub heal_ticks: usize,
522 pub hot_size_high: HumanSize,
525 pub hot_size_low: HumanSize,
528 pub errors_per_tick_high: usize,
530}
531
532impl Default for ColdHealthConfig {
533 fn default() -> Self {
534 Self {
535 interval: HumanDuration::sec(2),
536 unhealthy_ticks: 3,
537 heal_ticks: 5,
538 hot_size_high: HumanSize::mib(7),
539 hot_size_low: HumanSize::mib(4),
540 errors_per_tick_high: 1,
541 }
542 }
543}
544
545#[derive(Debug, Clone, Default, Deserialize, Serialize)]
547#[serde(default, deny_unknown_fields)]
548pub struct ObservabilityConfig {
549 pub tokio_console: bool,
551}