use backbone_cache::{RedisCache, MemoryCache, CacheKey};
use serde::{Serialize, Deserialize};
use std::time::Duration;
use tokio::time::sleep;
use std::collections::HashMap;
use uuid::Uuid;
use chrono::{DateTime, Utc};
#[derive(Serialize, Deserialize, Debug, Clone)]
struct UserProfile {
id: String,
username: String,
email: String,
profile_data: ProfileData,
preferences: UserPreferences,
social_links: Vec<SocialLink>,
created_at: DateTime<Utc>,
updated_at: DateTime<Utc>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct ProfileData {
bio: Option<String>,
avatar_url: Option<String>,
location: Option<String>,
website: Option<String>,
birth_date: Option<DateTime<Utc>>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct UserPreferences {
theme: String,
language: String,
timezone: String,
email_notifications: bool,
push_notifications: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct SocialLink {
platform: String,
url: String,
verified: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct ApiRequest {
id: String,
method: String,
endpoint: String,
headers: HashMap<String, String>,
body: Option<String>,
user_id: String,
timestamp: DateTime<Utc>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct CachedResponse {
request_id: String,
status_code: u16,
headers: HashMap<String, String>,
body: String,
cached_at: DateTime<Utc>,
ttl_seconds: u64,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct SessionToken {
token: String,
user_id: String,
permissions: Vec<String>,
expires_at: DateTime<Utc>,
last_accessed: DateTime<Utc>,
ip_address: String,
user_agent: String,
}
#[derive(Debug, Clone)]
enum CacheStrategy {
CacheAside,
WriteThrough,
WriteBehind,
RefreshAhead,
}
struct AdvancedCacheManager {
memory_cache: MemoryCache,
redis_cache: RedisCache,
strategy: CacheStrategy,
write_buffer: Vec<(String, String, Option<u64>)>,
buffer_size: usize,
}
impl AdvancedCacheManager {
async fn new(redis_url: &str, strategy: CacheStrategy) -> Result<Self, Box<dyn std::error::Error>> {
let memory_cache = MemoryCache::new(Some(5000)); let redis_cache = RedisCache::new(redis_url).await?;
Ok(Self {
memory_cache,
redis_cache,
strategy,
write_buffer: Vec::new(),
buffer_size: 100,
})
}
async fn get_cache_aside<T>(&self, key: &str) -> Result<Option<T>, Box<dyn std::error::Error>>
where
T: for<'de> Deserialize<'de> + Send + Sync + Clone,
{
if let Some(value) = self.memory_cache.get::<T>(key).await? {
return Ok(Some(value));
}
if let Some(value) = self.redis_cache.get::<T>(key).await? {
self.memory_cache.set(key, &value, Some(300)).await?; return Ok(Some(value));
}
Ok(None)
}
async fn set_cache_aside<T>(&self, key: &str, value: &T, ttl_seconds: Option<u64>) -> Result<(), Box<dyn std::error::Error>>
where
T: Serialize + Send + Sync + Clone,
{
self.memory_cache.set(key, value, ttl_seconds).await?;
self.redis_cache.set(key, value, ttl_seconds).await?;
Ok(())
}
async fn write_through<T>(&mut self, key: &str, value: &T, ttl_seconds: Option<u64>) -> Result<(), Box<dyn std::error::Error>>
where
T: Serialize + Send + Sync + Clone,
{
self.memory_cache.set(key, value, ttl_seconds).await?;
self.redis_cache.set(key, value, ttl_seconds).await?;
println!("๐ Writing to database: {}", key);
Ok(())
}
async fn write_behind<T>(&mut self, key: &str, value: &T, ttl_seconds: Option<u64>) -> Result<(), Box<dyn std::error::Error>>
where
T: Serialize + Send + Sync,
{
let serialized = serde_json::to_string(value)?;
self.write_buffer.push((key.to_string(), serialized, ttl_seconds.unwrap_or(3600)));
if self.write_buffer.len() >= self.buffer_size {
self.flush_write_buffer().await?;
}
Ok(())
}
async fn flush_write_buffer(&mut self) -> Result<(), Box<dyn std::error::Error>> {
if self.write_buffer.is_empty() {
return Ok(());
}
println!("๐ Flushing write buffer: {} entries", self.write_buffer.len());
let entries: Vec<(String, String, Option<u64>)> = self.write_buffer
.drain(..)
.collect();
let redis_entries: Vec<(String, String, Option<u64>)> = entries
.iter()
.map(|(key, value, ttl)| (key.clone(), value.clone(), *ttl))
.collect();
self.redis_cache.mset(redis_entries).await?;
for (key, value, ttl) in entries {
let deserialized: Result<serde_json::Value, _> = serde_json::from_str(&value);
if let Ok(val) = deserialized {
self.memory_cache.set(&key, &val, ttl).await?;
}
}
Ok(())
}
async fn refresh_ahead<T>(&self, key_prefix: &str, threshold_seconds: u64) -> Result<(), Box<dyn std::error::Error>>
where
T: for<'de> Deserialize<'de> + Send + Sync + Serialize,
{
println!("๐ Scanning for expiring keys with prefix: {}", key_prefix);
println!("โฐ Refreshing keys expiring within {} seconds", threshold_seconds);
let keys_to_refresh = vec![
format!("{}:123", key_prefix),
format!("{}:456", key_prefix),
];
for key in keys_to_refresh {
if let Some(ttl) = self.redis_cache.ttl(&key).await? {
if ttl < threshold_seconds {
println!("๐ Refreshing key: {} (TTL: {}s)", key, ttl);
}
}
}
Ok(())
}
async fn warm_cache_tier(&self, data_type: &str) -> Result<(), Box<dyn std::error::Error>> {
println!("๐ฅ Warming cache tier for: {}", data_type);
match data_type {
"user_profiles" => {
let user_ids = vec!["user_1", "user_2", "user_3"];
for user_id in user_ids {
let profile = self.create_sample_profile(user_id).await?;
let key = CacheKey::user(user_id);
self.memory_cache.set(&key, &profile, Some(300)).await?; self.redis_cache.set(&key, &profile, Some(3600)).await?; }
}
"api_responses" => {
let endpoints = vec!["/api/v1/users", "/api/v1/products"];
for endpoint in endpoints {
let response = self.create_sample_response(endpoint).await?;
let key = CacheKey::api_response(endpoint, "default");
self.memory_cache.set(&key, &response, Some(60)).await?; self.redis_cache.set(&key, &response, Some(300)).await?; }
}
_ => {
println!("โ ๏ธ Unknown data type: {}", data_type);
}
}
Ok(())
}
async fn invalidate_user_cache(&self, user_id: &str) -> Result<(), Box<dyn std::error::Error>> {
println!("๐๏ธ Invalidating cache for user: {}", user_id);
let user_key = CacheKey::user(user_id);
self.memory_cache.delete(&user_key).await?;
self.redis_cache.delete(&user_key).await?;
let profile_key = format!("profile:{}", user_id);
self.memory_cache.delete(&profile_key).await?;
self.redis_cache.delete(&profile_key).await?;
let session_pattern = format!("session:*:{}*", user_id);
println!("๐๏ธ Invalidating sessions matching pattern: {}", session_pattern);
Ok(())
}
async fn create_sample_profile(&self, user_id: &str) -> Result<UserProfile, Box<dyn std::error::Error>> {
Ok(UserProfile {
id: user_id.to_string(),
username: format!("user_{}", user_id),
email: format!("user{}@example.com", user_id),
profile_data: ProfileData {
bio: Some(format!("Bio for user {}", user_id)),
avatar_url: Some(format!("https://api.dicebear.com/7.x/avataaars/svg?seed={}", user_id)),
location: Some("San Francisco, CA".to_string()),
website: Some(format!("https://user{}.example.com", user_id)),
birth_date: Some(Utc::now() - chrono::Duration::days(365 * 25)),
},
preferences: UserPreferences {
theme: "dark".to_string(),
language: "en".to_string(),
timezone: "UTC".to_string(),
email_notifications: true,
push_notifications: false,
},
social_links: vec![
SocialLink {
platform: "twitter".to_string(),
url: format!("https://twitter.com/user_{}", user_id),
verified: true,
},
SocialLink {
platform: "github".to_string(),
url: format!("https://github.com/user_{}", user_id),
verified: true,
},
],
created_at: Utc::now() - chrono::Duration::days(30),
updated_at: Utc::now(),
})
}
async fn create_sample_response(&self, endpoint: &str) -> Result<CachedResponse, Box<dyn std::error::Error>> {
Ok(CachedResponse {
request_id: Uuid::new_v4().to_string(),
status_code: 200,
headers: {
let mut headers = HashMap::new();
headers.insert("Content-Type".to_string(), "application/json".to_string());
headers.insert("Cache-Control".to_string(), "max-age=300".to_string());
headers
},
body: format!("{{\"endpoint\": \"{}\", \"data\": \"sample_data\"}}", endpoint),
cached_at: Utc::now(),
ttl_seconds: 300,
})
}
async fn compare_cache_stats(&self) -> Result<(), Box<dyn std::error::Error>> {
println!("\n๐ Cache Statistics Comparison");
let memory_stats = self.memory_cache.stats().await?;
let redis_stats = self.redis_cache.stats().await?;
println!("๐ง Memory Cache:");
println!(" Hit Rate: {:.2}%", memory_stats.hit_rate * 100.0);
println!(" Total Entries: {}", memory_stats.total_entries);
println!(" Hits: {} | Misses: {}", memory_stats.hits, memory_stats.misses);
println!("๐ Redis Cache:");
println!(" Hit Rate: {:.2}%", redis_stats.hit_rate * 100.0);
println!(" Total Entries: {}", redis_stats.total_entries);
println!(" Hits: {} | Misses: {}", redis_stats.hits, redis_stats.misses);
if let Some(memory_usage) = redis_stats.memory_usage {
println!(" Memory Usage: {} bytes", memory_usage);
}
Ok(())
}
async fn cleanup(&mut self) -> Result<(), Box<dyn std::error::Error>> {
println!("๐งน Cleaning up resources...");
self.flush_write_buffer().await?;
self.memory_cache.clear().await?;
println!("โ
Cleanup completed");
Ok(())
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== Backbone Cache Advanced Usage Examples ===\n");
let mut cache_manager = AdvancedCacheManager::new("redis://localhost:6379", CacheStrategy::CacheAside).await
.map_err(|e| println!("โ ๏ธ Redis not available, using memory-only mode: {}", e))?;
println!("๐ 1. Cache-Aside Pattern Demonstration");
println!("=======================================");
let user_id = "user_123";
let user_key = CacheKey::user(user_id);
println!("๐ First access (cache miss):");
let cached_user: Option<UserProfile> = cache_manager.get_cache_aside(&user_key).await?;
match cached_user {
None => {
println!(" โ Cache miss - loading from database");
let profile = cache_manager.create_sample_profile(user_id).await?;
cache_manager.set_cache_aside(&user_key, &profile, Some(3600)).await?;
println!(" โ
Loaded from database and cached");
}
Some(user) => println!(" โ
Found in cache: {}", user.username),
}
println!("๐ Second access (cache hit):");
let cached_user: Option<UserProfile> = cache_manager.get_cache_aside(&user_key).await?;
match cached_user {
Some(user) => println!(" โ
Cache hit: {} (from {})", user.username,
if cache_manager.memory_cache.exists(&user_key).await? { "Memory" } else { "Redis" }),
None => println!(" โ Cache miss (unexpected)"),
}
println!("\nโ๏ธ 2. Write-Through Pattern Demonstration");
println!("==========================================");
cache_manager.strategy = CacheStrategy::WriteThrough;
let new_user_id = "user_456";
let new_profile = cache_manager.create_sample_profile(new_user_id).await?;
let new_user_key = CacheKey::user(new_user_id);
println!("๐ Writing user data with write-through pattern");
cache_manager.write_through(&new_user_key, &new_profile, Some(3600)).await?;
println!("โ
Data written to cache and database");
let retrieved: Option<UserProfile> = cache_manager.get_cache_aside(&new_user_key).await?;
match retrieved {
Some(user) => println!("โ
Verified data in cache: {}", user.username),
None => println!("โ Data not found in cache"),
}
println!("\n๐ 3. Write-Behind Pattern Demonstration");
println!("=========================================");
cache_manager.strategy = CacheStrategy::WriteBehind;
println!("๐ Adding multiple writes to buffer:");
for i in 1..=5 {
let user_id = &format!("buffer_user_{}", i);
let profile = cache_manager.create_sample_profile(user_id).await?;
let key = CacheKey::user(user_id);
cache_manager.write_behind(&key, &profile, Some(7200)).await?;
println!(" โ
Added to buffer: {}", user_id);
}
for i in 6..=8 {
let user_id = &format!("buffer_user_{}", i);
let profile = cache_manager.create_sample_profile(user_id).await?;
let key = CacheKey::user(user_id);
cache_manager.write_behind(&key, &profile, Some(7200)).await?;
println!(" โ
Added to buffer: {}", user_id);
}
cache_manager.flush_write_buffer().await?;
println!("\n๐ฅ 4. Cache Warming Demonstration");
println!("=================================");
println!("๐ฅ Warming user profiles cache");
cache_manager.warm_cache_tier("user_profiles").await?;
println!("๐ฅ Warming API responses cache");
cache_manager.warm_cache_tier("api_responses").await?;
let warm_user: Option<UserProfile> = cache_manager.get_cache_aside(&CacheKey::user("user_1")).await?;
match warm_user {
Some(user) => println!("โ
Warmed user found: {}", user.username),
None => println!("โ Warmed user not found"),
}
println!("\n๐๏ธ 5. Cache Invalidation Demonstration");
println!("=====================================");
let invalidate_user_id = "user_1";
let invalidate_key = CacheKey::user(invalidate_user_id);
let profile = cache_manager.create_sample_profile(invalidate_user_id).await?;
cache_manager.set_cache_aside(&invalidate_key, &profile, Some(3600)).await?;
println!("๐ User cached: {}", invalidate_user_id);
let exists_in_memory = cache_manager.memory_cache.exists(&invalidate_key).await?;
let exists_in_redis = cache_manager.redis_cache.exists(&invalidate_key).await?;
println!("๐ User exists before invalidation - Memory: {}, Redis: {}", exists_in_memory, exists_in_redis);
cache_manager.invalidate_user_cache(invalidate_user_id).await?;
let exists_after_memory = cache_manager.memory_cache.exists(&invalidate_key).await?;
let exists_after_redis = cache_manager.redis_cache.exists(&invalidate_key).await?;
println!("๐ User exists after invalidation - Memory: {}, Redis: {}", exists_after_memory, exists_after_redis);
println!("\nโก 6. Multi-Tier Cache Performance Demo");
println!("====================================");
let test_user_id = "perf_test_user";
let test_key = CacheKey::user(test_user_id);
let test_profile = cache_manager.create_sample_profile(test_user_id).await?;
cache_manager.set_cache_aside(&test_key, &test_profile, Some(3600)).await?;
println!("๐โโ๏ธ Testing performance across cache tiers:");
let iterations = 1000;
println!("๐ง Testing Memory Cache ({} iterations)...", iterations);
let memory_start = std::time::Instant::now();
for _ in 0..iterations {
let _: Option<UserProfile> = cache_manager.memory_cache.get(&test_key).await?;
}
let memory_duration = memory_start.elapsed();
println!(" Memory Cache: {:.2}ms total, {:.2}ฮผs avg",
memory_duration.as_millis(),
memory_duration.as_micros() as f64 / iterations as f64);
println!("๐ Testing Redis Cache ({} iterations)...", iterations);
let redis_start = std::time::Instant::now();
for _ in 0..iterations {
let _: Option<UserProfile> = cache_manager.redis_cache.get(&test_key).await?;
}
let redis_duration = redis_start.elapsed();
println!(" Redis Cache: {:.2}ms total, {:.2}ฮผs avg",
redis_duration.as_millis(),
redis_duration.as_micros() as f64 / iterations as f64);
println!("๐ Memory cache is {:.2}x faster than Redis",
redis_duration.as_micros() as f64 / memory_duration.as_micros() as f64);
println!("\n๐ 7. Refresh-Ahead Pattern Demonstration");
println!("=========================================");
cache_manager.strategy = CacheStrategy::RefreshAhead;
let refresh_user_id = "refresh_test_user";
let refresh_key = CacheKey::user(refresh_user_id);
let refresh_profile = cache_manager.create_sample_profile(refresh_user_id).await?;
cache_manager.redis_cache.set(&refresh_key, &refresh_profile, Some(30)).await?;
println!("๐ Setting up refresh-ahead scan");
cache_manager.refresh_ahead::<UserProfile>("user", 60).await?;
println!("\n๐ 8. Cache Statistics Comparison");
println!("================================");
cache_manager.compare_cache_stats().await?;
println!("\n๐ฆ 9. Advanced Batch Operations");
println!("=================================");
let batch_keys = vec![
CacheKey::user("user_1"),
CacheKey::user("user_2"),
CacheKey::api_response("/api/products", "category=electronics"),
CacheKey::api_response("/api/users", "page=1&limit=10"),
];
println!("๐ Batch retrieving {} keys", batch_keys.len());
let batch_results = cache_manager.redis_cache.mget::<UserProfile>(batch_keys).await?;
for (key, result) in batch_results {
match result {
Some(_) => println!(" โ
Found: {}", key),
None => println!(" โ Miss: {}", key),
}
}
let batch_entries: Vec<(String, UserProfile, Option<u64>)> = (1..=5)
.map(|i| {
let user_id = format!("batch_user_{}", i);
let key = CacheKey::user(&user_id);
(key, format!("Batch User {}", i), Some(3600 + i * 60)) })
.collect();
let user_entries: Vec<(String, UserProfile, Option<u64>)> = batch_entries
.into_iter()
.map(|(key, name, ttl)| {
let profile = UserProfile {
id: key.clone(),
username: name.clone(),
email: format!("{}@example.com", name.to_lowercase().replace(" ", "_")),
profile_data: ProfileData {
bio: Some(format!("Batch created: {}", name)),
avatar_url: None,
location: None,
website: None,
birth_date: None,
},
preferences: UserPreferences {
theme: "light".to_string(),
language: "en".to_string(),
timezone: "UTC".to_string(),
email_notifications: true,
push_notifications: false,
},
social_links: vec![],
created_at: Utc::now(),
updated_at: Utc::now(),
};
(key, profile, ttl)
})
.collect();
println!("๐ Batch setting {} users with different TTLs", user_entries.len());
cache_manager.redis_cache.mset(user_entries).await?;
println!("\n๐งน 10. Cache Eviction and Memory Management");
println!("=========================================");
let small_cache = MemoryCache::new(Some(3));
println!("๐ Testing LRU eviction with 3-entry cache:");
for i in 1..=5 {
let key = format!("evict_test_{}", i);
let value = format!("Value {}", i);
small_cache.set(&key, &value, None).await?;
println!(" โ
Added: {}", key);
let stats = small_cache.stats().await?;
println!(" ๐ Cache size: {} entries", stats.total_entries);
}
println!("๐ Checking which entries survived eviction:");
for i in 1..=5 {
let key = format!("evict_test_{}", i);
let value: Option<String> = small_cache.get(&key).await?;
match value {
Some(v) => println!(" โ
{} = {}", key, v),
None => println!(" โ {} = evicted", key),
}
}
println!("\nโ ๏ธ 11. Error Handling and Recovery");
println!("=================================");
println!("๐งช Testing error handling scenarios:");
let invalid_key = "nonexistent:key";
let missing_value: Option<UserProfile> = cache_manager.memory_cache.get(invalid_key).await?;
println!(" ๐ Invalid key lookup: {:?}", missing_value);
let missing_ttl = cache_manager.memory_cache.ttl(invalid_key).await?;
println!(" ๐ TTL on missing key: {:?}", missing_ttl);
let delete_result = cache_manager.memory_cache.delete(invalid_key).await?;
println!(" ๐ Delete missing key: {}", if delete_result { "deleted" } else { "not found" });
println!("\n๐งน 12. Final Cleanup and Statistics");
println!("=================================");
cache_manager.cleanup().await?;
cache_manager.compare_cache_stats().await?;
println!("\n๐ Advanced Usage Examples Complete!");
println!("=====================================");
println!("โ
All advanced caching patterns demonstrated");
println!("โ
Multi-tier cache performance compared");
println!("โ
Cache invalidation and warming implemented");
println!("โ
Error handling and recovery tested");
println!("โ
Batch operations and eviction strategies verified");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_cache_manager_creation() {
let result = AdvancedCacheManager::new("redis://invalid:6379", CacheStrategy::CacheAside).await;
assert!(result.is_ok()); }
#[tokio::test]
async fn test_cache_aside_pattern() {
let cache_manager = AdvancedCacheManager::new("redis://localhost:6379", CacheStrategy::CacheAside).await;
if let Ok(manager) = cache_manager {
let key = "test_key";
let test_value = "test_value";
let result: Option<String> = manager.get_cache_aside(key).await.unwrap();
assert!(result.is_none());
manager.set_cache_aside(key, &test_value, Some(60)).await.unwrap();
let result: Option<String> = manager.get_cache_aside(key).await.unwrap();
assert_eq!(result, Some(test_value.to_string()));
}
}
#[tokio::test]
async fn test_write_behind_buffer() {
let mut cache_manager = AdvancedCacheManager::new("redis://localhost:6379", CacheStrategy::WriteBehind).await;
if let Ok(manager) = cache_manager {
for i in 1..=3 {
let key = format!("buffer_test_{}", i);
let value = format!("Value {}", i);
manager.write_behind(&key, &value, Some(60)).await.unwrap();
}
assert_eq!(manager.write_buffer.len(), 3);
manager.flush_write_buffer().await.unwrap();
assert_eq!(manager.write_buffer.len(), 0);
}
}
#[tokio::test]
async fn test_cache_invalidation() {
let cache_manager = AdvancedCacheManager::new("redis://localhost:6379", CacheStrategy::CacheAside).await;
if let Ok(manager) = cache_manager {
let user_id = "test_user_invalidate";
let key = CacheKey::user(user_id);
let profile = manager.create_sample_profile(user_id).await.unwrap();
manager.set_cache_aside(&key, &profile, Some(60)).await.unwrap();
let exists = manager.memory_cache.exists(&key).await.unwrap();
assert!(exists);
manager.invalidate_user_cache(user_id).await.unwrap();
let exists = manager.memory_cache.exists(&key).await.unwrap();
assert!(!exists);
}
}
}