pub struct QueryResultCache { /* private fields */ }Expand description
Thread-safe LRU cache for query results.
§Thread Safety
The LRU structure uses a single Mutex for correctness. Metrics counters
use AtomicU64 / AtomicUsize so no second lock is acquired in the hot path.
Under high concurrency this eliminates the double-lock contention that caused
cache hits to be slower than cache misses.
§Memory Safety
- Hard LRU limit: Configured via
max_entries, automatically evicts least-recently-used entries when limit is reached - TTL expiry: Entries older than
ttl_secondsare considered expired and removed on next access - Memory tracking: Metrics include estimated memory usage
§Example
use fraiseql_core::cache::{QueryResultCache, CacheConfig};
use fraiseql_core::db::types::JsonbValue;
use serde_json::json;
let cache = QueryResultCache::new(CacheConfig::default());
// Cache a result
let result = vec![JsonbValue::new(json!({"id": 1, "name": "Alice"}))];
cache.put(
"cache_key_123".to_string(),
result.clone(),
vec!["v_user".to_string()]
).unwrap();
// Retrieve from cache
if let Some(cached) = cache.get("cache_key_123").unwrap() {
println!("Cache hit! {} results", cached.len());
}Implementations§
Source§impl QueryResultCache
impl QueryResultCache
Sourcepub fn new(config: CacheConfig) -> Self
pub fn new(config: CacheConfig) -> Self
Sourcepub fn is_enabled(&self) -> bool
pub fn is_enabled(&self) -> bool
Get cached result by key.
Returns None if:
- Caching is disabled (
config.enabled = false) - Entry not in cache (cache miss)
- Entry expired (TTL exceeded)
§Errors
Returns error if cache mutex is poisoned (should never happen).
§Example
use fraiseql_core::cache::{QueryResultCache, CacheConfig};
let cache = QueryResultCache::new(CacheConfig::default());
if let Some(result) = cache.get("cache_key_abc123")? {
// Cache hit - use result
println!("Found {} results in cache", result.len());
} else {
// Cache miss - execute query
println!("Cache miss, executing query");
}Returns whether caching is enabled.
Used by CachedDatabaseAdapter to short-circuit the SHA-256 key generation
and result clone overhead when caching is disabled.
pub fn get(&self, cache_key: &str) -> Result<Option<Arc<Vec<JsonbValue>>>>
Sourcepub fn put(
&self,
cache_key: String,
result: Vec<JsonbValue>,
accessed_views: Vec<String>,
) -> Result<()>
pub fn put( &self, cache_key: String, result: Vec<JsonbValue>, accessed_views: Vec<String>, ) -> Result<()>
Store query result in cache.
If caching is disabled, this is a no-op.
§Arguments
cache_key- Cache key (fromgenerate_cache_key())result- Query result to cacheaccessed_views- List of views accessed by this query
§Errors
Returns error if cache mutex is poisoned.
§Example
use fraiseql_core::cache::{QueryResultCache, CacheConfig};
use fraiseql_core::db::types::JsonbValue;
use serde_json::json;
let cache = QueryResultCache::new(CacheConfig::default());
let result = vec![JsonbValue::new(json!({"id": 1}))];
cache.put(
"cache_key_abc123".to_string(),
result,
vec!["v_user".to_string()]
)?;Sourcepub fn invalidate_views(&self, views: &[String]) -> Result<u64>
pub fn invalidate_views(&self, views: &[String]) -> Result<u64>
Invalidate entries accessing specified views.
Called after mutations to invalidate affected cache entries.
§Arguments
views- List of view/table names modified by mutation
§Returns
Number of cache entries invalidated
§Errors
Returns error if cache mutex is poisoned.
§Example
use fraiseql_core::cache::{QueryResultCache, CacheConfig};
let cache = QueryResultCache::new(CacheConfig::default());
// After createUser mutation
let invalidated = cache.invalidate_views(&["v_user".to_string()])?;
println!("Invalidated {} cache entries", invalidated);Sourcepub fn metrics(&self) -> Result<CacheMetrics>
pub fn metrics(&self) -> Result<CacheMetrics>
Get cache metrics snapshot.
Returns a consistent snapshot of current counters. Individual fields may be updated independently (atomics), so the snapshot is not a single atomic transaction, but is accurate enough for monitoring.
§Errors
Always returns Ok. The Result return type is kept for API compatibility.
§Example
use fraiseql_core::cache::{QueryResultCache, CacheConfig};
let cache = QueryResultCache::new(CacheConfig::default());
let metrics = cache.metrics()?;
println!("Hit rate: {:.1}%", metrics.hit_rate() * 100.0);
println!("Size: {} / {} entries", metrics.size, 10_000);