1use super::types::{
7 AtomicCacheStats, CacheEntry, CacheKey, DualCacheConfig, SerializableCacheEntry,
8};
9use crate::storage::redis::RedisPool;
10use crate::utils::error::gateway_error::{GatewayError, Result};
11use serde::{Serialize, de::DeserializeOwned};
12use std::marker::PhantomData;
13use std::sync::Arc;
14use std::time::Duration;
15use tracing::{trace, warn};
16
17pub struct RedisCache<T> {
19 pool: Arc<RedisPool>,
21 config: DualCacheConfig,
23 stats: Arc<AtomicCacheStats>,
25 _marker: PhantomData<T>,
27}
28
29impl<T> RedisCache<T>
30where
31 T: Serialize + DeserializeOwned + Clone + Send + Sync + 'static,
32{
33 pub fn new(pool: Arc<RedisPool>, config: DualCacheConfig) -> Self {
35 Self {
36 pool,
37 config,
38 stats: Arc::new(AtomicCacheStats::new()),
39 _marker: PhantomData,
40 }
41 }
42
43 pub fn with_stats(
45 pool: Arc<RedisPool>,
46 config: DualCacheConfig,
47 stats: Arc<AtomicCacheStats>,
48 ) -> Self {
49 Self {
50 pool,
51 config,
52 stats,
53 _marker: PhantomData,
54 }
55 }
56
57 pub async fn get(&self, key: &CacheKey) -> Result<Option<T>> {
59 if self.pool.is_noop() {
60 self.stats.record_redis_miss();
61 return Ok(None);
62 }
63
64 let redis_key = self.make_redis_key(key);
65 match self.pool.get(&redis_key).await? {
66 Some(data) => {
67 match self.deserialize::<SerializableCacheEntry<T>>(&data) {
68 Ok(entry) => {
69 if entry.is_expired() {
70 let _ = self.pool.delete(&redis_key).await;
72 self.stats.record_redis_miss();
73 trace!(key = %key, "Redis cache entry expired");
74 Ok(None)
75 } else {
76 self.stats.record_redis_hit();
77 trace!(key = %key, "Redis cache hit");
78 Ok(Some(entry.value))
79 }
80 }
81 Err(e) => {
82 warn!(key = %key, error = %e, "Failed to deserialize cache entry");
83 let _ = self.pool.delete(&redis_key).await;
85 self.stats.record_redis_miss();
86 Ok(None)
87 }
88 }
89 }
90 None => {
91 self.stats.record_redis_miss();
92 trace!(key = %key, "Redis cache miss");
93 Ok(None)
94 }
95 }
96 }
97
98 pub async fn get_entry(&self, key: &CacheKey) -> Result<Option<CacheEntry<T>>> {
100 if self.pool.is_noop() {
101 self.stats.record_redis_miss();
102 return Ok(None);
103 }
104
105 let redis_key = self.make_redis_key(key);
106 match self.pool.get(&redis_key).await? {
107 Some(data) => match self.deserialize::<SerializableCacheEntry<T>>(&data) {
108 Ok(entry) => {
109 if entry.is_expired() {
110 let _ = self.pool.delete(&redis_key).await;
111 self.stats.record_redis_miss();
112 Ok(None)
113 } else {
114 self.stats.record_redis_hit();
115 Ok(Some(entry.into_cache_entry()))
116 }
117 }
118 Err(e) => {
119 warn!(key = %key, error = %e, "Failed to deserialize cache entry");
120 let _ = self.pool.delete(&redis_key).await;
121 self.stats.record_redis_miss();
122 Ok(None)
123 }
124 },
125 None => {
126 self.stats.record_redis_miss();
127 Ok(None)
128 }
129 }
130 }
131
132 pub async fn set(&self, key: CacheKey, value: T) -> Result<()> {
134 self.set_with_ttl(key, value, self.config.default_ttl).await
135 }
136
137 pub async fn set_with_ttl(&self, key: CacheKey, value: T, ttl: Duration) -> Result<()> {
139 if self.pool.is_noop() {
140 return Ok(());
141 }
142
143 let entry = CacheEntry::new(value, ttl);
144 let serializable: SerializableCacheEntry<T> = (&entry).into();
145 let data = self.serialize(&serializable)?;
146 let redis_key = self.make_redis_key(&key);
147
148 self.pool
149 .set(&redis_key, &data, Some(ttl.as_secs()))
150 .await?;
151 self.stats.record_write();
152 trace!(key = %key, ttl_secs = ttl.as_secs(), "Redis cache set");
153
154 Ok(())
155 }
156
157 pub async fn set_with_size(
159 &self,
160 key: CacheKey,
161 value: T,
162 ttl: Duration,
163 size_bytes: usize,
164 ) -> Result<()> {
165 if self.pool.is_noop() {
166 return Ok(());
167 }
168
169 let entry = CacheEntry::with_size(value, ttl, size_bytes);
170 let serializable: SerializableCacheEntry<T> = (&entry).into();
171 let data = self.serialize(&serializable)?;
172 let redis_key = self.make_redis_key(&key);
173
174 self.pool
175 .set(&redis_key, &data, Some(ttl.as_secs()))
176 .await?;
177 self.stats.record_write();
178
179 Ok(())
180 }
181
182 pub async fn set_entry(&self, key: CacheKey, entry: CacheEntry<T>) -> Result<()> {
184 if self.pool.is_noop() {
185 return Ok(());
186 }
187
188 let ttl = entry.remaining_ttl().unwrap_or(Duration::from_secs(1));
189 let serializable: SerializableCacheEntry<T> = (&entry).into();
190 let data = self.serialize(&serializable)?;
191 let redis_key = self.make_redis_key(&key);
192
193 self.pool
194 .set(&redis_key, &data, Some(ttl.as_secs()))
195 .await?;
196 self.stats.record_write();
197
198 Ok(())
199 }
200
201 pub async fn delete(&self, key: &CacheKey) -> Result<bool> {
203 if self.pool.is_noop() {
204 return Ok(false);
205 }
206
207 let redis_key = self.make_redis_key(key);
208 let existed = self.pool.exists(&redis_key).await?;
209
210 if existed {
211 self.pool.delete(&redis_key).await?;
212 self.stats.record_deletion();
213 trace!(key = %key, "Redis cache delete");
214 }
215
216 Ok(existed)
217 }
218
219 pub async fn delete_if<F>(&self, key: &CacheKey, predicate: F) -> Result<Option<T>>
228 where
229 F: Fn(&T) -> bool,
230 {
231 if self.pool.is_noop() {
232 return Ok(None);
233 }
234
235 let redis_key = self.make_redis_key(key);
236 const MAX_CAS_ATTEMPTS: u8 = 8;
238 for attempt in 1..=MAX_CAS_ATTEMPTS {
239 let Some(data) = self.pool.get(&redis_key).await? else {
240 return Ok(None);
241 };
242
243 let entry: SerializableCacheEntry<T> = match self.deserialize(&data) {
244 Ok(entry) => entry,
245 Err(e) => {
246 warn!(key = %key, error = %e, "Failed to deserialize cache entry during conditional delete");
250 let deleted = self.pool.delete_if_equals(&redis_key, &data).await?;
251 if deleted {
252 return Ok(None);
253 }
254 warn!(
255 key = %key,
256 attempt,
257 "Redis malformed cleanup CAS miss; re-reading live value"
258 );
259 continue;
260 }
261 };
262
263 if entry.is_expired() {
264 let deleted = self.pool.delete_if_equals(&redis_key, &data).await?;
268 if deleted {
269 return Ok(None);
270 }
271 warn!(
272 key = %key,
273 attempt,
274 "Redis expired cleanup CAS miss; re-reading live value"
275 );
276 continue;
277 }
278
279 if !predicate(&entry.value) {
280 return Ok(None);
281 }
282
283 let deleted = self.pool.delete_if_equals(&redis_key, &data).await?;
284 if deleted {
285 self.stats.record_deletion();
286 trace!(key = %key, "Redis cache conditional delete");
287 return Ok(Some(entry.value));
288 }
289
290 warn!(
291 key = %key,
292 attempt,
293 "Redis conditional delete CAS miss; re-reading live value"
294 );
295 }
296
297 Err(GatewayError::Internal(format!(
298 "Redis conditional delete exhausted CAS retries for key {redis_key}"
299 )))
300 }
301
302 pub async fn clear(&self) -> Result<usize> {
304 if self.pool.is_noop() {
305 return Ok(0);
306 }
307
308 let prefix = format!("{}:", self.config.key_prefix);
309 self.pool.delete_by_prefix(&prefix).await
310 }
311
312 pub async fn exists(&self, key: &CacheKey) -> Result<bool> {
314 if self.pool.is_noop() {
315 return Ok(false);
316 }
317
318 let redis_key = self.make_redis_key(key);
319 self.pool.exists(&redis_key).await
320 }
321
322 pub async fn ttl(&self, key: &CacheKey) -> Result<Option<Duration>> {
324 if self.pool.is_noop() {
325 return Ok(None);
326 }
327
328 let redis_key = self.make_redis_key(key);
329 let ttl_secs = self.pool.ttl(&redis_key).await?;
330
331 if ttl_secs < 0 {
332 Ok(None)
333 } else {
334 Ok(Some(Duration::from_secs(ttl_secs as u64)))
335 }
336 }
337
338 pub fn stats(&self) -> Arc<AtomicCacheStats> {
340 Arc::clone(&self.stats)
341 }
342
343 pub async fn is_available(&self) -> bool {
345 if self.pool.is_noop() {
346 return false;
347 }
348 self.pool.health_check().await.is_ok()
349 }
350
351 pub async fn health_check(&self) -> Result<()> {
353 self.pool.health_check().await
354 }
355
356 fn make_redis_key(&self, key: &CacheKey) -> String {
360 format!("{}:{}", self.config.key_prefix, key.as_str())
361 }
362
363 fn serialize<S: Serialize>(&self, value: &S) -> Result<String> {
365 serde_json::to_string(value)
366 .map_err(|e| GatewayError::Config(format!("Failed to serialize cache value: {}", e)))
367 }
368
369 fn deserialize<D: DeserializeOwned>(&self, data: &str) -> Result<D> {
371 serde_json::from_str(data)
372 .map_err(|e| GatewayError::Config(format!("Failed to deserialize cache value: {}", e)))
373 }
374}
375
376impl<T> RedisCache<T>
378where
379 T: Serialize + DeserializeOwned + Clone + Send + Sync + 'static,
380{
381 pub async fn get_many(&self, keys: &[CacheKey]) -> Result<Vec<Option<T>>> {
383 if self.pool.is_noop() || keys.is_empty() {
384 return Ok(vec![None; keys.len()]);
385 }
386
387 let redis_keys: Vec<String> = keys.iter().map(|k| self.make_redis_key(k)).collect();
388 let raw_values = self.pool.mget(&redis_keys).await?;
389
390 let mut results = Vec::with_capacity(keys.len());
391 for (i, raw) in raw_values.into_iter().enumerate() {
392 match raw {
393 Some(data) => match self.deserialize::<SerializableCacheEntry<T>>(&data) {
394 Ok(entry) if !entry.is_expired() => {
395 self.stats.record_redis_hit();
396 results.push(Some(entry.value));
397 }
398 Ok(_) => {
399 let _ = self.pool.delete(&redis_keys[i]).await;
400 self.stats.record_redis_miss();
401 results.push(None);
402 }
403 Err(e) => {
404 warn!(key = %keys[i], error = %e, "Failed to deserialize cache entry");
405 let _ = self.pool.delete(&redis_keys[i]).await;
406 self.stats.record_redis_miss();
407 results.push(None);
408 }
409 },
410 None => {
411 self.stats.record_redis_miss();
412 results.push(None);
413 }
414 }
415 }
416
417 Ok(results)
418 }
419
420 pub async fn set_many(&self, entries: &[(CacheKey, T, Duration)]) -> Result<()> {
422 if self.pool.is_noop() || entries.is_empty() {
423 return Ok(());
424 }
425
426 for (key, value, ttl) in entries {
427 self.set_with_ttl(key.clone(), value.clone(), *ttl).await?;
428 }
429
430 Ok(())
431 }
432
433 pub async fn delete_many(&self, keys: &[CacheKey]) -> Result<usize> {
435 if self.pool.is_noop() || keys.is_empty() {
436 return Ok(0);
437 }
438
439 let mut deleted = 0;
440 for key in keys {
441 if self.delete(key).await? {
442 deleted += 1;
443 }
444 }
445
446 Ok(deleted)
447 }
448}
449
450#[cfg(test)]
451mod tests {
452 use super::*;
453
454 fn create_noop_cache() -> RedisCache<String> {
456 let pool = Arc::new(RedisPool::create_noop());
457 let config = DualCacheConfig::redis_only();
458 RedisCache::new(pool, config)
459 }
460
461 #[tokio::test]
465 async fn test_redis_cache_noop_get() {
466 let cache = create_noop_cache();
467 let key = CacheKey::new("test-key");
468
469 let result = cache.get(&key).await.unwrap();
470 assert!(result.is_none());
471 }
472
473 #[tokio::test]
474 async fn test_redis_cache_noop_set() {
475 let cache = create_noop_cache();
476 let key = CacheKey::new("test-key");
477
478 let result = cache.set(key, "value".to_string()).await;
480 assert!(result.is_ok());
481 }
482
483 #[tokio::test]
484 async fn test_redis_cache_noop_delete() {
485 let cache = create_noop_cache();
486 let key = CacheKey::new("test-key");
487
488 let result = cache.delete(&key).await.unwrap();
489 assert!(!result);
490 }
491
492 #[tokio::test]
493 async fn test_redis_cache_noop_clear() {
494 let cache = create_noop_cache();
495
496 assert_eq!(cache.clear().await.ok(), Some(0));
497 }
498
499 #[tokio::test]
500 async fn test_redis_cache_noop_exists() {
501 let cache = create_noop_cache();
502 let key = CacheKey::new("test-key");
503
504 let result = cache.exists(&key).await.unwrap();
505 assert!(!result);
506 }
507
508 #[tokio::test]
509 async fn test_redis_cache_noop_ttl() {
510 let cache = create_noop_cache();
511 let key = CacheKey::new("test-key");
512
513 let result = cache.ttl(&key).await.unwrap();
514 assert!(result.is_none());
515 }
516
517 #[tokio::test]
518 async fn test_redis_cache_noop_availability() {
519 let cache = create_noop_cache();
520 assert!(!cache.is_available().await);
521 }
522
523 #[test]
526 fn test_redis_key_generation() {
527 let pool = Arc::new(RedisPool::create_noop());
528 let config = DualCacheConfig::default();
529 let cache: RedisCache<String> = RedisCache::new(pool, config);
530
531 let key = CacheKey::new("my-key");
532 let redis_key = cache.make_redis_key(&key);
533
534 assert!(redis_key.starts_with("litellm:cache:"));
535 assert!(redis_key.ends_with("my-key"));
536 }
537
538 #[test]
539 fn test_redis_key_with_custom_prefix() {
540 let pool = Arc::new(RedisPool::create_noop());
541 let config = DualCacheConfig {
542 key_prefix: "custom:prefix".to_string(),
543 ..Default::default()
544 };
545 let cache: RedisCache<String> = RedisCache::new(pool, config);
546
547 let key = CacheKey::new("my-key");
548 let redis_key = cache.make_redis_key(&key);
549
550 assert_eq!(redis_key, "custom:prefix:my-key");
551 }
552
553 #[test]
556 fn test_serialization_roundtrip() {
557 let pool = Arc::new(RedisPool::create_noop());
558 let config = DualCacheConfig::default();
559 let cache: RedisCache<String> = RedisCache::new(pool, config);
560
561 let original = "test-value".to_string();
562 let serialized = cache.serialize(&original).unwrap();
563 let deserialized: String = cache.deserialize(&serialized).unwrap();
564
565 assert_eq!(original, deserialized);
566 }
567
568 #[test]
569 fn test_serialization_complex_type() {
570 use serde::{Deserialize, Serialize};
571
572 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
573 struct ComplexType {
574 id: u64,
575 name: String,
576 tags: Vec<String>,
577 }
578
579 let pool = Arc::new(RedisPool::create_noop());
580 let config = DualCacheConfig::default();
581 let cache: RedisCache<ComplexType> = RedisCache::new(pool, config);
582
583 let original = ComplexType {
584 id: 123,
585 name: "test".to_string(),
586 tags: vec!["a".to_string(), "b".to_string()],
587 };
588
589 let serialized = cache.serialize(&original).unwrap();
590 let deserialized: ComplexType = cache.deserialize(&serialized).unwrap();
591
592 assert_eq!(original, deserialized);
593 }
594
595 #[tokio::test]
598 async fn test_redis_cache_stats() {
599 let cache = create_noop_cache();
600
601 let _ = cache.get(&CacheKey::new("miss1")).await;
603 let _ = cache.get(&CacheKey::new("miss2")).await;
604
605 let stats = cache.stats().snapshot();
606 assert_eq!(stats.redis_misses, 2);
607 }
608
609 #[tokio::test]
612 async fn test_redis_cache_get_many_noop() {
613 let cache = create_noop_cache();
614
615 let keys = vec![
616 CacheKey::new("key1"),
617 CacheKey::new("key2"),
618 CacheKey::new("key3"),
619 ];
620
621 let results = cache.get_many(&keys).await.unwrap();
622 assert_eq!(results.len(), 3);
623 assert!(results.iter().all(|r| r.is_none()));
624 }
625
626 #[tokio::test]
627 async fn test_redis_cache_set_many_noop() {
628 let cache = create_noop_cache();
629
630 let entries = vec![
631 (
632 CacheKey::new("key1"),
633 "value1".to_string(),
634 Duration::from_secs(60),
635 ),
636 (
637 CacheKey::new("key2"),
638 "value2".to_string(),
639 Duration::from_secs(60),
640 ),
641 ];
642
643 let result = cache.set_many(&entries).await;
644 assert!(result.is_ok());
645 }
646
647 #[tokio::test]
648 async fn test_redis_cache_delete_many_noop() {
649 let cache = create_noop_cache();
650
651 let keys = vec![CacheKey::new("key1"), CacheKey::new("key2")];
652
653 let deleted = cache.delete_many(&keys).await.unwrap();
654 assert_eq!(deleted, 0);
655 }
656
657 #[tokio::test]
658 async fn test_redis_cache_empty_batch() {
659 let cache = create_noop_cache();
660
661 assert!(cache.get_many(&[]).await.unwrap().is_empty());
662 assert!(cache.set_many(&[]).await.is_ok());
663 assert_eq!(cache.delete_many(&[]).await.unwrap(), 0);
664 }
665}