# Caching Strategy Redis caching patterns, TTL strategies, and cache invalidation across the ops-db-api. ```{contents} Table of Contents :depth: 2 :local: true ``` ## Cache Types The API uses Redis for multiple caching purposes: 1. **Transaction Buffer** - Pending operations queue 2. **Write-Through Cache** - Generated IDs immediately available 3. **Buffered Data Cache** - Records pending replication 4. **Read Buffer** - Mutable updates to buffered records 5. **Query Result Cache** - Expensive query results 6. **Endpoint Cache** - Full endpoint responses ## Transaction-Related Caches These caches are managed by the transaction buffering system: **Transaction Buffer**: - Key: `site:{site_name}:transaction_buffer` - Type: List (LPUSH/RPOP) - TTL: None (durable queue) - Cleanup: After successful execution **Write-Through Cache**: - Key: `site:{site_name}:cache:ids:{model}:{id}` - Type: String - TTL: Extended until replicated - Cleanup: When LSN confirms replication **Buffered Data Cache**: - Key: `site:{site_name}:buffered:{model}:{id}` - Type: String - TTL: Extended until replicated - Cleanup: When LSN confirms replication ## Endpoint Caching The `@cached_endpoint` decorator: ```{literalinclude} ../../ccat_ops_db_api/transaction_buffering/decorators.py :language: python :lines: 117-187 ``` **Usage**: ```python from ccat_ops_db_api.transaction_buffering import cached_endpoint @router.get("/expensive-calculation/{id}") @cached_endpoint(ttl=600) # Cache for 10 minutes async def expensive_calculation(id: int): # Complex computation result = await perform_expensive_calculation(id) return result ``` **Cache key format**: ```text site:{site_name}:cache:{function_name}:{args_hash} ``` ## TTL Strategies Different caches have different TTL strategies: ```{eval-rst} .. list-table:: :header-rows: 1 :widths: 30 20 50 * - Cache Type - TTL - Strategy * - Transaction buffer - None - Durable until processed * - Write-through cache - Dynamic - Extended until LSN confirms * - Buffered data cache - Dynamic - Extended until LSN confirms * - Read buffer - Dynamic - Extended until LSN confirms * - Query results - Fixed - 5-60 minutes typical * - Endpoint responses - Fixed - 1-10 minutes typical ``` ## Cache Invalidation **LSN-Based Invalidation**: When LSN tracker confirms replication: ```python async def cleanup_caches(transaction_id): transaction = await get_transaction(transaction_id) for step in transaction.steps: # Remove write-through cache await redis.delete(f"site:{site}:cache:ids:{step.model}:{step.id}") # Remove buffered data cache await redis.delete(f"site:{site}:buffered:{step.model}:{step.id}") # Remove read buffer await redis.delete(f"site:{site}:read_buffer:{step.model}:{step.id}") ``` **Time-Based Expiration**: Most caches use TTL for automatic cleanup: ```python await redis.setex(cache_key, ttl=300, value=data) # 5 minutes ``` **Manual Invalidation**: For critical updates: ```python # Invalidate specific cache await redis.delete(cache_key) # Invalidate pattern keys = await redis.keys(f"site:{site}:cache:visibility:*") if keys: await redis.delete(*keys) ``` ## Cache Monitoring **Cache Hit Rate**: ```python cache_hits = await redis.get("metrics:cache:hits") cache_misses = await redis.get("metrics:cache:misses") hit_rate = cache_hits / (cache_hits + cache_misses) ``` **Cache Size**: ```bash redis-cli > INFO memory > DBSIZE ``` **Monitor Cache Operations**: ```bash redis-cli MONITOR ``` ## Best Practices **DO**: - Use appropriate TTLs (shorter for frequently changing data) - Namespace keys by site - Monitor cache hit rates - Invalidate on updates - Use write-through for generated IDs **DON'T**: - Cache user-specific data without user ID in key - Use very long TTLs for volatile data - Forget to handle cache misses - Cache large objects (> 1MB) without compression ## Example: Visibility Caching Visibility calculations are expensive, so we cache aggressively: ```python @router.get("/visibility/{source_id}") @cached_endpoint(ttl=3600) # 1 hour async def get_visibility( source_id: int, date_start: datetime, date_end: datetime ): # Expensive calculation visibility = await calculate_visibility( source_id, date_start, date_end ) return visibility ``` **Key includes**: `source_id`, `date_start`, `date_end` **TTL**: 1 hour (visibility doesn't change rapidly) **Invalidation**: Admin can trigger precalculation ## Summary Caching in ops-db-api: - **Multiple types**: Transaction, write-through, buffered, query, endpoint - **Dynamic TTLs**: LSN-based for transaction caches - **Namespaced keys**: Site-aware cache isolation - **Smart invalidation**: LSN confirms when safe to cleanup - **Monitoring**: Track hit rates and cache size ## Next Steps - {doc}`transaction-buffering/transaction-manager` - Write-through cache implementation - {doc}`transaction-buffering/lsn-tracking` - LSN-based invalidation - {doc}`../development/redis-inspection` - Redis debugging