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>,
157 pub grpc_reconnect_after_failures: usize,
159 pub snapshot_build_max_concurrency: usize,
161 pub snapshot_install_max_concurrency: usize,
163}
164
165impl Default for RaftConfig {
166 fn default() -> Self {
167 Self {
168 node_id: 0,
169 group_count: std::thread::available_parallelism()
170 .map(|n| n.get().saturating_mul(16).max(1))
171 .unwrap_or(16),
172 max_uncommitted_size_per_group: None,
173 init_membership: false,
174 init_membership_per_group: false,
175 wal: WalConfig::default(),
176 peers: Vec::new(),
177 groups: Vec::new(),
178 rejoin_probe: HumanDuration::sec(6),
179 bootstrap_peer_probe: HumanDuration::sec(60),
180 bootstrap_peer_probe_interval: HumanDuration::milli(250),
181 bootstrap_peer_connect: HumanDuration::milli(500),
182 install_snapshot_timeout: HumanDuration::sec(120),
183 memory_bootstrap_marker_dir: None,
184 grpc_reconnect_after_failures: 8,
185 snapshot_build_max_concurrency: 1,
186 snapshot_install_max_concurrency: 1,
187 }
188 }
189}
190
191#[derive(Debug, Clone, Deserialize, Serialize)]
193#[serde(default, deny_unknown_fields)]
194pub struct WalConfig {
195 pub backend: WalBackend,
197 pub path: Option<PathBuf>,
199}
200
201impl WalConfig {
202 pub fn resolved_path(&self) -> Option<PathBuf> {
208 match self.backend {
209 WalBackend::Memory => None,
210 WalBackend::Disk => self.path.as_ref().map(|p| p.join("raft-log")),
211 }
212 }
213}
214
215impl Default for WalConfig {
216 fn default() -> Self {
217 Self {
218 backend: WalBackend::Memory,
219 path: None,
220 }
221 }
222}
223
224#[derive(Debug, Clone, Deserialize, Serialize)]
226#[serde(deny_unknown_fields)]
227pub struct RaftPeerConfig {
228 pub node_id: u64,
230 pub url: String,
232}
233
234#[derive(Debug, Clone, Deserialize, Serialize)]
238#[serde(deny_unknown_fields)]
239pub struct RaftGroupConfig {
240 pub raft_group_id: u32,
242 pub voters: Vec<u64>,
244}
245
246#[derive(Debug, Clone, Default, Deserialize, Serialize)]
248#[serde(default, deny_unknown_fields)]
249pub struct StorageConfig {
250 pub cold: ColdConfig,
252 pub snapshot: RaftSnapshotConfig,
254}
255
256#[derive(Debug, Clone, Deserialize, Serialize)]
258#[serde(default, deny_unknown_fields)]
259pub struct ColdConfig {
260 pub backend: ColdBackend,
262 pub root: Option<String>,
264 pub s3: Option<S3Config>,
267 pub cache: Option<ColdCacheConfig>,
269 pub flush_interval: HumanDuration,
271 pub flush_size: HumanSize,
273 pub flush_min_hot_size: Option<HumanSize>,
276 pub flush_max_size: Option<HumanSize>,
279 pub flush_max_concurrency: usize,
281 pub max_hot_size_per_group: Option<HumanSize>,
284 pub gc_interval: HumanDuration,
286 pub gc_max_entries: usize,
288}
289
290impl ColdConfig {
291 pub fn flush_min_hot_size(&self) -> HumanSize {
296 self.flush_min_hot_size.unwrap_or(self.flush_size)
297 }
298
299 pub fn flush_max_size(&self) -> HumanSize {
304 self.flush_max_size.unwrap_or(self.flush_size)
305 }
306}
307
308impl Default for ColdConfig {
309 fn default() -> Self {
310 Self {
311 backend: ColdBackend::None,
312 root: None,
313 s3: None,
314 cache: None,
315 flush_interval: HumanDuration::sec(1),
316 flush_size: HumanSize::mib(8),
317 flush_min_hot_size: None,
318 flush_max_size: None,
319 flush_max_concurrency: 4,
320 max_hot_size_per_group: Some(HumanSize::mib(64)),
321 gc_interval: HumanDuration::sec(5),
322 gc_max_entries: 256,
323 }
324 }
325}
326
327#[derive(Debug, Clone, Deserialize, Serialize)]
329#[serde(default, deny_unknown_fields)]
330pub struct S3Config {
331 pub bucket: Option<String>,
333 pub region: Option<String>,
335 pub endpoint: Option<String>,
337 pub access_key_id: Option<String>,
339 pub secret_access_key: Option<String>,
341 pub session_token: Option<String>,
343 pub timeout: HumanDuration,
345 pub max_retries: usize,
347 pub probe_timeout: HumanDuration,
349 pub unhealthy_ticks: usize,
351 pub heal_ticks: usize,
353}
354
355impl Default for S3Config {
356 fn default() -> Self {
357 Self {
358 bucket: None,
359 region: None,
360 endpoint: None,
361 access_key_id: None,
362 secret_access_key: None,
363 session_token: None,
364 timeout: HumanDuration::sec(10),
365 max_retries: 3,
366 probe_timeout: HumanDuration::sec(2),
367 unhealthy_ticks: 1,
368 heal_ticks: 2,
369 }
370 }
371}
372
373#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
375#[serde(default, deny_unknown_fields)]
376pub struct ColdCacheConfig {
377 pub max_size: HumanSize,
379 pub block_size: HumanSize,
381 pub readahead_blocks: usize,
383}
384
385impl Default for ColdCacheConfig {
386 fn default() -> Self {
387 Self {
388 max_size: HumanSize::mib(256),
389 block_size: HumanSize::mib(1),
390 readahead_blocks: 4,
391 }
392 }
393}
394
395#[derive(Debug, Clone, Deserialize, Serialize)]
397#[serde(default, deny_unknown_fields)]
398pub struct RaftSnapshotConfig {
399 pub backend: RaftSnapshotBackend,
401 pub local_root: Option<PathBuf>,
404 pub s3_prefix: Option<String>,
406 pub drive_interval: Option<HumanDuration>,
412 pub drive_flush_concurrency: usize,
414}
415
416impl Default for RaftSnapshotConfig {
417 fn default() -> Self {
418 Self {
419 backend: RaftSnapshotBackend::Inline,
420 local_root: None,
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}