d_engine_core/config/lease.rs
1//! Lease configuration for time-based key expiration management
2//!
3//! Lease provides TTL (time-to-live) functionality for keys with automatic
4//! background cleanup of expired entries.
5//!
6//! # Design Philosophy
7//!
8//! - **Simple**: Single `enabled` flag - no strategy choices
9//! - **Best Practice**: Fixed background cleanup (industry standard)
10//! - **Zero Overhead**: When disabled, no lease components are initialized
11//! - **Graceful Degradation**: TTL requests are rejected when disabled
12//!
13//! # Usage
14//!
15//! ```toml
16//! # Enable TTL feature (default: disabled)
17//! [raft.state_machine.lease]
18//! enabled = true
19//! interval_ms = 1000 # Optional, default 1 second
20//! ```
21
22use config::ConfigError;
23use serde::Deserialize;
24use serde::Serialize;
25
26use crate::errors::Error;
27use crate::errors::Result;
28
29/// Lease configuration for TTL-based key expiration
30///
31/// When `enabled = true`:
32/// - Server initializes lease manager at startup
33/// - Background worker periodically cleans expired keys
34/// - Client TTL requests (ttl_secs > 0) are accepted
35///
36/// When `enabled = false` (default):
37/// - No lease components initialized (zero overhead)
38/// - Client TTL requests (ttl_secs > 0) are rejected with error
39/// - All keys are permanent (ttl_secs = 0 is always allowed)
40///
41/// # Performance
42///
43/// Background cleanup overhead: ~0.001% CPU
44/// - Wakes up every `interval_ms` (default 1000ms)
45/// - Scans expired keys (limited by `max_cleanup_duration_ms`)
46/// - Deletes expired entries from storage
47///
48/// # Examples
49///
50/// ```rust
51/// use d_engine_core::config::LeaseConfig;
52///
53/// // Default: disabled
54/// let config = LeaseConfig::default();
55/// assert!(!config.enabled);
56///
57/// // Enable with defaults
58/// let config = LeaseConfig {
59/// enabled: true,
60/// ..Default::default()
61/// };
62/// assert_eq!(config.interval_ms, 1000);
63/// ```
64#[derive(Serialize, Deserialize, Clone, Debug)]
65pub struct LeaseConfig {
66 /// Enable TTL feature
67 ///
68 /// - `true`: Initialize lease manager + start background cleanup worker
69 /// - `false`: No TTL support, reject requests with ttl_secs > 0
70 ///
71 /// Default: false (zero overhead when TTL not needed)
72 #[serde(default)]
73 pub enabled: bool,
74
75 /// Background cleanup interval in milliseconds
76 ///
77 /// How often the background worker wakes up to scan and delete expired keys.
78 ///
79 /// Range: 100-3600000 (100ms to 1 hour)
80 /// Default: 1000 (1 second)
81 ///
82 /// Tuning guide:
83 /// - 1000ms (default): Balanced for most workloads
84 /// - 100-500ms: Aggressive cleanup for memory-sensitive apps
85 /// - 5000-10000ms: Relaxed cleanup for low TTL usage
86 ///
87 /// Only used when `enabled = true`
88 #[serde(default = "default_interval_ms")]
89 pub interval_ms: u64,
90
91 /// Maximum cleanup duration per cycle (milliseconds)
92 ///
93 /// Limits how long a single cleanup cycle can run to prevent
94 /// excessive CPU usage in the background worker.
95 ///
96 /// Range: 1-100ms
97 /// Default: 1ms
98 ///
99 /// Tuning guide:
100 /// - 1ms (default): Safe for latency-sensitive apps
101 /// - 5-10ms: Aggressive cleanup when backlog exists
102 /// - >10ms: Not recommended (can impact foreground operations)
103 ///
104 /// Only used when `enabled = true`
105 #[serde(default = "default_max_cleanup_duration_ms")]
106 pub max_cleanup_duration_ms: u64,
107}
108
109fn default_interval_ms() -> u64 {
110 1000
111}
112
113fn default_max_cleanup_duration_ms() -> u64 {
114 1
115}
116
117impl Default for LeaseConfig {
118 fn default() -> Self {
119 Self {
120 enabled: false, // Default: TTL disabled (zero overhead)
121 interval_ms: default_interval_ms(),
122 max_cleanup_duration_ms: default_max_cleanup_duration_ms(),
123 }
124 }
125}
126
127impl LeaseConfig {
128 /// Validates configuration parameters
129 ///
130 /// Returns error if:
131 /// - `interval_ms` is out of range (100-3600000)
132 /// - `max_cleanup_duration_ms` is out of range (1-100)
133 pub fn validate(&self) -> Result<()> {
134 // Skip validation if disabled
135 if !self.enabled {
136 return Ok(());
137 }
138
139 // Validate interval_ms
140 // Range: 100ms to 1 hour
141 // - Lower bound (100ms): Prevents excessive wakeups (CPU waste)
142 // - Upper bound (3600000ms = 1h): Ensures reasonable cleanup frequency
143 if !(100..=3_600_000).contains(&self.interval_ms) {
144 return Err(Error::Config(ConfigError::Message(format!(
145 "lease cleanup interval_ms must be between 100 and 3600000, got {}",
146 self.interval_ms
147 ))));
148 }
149
150 // Validate max_cleanup_duration_ms
151 // Range: 1-100ms
152 // - Lower bound (1ms): Minimum useful duration
153 // - Upper bound (100ms): Prevents blocking too long
154 if !(1..=100).contains(&self.max_cleanup_duration_ms) {
155 return Err(Error::Config(ConfigError::Message(format!(
156 "max_cleanup_duration_ms must be between 1 and 100, got {}",
157 self.max_cleanup_duration_ms
158 ))));
159 }
160
161 Ok(())
162 }
163}