---
description: Multi-layer caching strategy for optimized performance
globs: **/cache*.py, **/services/*.py, **/repositories/*.py, **/models/*.py
alwaysApply: false
---

# Caching Strategy Rule

## Core Principles
- **Multi-Layer Approach**: Different TTLs for different data types
- **Prioritize Expensive Operations**: Cache the most resource-intensive operations
- **Storage Hierarchy**: Use appropriate storage for different cache types
- **Performance Monitoring**: Track cache hit rates and effectiveness
- **Cost-Driven Decisions**: Cache expensive operations first
- **Invalidation Over Staleness**: Better to miss than serve stale data

## Implementation Patterns

### TTL Configuration```python
# ✅ DO: Use appropriate TTLs for different data types
class CacheTTL:
    """Time-to-live constants for different cache types."""
    # Long-lived data
    TRANSCRIPTS = 30 * 24 * 60 * 60  # 30 days in seconds
    
    # Medium-lived data
    AI_ENHANCEMENTS = 7 * 24 * 60 * 60  # 7 days in seconds
    AUDIO_PREPROCESSING = 7 * 24 * 60 * 60  # 7 days in seconds
    
    # Short-lived data
    SEARCH_RESULTS = 24 * 60 * 60  # 1 day in seconds
    USER_PREFERENCES = 12 * 60 * 60  # 12 hours in seconds
```

### Storage Selection
```python
# ✅ DO: Use appropriate storage for different cache types
class CacheStorage:
    def __init__(self):
        self.redis = RedisClient()  # Fast, in-memory cache
        self.db = DatabaseClient()  # Persistent storage
        self.fs = FileSystemCache()  # Large file storage
    
    async def get_transcript(self, key: str) -> Optional[Dict[str, Any]]:
        """Get transcript from cache, trying Redis first, then DB."""
        # Try Redis first (fast)
        result = await self.redis.get(f"transcript:{key}")
        if result:
            return result
            
        # Fall back to database (persistent)
        result = await self.db.get_transcript(key)
        if result:
            # Backfill Redis cache for next time
            await self.redis.set(f"transcript:{key}", result, ex=CacheTTL.TRANSCRIPTS)
            return result
            
        return None
    
    async def get_audio_preprocessing(self, key: str) -> Optional[Path]:
        """Get preprocessed audio from filesystem cache."""
        # Large files stored on filesystem
        return await self.fs.get(f"audio:{key}")
```

### Cache Monitoring
```python
# ✅ DO: Track cache performance metrics
class CacheMetrics:
    def __init__(self):
        self.hits = 0
        self.misses = 0
        self.total = 0
    
    def record_hit(self):
        self.hits += 1
        self.total += 1
    
    def record_miss(self):
        self.misses += 1
        self.total += 1
    
    @property
    def hit_rate(self) -> float:
        """Calculate cache hit rate."""
        if self.total == 0:
            return 0.0
        return self.hits / self.total
```

### Compression
```python
# ✅ DO: Compress cached data
def cache_with_compression(data: Dict[str, Any]) -> bytes:
    """Compress data before caching."""
    json_data = json.dumps(data).encode('utf-8')
    return lz4.frame.compress(json_data)

def decompress_cached_data(compressed_data: bytes) -> Dict[str, Any]:
    """Decompress data from cache."""
    json_data = lz4.frame.decompress(compressed_data)
    return json.loads(json_data.decode('utf-8'))
```

### Anti-Patterns
```python
# ❌ DON'T: Use same TTL for all data types
# Setting everything to the same TTL is inefficient
cache.set("transcript", data, ex=86400)  # Wrong! Should be 30 days
cache.set("ai_enhancement", data, ex=86400)  # Wrong! Should be 7 days

# ❌ DON'T: Store large files in Redis
# This will consume too much memory
redis.set(f"audio:{key}", large_binary_data)  # Wrong! Use filesystem
```


