Skip to main content

multi_tier_cache/backends/
quickcache_cache.rs

1//! `QuickCache` - Fast In-Memory Cache Backend
2//!
3//! Lightweight and extremely fast in-memory cache optimized for maximum performance.
4
5use crate::error::CacheResult;
6use bytes::Bytes;
7use futures_util::future::BoxFuture;
8use parking_lot::RwLock;
9use quick_cache::sync::Cache;
10use std::sync::Arc;
11use std::sync::atomic::{AtomicU64, Ordering};
12use std::time::{Duration, Instant};
13use tracing::{debug, info};
14
15/// Cache entry with TTL information
16#[derive(Debug, Clone)]
17struct CacheEntry {
18    value: Bytes,
19    expires_at: Instant,
20}
21
22impl CacheEntry {
23    fn new(value: Bytes, ttl: Duration) -> Self {
24        Self {
25            value,
26            expires_at: Instant::now() + ttl,
27        }
28    }
29
30    fn is_expired(&self) -> bool {
31        Instant::now() > self.expires_at
32    }
33}
34
35/// `QuickCache` in-memory cache with per-key TTL support
36///
37/// This is an alternative L1 (hot tier) cache backend optimized for maximum performance:
38/// - Extremely fast in-memory access (sub-microsecond latency)
39/// - Automatic eviction via LRU
40/// - Per-key TTL support
41/// - Minimal memory overhead
42/// - Lock-free design for concurrent access
43///
44/// **When to use `QuickCache` vs Moka**:
45/// - Use `QuickCache` when you need maximum throughput and minimal latency
46/// - Use Moka when you need advanced features like time-to-idle or weight-based eviction
47pub struct QuickCacheBackend {
48    /// `QuickCache` instance
49    cache: Cache<String, Arc<RwLock<CacheEntry>>>,
50    /// Hit counter
51    hits: Arc<AtomicU64>,
52    /// Miss counter
53    misses: Arc<AtomicU64>,
54    /// Set counter
55    sets: Arc<AtomicU64>,
56}
57
58impl QuickCacheBackend {
59    /// Create new `QuickCache`
60    ///
61    /// # Arguments
62    ///
63    /// * `max_capacity` - Maximum number of entries (default: 2000)
64    ///
65    /// # Errors
66    ///
67    /// Returns an error if the capacity is invalid.
68    pub fn new(max_capacity: u64) -> CacheResult<Self> {
69        info!(capacity = max_capacity, "Initializing QuickCache");
70
71        let cache = Cache::new(usize::try_from(max_capacity)?);
72
73        Ok(Self {
74            cache,
75            hits: Arc::new(AtomicU64::new(0)),
76            misses: Arc::new(AtomicU64::new(0)),
77            sets: Arc::new(AtomicU64::new(0)),
78        })
79    }
80
81    /// Get current cache size
82    #[must_use]
83    pub const fn size(&self) -> usize {
84        0 // Placeholder - quick_cache doesn't expose size
85    }
86}
87
88// ===== Trait Implementations =====
89
90use crate::traits::CacheBackend;
91
92/// Implement `CacheBackend` trait for `QuickCacheBackend`
93impl CacheBackend for QuickCacheBackend {
94    fn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Option<Bytes>> {
95        Box::pin(async move {
96            if let Some(entry_lock) = self.cache.get(key) {
97                let entry: parking_lot::RwLockReadGuard<'_, CacheEntry> = entry_lock.read();
98                if entry.is_expired() {
99                    // Remove expired entry
100                    drop(entry); // Release read lock before removing
101                    self.cache.remove(key);
102                    self.misses.fetch_add(1, Ordering::Relaxed);
103                    None
104                } else {
105                    self.hits.fetch_add(1, Ordering::Relaxed);
106                    Some(entry.value.clone())
107                }
108            } else {
109                self.misses.fetch_add(1, Ordering::Relaxed);
110                None
111            }
112        })
113    }
114
115    fn set_with_ttl<'a>(
116        &'a self,
117        key: &'a str,
118        value: Bytes,
119        ttl: Duration,
120    ) -> BoxFuture<'a, CacheResult<()>> {
121        Box::pin(async move {
122            let entry = Arc::new(RwLock::new(CacheEntry::new(value, ttl)));
123            self.cache.insert(key.to_string(), entry);
124            self.sets.fetch_add(1, Ordering::Relaxed);
125            debug!(key = %key, ttl_secs = %ttl.as_secs(), "[QuickCache] Cached key with TTL");
126            Ok(())
127        })
128    }
129
130    fn remove<'a>(&'a self, key: &'a str) -> BoxFuture<'a, CacheResult<()>> {
131        Box::pin(async move {
132            self.cache.remove(key);
133            Ok(())
134        })
135    }
136
137    fn health_check(&self) -> BoxFuture<'_, bool> {
138        Box::pin(async move {
139            let test_key = "health_check_quickcache";
140            let test_value = Bytes::from_static(b"health_check");
141
142            match self
143                .set_with_ttl(test_key, test_value.clone(), Duration::from_secs(60))
144                .await
145            {
146                Ok(()) => match self.get(test_key).await {
147                    Some(retrieved) => {
148                        let _ = self.remove(test_key).await;
149                        retrieved == test_value
150                    }
151                    None => false,
152                },
153                Err(_) => false,
154            }
155        })
156    }
157
158    fn name(&self) -> &'static str {
159        "QuickCache"
160    }
161}