Skip to main content

do_memory_storage_redb/cache/
adapter.rs

1//! Adapter for using AdaptiveCache as a metadata-only cache
2//!
3//! This module provides `AdaptiveCacheAdapter`, which wraps `AdaptiveCache<()>`
4//! to implement the `Cache` trait for metadata-only tracking. This enables
5//! using AdaptiveCache's intelligent TTL adjustment without storing values
6//! (since redb already stores the actual data).
7
8use super::adaptive::{AdaptiveCache, AdaptiveCacheConfig, AdaptiveCacheMetrics};
9use super::traits::Cache;
10use super::types::CacheMetrics;
11use async_trait::async_trait;
12use uuid::Uuid;
13
14/// Adapter that wraps AdaptiveCache for metadata-only caching.
15///
16/// This adapter uses `AdaptiveCache<()>` (unit type) to track cache metadata
17/// without storing actual values. This is useful when the actual data is
18/// already stored in a persistent layer (like redb), and you only need
19/// metadata tracking with intelligent TTL adjustment.
20///
21/// # Benefits over LRUCache
22///
23/// - Adaptive TTL: Frequently accessed items get longer TTL
24/// - Cold item detection: Rarely accessed items get shorter TTL
25/// - Better memory efficiency for cold items
26///
27/// # Example
28///
29/// ```no_run
30/// use do_memory_storage_redb::{AdaptiveCacheAdapter, AdaptiveCacheConfig};
31/// use std::time::Duration;
32///
33/// let config = AdaptiveCacheConfig {
34///     max_size: 1000,
35///     default_ttl: Duration::from_secs(1800),
36///     ..Default::default()
37/// };
38/// let cache = AdaptiveCacheAdapter::new(config);
39/// ```
40pub struct AdaptiveCacheAdapter {
41    inner: AdaptiveCache<()>,
42}
43
44impl AdaptiveCacheAdapter {
45    /// Create a new adaptive cache adapter with the given configuration
46    pub fn new(config: AdaptiveCacheConfig) -> Self {
47        Self {
48            inner: AdaptiveCache::new(config),
49        }
50    }
51
52    /// Create a new adaptive cache adapter with default configuration
53    pub fn with_defaults() -> Self {
54        Self::new(AdaptiveCacheConfig::default())
55    }
56
57    /// Get the inner AdaptiveCache for advanced operations
58    pub fn inner(&self) -> &AdaptiveCache<()> {
59        &self.inner
60    }
61
62    /// Get adaptive-specific metrics
63    pub async fn get_adaptive_metrics(&self) -> AdaptiveCacheMetrics {
64        self.inner.get_metrics().await
65    }
66
67    /// Get the number of hot items
68    pub async fn hot_count(&self) -> usize {
69        self.inner.hot_count().await
70    }
71
72    /// Get the number of cold items
73    pub async fn cold_count(&self) -> usize {
74        self.inner.cold_count().await
75    }
76
77    /// Get cache size (number of entries)
78    pub async fn len(&self) -> usize {
79        self.inner.len().await
80    }
81
82    /// Check if cache is empty
83    pub async fn is_empty(&self) -> bool {
84        self.inner.is_empty().await
85    }
86}
87
88#[async_trait]
89impl Cache for AdaptiveCacheAdapter {
90    async fn record_access(&self, id: Uuid, hit: bool, _size_bytes: Option<usize>) -> bool {
91        // Use () as the value for metadata-only tracking
92        // On miss, we store () to track the entry
93        // On hit, we just record the access
94        let value = if hit { None } else { Some(()) };
95        self.inner.record_access(id, hit, value).await
96    }
97
98    async fn remove(&self, id: Uuid) {
99        self.inner.remove(id).await
100    }
101
102    async fn contains(&self, id: Uuid) -> bool {
103        self.inner.contains(id).await
104    }
105
106    async fn get_metrics(&self) -> CacheMetrics {
107        let adaptive_metrics = self.inner.get_metrics().await;
108        adaptive_metrics.base
109    }
110
111    async fn clear(&self) {
112        self.inner.clear().await
113    }
114
115    async fn cleanup_expired(&self) -> usize {
116        self.inner.cleanup_expired().await
117    }
118}
119
120impl From<AdaptiveCacheConfig> for AdaptiveCacheAdapter {
121    fn from(config: AdaptiveCacheConfig) -> Self {
122        Self::new(config)
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129    use std::time::Duration;
130
131    #[tokio::test]
132    async fn test_adapter_basic_operations() {
133        let config = AdaptiveCacheConfig {
134            max_size: 100,
135            default_ttl: Duration::from_secs(60),
136            ..Default::default()
137        };
138        let cache = AdaptiveCacheAdapter::new(config);
139
140        let id = Uuid::new_v4();
141
142        // Record a miss (new entry)
143        let result = cache.record_access(id, false, Some(100)).await;
144        assert!(!result); // New entry returns false
145
146        // Check contains
147        assert!(cache.contains(id).await);
148
149        // Record a hit
150        let result = cache.record_access(id, true, None).await;
151        assert!(result); // Hit returns true
152
153        // Get metrics
154        let metrics = cache.get_metrics().await;
155        assert_eq!(metrics.hits, 1);
156        assert_eq!(metrics.misses, 1);
157
158        // Remove
159        cache.remove(id).await;
160        assert!(!cache.contains(id).await);
161    }
162
163    #[tokio::test]
164    async fn test_adapter_adaptive_features() {
165        let config = AdaptiveCacheConfig {
166            max_size: 100,
167            default_ttl: Duration::from_secs(60),
168            hot_threshold: 3,
169            ..Default::default()
170        };
171        let cache = AdaptiveCacheAdapter::new(config);
172
173        let id = Uuid::new_v4();
174
175        // Add entry
176        cache.record_access(id, false, None).await;
177
178        // Access multiple times to make it "hot"
179        for _ in 0..5 {
180            cache.record_access(id, true, None).await;
181        }
182
183        // Check hot count
184        assert!(cache.hot_count().await > 0);
185    }
186
187    #[tokio::test]
188    async fn test_adapter_cleanup() {
189        let config = AdaptiveCacheConfig {
190            max_size: 100,
191            default_ttl: Duration::from_millis(10), // Very short TTL
192            min_ttl: Duration::from_millis(1),
193            enable_background_cleanup: false,
194            ..Default::default()
195        };
196        let cache = AdaptiveCacheAdapter::new(config);
197
198        let id = Uuid::new_v4();
199        cache.record_access(id, false, None).await;
200
201        // Wait for expiration
202        tokio::time::sleep(Duration::from_millis(50)).await;
203
204        // Cleanup expired entries
205        let removed = cache.cleanup_expired().await;
206        assert!(removed > 0);
207        assert!(!cache.contains(id).await);
208    }
209}