1use std::sync::atomic::{AtomicU64, Ordering};
93use std::sync::Arc;
94use std::time::{Duration, Instant, SystemTime};
95
96use async_trait::async_trait;
97use moka::future::Cache as MokaCache;
98use redis::aio::ConnectionManager;
99use redis::{AsyncCommands, Client as RedisClient};
100use serde::{Deserialize, Serialize};
101use tokio::sync::RwLock;
102use tracing::{debug, warn, instrument};
103
104use crate::error::{StateError, StateResult};
105use crate::state::StateBackend;
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
109pub enum WriteStrategy {
110 WriteThrough,
115
116 WriteBack,
121
122 WriteBehind,
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
131pub enum EvictionPolicy {
132 LRU,
134
135 LFU,
137
138 FIFO,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct CacheConfig {
145 pub l1_capacity: usize,
147
148 pub l1_ttl: Duration,
150
151 pub l2_ttl: Duration,
153
154 pub write_strategy: WriteStrategy,
156
157 pub eviction_policy: EvictionPolicy,
159
160 pub enable_stats: bool,
162
163 pub refresh_ahead_threshold: Option<f64>,
167
168 pub write_behind_interval: Option<Duration>,
170
171 pub write_behind_batch_size: Option<usize>,
173}
174
175impl Default for CacheConfig {
176 fn default() -> Self {
177 Self {
178 l1_capacity: 10_000,
179 l1_ttl: Duration::from_secs(300), l2_ttl: Duration::from_secs(3600), write_strategy: WriteStrategy::WriteThrough,
182 eviction_policy: EvictionPolicy::LRU,
183 enable_stats: true,
184 refresh_ahead_threshold: Some(0.8),
185 write_behind_interval: Some(Duration::from_secs(10)),
186 write_behind_batch_size: Some(1000),
187 }
188 }
189}
190
191#[derive(Debug, Clone, Default, Serialize, Deserialize)]
193pub struct CacheStats {
194 pub l1_hits: u64,
196 pub l1_misses: u64,
197 pub l2_hits: u64,
198 pub l2_misses: u64,
199 pub l3_hits: u64,
200 pub l3_misses: u64,
201
202 pub l1_hit_rate: f64,
204 pub l2_hit_rate: f64,
205 pub l3_hit_rate: f64,
206 pub overall_hit_rate: f64,
207
208 pub l1_avg_latency_us: f64,
210 pub l2_avg_latency_us: f64,
211 pub l3_avg_latency_us: f64,
212
213 pub l1_evictions: u64,
215 pub l2_evictions: u64,
216
217 pub l1_size: u64,
219 pub l1_capacity: u64,
220 pub l2_size: u64,
221
222 pub writes_total: u64,
224 pub writes_batched: u64,
225
226 pub refresh_ahead_count: u64,
228}
229
230impl CacheStats {
231 fn calculate_rates(&mut self) {
233 let l1_total = self.l1_hits + self.l1_misses;
234 let l2_total = self.l2_hits + self.l2_misses;
235 let l3_total = self.l3_hits + self.l3_misses;
236
237 self.l1_hit_rate = if l1_total > 0 {
238 self.l1_hits as f64 / l1_total as f64
239 } else {
240 0.0
241 };
242
243 self.l2_hit_rate = if l2_total > 0 {
244 self.l2_hits as f64 / l2_total as f64
245 } else {
246 0.0
247 };
248
249 self.l3_hit_rate = if l3_total > 0 {
250 self.l3_hits as f64 / l3_total as f64
251 } else {
252 0.0
253 };
254
255 let overall_total = self.l1_hits + self.l2_hits + self.l3_hits
256 + self.l1_misses + self.l2_misses + self.l3_misses;
257 let overall_hits = self.l1_hits + self.l2_hits + self.l3_hits;
258
259 self.overall_hit_rate = if overall_total > 0 {
260 overall_hits as f64 / overall_total as f64
261 } else {
262 0.0
263 };
264 }
265}
266
267#[derive(Debug, Clone)]
269struct CacheEntry {
270 value: Vec<u8>,
271 inserted_at: SystemTime,
272 ttl: Duration,
273}
274
275impl CacheEntry {
276 fn new(value: Vec<u8>, ttl: Duration) -> Self {
277 Self {
278 value,
279 inserted_at: SystemTime::now(),
280 ttl,
281 }
282 }
283
284 fn is_expired(&self) -> bool {
285 self.inserted_at
286 .elapsed()
287 .map(|elapsed| elapsed >= self.ttl)
288 .unwrap_or(true)
289 }
290
291 fn needs_refresh(&self, threshold: f64) -> bool {
292 self.inserted_at
293 .elapsed()
294 .map(|elapsed| {
295 let threshold_duration = Duration::from_secs_f64(self.ttl.as_secs_f64() * threshold);
296 elapsed >= threshold_duration
297 })
298 .unwrap_or(false)
299 }
300}
301
302struct StatsCollector {
304 l1_hits: AtomicU64,
305 l1_misses: AtomicU64,
306 l2_hits: AtomicU64,
307 l2_misses: AtomicU64,
308 l3_hits: AtomicU64,
309 l3_misses: AtomicU64,
310 l1_evictions: AtomicU64,
311 l2_evictions: AtomicU64,
312 writes_total: AtomicU64,
313 writes_batched: AtomicU64,
314 refresh_ahead_count: AtomicU64,
315
316 l1_latency_sum_us: AtomicU64,
318 l2_latency_sum_us: AtomicU64,
319 l3_latency_sum_us: AtomicU64,
320}
321
322impl StatsCollector {
323 fn new() -> Self {
324 Self {
325 l1_hits: AtomicU64::new(0),
326 l1_misses: AtomicU64::new(0),
327 l2_hits: AtomicU64::new(0),
328 l2_misses: AtomicU64::new(0),
329 l3_hits: AtomicU64::new(0),
330 l3_misses: AtomicU64::new(0),
331 l1_evictions: AtomicU64::new(0),
332 l2_evictions: AtomicU64::new(0),
333 writes_total: AtomicU64::new(0),
334 writes_batched: AtomicU64::new(0),
335 refresh_ahead_count: AtomicU64::new(0),
336 l1_latency_sum_us: AtomicU64::new(0),
337 l2_latency_sum_us: AtomicU64::new(0),
338 l3_latency_sum_us: AtomicU64::new(0),
339 }
340 }
341
342 fn record_l1_hit(&self, latency_us: u64) {
343 self.l1_hits.fetch_add(1, Ordering::Relaxed);
344 self.l1_latency_sum_us.fetch_add(latency_us, Ordering::Relaxed);
345 }
346
347 fn record_l1_miss(&self) {
348 self.l1_misses.fetch_add(1, Ordering::Relaxed);
349 }
350
351 fn record_l2_hit(&self, latency_us: u64) {
352 self.l2_hits.fetch_add(1, Ordering::Relaxed);
353 self.l2_latency_sum_us.fetch_add(latency_us, Ordering::Relaxed);
354 }
355
356 fn record_l2_miss(&self) {
357 self.l2_misses.fetch_add(1, Ordering::Relaxed);
358 }
359
360 fn record_l3_hit(&self, latency_us: u64) {
361 self.l3_hits.fetch_add(1, Ordering::Relaxed);
362 self.l3_latency_sum_us.fetch_add(latency_us, Ordering::Relaxed);
363 }
364
365 fn record_l3_miss(&self) {
366 self.l3_misses.fetch_add(1, Ordering::Relaxed);
367 }
368
369 fn record_write(&self, batched: bool) {
370 self.writes_total.fetch_add(1, Ordering::Relaxed);
371 if batched {
372 self.writes_batched.fetch_add(1, Ordering::Relaxed);
373 }
374 }
375
376 fn record_refresh_ahead(&self) {
377 self.refresh_ahead_count.fetch_add(1, Ordering::Relaxed);
378 }
379
380 fn snapshot(&self, l1_cache: &MokaCache<Vec<u8>, CacheEntry>, config: &CacheConfig) -> CacheStats {
381 let l1_hits = self.l1_hits.load(Ordering::Relaxed);
382 let l1_misses = self.l1_misses.load(Ordering::Relaxed);
383 let l2_hits = self.l2_hits.load(Ordering::Relaxed);
384 let l2_misses = self.l2_misses.load(Ordering::Relaxed);
385 let l3_hits = self.l3_hits.load(Ordering::Relaxed);
386 let l3_misses = self.l3_misses.load(Ordering::Relaxed);
387
388 let l1_latency_sum = self.l1_latency_sum_us.load(Ordering::Relaxed);
389 let l2_latency_sum = self.l2_latency_sum_us.load(Ordering::Relaxed);
390 let l3_latency_sum = self.l3_latency_sum_us.load(Ordering::Relaxed);
391
392 let mut stats = CacheStats {
393 l1_hits,
394 l1_misses,
395 l2_hits,
396 l2_misses,
397 l3_hits,
398 l3_misses,
399 l1_evictions: self.l1_evictions.load(Ordering::Relaxed),
400 l2_evictions: self.l2_evictions.load(Ordering::Relaxed),
401 writes_total: self.writes_total.load(Ordering::Relaxed),
402 writes_batched: self.writes_batched.load(Ordering::Relaxed),
403 refresh_ahead_count: self.refresh_ahead_count.load(Ordering::Relaxed),
404 l1_size: l1_cache.entry_count(),
405 l1_capacity: config.l1_capacity as u64,
406 l2_size: 0, l1_avg_latency_us: if l1_hits > 0 {
408 l1_latency_sum as f64 / l1_hits as f64
409 } else {
410 0.0
411 },
412 l2_avg_latency_us: if l2_hits > 0 {
413 l2_latency_sum as f64 / l2_hits as f64
414 } else {
415 0.0
416 },
417 l3_avg_latency_us: if l3_hits > 0 {
418 l3_latency_sum as f64 / l3_hits as f64
419 } else {
420 0.0
421 },
422 ..Default::default()
423 };
424
425 stats.calculate_rates();
426 stats
427 }
428}
429
430struct WriteBehindQueue {
432 pending: RwLock<Vec<(Vec<u8>, Vec<u8>)>>,
433 config: CacheConfig,
434}
435
436impl WriteBehindQueue {
437 fn new(config: CacheConfig) -> Self {
438 Self {
439 pending: RwLock::new(Vec::new()),
440 config,
441 }
442 }
443
444 async fn enqueue(&self, key: Vec<u8>, value: Vec<u8>) -> bool {
445 let mut pending = self.pending.write().await;
446 pending.push((key, value));
447
448 pending.len() >= self.config.write_behind_batch_size.unwrap_or(1000)
449 }
450
451 async fn drain(&self) -> Vec<(Vec<u8>, Vec<u8>)> {
452 let mut pending = self.pending.write().await;
453 std::mem::take(&mut *pending)
454 }
455
456 async fn len(&self) -> usize {
457 self.pending.read().await.len()
458 }
459}
460
461pub struct CachedStateBackend<B: StateBackend> {
466 l1_cache: MokaCache<Vec<u8>, CacheEntry>,
468
469 l2_cache: Option<ConnectionManager>,
471
472 backend: Arc<B>,
474
475 config: CacheConfig,
477
478 stats: Arc<StatsCollector>,
480
481 write_behind_queue: Option<Arc<WriteBehindQueue>>,
483}
484
485impl<B: StateBackend + 'static> CachedStateBackend<B> {
486 pub async fn new(
498 backend: B,
499 redis_client: Option<RedisClient>,
500 config: CacheConfig,
501 ) -> StateResult<Self> {
502 let l1_cache = MokaCache::builder()
504 .max_capacity(config.l1_capacity as u64)
505 .time_to_live(config.l1_ttl)
506 .build();
507
508 let l2_cache = if let Some(client) = redis_client {
510 let manager = client
511 .get_connection_manager()
512 .await
513 .map_err(|e| StateError::StorageError {
514 backend_type: "redis".to_string(),
515 details: e.to_string(),
516 })?;
517 Some(manager)
518 } else {
519 None
520 };
521
522 let write_behind_queue = if config.write_strategy == WriteStrategy::WriteBehind {
524 Some(Arc::new(WriteBehindQueue::new(config.clone())))
525 } else {
526 None
527 };
528
529 let cached = Self {
530 l1_cache,
531 l2_cache,
532 backend: Arc::new(backend),
533 config: config.clone(),
534 stats: Arc::new(StatsCollector::new()),
535 write_behind_queue,
536 };
537
538 if let Some(queue) = &cached.write_behind_queue {
540 cached.start_write_behind_task(queue.clone()).await;
541 }
542
543 Ok(cached)
544 }
545
546 async fn start_write_behind_task(&self, queue: Arc<WriteBehindQueue>) {
548 let backend = self.backend.clone();
549 let stats = self.stats.clone();
550 let interval = self.config.write_behind_interval.unwrap_or(Duration::from_secs(10));
551
552 tokio::spawn(async move {
553 let mut interval_timer = tokio::time::interval(interval);
554 loop {
555 interval_timer.tick().await;
556
557 let batch = queue.drain().await;
558 if batch.is_empty() {
559 continue;
560 }
561
562 debug!("Flushing write-behind batch: {} entries", batch.len());
563
564 for (key, value) in batch {
565 if let Err(e) = backend.put(&key, &value).await {
566 warn!("Write-behind flush failed for key: {:?}", e);
567 } else {
568 stats.record_write(true);
569 }
570 }
571 }
572 });
573 }
574
575 #[instrument(skip(self, key), level = "trace")]
577 async fn get_from_l1(&self, key: &[u8]) -> Option<Vec<u8>> {
578 let start = Instant::now();
579
580 if let Some(entry) = self.l1_cache.get(&key.to_vec()).await {
581 if !entry.is_expired() {
582 let latency_us = start.elapsed().as_micros() as u64;
583 if self.config.enable_stats {
584 self.stats.record_l1_hit(latency_us);
585 }
586
587 if let Some(threshold) = self.config.refresh_ahead_threshold {
589 if entry.needs_refresh(threshold) {
590 self.refresh_ahead(key.to_vec()).await;
591 }
592 }
593
594 return Some(entry.value.clone());
595 }
596 }
597
598 if self.config.enable_stats {
599 self.stats.record_l1_miss();
600 }
601 None
602 }
603
604 #[instrument(skip(self, key), level = "trace")]
606 async fn get_from_l2(&self, key: &[u8]) -> StateResult<Option<Vec<u8>>> {
607 let Some(ref mut conn) = self.l2_cache.clone() else {
608 return Ok(None);
609 };
610
611 let start = Instant::now();
612 let redis_key = format!("state:{}", hex::encode(key));
613
614 let result: Result<Option<Vec<u8>>, redis::RedisError> =
615 conn.clone().get(&redis_key).await;
616
617 match result {
618 Ok(Some(value)) => {
619 let latency_us = start.elapsed().as_micros() as u64;
620 if self.config.enable_stats {
621 self.stats.record_l2_hit(latency_us);
622 }
623
624 let entry = CacheEntry::new(value.clone(), self.config.l1_ttl);
626 self.l1_cache.insert(key.to_vec(), entry).await;
627
628 Ok(Some(value))
629 }
630 Ok(None) => {
631 if self.config.enable_stats {
632 self.stats.record_l2_miss();
633 }
634 Ok(None)
635 }
636 Err(e) => {
637 warn!("L2 cache error: {}", e);
638 if self.config.enable_stats {
639 self.stats.record_l2_miss();
640 }
641 Ok(None)
642 }
643 }
644 }
645
646 #[instrument(skip(self, key), level = "trace")]
648 async fn get_from_l3(&self, key: &[u8]) -> StateResult<Option<Vec<u8>>> {
649 let start = Instant::now();
650
651 let result = self.backend.get(key).await?;
652
653 let latency_us = start.elapsed().as_micros() as u64;
654
655 if result.is_some() {
656 if self.config.enable_stats {
657 self.stats.record_l3_hit(latency_us);
658 }
659 } else {
660 if self.config.enable_stats {
661 self.stats.record_l3_miss();
662 }
663 }
664
665 Ok(result)
666 }
667
668 async fn put_to_l1(&self, key: &[u8], value: &[u8]) {
670 let entry = CacheEntry::new(value.to_vec(), self.config.l1_ttl);
671 self.l1_cache.insert(key.to_vec(), entry).await;
672 }
673
674 async fn put_to_l2(&self, key: &[u8], value: &[u8]) -> StateResult<()> {
676 let Some(ref mut conn) = self.l2_cache.clone() else {
677 return Ok(());
678 };
679
680 let redis_key = format!("state:{}", hex::encode(key));
681 let ttl_seconds = self.config.l2_ttl.as_secs();
682
683 let _: () = conn.clone()
684 .set_ex(&redis_key, value, ttl_seconds as u64)
685 .await
686 .map_err(|e| StateError::StorageError {
687 backend_type: "redis".to_string(),
688 details: e.to_string(),
689 })?;
690
691 Ok(())
692 }
693
694 async fn put_to_l3(&self, key: &[u8], value: &[u8]) -> StateResult<()> {
696 self.backend.put(key, value).await
697 }
698
699 async fn refresh_ahead(&self, key: Vec<u8>) {
701 if self.config.enable_stats {
702 self.stats.record_refresh_ahead();
703 }
704
705 let backend = self.backend.clone();
706 let l1_cache = self.l1_cache.clone();
707 let l2_cache = self.l2_cache.clone();
708 let l1_ttl = self.config.l1_ttl;
709 let l2_ttl = self.config.l2_ttl;
710
711 tokio::spawn(async move {
712 debug!("Refresh-ahead for key: {:?}", key);
713
714 if let Ok(Some(value)) = backend.get(&key).await {
716 let entry = CacheEntry::new(value.clone(), l1_ttl);
718 l1_cache.insert(key.clone(), entry).await;
719
720 if let Some(mut conn) = l2_cache {
722 let redis_key = format!("state:{}", hex::encode(&key));
723 let ttl_seconds = l2_ttl.as_secs();
724 let _: Result<(), _> = conn.set_ex(&redis_key, &value, ttl_seconds as u64).await;
725 }
726 }
727 });
728 }
729
730 #[instrument(skip(self, key), level = "debug")]
732 pub async fn invalidate(&self, key: &[u8]) -> StateResult<()> {
733 self.l1_cache.invalidate(&key.to_vec()).await;
735
736 if let Some(ref mut conn) = self.l2_cache.clone() {
738 let redis_key = format!("state:{}", hex::encode(key));
739 let _: Result<(), _> = conn.clone().del(&redis_key).await;
740 }
741
742 Ok(())
743 }
744
745 pub async fn clear_cache(&self) -> StateResult<()> {
747 self.l1_cache.invalidate_all();
749
750 if let Some(ref mut conn) = self.l2_cache.clone() {
752 let pattern = "state:*";
755 let keys: Result<Vec<String>, _> = redis::cmd("KEYS")
756 .arg(pattern)
757 .query_async(&mut conn.clone())
758 .await;
759
760 if let Ok(keys) = keys {
761 if !keys.is_empty() {
762 let _: Result<(), _> = conn.clone().del(keys).await;
763 }
764 }
765 }
766
767 Ok(())
768 }
769
770 pub async fn stats(&self) -> CacheStats {
772 self.stats.snapshot(&self.l1_cache, &self.config)
773 }
774
775 pub async fn warm_cache(&self, keys: &[Vec<u8>]) -> StateResult<()> {
779 debug!("Warming cache with {} keys", keys.len());
780
781 for key in keys {
782 if let Some(value) = self.backend.get(key).await? {
783 self.put_to_l1(key, &value).await;
785
786 let _ = self.put_to_l2(key, &value).await;
788 }
789 }
790
791 Ok(())
792 }
793
794 pub async fn get_batch(&self, keys: &[Vec<u8>]) -> StateResult<Vec<Option<Vec<u8>>>> {
796 let mut results = Vec::with_capacity(keys.len());
797
798 for key in keys {
799 results.push(self.get(key).await?);
800 }
801
802 Ok(results)
803 }
804
805 pub async fn put_batch(&self, pairs: &[(Vec<u8>, Vec<u8>)]) -> StateResult<()> {
807 for (key, value) in pairs {
808 self.put(key, value).await?;
809 }
810
811 Ok(())
812 }
813}
814
815#[async_trait]
820impl<B: StateBackend + 'static> StateBackend for CachedStateBackend<B> {
821 #[instrument(skip(self, key), level = "debug")]
822 async fn get(&self, key: &[u8]) -> StateResult<Option<Vec<u8>>> {
823 if let Some(value) = self.get_from_l1(key).await {
825 return Ok(Some(value));
826 }
827
828 if let Some(value) = self.get_from_l2(key).await? {
830 return Ok(Some(value));
831 }
832
833 let value = self.get_from_l3(key).await?;
835
836 if let Some(ref val) = value {
838 self.put_to_l1(key, val).await;
839 let _ = self.put_to_l2(key, val).await; }
841
842 Ok(value)
843 }
844
845 #[instrument(skip(self, key, value), level = "debug")]
846 async fn put(&self, key: &[u8], value: &[u8]) -> StateResult<()> {
847 match self.config.write_strategy {
848 WriteStrategy::WriteThrough => {
849 self.put_to_l1(key, value).await;
851 let _ = self.put_to_l2(key, value).await;
852 self.put_to_l3(key, value).await?;
853
854 if self.config.enable_stats {
855 self.stats.record_write(false);
856 }
857 }
858 WriteStrategy::WriteBack => {
859 self.put_to_l1(key, value).await;
861 let _ = self.put_to_l2(key, value).await;
862
863 let backend = self.backend.clone();
864 let key = key.to_vec();
865 let value = value.to_vec();
866 let stats = self.stats.clone();
867 let enable_stats = self.config.enable_stats;
868
869 tokio::spawn(async move {
870 if let Err(e) = backend.put(&key, &value).await {
871 warn!("Write-back failed: {:?}", e);
872 } else if enable_stats {
873 stats.record_write(false);
874 }
875 });
876 }
877 WriteStrategy::WriteBehind => {
878 self.put_to_l1(key, value).await;
880 let _ = self.put_to_l2(key, value).await;
881
882 if let Some(ref queue) = self.write_behind_queue {
883 queue.enqueue(key.to_vec(), value.to_vec()).await;
884 }
885 }
886 }
887
888 Ok(())
889 }
890
891 #[instrument(skip(self, key), level = "debug")]
892 async fn delete(&self, key: &[u8]) -> StateResult<()> {
893 self.invalidate(key).await?;
895
896 self.backend.delete(key).await
898 }
899
900 async fn list_keys(&self, prefix: &[u8]) -> StateResult<Vec<Vec<u8>>> {
901 self.backend.list_keys(prefix).await
903 }
904
905 async fn clear(&self) -> StateResult<()> {
906 self.clear_cache().await?;
908
909 self.backend.clear().await
911 }
912
913 async fn count(&self) -> StateResult<usize> {
914 self.backend.count().await
916 }
917
918 async fn contains(&self, key: &[u8]) -> StateResult<bool> {
919 if self.l1_cache.get(&key.to_vec()).await.is_some() {
921 return Ok(true);
922 }
923
924 self.backend.contains(key).await
926 }
927}
928
929#[cfg(test)]
930mod tests {
931 use super::*;
932 use crate::state::MemoryStateBackend;
933 use std::time::Duration;
934
935 #[tokio::test]
936 async fn test_cache_config_default() {
937 let config = CacheConfig::default();
938 assert_eq!(config.l1_capacity, 10_000);
939 assert_eq!(config.write_strategy, WriteStrategy::WriteThrough);
940 assert_eq!(config.eviction_policy, EvictionPolicy::LRU);
941 }
942
943 #[tokio::test]
944 async fn test_cached_backend_creation() {
945 let backend = MemoryStateBackend::new(None);
946 let config = CacheConfig::default();
947
948 let cached = CachedStateBackend::new(backend, None, config).await;
949 assert!(cached.is_ok());
950 }
951
952 #[tokio::test]
953 async fn test_l1_cache_hit() {
954 let backend = MemoryStateBackend::new(None);
955 let config = CacheConfig {
956 enable_stats: true,
957 ..Default::default()
958 };
959
960 let cached = CachedStateBackend::new(backend, None, config).await.unwrap();
961
962 cached.put(b"key1", b"value1").await.unwrap();
964
965 let value = cached.get(b"key1").await.unwrap();
967 assert_eq!(value, Some(b"value1".to_vec()));
968
969 let stats = cached.stats().await;
971 assert_eq!(stats.l1_hits, 1);
972 assert!(stats.l1_hit_rate > 0.0);
973 }
974
975 #[tokio::test]
976 async fn test_l3_fallback() {
977 let backend = MemoryStateBackend::new(None);
978 backend.put(b"existing", b"data").await.unwrap();
979
980 let config = CacheConfig {
981 enable_stats: true,
982 ..Default::default()
983 };
984
985 let cached = CachedStateBackend::new(backend, None, config).await.unwrap();
986
987 let value = cached.get(b"existing").await.unwrap();
989 assert_eq!(value, Some(b"data".to_vec()));
990
991 let stats = cached.stats().await;
993 assert_eq!(stats.l3_hits, 1);
994 assert_eq!(stats.l1_hits, 0);
995 }
996
997 #[tokio::test]
998 async fn test_write_through_strategy() {
999 let backend = MemoryStateBackend::new(None);
1000 let config = CacheConfig {
1001 write_strategy: WriteStrategy::WriteThrough,
1002 ..Default::default()
1003 };
1004
1005 let cached = CachedStateBackend::new(backend.clone(), None, config).await.unwrap();
1006
1007 cached.put(b"key1", b"value1").await.unwrap();
1008
1009 let backend_value = backend.get(b"key1").await.unwrap();
1011 assert_eq!(backend_value, Some(b"value1".to_vec()));
1012 }
1013
1014 #[tokio::test]
1015 async fn test_invalidate() {
1016 let backend = MemoryStateBackend::new(None);
1017 let config = CacheConfig::default();
1018
1019 let cached = CachedStateBackend::new(backend, None, config).await.unwrap();
1020
1021 cached.put(b"key1", b"value1").await.unwrap();
1022
1023 let value = cached.get(b"key1").await.unwrap();
1025 assert_eq!(value, Some(b"value1".to_vec()));
1026
1027 cached.invalidate(b"key1").await.unwrap();
1029
1030 let value = cached.get(b"key1").await.unwrap();
1032 assert_eq!(value, Some(b"value1".to_vec()));
1033 }
1034
1035 #[tokio::test]
1036 async fn test_cache_stats() {
1037 let backend = MemoryStateBackend::new(None);
1038 let config = CacheConfig {
1039 enable_stats: true,
1040 ..Default::default()
1041 };
1042
1043 let cached = CachedStateBackend::new(backend, None, config).await.unwrap();
1044
1045 cached.put(b"k1", b"v1").await.unwrap();
1047 cached.put(b"k2", b"v2").await.unwrap();
1048
1049 cached.get(b"k1").await.unwrap(); cached.get(b"k1").await.unwrap(); cached.get(b"k3").await.unwrap(); let stats = cached.stats().await;
1054 assert_eq!(stats.l1_hits, 2);
1055 assert_eq!(stats.l3_misses, 1);
1056 assert!(stats.l1_hit_rate > 0.0);
1057 }
1058
1059 #[tokio::test]
1060 async fn test_batch_operations() {
1061 let backend = MemoryStateBackend::new(None);
1062 let config = CacheConfig::default();
1063
1064 let cached = CachedStateBackend::new(backend, None, config).await.unwrap();
1065
1066 let pairs = vec![
1068 (b"k1".to_vec(), b"v1".to_vec()),
1069 (b"k2".to_vec(), b"v2".to_vec()),
1070 (b"k3".to_vec(), b"v3".to_vec()),
1071 ];
1072 cached.put_batch(&pairs).await.unwrap();
1073
1074 let keys = vec![b"k1".to_vec(), b"k2".to_vec(), b"k3".to_vec()];
1076 let values = cached.get_batch(&keys).await.unwrap();
1077
1078 assert_eq!(values.len(), 3);
1079 assert_eq!(values[0], Some(b"v1".to_vec()));
1080 assert_eq!(values[1], Some(b"v2".to_vec()));
1081 assert_eq!(values[2], Some(b"v3".to_vec()));
1082 }
1083
1084 #[tokio::test]
1085 async fn test_cache_warming() {
1086 let backend = MemoryStateBackend::new(None);
1087
1088 backend.put(b"hot1", b"data1").await.unwrap();
1090 backend.put(b"hot2", b"data2").await.unwrap();
1091 backend.put(b"hot3", b"data3").await.unwrap();
1092
1093 let config = CacheConfig {
1094 enable_stats: true,
1095 ..Default::default()
1096 };
1097
1098 let cached = CachedStateBackend::new(backend, None, config).await.unwrap();
1099
1100 let hot_keys = vec![b"hot1".to_vec(), b"hot2".to_vec(), b"hot3".to_vec()];
1102 cached.warm_cache(&hot_keys).await.unwrap();
1103
1104 cached.get(b"hot1").await.unwrap();
1106 cached.get(b"hot2").await.unwrap();
1107 cached.get(b"hot3").await.unwrap();
1108
1109 let stats = cached.stats().await;
1110 assert_eq!(stats.l1_hits, 3);
1111 assert_eq!(stats.l3_hits, 0);
1112 }
1113
1114 #[tokio::test]
1115 async fn test_clear_cache() {
1116 let backend = MemoryStateBackend::new(None);
1117 let config = CacheConfig::default();
1118
1119 let cached = CachedStateBackend::new(backend, None, config).await.unwrap();
1120
1121 cached.put(b"k1", b"v1").await.unwrap();
1122 cached.put(b"k2", b"v2").await.unwrap();
1123
1124 cached.clear_cache().await.unwrap();
1126
1127 let value = cached.get(b"k1").await.unwrap();
1130 assert_eq!(value, Some(b"v1".to_vec()));
1131
1132 let stats = cached.stats().await;
1133 assert_eq!(stats.l3_hits, 1); }
1135
1136 #[tokio::test]
1137 async fn test_entry_expiration() {
1138 let entry = CacheEntry::new(b"value".to_vec(), Duration::from_millis(50));
1139
1140 assert!(!entry.is_expired());
1141
1142 tokio::time::sleep(Duration::from_millis(60)).await;
1143
1144 assert!(entry.is_expired());
1145 }
1146
1147 #[tokio::test]
1148 async fn test_refresh_ahead_detection() {
1149 let entry = CacheEntry::new(b"value".to_vec(), Duration::from_millis(100));
1150
1151 assert!(!entry.needs_refresh(0.8));
1152
1153 tokio::time::sleep(Duration::from_millis(85)).await;
1154
1155 assert!(entry.needs_refresh(0.8));
1156 }
1157
1158 #[tokio::test]
1159 async fn test_stats_calculation() {
1160 let mut stats = CacheStats {
1161 l1_hits: 80,
1162 l1_misses: 20,
1163 l2_hits: 15,
1164 l2_misses: 5,
1165 l3_hits: 4,
1166 l3_misses: 1,
1167 ..Default::default()
1168 };
1169
1170 stats.calculate_rates();
1171
1172 assert_eq!(stats.l1_hit_rate, 0.8);
1173 assert_eq!(stats.l2_hit_rate, 0.75);
1174 assert_eq!(stats.l3_hit_rate, 0.8);
1175
1176 assert!((stats.overall_hit_rate - 0.792).abs() < 0.001);
1178 }
1179}