multi_tier_cache/backends/
quickcache_cache.rs1use 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#[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
35pub struct QuickCacheBackend {
48 cache: Cache<String, Arc<RwLock<CacheEntry>>>,
50 hits: Arc<AtomicU64>,
52 misses: Arc<AtomicU64>,
54 sets: Arc<AtomicU64>,
56}
57
58impl QuickCacheBackend {
59 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 #[must_use]
83 pub const fn size(&self) -> usize {
84 0 }
86}
87
88use crate::traits::CacheBackend;
91
92impl 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 drop(entry); 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}