uri_register/
cache.rs

1// Copyright TELICENT LTD
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Cache implementations for URI-to-ID mapping
16//!
17//! This module provides different caching strategies for the URI register:
18//! - **Moka** (W-TinyLFU): Default. Better hit rates for most workloads
19//! - **LRU**: Simple least-recently-used eviction policy
20//!
21//! W-TinyLFU (Window Tiny Least Frequently Used) combines recency and frequency
22//! tracking to provide better cache admission policies compared to plain LRU.
23
24use lru::LruCache;
25use moka::sync::Cache as MokaCache;
26use std::num::NonZeroUsize;
27use std::sync::atomic::{AtomicU64, Ordering};
28use std::sync::{Arc, RwLock};
29
30/// Cache strategy for URI-to-ID mapping
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
32pub enum CacheStrategy {
33    /// LRU (Least Recently Used) cache
34    /// Simple eviction based on recency of access
35    Lru,
36
37    /// Moka cache with W-TinyLFU admission policy
38    /// Better hit rates by considering both recency and frequency
39    /// This is the default and recommended strategy
40    #[default]
41    Moka,
42}
43
44/// Cache performance statistics for observability
45#[derive(Debug, Clone)]
46pub struct CacheStats {
47    /// Number of cache hits
48    pub hits: u64,
49    /// Number of cache misses
50    pub misses: u64,
51    /// Current number of entries in the cache
52    pub entry_count: u64,
53    /// Maximum capacity of the cache
54    pub capacity: u64,
55}
56
57impl CacheStats {
58    /// Calculate hit rate as a percentage (0.0 to 100.0)
59    pub fn hit_rate(&self) -> f64 {
60        let total = self.hits + self.misses;
61        if total == 0 {
62            0.0
63        } else {
64            (self.hits as f64 / total as f64) * 100.0
65        }
66    }
67}
68
69/// Internal trait for cache operations
70pub(crate) trait Cache: Send + Sync {
71    /// Get a value from the cache
72    fn get(&self, key: &str) -> Option<u64>;
73
74    /// Put a value into the cache
75    fn put(&self, key: String, value: u64);
76
77    /// Get cache statistics
78    fn stats(&self) -> CacheStats;
79}
80
81/// LRU cache wrapper with metrics tracking
82pub(crate) struct LruCacheWrapper {
83    cache: Arc<RwLock<LruCache<String, u64>>>,
84    capacity: usize,
85    hits: Arc<AtomicU64>,
86    misses: Arc<AtomicU64>,
87}
88
89impl LruCacheWrapper {
90    pub fn new(capacity: usize) -> Self {
91        let capacity_nz = NonZeroUsize::new(capacity).expect("Cache capacity must be non-zero");
92        Self {
93            cache: Arc::new(RwLock::new(LruCache::new(capacity_nz))),
94            capacity,
95            hits: Arc::new(AtomicU64::new(0)),
96            misses: Arc::new(AtomicU64::new(0)),
97        }
98    }
99}
100
101impl Cache for LruCacheWrapper {
102    fn get(&self, key: &str) -> Option<u64> {
103        if let Ok(mut cache) = self.cache.write() {
104            let result = cache.get(key).copied();
105            if result.is_some() {
106                self.hits.fetch_add(1, Ordering::Relaxed);
107            } else {
108                self.misses.fetch_add(1, Ordering::Relaxed);
109            }
110            result
111        } else {
112            None
113        }
114    }
115
116    fn put(&self, key: String, value: u64) {
117        if let Ok(mut cache) = self.cache.write() {
118            cache.put(key, value);
119        }
120    }
121
122    fn stats(&self) -> CacheStats {
123        let entry_count = if let Ok(cache) = self.cache.read() {
124            cache.len() as u64
125        } else {
126            0
127        };
128
129        CacheStats {
130            hits: self.hits.load(Ordering::Relaxed),
131            misses: self.misses.load(Ordering::Relaxed),
132            entry_count,
133            capacity: self.capacity as u64,
134        }
135    }
136}
137
138/// Moka (W-TinyLFU) cache wrapper with metrics tracking
139pub(crate) struct MokaCacheWrapper {
140    cache: MokaCache<String, u64>,
141    capacity: usize,
142    hits: Arc<AtomicU64>,
143    misses: Arc<AtomicU64>,
144}
145
146impl MokaCacheWrapper {
147    pub fn new(capacity: usize) -> Self {
148        Self {
149            cache: MokaCache::builder().max_capacity(capacity as u64).build(),
150            capacity,
151            hits: Arc::new(AtomicU64::new(0)),
152            misses: Arc::new(AtomicU64::new(0)),
153        }
154    }
155}
156
157impl Cache for MokaCacheWrapper {
158    fn get(&self, key: &str) -> Option<u64> {
159        let result = self.cache.get(key);
160        if result.is_some() {
161            self.hits.fetch_add(1, Ordering::Relaxed);
162        } else {
163            self.misses.fetch_add(1, Ordering::Relaxed);
164        }
165        result
166    }
167
168    fn put(&self, key: String, value: u64) {
169        self.cache.insert(key, value);
170    }
171
172    fn stats(&self) -> CacheStats {
173        let hits = self.hits.load(Ordering::Relaxed);
174        let misses = self.misses.load(Ordering::Relaxed);
175        let entry_count = self.cache.entry_count();
176
177        CacheStats {
178            hits,
179            misses,
180            entry_count,
181            capacity: self.capacity as u64,
182        }
183    }
184}
185
186/// Create a cache instance based on the strategy
187pub(crate) fn create_cache(strategy: CacheStrategy, capacity: usize) -> Arc<dyn Cache> {
188    match strategy {
189        CacheStrategy::Lru => Arc::new(LruCacheWrapper::new(capacity)),
190        CacheStrategy::Moka => Arc::new(MokaCacheWrapper::new(capacity)),
191    }
192}