Skip to main content

mytheclipse_cache/
multilayer.rs

1//! Multi-layer (L1/L2) caching behind a single [`Cache`] face.
2//!
3//! [`MultiLayerCache`] layers a fast in-process L1 over a slower but larger
4//! L2 (e.g. Redis). Reads are L1-first with an L2 fallback; a hit on L2 is
5//! backfilled into L1. Writes and invalidations go to both layers.
6
7use std::time::Duration;
8
9use async_trait::async_trait;
10
11use crate::traits::{Cache, CacheError};
12
13/// A read-through, write-through composition of an L1 and L2 cache.
14///
15/// `L1` is typically [`crate::memory::MemoryCache`] or
16/// [`crate::moka_cache::MokaL1`]; `L2` is typically a distributed cache such
17/// as a Redis backend. Order of layers fixed: `L1` is consulted first.
18#[derive(Clone)]
19pub struct MultiLayerCache<L1, L2> {
20    l1: L1,
21    l2: L2,
22    /// When `true`, an L2 hit is written back into L1 (default `true`).
23    populate_l1: bool,
24}
25
26impl<L1, L2> MultiLayerCache<L1, L2>
27where
28    L1: Cache,
29    L2: Cache,
30{
31    /// Builds a two-layer cache with L1-backfill enabled.
32    pub fn new(l1: L1, l2: L2) -> Self {
33        Self {
34            l1,
35            l2,
36            populate_l1: true,
37        }
38    }
39
40    /// Disables L1 backfill-on-read.
41    pub fn without_l1_backfill(mut self) -> Self {
42        self.populate_l1 = false;
43        self
44    }
45
46    /// Returns a reference to the L1 layer.
47    pub fn l1(&self) -> &L1 {
48        &self.l1
49    }
50
51    /// Returns a reference to the L2 layer.
52    pub fn l2(&self) -> &L2 {
53        &self.l2
54    }
55}
56
57#[async_trait]
58impl<L1, L2> Cache for MultiLayerCache<L1, L2>
59where
60    L1: Cache,
61    L2: Cache,
62{
63    async fn get(&self, key: &str) -> Result<Option<Vec<u8>>, CacheError> {
64        // L1 first.
65        if let Some(value) = self.l1.get(key).await? {
66            return Ok(Some(value));
67        }
68        // L2 fallback.
69        if let Some(value) = self.l2.get(key).await? {
70            if self.populate_l1 {
71                self.l1.set(key, value.clone(), None).await?;
72            }
73            return Ok(Some(value));
74        }
75        Ok(None)
76    }
77
78    async fn set(
79        &self,
80        key: &str,
81        value: Vec<u8>,
82        ttl: Option<Duration>,
83    ) -> Result<(), CacheError> {
84        self.l1.set(key, value.clone(), ttl).await?;
85        self.l2.set(key, value, ttl).await
86    }
87
88    async fn invalidate(&self, key: &str) -> Result<(), CacheError> {
89        self.l1.invalidate(key).await?;
90        self.l2.invalidate(key).await
91    }
92
93    async fn clear(&self) -> Result<(), CacheError> {
94        self.l1.clear().await?;
95        self.l2.clear().await
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use crate::memory::MemoryCache;
103
104    #[tokio::test]
105    async fn read_through_populates_l1() {
106        let l2 = MemoryCache::new();
107        l2.set("k", b"l2-value".to_vec(), None).await.unwrap();
108
109        let layered = MultiLayerCache::new(MemoryCache::new(), l2);
110        assert_eq!(layered.l1().get("k").await.unwrap(), None);
111        assert_eq!(layered.get("k").await.unwrap(), Some(b"l2-value".to_vec()));
112        // L2 hit should have populated L1.
113        assert_eq!(
114            layered.l1().get("k").await.unwrap(),
115            Some(b"l2-value".to_vec())
116        );
117    }
118
119    #[tokio::test]
120    async fn write_goes_to_both() {
121        let l1 = MemoryCache::new();
122        let l2 = MemoryCache::new();
123        let layered = MultiLayerCache::new(l1, l2.clone());
124        layered.set("k", b"v".to_vec(), None).await.unwrap();
125        assert_eq!(layered.l1().get("k").await.unwrap(), Some(b"v".to_vec()));
126        assert_eq!(l2.get("k").await.unwrap(), Some(b"v".to_vec()));
127    }
128
129    #[tokio::test]
130    async fn invalidate_clears_both() {
131        let l1 = MemoryCache::new();
132        let l2 = MemoryCache::new();
133        let layered = MultiLayerCache::new(l1, l2);
134        layered.set("k", b"v".to_vec(), None).await.unwrap();
135        layered.invalidate("k").await.unwrap();
136        assert_eq!(layered.l1().get("k").await.unwrap(), None);
137        assert_eq!(layered.l2().get("k").await.unwrap(), None);
138    }
139}