use bytes::Bytes;
use futures_util::future::BoxFuture;
use multi_tier_cache::error::CacheResult;
use multi_tier_cache::{CacheBackend, CacheSystemBuilder, L2CacheBackend};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use std::time::{Duration, Instant};
type L3Store = Arc<RwLock<HashMap<String, (Bytes, Instant, Duration)>>>;
struct MockL3Cache {
name: String,
store: L3Store,
}
impl MockL3Cache {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
store: Arc::new(RwLock::new(HashMap::new())),
}
}
}
impl CacheBackend for MockL3Cache {
fn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Option<Bytes>> {
let store: L3Store = Arc::clone(&self.store);
Box::pin(async move {
tokio::time::sleep(Duration::from_millis(50)).await;
let store = store.read().unwrap_or_else(|_| panic!("Lock poisoned"));
store.get(key).and_then(
|(value, expiry, _): &(Bytes, std::time::Instant, std::time::Duration)| {
if *expiry > Instant::now() {
Some(value.clone())
} else {
None
}
},
)
})
}
fn set_with_ttl<'a>(
&'a self,
key: &'a str,
value: Bytes,
ttl: Duration,
) -> BoxFuture<'a, CacheResult<()>> {
let store: L3Store = Arc::clone(&self.store);
let key = key.to_string();
let name = self.name.clone();
Box::pin(async move {
tokio::time::sleep(Duration::from_millis(50)).await;
let mut store = store.write().unwrap_or_else(|_| panic!("Lock poisoned"));
let expiry = Instant::now() + ttl;
store.insert(key.clone(), (value, expiry, ttl));
println!("💾 [{name}] Cached '{key}' with TTL {ttl:?}");
Ok(())
})
}
fn remove<'a>(&'a self, key: &'a str) -> BoxFuture<'a, CacheResult<()>> {
let store: L3Store = Arc::clone(&self.store);
let key = key.to_string();
Box::pin(async move {
let mut store = store.write().unwrap_or_else(|_| panic!("Lock poisoned"));
store.remove(&key);
Ok(())
})
}
fn health_check(&self) -> BoxFuture<'_, bool> {
Box::pin(async move { true })
}
fn name(&self) -> &'static str {
"MockL3"
}
}
impl L2CacheBackend for MockL3Cache {
fn get_with_ttl<'a>(
&'a self,
key: &'a str,
) -> BoxFuture<'a, Option<(Bytes, Option<Duration>)>> {
let store: L3Store = Arc::clone(&self.store);
Box::pin(async move {
tokio::time::sleep(Duration::from_millis(50)).await;
let store = store.read().unwrap_or_else(|_| panic!("Lock poisoned"));
store.get(key).and_then(
|(value, expiry, _): &(Bytes, std::time::Instant, std::time::Duration)| {
let now = Instant::now();
if *expiry > now {
let remaining = expiry.duration_since(now);
Some((value.clone(), Some(remaining)))
} else {
None
}
},
)
})
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
println!("=== Multi-Tier Cache: 3-Tier Architecture Example ===\n");
let l3_backend = Arc::new(MockL3Cache::new("Mock L3 (Disk)"));
println!("Building 3-tier cache system...");
let cache = CacheSystemBuilder::new()
.with_l3(l3_backend.clone()) .build()
.await?;
println!("✅ Cache system initialized with 3 tiers!");
println!("\n--- Storing Data ---");
let data = Bytes::from("{\"id\": \"user_123\", \"name\": \"Alice\", \"role\": \"premium\"}");
println!("Setting 'user:123' with ShortTerm strategy (5 min)...");
cache
.cache_manager()
.set_with_strategy(
"user:123",
data.clone(),
multi_tier_cache::CacheStrategy::ShortTerm,
)
.await?;
println!("\n--- Simulating Access Patterns ---");
let cold_data = Bytes::from("{\"status\": \"archived\"}");
let l3_ref = l3_backend.as_ref();
l3_ref
.set_with_ttl("archive:doc1", cold_data, Duration::from_secs(3600))
.await?;
println!("(Seeded 'archive:doc1' directly into L3 only)");
println!("Requesting 'archive:doc1' (should miss L1/L2, hit L3, and promote)...");
let start = Instant::now();
match cache.cache_manager().get("archive:doc1").await? {
Some(val) => {
println!("✅ Found value: {val:?}");
println!(" Latency: {:?}", start.elapsed());
}
_ => {
println!("❌ Value not found!");
}
}
println!("Requesting 'archive:doc1' again (should hit L1)...");
let start = Instant::now();
if let Some(val) = cache.cache_manager().get("archive:doc1").await? {
println!("✅ Found value: {val:?}");
println!(" Latency: {:?}", start.elapsed());
}
println!("\n--- Statistics ---");
let stats = cache.cache_manager().get_stats();
println!("Total Requests: {}", stats.total_requests);
println!("L1 Hits: {}", stats.l1_hits);
println!("L2 Hits: {}", stats.l2_hits);
println!("Misses: {}", stats.misses);
println!("Promotions: {}", stats.promotions);
Ok(())
}