Caching Strategy ================ Redis caching patterns, TTL strategies, and cache invalidation across the ops-db-api. .. contents:: Table of Contents :local: :depth: 2 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**: .. code-block:: 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**: .. code-block:: text site:{site_name}:cache:{function_name}:{args_hash} TTL Strategies -------------- Different caches have different TTL strategies: .. 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: .. code-block:: 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: .. code-block:: python await redis.setex(cache_key, ttl=300, value=data) # 5 minutes **Manual Invalidation**: For critical updates: .. code-block:: 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**: .. code-block:: 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**: .. code-block:: bash redis-cli > INFO memory > DBSIZE **Monitor Cache Operations**: .. code-block:: 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: .. code-block:: 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