Skip to main content

rs_zero/cache/
ttl.rs

1use std::{
2    collections::hash_map::DefaultHasher,
3    hash::{Hash, Hasher},
4    time::Duration,
5};
6
7/// Applies deterministic per-key TTL jitter.
8///
9/// `ratio` is clamped to `[0.0, 1.0]`. A ratio of `0.05` produces a TTL in
10/// approximately `[0.95, 1.05] * base`, which reduces synchronized expiry.
11pub fn jitter_ttl(base: Duration, ratio: f64, seed: impl Hash) -> Duration {
12    if base.is_zero() {
13        return base;
14    }
15
16    let ratio = ratio.clamp(0.0, 1.0);
17    if ratio == 0.0 {
18        return base;
19    }
20
21    let mut hasher = DefaultHasher::new();
22    seed.hash(&mut hasher);
23    let bucket = (hasher.finish() % 10_001) as f64 / 10_000.0;
24    let factor = 1.0 - ratio + bucket * ratio * 2.0;
25    Duration::from_secs_f64((base.as_secs_f64() * factor).max(0.001))
26}
27
28#[cfg(test)]
29mod tests {
30    use std::time::Duration;
31
32    use super::jitter_ttl;
33
34    #[test]
35    fn jitter_stays_inside_ratio_bounds() {
36        let ttl = jitter_ttl(Duration::from_secs(100), 0.05, "user:1");
37        assert!(ttl >= Duration::from_secs(95));
38        assert!(ttl <= Duration::from_secs(105));
39    }
40
41    #[test]
42    fn zero_ratio_keeps_original_ttl() {
43        let ttl = Duration::from_secs(60);
44        assert_eq!(jitter_ttl(ttl, 0.0, "key"), ttl);
45    }
46}