pub struct QueryResultCache { /* private fields */ }Expand description
Thread-safe 64-shard striped LRU cache for query results.
§Thread Safety
Each shard is an independent parking_lot::Mutex<LruCache> holding
capacity / 64 entries. Concurrent requests that hash to different shards
never contend on the same lock. parking_lot::Mutex is used over
std::sync::Mutex for:
- No poisoning: a panic in one thread does not permanently break the cache
- Smaller footprint: 1 byte vs 40 bytes per mutex on Linux
- Faster lock/unlock: optimized futex-based implementation
Metrics counters use AtomicU64 / AtomicUsize so no second lock is
acquired in the hot path.
§Memory Safety
- Hard LRU limit: Each shard evicts least-recently-used entries independently
- TTL expiry: Entries older than
ttl_secondsare considered expired and removed on next access - Memory tracking:
memory_bytestracked via atomic add/sub;sizecomputed lazily inmetrics()
§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(
12345_u64,
result.clone(),
vec!["v_user".to_string()],
None, // use global TTL
None, // no entity type index
).unwrap();
// Retrieve from cache
if let Some(cached) = cache.get(12345).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 new_with_clock(config: CacheConfig, clock: Arc<dyn Clock>) -> Self
pub fn new_with_clock(config: CacheConfig, clock: Arc<dyn Clock>) -> Self
Create a cache with a custom clock for deterministic time-based testing.
§Panics
Panics if config.max_entries is 0.
Sourcepub const fn is_enabled(&self) -> bool
pub const fn is_enabled(&self) -> bool
Returns whether caching is enabled.
Used by CachedDatabaseAdapter to short-circuit key generation
and result clone overhead when caching is disabled.
Sourcepub fn get(&self, cache_key: u64) -> Result<Option<Arc<Vec<JsonbValue>>>>
pub fn get(&self, cache_key: u64) -> Result<Option<Arc<Vec<JsonbValue>>>>
Look up a cached result by its cache key.
Returns None when caching is disabled or the key is not present or expired.
§Errors
This method is infallible with parking_lot::Mutex (no poisoning).
The Result return type is kept for API compatibility.
Sourcepub fn put(
&self,
cache_key: u64,
result: Vec<JsonbValue>,
accessed_views: Vec<String>,
ttl_override: Option<u64>,
entity_type: Option<&str>,
) -> Result<()>
pub fn put( &self, cache_key: u64, result: Vec<JsonbValue>, accessed_views: Vec<String>, ttl_override: Option<u64>, entity_type: Option<&str>, ) -> 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 queryttl_override- Per-entry TTL in seconds;NoneusesCacheConfig::ttl_secondsentity_type- Optional GraphQL type name (e.g."User") for entity-ID indexing. When provided, each row’s"id"field is extracted and stored inentity_idsso thatinvalidate_by_entity()can perform selective eviction.
§Errors
This method is infallible with parking_lot::Mutex (no poisoning).
The Result return type is kept for API compatibility.
§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": "uuid-1"}))];
cache.put(0xabc123, result, vec!["v_user".to_string()], None, Some("User"))?;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
This method is infallible with parking_lot::Mutex (no poisoning).
The Result return type is kept for API compatibility.
§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 invalidate_by_entity(
&self,
entity_type: &str,
entity_id: &str,
) -> Result<u64>
pub fn invalidate_by_entity( &self, entity_type: &str, entity_id: &str, ) -> Result<u64>
Evict cache entries that contain a specific entity UUID.
Scans all entries whose entity_ids index contains the given entity_id
under the given entity_type key, and removes them. Entries that do not
reference this entity are left untouched.
§Arguments
entity_type- GraphQL type name (e.g."User")entity_id- UUID string of the mutated entity
§Returns
Number of cache entries evicted.
§Errors
This method is infallible with parking_lot::Mutex (no poisoning).
The Result return type is kept for API compatibility.
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.
size is computed lazily by scanning all shards — this keeps the
get()/put() hot paths free of cross-shard coordination.
§Errors
This method is infallible with parking_lot::Mutex (no poisoning).
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);Sourcepub fn clear(&self) -> Result<()>
pub fn clear(&self) -> Result<()>
Clear all cache entries.
Used for testing and manual cache flush.
§Errors
This method is infallible with parking_lot::Mutex (no poisoning).
The Result return type is kept for API compatibility.
§Example
use fraiseql_core::cache::{QueryResultCache, CacheConfig};
let cache = QueryResultCache::new(CacheConfig::default());
cache.clear()?;Auto Trait Implementations§
impl !Freeze for QueryResultCache
impl !RefUnwindSafe for QueryResultCache
impl Send for QueryResultCache
impl Sync for QueryResultCache
impl Unpin for QueryResultCache
impl UnsafeUnpin for QueryResultCache
impl !UnwindSafe for QueryResultCache
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more