Expand description
Query result caching for FraiseQL v2.
§Overview
This module provides transparent LRU-based query result caching with view-based invalidation. Cache entries are automatically invalidated when mutations modify the underlying data.
§Scope
- LRU-based result caching with TTL expiry
- View-based invalidation (not entity-level)
- Security-aware cache key generation (prevents data leakage)
- Integration with
DatabaseAdaptervia wrapper
§Architecture
┌─────────────────────┐
│ GraphQL Query │
│ + Variables │
│ + WHERE Clause │
└──────────┬──────────┘
│
↓ generate_cache_key()
┌─────────────────────┐
│ SHA-256 Cache Key │ ← Includes variables for security
└──────────┬──────────┘
│
↓ QueryResultCache::get()
┌─────────────────────┐
│ Cache Hit? │
│ - Check TTL │
│ - Check LRU │
└──────────┬──────────┘
│
┌─────┴─────┐
│ │
HIT MISS
│ │
↓ ↓ execute_query()
Return Database Query
Cached + Store Result
Result + Track Views
Mutation:
┌─────────────────────┐
│ Mutation executed │
│ "createUser" │
└──────────┬──────────┘
│
↓ InvalidationContext::for_mutation()
┌─────────────────────┐
│ Modified Views: │
│ - v_user │
└──────────┬──────────┘
│
↓ cache.invalidate_views()
┌─────────────────────┐
│ Remove all caches │
│ reading from v_user │
└─────────────────────┘§Configuration
use fraiseql_core::cache::CacheConfig;
// Production configuration
let config = CacheConfig {
enabled: true,
max_entries: 50_000,
ttl_seconds: 86_400, // 24 hours
cache_list_queries: true,
};
// Development (disable for deterministic tests)
let config = CacheConfig::disabled();§Usage Example
ⓘ
use fraiseql_core::cache::{CachedDatabaseAdapter, QueryResultCache, CacheConfig, InvalidationContext};
use fraiseql_core::db::postgres::PostgresAdapter;
// Create database adapter
let db_adapter = PostgresAdapter::new("postgresql://localhost/db").await?;
// Wrap with caching
let cache = QueryResultCache::new(CacheConfig::default());
let adapter = CachedDatabaseAdapter::new(
db_adapter,
cache,
"1.0.0".to_string() // schema version
);
// Use as normal DatabaseAdapter - caching is transparent
let users = adapter
.execute_where_query("v_user", None, Some(10), None)
.await?;
println!("Found {} users", users.len());
// After mutation, invalidate
let invalidation = InvalidationContext::for_mutation(
"createUser",
vec!["v_user".to_string()]
);
adapter.invalidate_views(&invalidation.modified_views)?;§Performance
- Cache hit latency: ~0.1ms (P99 < 1ms)
- Expected hit rate: 60-80% for typical workloads
- Memory usage: ~100 MB for default config (10,000 entries @ 10 KB avg)
- Speedup: 50-200x faster than database queries
§Security
Cache keys include variable values to prevent data leakage between users. Different users with different query variables get different cache entries.
Example:
User A: query { user(id: 1) } → Cache key: abc123...
User B: query { user(id: 2) } → Cache key: def456... (DIFFERENT)This prevents User B from accidentally seeing User A’s cached data.
§View-Based Invalidation
Invalidation operates at the view/table level:
- Mutation modifies
v_user→ Invalidate ALL caches reading fromv_user - Expected hit rate: 60-70% (some over-invalidation)
Example:
Cache Entry 1: query { user(id: 1) } → reads v_user
Cache Entry 2: query { user(id: 2) } → reads v_user
Cache Entry 3: query { post(id: 100) } → reads v_post
Mutation: updateUser(id: 1)
→ Invalidates Entry 1 AND Entry 2 (even though Entry 2 not affected)
→ Entry 3 remains cached§Future Enhancements
- Entity-level tracking: Track by
User:123, not justv_user - Cascade integration: Parse mutation metadata for precise invalidation
- Selective invalidation: Only invalidate affected entity IDs
- Expected hit rate: 90-95% with entity-level tracking
§Module Organization
adapter:CachedDatabaseAdapterwrapper for transparent cachingconfig: Cache configuration with memory-safe boundskey: Security-critical cache key generation (includes APQ integration)result: LRU cache storage with TTL and metricsdependency_tracker: Bidirectional view↔cache mappinginvalidation: Public invalidation API with structured contexts
Re-exports§
pub use cascade_invalidator::CascadeInvalidator;pub use cascade_invalidator::InvalidationStats;pub use cascade_metadata::CascadeMetadata;pub use cascade_response_parser::CascadeResponseParser;pub use entity_key::EntityKey;pub use fact_table_version::FactTableCacheConfig;pub use fact_table_version::FactTableVersionProvider;pub use fact_table_version::FactTableVersionStrategy;pub use fact_table_version::VERSION_TABLE_SCHEMA;pub use query_analyzer::QueryAnalyzer;pub use query_analyzer::QueryCardinality;pub use query_analyzer::QueryEntityProfile;pub use uuid_extractor::UUIDExtractor;
Modules§
- cascade_
invalidator - Cascading cache invalidation for transitive view dependencies.
- cascade_
metadata - Cascade metadata for mapping mutations to entity types.
- cascade_
response_ parser - Parser for GraphQL Cascade responses to extract entity invalidation data.
- entity_
key - Type-safe entity keys for entity-level cache invalidation.
- fact_
table_ version - Fact table versioning for aggregation query caching.
- query_
analyzer - Query analyzer for extracting entity constraints from compiled queries.
- uuid_
extractor - UUID extraction from mutation responses for entity-level cache invalidation.
Structs§
- Cache
Config - Cache configuration - disabled by default as of v2.0.0-rc.12.
- Cache
Metrics - Cache metrics for monitoring.
- Cached
Database Adapter - Cached database adapter wrapper.
- Cached
Result - Cached query result with metadata.
- Dependency
Tracker - Tracks which cache entries depend on which views/tables.
- Invalidation
Context - Context for cache invalidation operations.
- Query
Result Cache - Thread-safe LRU cache for query results.
Enums§
- Invalidation
Reason - Reason for cache invalidation.
Functions§
- extract_
accessed_ views - Extract accessed views from query definition.
- generate_
cache_ key - Generate cache key for query result.