multi_tier_cache/backends/
memcached_cache.rs1use crate::error::{CacheError, CacheResult};
6use bytes::Bytes;
7use futures_util::future::BoxFuture;
8use std::sync::Arc;
9use std::sync::atomic::{AtomicU64, Ordering};
10use std::time::Duration;
11use tracing::{debug, info};
12
13pub struct MemcachedCache {
26 client: Arc<memcache::Client>,
28 hits: Arc<AtomicU64>,
30 misses: Arc<AtomicU64>,
32 sets: Arc<AtomicU64>,
34}
35
36impl MemcachedCache {
37 pub fn new() -> CacheResult<Self> {
47 info!("Initializing Memcached Cache");
48
49 let memcached_url = std::env::var("MEMCACHED_URL")
51 .unwrap_or_else(|_| "memcache://127.0.0.1:11211".to_string());
52
53 let client = memcache::connect(memcached_url.as_str())
55 .map_err(|e| CacheError::ConfigError(format!("Failed to connect to Memcached: {e}")))?;
56
57 match client.version() {
59 Ok(versions) => {
60 info!(
61 url = %memcached_url,
62 server_count = versions.len(),
63 "Memcached Cache connected successfully"
64 );
65 }
66 Err(e) => {
67 return Err(CacheError::ConfigError(format!(
68 "Memcached connection test failed: {e}"
69 )));
70 }
71 }
72
73 Ok(Self {
74 client: Arc::new(client),
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 pub fn get_server_stats(
90 &self,
91 ) -> CacheResult<Vec<(String, std::collections::HashMap<String, String>)>> {
92 self.client
93 .stats()
94 .map_err(|e| CacheError::BackendError(format!("Failed to get Memcached stats: {e}")))
95 }
96}
97
98use crate::traits::CacheBackend;
101
102impl CacheBackend for MemcachedCache {
104 fn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Option<Bytes>> {
105 let client = Arc::clone(&self.client);
106 let key = key.to_string();
107 let hits = Arc::clone(&self.hits);
108 let misses = Arc::clone(&self.misses);
109 Box::pin(async move {
110 tokio::task::spawn_blocking(move || {
111 if let Ok(Some(bytes_vec)) = client.get::<Vec<u8>>(&key) {
112 hits.fetch_add(1, Ordering::Relaxed);
113 Some(Bytes::from(bytes_vec))
114 } else {
115 misses.fetch_add(1, Ordering::Relaxed);
116 None
117 }
118 })
119 .await
120 .unwrap_or(None)
121 })
122 }
123
124 fn set_with_ttl<'a>(
125 &'a self,
126 key: &'a str,
127 value: Bytes,
128 ttl: Duration,
129 ) -> BoxFuture<'a, CacheResult<()>> {
130 let client = Arc::clone(&self.client);
131 let key = key.to_string();
132 let sets = Arc::clone(&self.sets);
133 Box::pin(async move {
134 tokio::task::spawn_blocking(move || {
135 client
136 .set(
137 &key,
138 value.as_ref(),
139 u32::try_from(ttl.as_secs()).unwrap_or(u32::MAX),
140 )
141 .map_err(|e| {
142 crate::error::CacheError::BackendError(format!(
143 "Memcached operation failed: {e}"
144 ))
145 })?;
146
147 sets.fetch_add(1, Ordering::Relaxed);
148 debug!(key = %key, ttl_secs = %ttl.as_secs(), "[Memcached] Cached key with TTL");
149 Ok(())
150 })
151 .await
152 .map_err(|_| {
153 crate::error::CacheError::InternalError("Spawn blocking task failed".to_string())
154 })?
155 })
156 }
157
158 fn remove<'a>(&'a self, key: &'a str) -> BoxFuture<'a, CacheResult<()>> {
159 let client = Arc::clone(&self.client);
160 let key = key.to_string();
161 Box::pin(async move {
162 tokio::task::spawn_blocking(move || {
163 client.delete(&key).map_err(|e| {
164 crate::error::CacheError::BackendError(format!(
165 "Memcached operation failed: {e}"
166 ))
167 })?;
168 Ok(())
169 })
170 .await
171 .map_err(|_| {
172 crate::error::CacheError::InternalError("Spawn blocking task failed".to_string())
173 })?
174 })
175 }
176
177 fn health_check(&self) -> BoxFuture<'_, bool> {
178 Box::pin(async move {
179 let test_key = "health_check_memcached";
180 let test_value = Bytes::from_static(b"health_check");
181
182 match self
183 .set_with_ttl(test_key, test_value.clone(), Duration::from_secs(10))
184 .await
185 {
186 Ok(()) => match self.get(test_key).await {
187 Some(retrieved) => {
188 let _ = self.remove(test_key).await;
189 retrieved == test_value
190 }
191 None => false,
192 },
193 Err(_) => false,
194 }
195 })
196 }
197
198 fn name(&self) -> &'static str {
199 "Memcached"
200 }
201}
202
203impl crate::traits::L2CacheBackend for MemcachedCache {
204 fn get_with_ttl<'a>(
205 &'a self,
206 key: &'a str,
207 ) -> BoxFuture<'a, Option<(Bytes, Option<Duration>)>> {
208 Box::pin(async move { self.get(key).await.map(|v| (v, None)) })
209 }
210}