use std::path::PathBuf;
use serde::Deserialize;
use serde::Serialize;
use crate::human::HumanDuration;
use crate::human::HumanSize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum ColdBackend {
#[default]
#[serde(alias = "disabled", alias = "off")]
None,
#[serde(alias = "mem", alias = "inmem")]
Memory,
S3,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum WalBackend {
#[default]
Memory,
Disk,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum RaftSnapshotBackend {
#[default]
#[serde(alias = "default", alias = "")]
Inline,
Local,
S3,
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct UrsulaConfig {
pub server: ServerConfig,
pub runtime: RuntimeConfig,
pub raft: RaftConfig,
pub storage: StorageConfig,
pub governance: GovernanceConfig,
pub observability: ObservabilityConfig,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ServerConfig {
pub listen: String,
pub cluster_listen: Option<String>,
pub http_inflight_body_size: HumanSize,
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
listen: "127.0.0.1:4437".to_string(),
cluster_listen: None,
http_inflight_body_size: HumanSize::mib(256),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct RuntimeConfig {
pub core_count: usize,
pub node_memory_abort_cap_size: Option<HumanSize>,
pub external_payload_min_size: Option<HumanSize>,
pub live_read_max_waiters_per_core: Option<usize>,
}
impl Default for RuntimeConfig {
fn default() -> Self {
Self {
core_count: std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4),
node_memory_abort_cap_size: None,
external_payload_min_size: None,
live_read_max_waiters_per_core: Some(65_536),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct RaftConfig {
pub node_id: u64,
pub group_count: usize,
pub max_uncommitted_size_per_group: Option<HumanSize>,
pub init_membership: bool,
pub init_membership_per_group: bool,
pub wal: WalConfig,
pub peers: Vec<RaftPeerConfig>,
#[serde(default)]
pub groups: Vec<RaftGroupConfig>,
pub rejoin_probe: HumanDuration,
pub bootstrap_peer_probe: HumanDuration,
pub bootstrap_peer_probe_interval: HumanDuration,
pub bootstrap_peer_connect: HumanDuration,
pub install_snapshot_timeout: HumanDuration,
pub memory_bootstrap_marker_dir: Option<PathBuf>,
pub grpc_reconnect_after_failures: usize,
pub snapshot_build_max_concurrency: usize,
pub snapshot_install_max_concurrency: usize,
}
impl Default for RaftConfig {
fn default() -> Self {
Self {
node_id: 0,
group_count: std::thread::available_parallelism()
.map(|n| n.get().saturating_mul(16).max(1))
.unwrap_or(16),
max_uncommitted_size_per_group: None,
init_membership: false,
init_membership_per_group: false,
wal: WalConfig::default(),
peers: Vec::new(),
groups: Vec::new(),
rejoin_probe: HumanDuration::sec(6),
bootstrap_peer_probe: HumanDuration::sec(60),
bootstrap_peer_probe_interval: HumanDuration::milli(250),
bootstrap_peer_connect: HumanDuration::milli(500),
install_snapshot_timeout: HumanDuration::sec(120),
memory_bootstrap_marker_dir: None,
grpc_reconnect_after_failures: 8,
snapshot_build_max_concurrency: 1,
snapshot_install_max_concurrency: 1,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct WalConfig {
pub backend: WalBackend,
pub path: Option<PathBuf>,
}
impl WalConfig {
pub fn resolved_path(&self) -> Option<PathBuf> {
match self.backend {
WalBackend::Memory => None,
WalBackend::Disk => self.path.as_ref().map(|p| p.join("raft-log")),
}
}
}
impl Default for WalConfig {
fn default() -> Self {
Self {
backend: WalBackend::Memory,
path: None,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct RaftPeerConfig {
pub node_id: u64,
pub url: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct RaftGroupConfig {
pub raft_group_id: u32,
pub voters: Vec<u64>,
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct StorageConfig {
pub cold: ColdConfig,
pub snapshot: RaftSnapshotConfig,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ColdConfig {
pub backend: ColdBackend,
pub root: Option<String>,
pub s3: Option<S3Config>,
pub cache: Option<ColdCacheConfig>,
pub flush_interval: HumanDuration,
pub flush_size: HumanSize,
pub flush_min_hot_size: Option<HumanSize>,
pub flush_max_size: Option<HumanSize>,
pub flush_max_concurrency: usize,
pub max_hot_size_per_group: Option<HumanSize>,
pub gc_interval: HumanDuration,
pub gc_max_entries: usize,
}
impl ColdConfig {
pub fn flush_min_hot_size(&self) -> HumanSize {
self.flush_min_hot_size.unwrap_or(self.flush_size)
}
pub fn flush_max_size(&self) -> HumanSize {
self.flush_max_size.unwrap_or(self.flush_size)
}
}
impl Default for ColdConfig {
fn default() -> Self {
Self {
backend: ColdBackend::None,
root: None,
s3: None,
cache: None,
flush_interval: HumanDuration::sec(1),
flush_size: HumanSize::mib(8),
flush_min_hot_size: None,
flush_max_size: None,
flush_max_concurrency: 4,
max_hot_size_per_group: Some(HumanSize::mib(64)),
gc_interval: HumanDuration::sec(5),
gc_max_entries: 256,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct S3Config {
pub bucket: Option<String>,
pub region: Option<String>,
pub endpoint: Option<String>,
pub access_key_id: Option<String>,
pub secret_access_key: Option<String>,
pub session_token: Option<String>,
pub timeout: HumanDuration,
pub max_retries: usize,
pub probe_timeout: HumanDuration,
pub unhealthy_ticks: usize,
pub heal_ticks: usize,
}
impl Default for S3Config {
fn default() -> Self {
Self {
bucket: None,
region: None,
endpoint: None,
access_key_id: None,
secret_access_key: None,
session_token: None,
timeout: HumanDuration::sec(10),
max_retries: 3,
probe_timeout: HumanDuration::sec(2),
unhealthy_ticks: 1,
heal_ticks: 2,
}
}
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ColdCacheConfig {
pub max_size: HumanSize,
pub block_size: HumanSize,
pub readahead_blocks: usize,
}
impl Default for ColdCacheConfig {
fn default() -> Self {
Self {
max_size: HumanSize::mib(256),
block_size: HumanSize::mib(1),
readahead_blocks: 4,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct RaftSnapshotConfig {
pub backend: RaftSnapshotBackend,
pub local_root: Option<PathBuf>,
pub s3_prefix: Option<String>,
pub drive_interval: Option<HumanDuration>,
pub drive_flush_concurrency: usize,
}
impl Default for RaftSnapshotConfig {
fn default() -> Self {
Self {
backend: RaftSnapshotBackend::Inline,
local_root: None,
s3_prefix: None,
drive_interval: None,
drive_flush_concurrency: 4,
}
}
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct GovernanceConfig {
pub leadership_balance: LeadershipBalanceConfig,
pub cluster_probe: ClusterProbeConfig,
pub commit_stall: CommitStallConfig,
pub cold_health: ColdHealthConfig,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct LeadershipBalanceConfig {
pub interval: HumanDuration,
pub max_per_tick: usize,
pub peer_timeout: HumanDuration,
}
impl Default for LeadershipBalanceConfig {
fn default() -> Self {
Self {
interval: HumanDuration::sec(5),
max_per_tick: 4,
peer_timeout: HumanDuration::milli(500),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ClusterProbeConfig {
pub interval: HumanDuration,
pub probe_size: HumanSize,
pub timeout: HumanDuration,
pub unhealthy_ticks: usize,
pub heal_ticks: usize,
}
impl Default for ClusterProbeConfig {
fn default() -> Self {
Self {
interval: HumanDuration::milli(500),
probe_size: HumanSize::kib(64),
timeout: HumanDuration::milli(200),
unhealthy_ticks: 2,
heal_ticks: 6,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct CommitStallConfig {
pub interval: HumanDuration,
pub threshold: HumanDuration,
}
impl Default for CommitStallConfig {
fn default() -> Self {
Self {
interval: HumanDuration::sec(2),
threshold: HumanDuration::sec(15),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ColdHealthConfig {
pub interval: HumanDuration,
pub unhealthy_ticks: usize,
pub heal_ticks: usize,
pub hot_size_high: HumanSize,
pub hot_size_low: HumanSize,
pub errors_per_tick_high: usize,
}
impl Default for ColdHealthConfig {
fn default() -> Self {
Self {
interval: HumanDuration::sec(2),
unhealthy_ticks: 3,
heal_ticks: 5,
hot_size_high: HumanSize::mib(7),
hot_size_low: HumanSize::mib(4),
errors_per_tick_high: 1,
}
}
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ObservabilityConfig {
pub tokio_console: bool,
}