multi_tier_cache/backends/
dashmap_cache.rs

1//! DashMap Cache - Simple Concurrent HashMap Backend
2//!
3//! A lightweight in-memory cache using DashMap for concurrent access.
4//! This is a reference implementation showing how to create custom cache backends.
5
6use std::sync::Arc;
7use std::time::{Duration, Instant};
8use anyhow::Result;
9use serde_json;
10use dashmap::DashMap;
11use std::sync::atomic::{AtomicU64, Ordering};
12
13/// Cache entry with expiration tracking
14#[derive(Debug, Clone)]
15struct CacheEntry {
16    value: serde_json::Value,
17    expires_at: Option<Instant>,
18}
19
20impl CacheEntry {
21    fn new(value: serde_json::Value, ttl: Duration) -> Self {
22        Self {
23            value,
24            expires_at: Some(Instant::now() + ttl),
25        }
26    }
27
28    fn is_expired(&self) -> bool {
29        match self.expires_at {
30            Some(expires_at) => Instant::now() > expires_at,
31            None => false,
32        }
33    }
34}
35
36/// Simple concurrent cache using DashMap
37///
38/// **Use Case**: Educational reference, simple concurrent scenarios
39///
40/// **Features**:
41/// - Lock-free concurrent reads/writes
42/// - Manual TTL tracking
43/// - No automatic eviction (manual cleanup needed)
44/// - Minimal memory overhead
45///
46/// **Limitations**:
47/// - No automatic eviction policy (LRU, LFU, etc.)
48/// - No size limits (unbounded growth)
49/// - Manual TTL cleanup required
50///
51/// **When to use**:
52/// - Learning how to implement cache backends
53/// - Simple use cases with predictable data sizes
54/// - When you need full control over eviction logic
55///
56/// **Example**:
57/// ```rust
58/// use multi_tier_cache::backends::DashMapCache;
59/// use multi_tier_cache::traits::CacheBackend;
60/// use std::time::Duration;
61///
62/// # async fn example() -> anyhow::Result<()> {
63/// let cache = DashMapCache::new();
64/// let value = serde_json::json!({"user": "alice"});
65///
66/// cache.set_with_ttl("user:1", value.clone(), Duration::from_secs(60)).await?;
67/// let cached = cache.get("user:1").await;
68/// assert_eq!(cached, Some(value));
69/// # Ok(())
70/// # }
71/// ```
72pub struct DashMapCache {
73    /// Concurrent HashMap
74    map: Arc<DashMap<String, CacheEntry>>,
75    /// Hit counter
76    hits: Arc<AtomicU64>,
77    /// Miss counter
78    misses: Arc<AtomicU64>,
79    /// Set counter
80    sets: Arc<AtomicU64>,
81}
82
83impl DashMapCache {
84    /// Create new DashMap cache
85    pub fn new() -> Self {
86        println!("  ๐Ÿ—บ๏ธ  Initializing DashMap Cache...");
87        println!("  โœ… DashMap Cache initialized (concurrent HashMap)");
88
89        Self {
90            map: Arc::new(DashMap::new()),
91            hits: Arc::new(AtomicU64::new(0)),
92            misses: Arc::new(AtomicU64::new(0)),
93            sets: Arc::new(AtomicU64::new(0)),
94        }
95    }
96
97    /// Get value from cache
98    pub async fn get(&self, key: &str) -> Option<serde_json::Value> {
99        match self.map.get(key) {
100            Some(entry) => {
101                if entry.is_expired() {
102                    // Remove expired entry
103                    drop(entry); // Release read lock
104                    self.map.remove(key);
105                    self.misses.fetch_add(1, Ordering::Relaxed);
106                    None
107                } else {
108                    self.hits.fetch_add(1, Ordering::Relaxed);
109                    Some(entry.value.clone())
110                }
111            }
112            None => {
113                self.misses.fetch_add(1, Ordering::Relaxed);
114                None
115            }
116        }
117    }
118
119    /// Set value with TTL
120    pub async fn set_with_ttl(&self, key: &str, value: serde_json::Value, ttl: Duration) -> Result<()> {
121        let entry = CacheEntry::new(value, ttl);
122        self.map.insert(key.to_string(), entry);
123        self.sets.fetch_add(1, Ordering::Relaxed);
124        println!("๐Ÿ’พ [DashMap] Cached '{}' with TTL {:?}", key, ttl);
125        Ok(())
126    }
127
128    /// Remove value from cache
129    pub async fn remove(&self, key: &str) -> Result<()> {
130        self.map.remove(key);
131        Ok(())
132    }
133
134    /// Health check
135    pub async fn health_check(&self) -> bool {
136        let test_key = "health_check_dashmap";
137        let test_value = serde_json::json!({"test": true});
138
139        match self.set_with_ttl(test_key, test_value.clone(), Duration::from_secs(60)).await {
140            Ok(_) => {
141                match self.get(test_key).await {
142                    Some(retrieved) => {
143                        let _ = self.remove(test_key).await;
144                        retrieved == test_value
145                    }
146                    None => false
147                }
148            }
149            Err(_) => false
150        }
151    }
152
153    /// Cleanup expired entries (should be called periodically)
154    ///
155    /// **Note**: DashMap doesn't have automatic eviction, so you need to
156    /// call this method periodically to remove expired entries.
157    pub fn cleanup_expired(&self) -> usize {
158        let mut removed = 0;
159        self.map.retain(|_, entry| {
160            if entry.is_expired() {
161                removed += 1;
162                false // Remove
163            } else {
164                true // Keep
165            }
166        });
167        if removed > 0 {
168            println!("๐Ÿงน [DashMap] Cleaned up {} expired entries", removed);
169        }
170        removed
171    }
172
173    /// Get current cache size
174    pub fn len(&self) -> usize {
175        self.map.len()
176    }
177
178    /// Check if cache is empty
179    pub fn is_empty(&self) -> bool {
180        self.map.is_empty()
181    }
182}
183
184impl Default for DashMapCache {
185    fn default() -> Self {
186        Self::new()
187    }
188}
189
190// ===== Trait Implementations =====
191
192use crate::traits::CacheBackend;
193use async_trait::async_trait;
194
195/// Implement CacheBackend trait for DashMapCache
196#[async_trait]
197impl CacheBackend for DashMapCache {
198    async fn get(&self, key: &str) -> Option<serde_json::Value> {
199        DashMapCache::get(self, key).await
200    }
201
202    async fn set_with_ttl(
203        &self,
204        key: &str,
205        value: serde_json::Value,
206        ttl: Duration,
207    ) -> Result<()> {
208        DashMapCache::set_with_ttl(self, key, value, ttl).await
209    }
210
211    async fn remove(&self, key: &str) -> Result<()> {
212        DashMapCache::remove(self, key).await
213    }
214
215    async fn health_check(&self) -> bool {
216        DashMapCache::health_check(self).await
217    }
218
219    fn name(&self) -> &str {
220        "DashMap"
221    }
222}