Skip to main content

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//! cleanup_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 `cleanup_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.cleanup_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    /// How often the background worker wakes up to scan and delete expired keys (milliseconds).
76    ///
77    /// This setting controls MEMORY EFFICIENCY only, not TTL correctness.
78    /// Expired keys are always rejected at read time via `is_expired()` regardless of cleanup
79    /// timing. Cleanup only reclaims the memory occupied by stale DashMap entries.
80    ///
81    /// Range: 100–60000 (100ms to 60 seconds)
82    /// Default: 1000 (1 second)
83    ///
84    /// Upper bound rationale: at 60s, a workload of 10K TTL writes/s with TTL=1s accumulates
85    /// ~600K stale entries (~30MB) between cleanups — acceptable for most deployments.
86    /// Beyond 60s, memory growth becomes unbounded in high-throughput short-TTL workloads.
87    ///
88    /// Tuning guide:
89    /// - 1000ms (default): Good for most workloads
90    /// - 100-500ms: High-throughput short-TTL workloads (e.g., rate limiting, sessions)
91    /// - 5000-60000ms: Low-frequency TTL usage where memory pressure is not a concern
92    ///
93    /// Only used when `enabled = true`
94    #[serde(default = "default_cleanup_interval_ms")]
95    pub cleanup_interval_ms: u64,
96
97    /// Maximum cleanup duration per cycle (milliseconds)
98    ///
99    /// Limits how long a single cleanup cycle can run to prevent
100    /// excessive CPU usage in the background worker.
101    ///
102    /// Range: 1-100ms
103    /// Default: 1ms
104    ///
105    /// Tuning guide:
106    /// - 1ms (default): Safe for latency-sensitive apps
107    /// - 5-10ms: Aggressive cleanup when backlog exists
108    /// - >10ms: Not recommended (can impact foreground operations)
109    ///
110    /// Only used when `enabled = true`
111    #[serde(default = "default_max_cleanup_duration_ms")]
112    pub max_cleanup_duration_ms: u64,
113}
114
115fn default_cleanup_interval_ms() -> u64 {
116    1000
117}
118
119fn default_max_cleanup_duration_ms() -> u64 {
120    1
121}
122
123impl Default for LeaseConfig {
124    fn default() -> Self {
125        Self {
126            enabled: false, // Default: TTL disabled (zero overhead)
127            cleanup_interval_ms: default_cleanup_interval_ms(),
128            max_cleanup_duration_ms: default_max_cleanup_duration_ms(),
129        }
130    }
131}
132
133impl LeaseConfig {
134    /// Validates configuration parameters
135    ///
136    /// Returns error if:
137    /// - `cleanup_interval_ms` is out of range (100-60000)
138    /// - `max_cleanup_duration_ms` is out of range (1-100)
139    pub fn validate(&self) -> Result<()> {
140        // Skip validation if disabled
141        if !self.enabled {
142            return Ok(());
143        }
144
145        // Validate cleanup_interval_ms
146        // Range: 100ms to 60s
147        // - Lower bound (100ms): Prevents excessive wakeups (CPU waste)
148        // - Upper bound (60000ms): Caps stale-entry accumulation; beyond 60s, high-throughput
149        //   short-TTL workloads accumulate unbounded memory (see field doc comment)
150        if !(100..=60_000).contains(&self.cleanup_interval_ms) {
151            return Err(Error::Config(ConfigError::Message(format!(
152                "lease cleanup_interval_ms must be between 100 and 60000, got {}",
153                self.cleanup_interval_ms
154            ))));
155        }
156
157        // Validate max_cleanup_duration_ms
158        // Range: 1-100ms
159        // - Lower bound (1ms): Minimum useful duration
160        // - Upper bound (100ms): Prevents blocking too long
161        if !(1..=100).contains(&self.max_cleanup_duration_ms) {
162            return Err(Error::Config(ConfigError::Message(format!(
163                "max_cleanup_duration_ms must be between 1 and 100, got {}",
164                self.max_cleanup_duration_ms
165            ))));
166        }
167
168        Ok(())
169    }
170}