ri/cache/manager.rs
1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of Ri.
4//! The Ri project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//! http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18#![allow(non_snake_case)]
19
20use serde::{Deserialize, Serialize};
21use std::sync::Arc;
22use std::collections::HashMap as FxHashMap;
23use tokio::sync::{RwLock, broadcast};
24use crate::cache::core::{RiCache, RiCacheStats};
25
26#[cfg(feature = "pyo3")]
27use pyo3::prelude::*;
28
29
30/// # Ri Cache Manager
31///
32/// This file implements a cache manager that coordinates different cache backends with
33/// consistency support across multiple instances. It provides a unified interface for cache
34/// operations while ensuring cache consistency through event-driven architecture.
35///
36/// ## Design Principles
37/// 1. **Consistency First**: Ensures cache consistency across multiple instances using broadcast events
38/// 2. **Unified Interface**: Provides a consistent API regardless of the underlying cache backend
39/// 3. **Event-Driven Architecture**: Uses broadcast channels for efficient cache invalidation
40/// 4. **Thread Safety**: Implements thread-safe operations using Arc and RwLock
41/// 5. **Flexibility**: Supports any backend implementing the RiCache trait
42/// 6. **Scalability**: Designed to handle high-throughput cache operations
43///
44/// ## Usage Examples
45/// ```rust
46/// // Create a cache manager with a Redis backend
47/// let redis_backend = Arc::new(RiRedisBackend::new(config).await?);
48/// let mut cache_manager = RiCacheManager::new(redis_backend);
49///
50/// // Start the consistency listener
51/// let listener_handle = cache_manager.start_consistency_listener().await;
52///
53/// // Set a value in cache
54/// cache_manager.set("user:123", &User { id: 123, name: "John" }, Some(3600)).await?;
55///
56/// // Get a value from cache
57/// let user: Option<User> = cache_manager.get("user:123").await?;
58///
59/// // Delete a value and invalidate across all instances
60/// cache_manager.delete("user:123").await?;
61///
62/// // Clear cache and broadcast to all instances
63/// cache_manager.clear().await?;
64/// ```
65/// Cache event type for maintaining cache consistency across instances
66///
67/// This enum defines the events that are broadcasted to ensure all cache instances
68/// remain consistent. Each event triggers a corresponding action on all cache instances.
69#[derive(Debug, Clone, Serialize, Deserialize)]
70#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
71pub enum RiCacheEvent {
72 /// Invalidate a specific cache key
73 ///
74 /// **Parameters:**
75 /// - `key`: The cache key to invalidate
76 Invalidate { key: String },
77
78 /// Invalidate all cache keys matching a pattern
79 ///
80 /// **Parameters:**
81 /// - `pattern`: The pattern to match cache keys (supports wildcards depending on backend)
82 InvalidatePattern { pattern: String },
83
84 /// Clear all cache entries
85 Clear(),
86}
87
88/// Cache manager that coordinates different cache backends with consistency support
89///
90/// This struct provides a unified interface for cache operations while ensuring cache
91/// consistency across multiple instances through event-driven architecture. It wraps
92/// any backend implementing the RiCache trait and adds consistency guarantees.
93#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
94pub struct RiCacheManager {
95 /// The underlying cache backend implementation
96 backend: Arc<dyn RiCache + Send + Sync>,
97
98 /// Broadcast sender for cache consistency events
99 event_sender: broadcast::Sender<RiCacheEvent>,
100
101 /// Broadcast receiver for cache consistency events (used internally)
102 event_receiver: Option<broadcast::Receiver<RiCacheEvent>>,
103
104 /// Map of subscribers to cache events (for internal use)
105 _subscribers: Arc<RwLock<FxHashMap<String, broadcast::Receiver<RiCacheEvent>>>>,
106}
107
108impl RiCacheManager {
109 /// Create a new cache manager with the specified backend
110 ///
111 /// **Parameters:**
112 /// - `backend`: The underlying cache backend implementation
113 ///
114 /// **Returns:**
115 /// - A new instance of `RiCacheManager`
116 pub fn new(backend: Arc<dyn RiCache + Send + Sync>) -> Self {
117 let (sender, receiver) = broadcast::channel(100);
118
119 Self {
120 backend,
121 event_sender: sender,
122 event_receiver: Some(receiver),
123 _subscribers: Arc::new(RwLock::new(FxHashMap::default())),
124 }
125 }
126
127 /// Start the cache consistency event listener
128 ///
129 /// This method starts a background task that listens for cache consistency events
130 /// and applies them to the underlying cache backend. This ensures that all cache
131 /// instances remain consistent across the system.
132 ///
133 /// **Returns:**
134 /// - A `JoinHandle` for the background task
135 pub async fn start_consistency_listener(&mut self) -> tokio::task::JoinHandle<()> {
136 let backend = self.backend.clone();
137 let mut receiver = match self.event_receiver.take() {
138 Some(r) => r,
139 None => {
140 log::error!("[Ri.Cache] Event receiver already started or not initialized");
141 return tokio::spawn(async {});
142 }
143 };
144
145 log::info!("[Ri.Cache] Starting cache consistency event listener");
146
147 tokio::spawn(async move {
148 let mut event_count = 0;
149 while let Ok(event) = receiver.recv().await {
150 event_count += 1;
151
152 match event {
153 RiCacheEvent::Invalidate { key } => {
154 log::info!("[Ri.Cache] Processing invalidate event for key: {key}");
155 if let Err(e) = backend.delete(&key).await {
156 log::error!("[Ri.Cache] Failed to invalidate cache key {key}: {e}");
157 } else {
158 log::info!("[Ri.Cache] Successfully invalidated cache key: {key}");
159 }
160 },
161 RiCacheEvent::InvalidatePattern { pattern } => {
162 log::info!("[Ri.Cache] Processing invalidate pattern event: {pattern}");
163 match backend.delete_by_pattern(&pattern).await {
164 Ok(count) => {
165 log::info!("[Ri.Cache] Successfully invalidated {} cache keys matching pattern: {pattern}", count);
166 }
167 Err(e) => {
168 log::error!("[Ri.Cache] Failed to invalidate cache pattern {pattern}: {e}");
169 }
170 }
171 },
172 RiCacheEvent::Clear() => {
173 log::info!("[Ri.Cache] Processing clear cache event");
174 if let Err(e) = backend.clear().await {
175 log::error!("[Ri.Cache] Failed to clear cache: {e}");
176 } else {
177 log::info!("[Ri.Cache] Successfully cleared cache");
178 }
179 },
180 }
181
182 // Log event processing statistics periodically
183 if event_count % 100 == 0 {
184 log::info!("[Ri.Cache] Processed {event_count} cache consistency events");
185 }
186 }
187
188 log::info!("[Ri.Cache] Cache consistency event listener stopped after processing {event_count} events");
189 })
190 }
191
192 /// Subscribe to cache consistency events
193 ///
194 /// This method allows external components to subscribe to cache consistency events,
195 /// enabling them to react to cache changes in real-time.
196 ///
197 /// **Returns:**
198 /// - A broadcast receiver for cache events
199 pub fn subscribe(&self) -> broadcast::Receiver<RiCacheEvent> {
200 self.event_sender.subscribe()
201 }
202
203 /// Publish a cache consistency event
204 ///
205 /// This method publishes a cache event to all subscribers, ensuring cache consistency
206 /// across all instances.
207 ///
208 /// **Parameters:**
209 /// - `event`: The cache event to publish
210 pub fn publish_event(&self, event: RiCacheEvent) {
211 let event_type = match &event {
212 RiCacheEvent::Invalidate { key } => format!("Invalidate(key: {key})"),
213 RiCacheEvent::InvalidatePattern { pattern } => format!("InvalidatePattern(pattern: {pattern})"),
214 RiCacheEvent::Clear() => "Clear".to_string(),
215 };
216
217 log::info!("[Ri.Cache] Publishing cache event: {event_type}");
218 let _ = self.event_sender.send(event);
219 }
220
221 /// Get a value from cache
222 ///
223 /// This method retrieves a value from the cache using the specified key. If the key
224 /// exists and the value is valid, it is deserialized and returned. Otherwise, `None`
225 /// is returned.
226 ///
227 /// **Parameters:**
228 /// - `key`: The cache key to retrieve
229 ///
230 /// **Returns:**
231 /// - `Ok(Some(T))` if the key exists and the value is valid
232 /// - `Ok(None)` if the key does not exist
233 /// - `Err(RiError)` if an error occurs during retrieval or deserialization
234 pub async fn get<T: serde::de::DeserializeOwned>(&self, key: &str) -> crate::core::RiResult<Option<T>> {
235 log::debug!("[Ri.Cache] Getting cache key: {key}");
236
237 match self.backend.get(key).await? {
238 Some(cached_value) => {
239 log::debug!("[Ri.Cache] Cache hit for key: {key}");
240 let value = serde_json::from_str(&cached_value)
241 .map_err(|e| crate::core::RiError::Other(format!("Deserialization error: {e}")))?;
242 Ok(Some(value))
243 }
244 None => {
245 log::debug!("[Ri.Cache] Cache miss for key: {key}");
246 Ok(None)
247 }
248 }
249 }
250
251 /// Set a value in cache with optional TTL
252 ///
253 /// This method stores a value in the cache with the specified key and optional TTL.
254 /// It also publishes an invalidate event to ensure cache consistency across all instances.
255 ///
256 /// **Parameters:**
257 /// - `key`: The cache key to set
258 /// - `value`: The value to store (must implement Serialize)
259 /// - `ttl_seconds`: Optional time-to-live in seconds
260 ///
261 /// **Returns:**
262 /// - `Ok(())` if the value was successfully stored
263 /// - `Err(RiError)` if an error occurs during serialization or storage
264 pub async fn set<T: serde::Serialize>(&self, key: &str, value: &T, ttl_seconds: Option<u64>) -> crate::core::RiResult<()> {
265 log::debug!("[Ri.Cache] Setting cache key: {key} with TTL: {ttl_seconds:?}");
266
267 let serialized = serde_json::to_string(value)
268 .map_err(|e| crate::core::RiError::Other(format!("Serialization error: {e}")))?;
269
270 let result = self.backend.set(key, &serialized, ttl_seconds).await;
271
272 match &result {
273 Ok(_) => log::debug!("[Ri.Cache] Successfully set cache key: {key}"),
274 Err(e) => log::error!("[Ri.Cache] Failed to set cache key {key}: {e}"),
275 }
276
277 // Note: Do NOT publish invalidate event after set
278 // The set operation should update the cache, not invalidate it
279 // Invalidate events should only be published for delete operations
280
281 result
282 }
283
284 /// Delete a value from cache
285 ///
286 /// This method deletes a value from the cache using the specified key. It also
287 /// publishes an invalidate event to ensure cache consistency across all instances.
288 ///
289 /// **Parameters:**
290 /// - `key`: The cache key to delete
291 ///
292 /// **Returns:**
293 /// - `Ok(true)` if the key was found and deleted
294 /// - `Ok(false)` if the key didn't exist
295 /// - `Err(RiError)` if an error occurs during deletion
296 pub async fn delete(&self, key: &str) -> crate::core::RiResult<bool> {
297 log::debug!("[Ri.Cache] Deleting cache key: {key}");
298
299 let result = self.backend.delete(key).await;
300
301 match &result {
302 Ok(true) => log::debug!("[Ri.Cache] Successfully deleted cache key: {key}"),
303 Ok(false) => log::debug!("[Ri.Cache] Cache key not found for deletion: {key}"),
304 Err(e) => log::error!("[Ri.Cache] Failed to delete cache key {key}: {e}"),
305 }
306
307 // Publish invalidate event to ensure consistency across instances
308 self.publish_event(RiCacheEvent::Invalidate { key: key.to_string() });
309
310 result
311 }
312
313 /// Check if a key exists in cache
314 ///
315 /// This method checks if the specified key exists in the cache.
316 ///
317 /// **Parameters:**
318 /// - `key`: The cache key to check
319 ///
320 /// **Returns:**
321 /// - `true` if the key exists, `false` otherwise
322 pub async fn exists(&self, key: &str) -> bool {
323 self.backend.exists(key).await
324 }
325
326 /// Clear all cache entries
327 ///
328 /// This method clears all entries from the cache. It also publishes a clear event
329 /// to ensure cache consistency across all instances.
330 ///
331 /// **Returns:**
332 /// - `Ok(())` if the cache was successfully cleared
333 /// - `Err(RiError)` if an error occurs during clearing
334 pub async fn clear(&self) -> crate::core::RiResult<()> {
335 log::info!("[Ri.Cache] Clearing all cache entries");
336
337 let result = self.backend.clear().await;
338
339 match &result {
340 Ok(_) => log::info!("[Ri.Cache] Successfully cleared all cache entries"),
341 Err(e) => log::error!("[Ri.Cache] Failed to clear cache: {e}"),
342 }
343
344 // Publish clear event to ensure consistency across instances
345 self.publish_event(RiCacheEvent::Clear());
346
347 result
348 }
349
350 /// Invalidate cache entries matching a pattern
351 ///
352 /// This method invalidates all cache entries matching the specified pattern. It
353 /// publishes an invalidate pattern event to ensure cache consistency across all instances.
354 ///
355 /// **Parameters:**
356 /// - `pattern`: The pattern to match cache keys (supports wildcards depending on backend)
357 ///
358 /// **Returns:**
359 /// - `Ok(())` if the invalidation event was successfully published
360 pub async fn invalidate_pattern(&self, pattern: &str) -> crate::core::RiResult<()> {
361 // Publish invalidate pattern event to ensure consistency across instances
362 self.publish_event(RiCacheEvent::InvalidatePattern { pattern: pattern.to_string() });
363
364 Ok(())
365 }
366
367 /// Get cache statistics
368 ///
369 /// This method retrieves statistics about the cache, including hit rate, miss rate,
370 /// and the number of entries.
371 ///
372 /// **Returns:**
373 /// - A `RiCacheStats` struct containing the cache statistics
374 pub async fn stats(&self) -> RiCacheStats {
375 let stats = self.backend.stats().await;
376
377 // Log cache statistics for monitoring
378 log::info!("[Ri.Cache] Cache Statistics: hits={}, misses={}, entries={}, hit_rate={:.2}%",
379 stats.hits, stats.misses, stats.entries, stats.avg_hit_rate * 100.0);
380
381 // Monitor cache performance
382 if stats.hits + stats.misses > 0 {
383 let current_hit_rate = stats.hits as f64 / (stats.hits + stats.misses) as f64;
384 if current_hit_rate < 0.5 && stats.hits + stats.misses > 100 {
385 log::warn!("[Ri.Cache] Warning: Low cache hit rate ({:.2}%) with {} total operations",
386 current_hit_rate * 100.0, stats.hits + stats.misses);
387 }
388 }
389
390 stats
391 }
392
393 /// Cleanup expired cache entries
394 ///
395 /// This method removes all expired entries from the cache.
396 ///
397 /// **Returns:**
398 /// - `Ok(usize)` with the number of expired entries cleaned up
399 /// - `Err(RiError)` if an error occurs during cleanup
400 pub async fn cleanup_expired(&self) -> crate::core::RiResult<usize> {
401 let cleaned = self.backend.cleanup_expired().await?;
402
403 // Log cleanup results for monitoring
404 if cleaned > 0 {
405 log::info!("[Ri.Cache] Cleanup completed: {cleaned} expired entries removed");
406 }
407
408 Ok(cleaned)
409 }
410
411 /// Get a value from cache or set it if it doesn't exist
412 ///
413 /// This method retrieves a value from the cache using the specified key. If the key
414 /// exists and the value is valid, it is returned. Otherwise, the provided factory
415 /// function is called to generate the value, which is then stored in the cache and
416 /// returned.
417 ///
418 /// **Parameters:**
419 /// - `key`: The cache key to retrieve or set
420 /// - `ttl_seconds`: Optional time-to-live in seconds for the new value
421 /// - `factory`: A function that generates the value if it doesn't exist in cache
422 ///
423 /// **Returns:**
424 /// - `Ok(T)` with the retrieved or generated value
425 /// - `Err(RiError)` if an error occurs during retrieval, generation, or storage
426 pub async fn get_or_set<T, F>(&self, key: &str, ttl_seconds: Option<u64>, factory: F) -> crate::core::RiResult<T>
427 where
428 T: serde::Serialize + serde::de::DeserializeOwned + Clone,
429 F: FnOnce() -> crate::core::RiResult<T>,
430 {
431 log::debug!("[Ri.Cache] get_or_set operation for key: {key} with TTL: {ttl_seconds:?}");
432
433 // Try to get from cache first
434 if let Some(value) = self.get::<T>(key).await? {
435 log::debug!("[Ri.Cache] get_or_set cache hit for key: {key}");
436 return Ok(value);
437 }
438
439 log::debug!("[Ri.Cache] get_or_set cache miss for key: {key}, generating value");
440
441 // If not found, generate the value
442 let value = factory()?;
443
444 // Store in cache
445 self.set(key, &value, ttl_seconds).await?;
446
447 log::debug!("[Ri.Cache] get_or_set successfully generated and cached value for key: {key}");
448 Ok(value)
449 }
450}
451
452#[cfg(feature = "pyo3")]
453#[pymethods]
454impl RiCacheManager {
455 #[new]
456 fn py_new() -> Self {
457 use crate::cache::backends::RiMemoryCache;
458 let backend = std::sync::Arc::new(RiMemoryCache::new());
459 Self::new(backend)
460 }
461
462 #[pyo3(name = "get")]
463 fn get_impl(&self, key: String) -> pyo3::PyResult<Option<String>> {
464 let rt = tokio::runtime::Runtime::new().map_err(|e| {
465 pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to create runtime: {}", e))
466 })?;
467
468 rt.block_on(async {
469 self.get::<String>(&key).await.map_err(|e| {
470 pyo3::exceptions::PyRuntimeError::new_err(format!("Cache error: {}", e))
471 })
472 })
473 }
474
475 #[pyo3(name = "set")]
476 fn set_impl(&self, key: String, value: String, ttl_seconds: Option<u64>) -> pyo3::PyResult<()> {
477 let rt = tokio::runtime::Handle::current();
478
479 rt.block_on(async {
480 self.set(&key, &value, ttl_seconds).await.map_err(|e| {
481 pyo3::exceptions::PyRuntimeError::new_err(format!("Cache error: {}", e))
482 })
483 })
484 }
485
486 #[pyo3(name = "delete")]
487 fn delete_impl(&self, key: String) -> pyo3::PyResult<bool> {
488 let rt = tokio::runtime::Runtime::new().map_err(|e| {
489 pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to create runtime: {}", e))
490 })?;
491
492 rt.block_on(async {
493 self.delete(&key).await.map_err(|e| {
494 pyo3::exceptions::PyRuntimeError::new_err(format!("Cache error: {}", e))
495 })
496 })
497 }
498
499 #[pyo3(name = "exists")]
500 fn exists_impl(&self, key: String) -> pyo3::PyResult<bool> {
501 let rt = tokio::runtime::Runtime::new().map_err(|e| {
502 pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to create runtime: {}", e))
503 })?;
504
505 Ok(rt.block_on(async {
506 self.exists(&key).await
507 }))
508 }
509
510 #[pyo3(name = "clear")]
511 fn clear_impl(&self) -> pyo3::PyResult<()> {
512 let rt = tokio::runtime::Runtime::new().map_err(|e| {
513 pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to create runtime: {}", e))
514 })?;
515
516 rt.block_on(async {
517 self.clear().await.map_err(|e| {
518 pyo3::exceptions::PyRuntimeError::new_err(format!("Cache error: {}", e))
519 })
520 })
521 }
522
523 #[pyo3(name = "stats")]
524 fn stats_impl(&self) -> pyo3::PyResult<RiCacheStats> {
525 let rt = tokio::runtime::Runtime::new().map_err(|e| {
526 pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to create runtime: {}", e))
527 })?;
528
529 Ok(rt.block_on(async {
530 self.stats().await
531 }))
532 }
533
534 #[pyo3(name = "cleanup_expired")]
535 fn cleanup_expired_impl(&self) -> pyo3::PyResult<usize> {
536 let rt = tokio::runtime::Runtime::new().map_err(|e| {
537 pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to create runtime: {}", e))
538 })?;
539
540 rt.block_on(async {
541 self.cleanup_expired().await.map_err(|e| {
542 pyo3::exceptions::PyRuntimeError::new_err(format!("Cache error: {}", e))
543 })
544 })
545 }
546}