Skip to main content

multi_tier_cache/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2//! Multi-Tier Cache
3//!
4//! A high-performance, production-ready multi-tier caching library for Rust featuring:
5//! - **L1 Cache**: In-memory caching with Moka (sub-millisecond latency)
6//! - **L2 Cache**: Distributed caching with Redis (persistent storage)
7//! - **Cache Stampede Protection**: `DashMap` + Mutex request coalescing
8//! - **Redis Streams**: Built-in support for event streaming
9//! - **Automatic L2-to-L1 Promotion**: Intelligent cache tier promotion
10//! - **Comprehensive Statistics**: Hit rates, promotions, in-flight tracking
11//!
12//! # Quick Start
13//!
14//! ```rust,no_run
15//! use multi_tier_cache::{CacheSystem, CacheStrategy};
16//!
17//! #[tokio::main]
18//! async fn main() -> anyhow::Result<()> {
19//!     // Initialize cache system
20//!     let cache = CacheSystem::new().await?;
21//!
22//!     // Store data with cache strategy
23//!     let data = bytes::Bytes::from("{\"user\": \"alice\", \"score\": 100}");
24//!     cache.cache_manager()
25//!         .set_with_strategy("user:1", data, CacheStrategy::ShortTerm)
26//!         .await?;
27//!
28//!     // Retrieve data (L1 first, then L2 fallback)
29//!     if let Some(cached) = cache.cache_manager().get("user:1").await? {
30//!         tracing::info!("Cached data: {:?}", cached);
31//!     }
32//!
33//!     // Get statistics
34//!     let stats = cache.cache_manager().get_stats();
35//!     tracing::info!("Hit rate: {:.2}%", stats.hit_rate);
36//!
37//!     Ok(())
38//! }
39//! ```
40//!
41//! # Features
42//!
43//! - **Multi-Tier Architecture**: Combines fast in-memory (L1) with persistent distributed (L2) caching
44//! - **Cache Stampede Protection**: Prevents duplicate computations during cache misses
45//! - **Redis Streams**: Publish/subscribe with automatic trimming
46//! - **Zero-Config**: Sensible defaults, works out of the box
47//! - **Production-Proven**: Battle-tested at 16,829+ RPS with 5.2ms latency
48//!
49//! # Architecture
50//!
51//! ```text
52//! Request → L1 Cache (Moka) → L2 Cache (Redis) → Compute/Fetch
53//!           ↓ Hit (90%)       ↓ Hit (75%)        ↓ Miss (5%)
54//!           Return            Promote to L1       Store in L1+L2
55//! ```
56
57pub mod backends;
58pub mod builder;
59pub mod cache_manager;
60pub mod error;
61#[cfg(feature = "redis")]
62#[cfg_attr(docsrs, doc(cfg(feature = "redis")))]
63pub mod invalidation;
64#[cfg(feature = "redis")]
65#[cfg_attr(docsrs, doc(cfg(feature = "redis")))]
66pub mod redis_streams;
67pub mod serialization;
68pub mod traits;
69
70pub use error::{CacheError, CacheResult};
71pub use serialization::{CacheSerializer, JsonSerializer};
72pub use std::sync::Arc;
73use tracing::{info, warn};
74
75// Re-export backend types (maintains backward compatibility)
76pub use backends::DashMapCache;
77#[cfg(feature = "moka")]
78#[cfg_attr(docsrs, doc(cfg(feature = "moka")))]
79pub use backends::{L1Cache, MokaCache, MokaCacheConfig};
80#[cfg(feature = "redis")]
81#[cfg_attr(docsrs, doc(cfg(feature = "redis")))]
82pub use backends::{L2Cache, RedisCache};
83
84// Optional backends (feature-gated)
85#[cfg(feature = "backend-memcached")]
86#[cfg_attr(docsrs, doc(cfg(feature = "backend-memcached")))]
87pub use backends::MemcachedCache;
88
89#[cfg(feature = "quick_cache")]
90#[cfg_attr(docsrs, doc(cfg(feature = "quick_cache")))]
91pub use backends::QuickCacheBackend;
92pub use builder::CacheSystemBuilder;
93pub use bytes::Bytes;
94pub use cache_manager::{
95    CacheManager,
96    CacheManagerStats,
97    CacheStrategy,
98    CacheTier,
99    // Multi-tier support (v0.5.0+)
100    TierConfig,
101    TierStats,
102};
103#[cfg(feature = "redis")]
104#[cfg_attr(docsrs, doc(cfg(feature = "redis")))]
105pub use invalidation::{
106    InvalidationConfig, InvalidationMessage, InvalidationPublisher, InvalidationStats,
107    InvalidationSubscriber, ReliableStreamSubscriber,
108};
109#[cfg(feature = "redis")]
110#[cfg_attr(docsrs, doc(cfg(feature = "redis")))]
111pub use redis_streams::RedisStreams;
112pub use traits::{CacheBackend, L2CacheBackend, StreamingBackend};
113
114// Re-export backend types (maintains backward compatibility)
115
116/// Main entry point for the Multi-Tier Cache system
117///
118/// Provides unified access to L1 (Moka) and L2 (Redis) caches with
119/// automatic failover, promotion, and stampede protection.
120///
121/// # Example
122///
123/// ```rust,no_run
124/// use multi_tier_cache::CacheSystem;
125///
126/// #[tokio::main]
127/// async fn main() -> anyhow::Result<()> {
128///     let cache = CacheSystem::new().await?;
129///
130///     // Use cache_manager for all operations
131///     let manager = cache.cache_manager();
132///
133///     Ok(())
134/// }
135/// ```
136///
137/// # Note on `l1_cache` and `l2_cache` Fields
138///
139/// When using multi-tier mode or custom backends, `l1_cache` and `l2_cache`
140/// may be `None`. Always use `cache_manager()` for cache operations.
141#[derive(Clone)]
142pub struct CacheSystem {
143    /// Unified cache manager (primary interface)
144    pub cache_manager: Arc<CacheManager>,
145    /// Optional L1 cache instance (for health checks)
146    #[cfg(feature = "moka")]
147    #[cfg_attr(docsrs, doc(cfg(feature = "moka")))]
148    pub l1_cache: Option<Arc<L1Cache>>,
149    /// Optional L2 cache instance (for health checks)
150    #[cfg(feature = "redis")]
151    #[cfg_attr(docsrs, doc(cfg(feature = "redis")))]
152    pub l2_cache: Option<Arc<L2Cache>>,
153}
154
155impl CacheSystem {
156    /// Create new cache system with default configuration
157    ///
158    /// # Configuration
159    ///
160    /// Redis connection is configured via `REDIS_URL` environment variable.
161    /// Default: `redis://127.0.0.1:6379`
162    ///
163    /// # Example
164    ///
165    /// ```rust,no_run
166    /// use multi_tier_cache::CacheSystem;
167    ///
168    /// #[tokio::main]
169    /// async fn main() -> anyhow::Result<()> {
170    ///     // Set environment variable (optional)
171    ///     unsafe { std::env::set_var("REDIS_URL", "redis://localhost:6379") };
172    ///
173    ///     let cache = CacheSystem::new().await?;
174    ///     Ok(())
175    /// }
176    /// ```
177    /// # Errors
178    ///
179    /// Returns an error if cache initialization fails.
180    #[cfg(all(feature = "moka", feature = "redis"))]
181    #[cfg_attr(docsrs, doc(cfg(all(feature = "moka", feature = "redis"))))]
182    pub async fn new() -> CacheResult<Self> {
183        info!("Initializing Multi-Tier Cache System");
184
185        // Initialize L1 cache (Moka)
186        let l1_cache = Arc::new(L1Cache::new(MokaCacheConfig::default())?);
187
188        // Initialize L2 cache (Redis)
189        let l2_cache = Arc::new(L2Cache::new().await?);
190
191        // Initialize cache manager
192        let cache_manager = Arc::new(CacheManager::new(l1_cache.clone(), l2_cache.clone()).await?);
193
194        info!("Multi-Tier Cache System initialized successfully");
195
196        Ok(Self {
197            cache_manager,
198            l1_cache: Some(l1_cache),
199            l2_cache: Some(l2_cache),
200        })
201    }
202
203    /// Create cache system with custom Redis URL
204    ///
205    /// # Arguments
206    ///
207    /// * `redis_url` - Redis connection string (e.g., `<redis://localhost:6379>`)
208    ///
209    /// # Example
210    ///
211    /// ```rust,no_run
212    /// use multi_tier_cache::CacheSystem;
213    ///
214    /// #[tokio::main]
215    /// async fn main() -> anyhow::Result<()> {
216    ///     let cache = CacheSystem::with_redis_url("redis://custom:6379").await?;
217    ///     Ok(())
218    /// }
219    /// ```
220    /// # Errors
221    ///
222    /// Returns an error if cache initialization fails.
223    #[cfg(all(feature = "moka", feature = "redis"))]
224    #[cfg_attr(docsrs, doc(cfg(all(feature = "moka", feature = "redis"))))]
225    pub async fn with_redis_url(redis_url: &str) -> CacheResult<Self> {
226        info!(redis_url = %redis_url, "Initializing Multi-Tier Cache System with custom Redis URL");
227
228        // Initialize L1 cache (Moka)
229        let l1_cache = Arc::new(L1Cache::new(MokaCacheConfig::default())?);
230
231        // Initialize L2 cache (Redis) with custom URL
232        let l2_cache = Arc::new(L2Cache::with_url(redis_url).await?);
233
234        // Initialize cache manager
235        let cache_manager =
236            Arc::new(CacheManager::new(Arc::clone(&l1_cache), Arc::clone(&l2_cache)).await?);
237
238        info!("Multi-Tier Cache System initialized successfully");
239
240        Ok(Self {
241            cache_manager,
242            l1_cache: Some(l1_cache),
243            l2_cache: Some(l2_cache),
244        })
245    }
246
247    /// Perform health check on all cache tiers
248    ///
249    /// Returns `true` if at least L1 is operational.
250    /// L2 failure is tolerated (graceful degradation).
251    ///
252    /// # Example
253    ///
254    /// ```rust,no_run
255    /// use multi_tier_cache::CacheSystem;
256    ///
257    /// #[tokio::main]
258    /// async fn main() -> anyhow::Result<()> {
259    ///     let cache = CacheSystem::new().await?;
260    ///
261    ///     if cache.health_check().await {
262    ///         tracing::info!("Cache system healthy");
263    ///     }
264    ///
265    ///     Ok(())
266    /// }
267    /// ```
268    pub async fn health_check(&self) -> bool {
269        #[cfg(feature = "moka")]
270        let l1_ok = match &self.l1_cache {
271            Some(cache) => {
272                let backend: &dyn crate::traits::CacheBackend = cache.as_ref();
273                backend.health_check().await
274            }
275            None => true,
276        };
277        #[cfg(not(feature = "moka"))]
278        let l1_ok = true;
279
280        #[cfg(feature = "redis")]
281        let l2_ok = match &self.l2_cache {
282            Some(cache) => {
283                let backend: &dyn crate::traits::CacheBackend = cache.as_ref();
284                backend.health_check().await
285            }
286            None => true,
287        };
288        #[cfg(not(feature = "redis"))]
289        let l2_ok = true;
290
291        if l1_ok && l2_ok {
292            info!("Multi-Tier Cache health check passed");
293            true
294        } else {
295            warn!(l1_ok = %l1_ok, l2_ok = %l2_ok, "Multi-Tier Cache health check - partial failure");
296            l1_ok // At minimum, L1 should work
297        }
298    }
299
300    /// Get reference to cache manager (primary interface)
301    ///
302    /// Use this for all cache operations: get, set, streams, etc.
303    #[must_use]
304    pub fn cache_manager(&self) -> &Arc<CacheManager> {
305        &self.cache_manager
306    }
307}