Caching

Catzilla provides a sophisticated SmartCache system with C-accelerated performance, featuring multi-tier caching (Memory, Redis, Disk), intelligent compression, and zero-allocation operations. Build ultra-high-performance applications with automatic cache optimization.

Overview

Catzilla’s SmartCache system provides:

  • C-Accelerated Performance - Native C implementation for maximum speed

  • Multi-Tier Architecture - Memory (L1), Redis (L2), and Disk (L3) cache layers with automatic fallback

  • Zero-Allocation Design - Memory-efficient operations with jemalloc integration

  • Intelligent Compression - Automatic data compression for optimal memory usage

  • Function-Level Caching - @cached decorator for automatic function result caching

  • Manual Cache Operations - Direct cache.get/set/clear operations for fine-grained control

  • Real-Time Analytics - Detailed hit/miss ratios and performance monitoring

  • High Throughput - Demonstrated 1,000x+ performance improvements

Quick Start

Basic SmartCache Setup

Simple SmartCache configuration for fast data access:

from catzilla import Catzilla, Request, Response, JSONResponse, SmartCache, SmartCacheConfig, cached, Path
import time

app = Catzilla()

# Configure SmartCache with multi-tier setup
cache_config = SmartCacheConfig(
    memory_capacity=1000,     # Store up to 1000 items in memory
    memory_ttl=300,          # 5 minutes default TTL
    compression_enabled=True, # Enable compression for large values
    jemalloc_enabled=True,   # Optimize memory allocation
    disk_enabled=True,       # Enable disk persistence
    disk_path="/tmp/catzilla_cache"
)

cache = SmartCache(cache_config)

@app.get("/cached-data/{key}")
def get_cached_data(
    request: Request,
    key: str = Path(..., description="Cache key to retrieve")
) -> Response:
    """Get data with SmartCache caching"""

    # Try to get from cache first
    cached_value, found = cache.get(f"data:{key}")

    if found:
        return JSONResponse({
            "data": cached_value,
            "source": "cache",
            "cache_hit": True
        })

    # Generate expensive data (simulation)
    time.sleep(0.1)  # Simulate expensive operation
    expensive_data = {
        "key": key,
        "value": f"Generated data for {key}",
        "timestamp": time.time(),
        "computation_time": "100ms"
    }

    # Store in cache for 5 minutes
    cache.set(f"data:{key}", expensive_data, ttl=300)

    return JSONResponse({
        "data": expensive_data,
        "source": "computation",
        "cache_hit": False
    })

if __name__ == "__main__":
    print("🚀 Starting cache demo server...")
    print("Try: http://localhost:8000/cached-data/example")
    app.listen(port=8000)

Cache Decorator

Use the @cached decorator for automatic function result caching:

from catzilla import Catzilla, Request, Response, JSONResponse, cached, Path
import time

app = Catzilla()

@cached(ttl=600, key_prefix="user_data")
def get_user_data(user_id: int):
    """Expensive user data retrieval with caching"""
    # Simulate database query
    time.sleep(0.05)

    return {
        "user_id": user_id,
        "name": f"User {user_id}",
        "email": f"user{user_id}@example.com",
        "profile": {
            "created_at": "2023-01-01",
            "last_active": time.time()
        }
    }

@app.get("/users/{user_id}")
def get_user(
    request: Request,
    user_id: int = Path(..., ge=1, description="User ID")
) -> Response:
    """Get user with automatic caching"""
    user_data = get_user_data(user_id)

    return JSONResponse({
        "user": user_data,
        "cached": True  # Automatically cached by decorator
    })

if __name__ == "__main__":
    print("🚀 Starting cached function demo...")
    print("Try: http://localhost:8000/users/123")
    app.listen(port=8000)

Multi-Tier SmartCache

Configure Multi-Tier Cache

Set up Memory (L1), Redis (L2), and Disk (L3) caching tiers:

from catzilla import Catzilla, Request, Response, JSONResponse, SmartCache, SmartCacheConfig, Path
import time

app = Catzilla()

# Configure multi-tier cache with all layers
cache_config = SmartCacheConfig(
    # Memory Cache (L1) - Ultra-fast C-level cache
    memory_capacity=5000,
    memory_ttl=300,  # 5 minutes
    compression_enabled=True,
    jemalloc_enabled=True,

    # Redis Cache (L2) - Distributed cache
    redis_enabled=True,
    redis_url="redis://localhost:6379/0",
    redis_ttl=1800,  # 30 minutes

    # Disk Cache (L3) - Persistent cache
    disk_enabled=True,
    disk_path="/tmp/catzilla_cache",
    disk_ttl=3600,  # 1 hour

    # Performance settings
    enable_stats=True,
    auto_expire_interval=60,
)

# Create SmartCache with multi-tier configuration
cache = SmartCache(cache_config)

def generate_complex_computation(key: str):
    """Simulate expensive computation"""
    time.sleep(0.2)  # Simulate 200ms computation

    return {
        "key": key,
        "result": f"Complex result for {key}",
        "computed_at": time.time(),
        "computation_cost": "expensive"
    }

@app.get("/multilayer-data/{key}")
def get_multilayer_data(
    request: Request,
    key: str = Path(..., description="Data key for multi-tier caching")
) -> Response:
    """Data retrieval with multi-tier caching"""

    cache_key = f"complex_data:{key}"

    # Cache will automatically check tiers in order: Memory → Redis → Disk
    cached_data, found = cache.get(cache_key)

    if found:
        return JSONResponse({
            "data": cached_data,
            "cache_tier": "multi_tier",
            "cache_hit": True
        })

    # Generate complex data
    complex_data = generate_complex_computation(key)

    # Store in cache (will be stored across all available tiers)
    cache.set(cache_key, complex_data, ttl=3600)  # 1 hour

    return JSONResponse({
        "data": complex_data,
        "cache_tier": "none",
        "cache_hit": False
    })

if __name__ == "__main__":
    print("🚀 Starting multi-tier cache demo...")
    print("Try: http://localhost:8000/multilayer-data/example")
    app.listen(port=8000)

Cache Strategies

Implement different caching patterns with SmartCache:

from catzilla import Catzilla, Request, Response, JSONResponse, SmartCache, SmartCacheConfig, Path
import time

app = Catzilla()

# Initialize cache
cache_config = SmartCacheConfig(
    memory_capacity=1000,
    memory_ttl=300,
    disk_enabled=True,
    disk_path="/tmp/catzilla_strategies"
)
cache = SmartCache(cache_config)

def expensive_database_query(key: str):
    """Simulate expensive database operation"""
    time.sleep(0.1)
    return {"key": key, "data": f"Database result for {key}", "timestamp": time.time()}

def fetch_data_for_key(key: str):
    """Fetch data for cache warming"""
    return {"key": key, "warmed_data": f"Warmed data for {key}"}

# Cache-Aside Pattern (Most Common)
@app.get("/cache-aside/{key}")
def cache_aside_example(
    request: Request,
    key: str = Path(..., description="Cache key")
) -> Response:
    """Cache-aside pattern with SmartCache"""
    cache_key = f"aside:{key}"

    # 1. Try cache first
    cached, found = cache.get(cache_key)
    if found:
        return JSONResponse({"data": cached, "strategy": "cache_aside", "hit": True})

    # 2. Cache miss - fetch from source
    data = expensive_database_query(key)

    # 3. Store in cache for next time
    cache.set(cache_key, data, ttl=300)

    return JSONResponse({"data": data, "strategy": "cache_aside", "hit": False})

# Write-Through Pattern
@app.post("/write-through/{key}")
def write_through_example(
    request: Request,
    key: str = Path(..., description="Write-through cache key")
) -> Response:
    """Write-through pattern with SmartCache"""
    data = request.json()

    # 1. Write to database (simulated)
    print(f"Saving to database: {key} = {data}")

    # 2. Immediately write to cache
    cache.set(f"writethrough:{key}", data, ttl=600)

    return JSONResponse({"message": "Data saved", "strategy": "write_through"})

# Cache Warming
@app.post("/warm-cache")
def warm_cache(request: Request) -> Response:
    """Proactively populate cache with frequently accessed data"""
    popular_keys = ["user:1", "user:2", "user:3", "config:app"]

    warmed_count = 0
    for key in popular_keys:
        cached_value, found = cache.get(key)
        if not found:
            data = fetch_data_for_key(key)
            cache.set(key, data, ttl=1800)  # 30 minutes
            warmed_count += 1

    return JSONResponse({
        "message": f"Warmed {warmed_count} cache entries",
        "strategy": "cache_warming",
        "warmed_keys": popular_keys
    })

if __name__ == "__main__":
    print("🚀 Starting cache strategies demo...")
    print("Try:")
    print("  GET  http://localhost:8000/cache-aside/example")
    print("  POST http://localhost:8000/write-through/example")
    print("  POST http://localhost:8000/warm-cache")
    app.listen(port=8000)

Performance Optimization

Cache Analytics

Monitor cache performance and optimize hit ratios:

from catzilla import Catzilla, Request, Response, JSONResponse, SmartCache, SmartCacheConfig

app = Catzilla()

# Initialize cache with stats enabled
cache_config = SmartCacheConfig(
    memory_capacity=1000,
    memory_ttl=300,
    enable_stats=True,
    disk_enabled=True,
    disk_path="/tmp/catzilla_analytics"
)
cache = SmartCache(cache_config)

@app.get("/cache/analytics")
def get_cache_analytics(request: Request) -> Response:
    """Get detailed cache performance analytics"""

    # Get comprehensive cache statistics
    stats = cache.get_stats()
    health = cache.health_check()

    return JSONResponse({
        "performance": {
            "hit_ratio": f"{stats.hit_ratio:.2%}",
            "total_hits": stats.hits,
            "total_misses": stats.misses,
            "operations_per_second": getattr(stats, 'ops_per_second', 0)
        },
        "memory_usage": {
            "current_usage": f"{stats.memory_usage:.2f}MB",
            "capacity": f"{stats.capacity} items",
            "size": stats.size,
            "compression_ratio": getattr(stats, 'compression_ratio', 1.0)
        },
        "tier_performance": {
            "memory": stats.tier_stats.get("memory", {}),
            "redis": stats.tier_stats.get("redis", {}),
            "disk": stats.tier_stats.get("disk", {})
        },
        "health": health,
        "jemalloc_enabled": cache.config.jemalloc_enabled
    })

if __name__ == "__main__":
    print("🚀 Starting cache analytics demo...")
    print("Try: http://localhost:8000/cache/analytics")
    app.listen(port=8000)

Cache Warming

Proactively populate cache with frequently accessed data:

from catzilla import Catzilla, Request, Response, JSONResponse, SmartCache, SmartCacheConfig
import time

app = Catzilla()

# Initialize cache
cache_config = SmartCacheConfig(
    memory_capacity=1000,
    memory_ttl=300,
    disk_enabled=True,
    disk_path="/tmp/catzilla_warming"
)
cache = SmartCache(cache_config)

def get_app_settings():
    """Simulate getting app settings"""
    time.sleep(0.05)
    return {"theme": "dark", "language": "en", "features": ["cache", "analytics"]}

def get_feature_flags():
    """Simulate getting feature flags"""
    time.sleep(0.03)
    return {"new_ui": True, "beta_features": False, "maintenance_mode": False}

def get_top_users(limit: int):
    """Simulate getting top users"""
    time.sleep(0.1)
    return [{"id": i, "name": f"User {i}", "score": 100 - i} for i in range(1, limit + 1)]

def get_daily_statistics():
    """Simulate getting daily stats"""
    time.sleep(0.2)
    return {"visitors": 1500, "page_views": 5200, "signups": 23}

def get_user_from_database(user_id: int):
    """Simulate database user lookup"""
    time.sleep(0.05)
    return {"id": user_id, "name": f"User {user_id}", "email": f"user{user_id}@example.com"}

@app.post("/cache/warm-startup")
def warm_cache_on_startup(request: Request) -> Response:
    """Warm cache with popular data during application startup"""

    # Popular data that should always be cached
    popular_data = {
        "config:app_settings": get_app_settings(),
        "config:feature_flags": get_feature_flags(),
        "users:top_100": get_top_users(100),
        "analytics:daily_stats": get_daily_statistics()
    }

    warmed_count = 0
    for cache_key, data in popular_data.items():
        if data:  # Only cache if data is valid
            cache.set(cache_key, data, ttl=1800)  # 30 minutes
            warmed_count += 1

    return JSONResponse({
        "message": f"Cache warmed with {warmed_count} entries",
        "warmed_keys": list(popular_data.keys()),
        "ttl_minutes": 30
    })

@app.post("/cache/warm-users")
def warm_user_data(request: Request) -> Response:
    """Warm cache with specific user data"""
    data = request.json()
    user_ids = data.get("user_ids", [1, 5, 10, 25, 50])

    warmed_users = []
    failed_users = []

    for user_id in user_ids:
        try:
            user_data = get_user_from_database(user_id)
            cache.set(f"user:{user_id}", user_data, ttl=600)
            warmed_users.append(user_id)
        except Exception as e:
            failed_users.append({"user_id": user_id, "error": str(e)})

    return JSONResponse({
        "message": f"Warmed {len(warmed_users)} user records",
        "warmed_users": warmed_users,
        "failed_users": failed_users,
        "ttl_minutes": 10
    })

if __name__ == "__main__":
    print("🚀 Starting cache warming demo...")
    print("Try:")
    print("  POST http://localhost:8000/cache/warm-startup")
    print("  POST http://localhost:8000/cache/warm-users")
    app.listen(port=8000)

Cache Key Design

Design effective cache keys for optimal performance:

from catzilla import Catzilla, Request, Response, JSONResponse, SmartCache, SmartCacheConfig
from datetime import datetime

app = Catzilla()

# Initialize cache
cache_config = SmartCacheConfig(memory_capacity=1000, memory_ttl=300)
cache = SmartCache(cache_config)

# Cache key best practices
class CacheKeyBuilder:
    @staticmethod
    def user_key(user_id: int) -> str:
        return f"user:{user_id}"

    @staticmethod
    def user_posts_key(user_id: int, page: int = 1) -> str:
        return f"user:{user_id}:posts:page:{page}"

    @staticmethod
    def search_key(query: str, filters: dict = None) -> str:
        """Create cache key for search results"""
        base_key = f"search:{query.lower().replace(' ', '_')}"

        if filters:
            # Sort filters for consistent key generation
            filter_str = ":".join(f"{k}_{v}" for k, v in sorted(filters.items()))
            return f"{base_key}:{filter_str}"

        return base_key

    @staticmethod
    def config_key(environment: str, feature: str) -> str:
        return f"config:{environment}:{feature}"

    @staticmethod
    def daily_key(date: datetime) -> str:
        return f"daily_stats:{date.strftime('%Y-%m-%d')}"

@app.get("/cache-key-examples")
def create_cache_keys(request: Request) -> Response:
    """Examples of well-designed cache keys"""

    user_id = 123
    query = "python programming"
    environment = "production"
    feature = "new_ui"
    date = datetime.now()

    cache_keys = {
        "user": CacheKeyBuilder.user_key(user_id),
        "user_posts": CacheKeyBuilder.user_posts_key(user_id, page=2),
        "search": CacheKeyBuilder.search_key(query),
        "search_filtered": CacheKeyBuilder.search_key(query, {"category": "tutorials", "level": "beginner"}),
        "config": CacheKeyBuilder.config_key(environment, feature),
        "daily": CacheKeyBuilder.daily_key(date)
    }

    # Store some sample data using these keys
    for key_name, cache_key in cache_keys.items():
        sample_data = {"key_type": key_name, "cached_at": datetime.now().isoformat()}
        cache.set(cache_key, sample_data, ttl=300)

    return JSONResponse({
        "cache_key_examples": cache_keys,
        "best_practices": [
            "Use consistent naming patterns (entity:id)",
            "Include relevant parameters in key",
            "Sort filters for consistent keys",
            "Use descriptive prefixes",
            "Avoid special characters",
            "Keep keys readable and debuggable"
        ]
    })

if __name__ == "__main__":
    print("🚀 Starting cache key design demo...")
    print("Try: http://localhost:8000/cache-key-examples")
    app.listen(port=8000)

Performance Benchmarking

Benchmark cache performance to optimize your application:

from catzilla import Catzilla, Request, Response, JSONResponse, SmartCache, SmartCacheConfig
import time
import statistics

app = Catzilla()

# Initialize cache
cache_config = SmartCacheConfig(
    memory_capacity=10000,
    memory_ttl=300,
    compression_enabled=True,
    jemalloc_enabled=True
)
cache = SmartCache(cache_config)

def benchmark_cache_performance(cache, num_operations=1000):
    """Benchmark SmartCache performance"""

    # Test data
    test_data = {"benchmark": "data", "value": list(range(100))}

    # Benchmark SET operations
    set_times = []
    for i in range(num_operations):
        start = time.time()
        cache.set(f"benchmark_key_{i}", test_data, ttl=60)
        set_times.append((time.time() - start) * 1000)  # Convert to ms

    # Benchmark GET operations (should be cache hits)
    get_times = []
    for i in range(num_operations):
        start = time.time()
        result, found = cache.get(f"benchmark_key_{i}")
        get_times.append((time.time() - start) * 1000)  # Convert to ms

    # Calculate statistics
    return {
        "set_operations": {
            "count": num_operations,
            "avg_time_ms": statistics.mean(set_times),
            "min_time_ms": min(set_times),
            "max_time_ms": max(set_times),
            "ops_per_second": 1000 / statistics.mean(set_times)
        },
        "get_operations": {
            "count": num_operations,
            "avg_time_ms": statistics.mean(get_times),
            "min_time_ms": min(get_times),
            "max_time_ms": max(get_times),
            "ops_per_second": 1000 / statistics.mean(get_times)
        }
    }

@app.get("/cache/benchmark")
def run_cache_benchmark(request: Request) -> Response:
    """Run cache performance benchmark"""

    results = benchmark_cache_performance(cache, num_operations=1000)

    return JSONResponse({
        "benchmark_results": results,
        "cache_config": {
            "memory_capacity": cache.config.memory_capacity,
            "compression_enabled": cache.config.compression_enabled,
            "jemalloc_enabled": cache.config.jemalloc_enabled
        },
        "recommendation": "SmartCache with C-acceleration provides sub-millisecond performance"
    })

if __name__ == "__main__":
    print("🚀 Starting cache benchmark demo...")
    print("Try: http://localhost:8000/cache/benchmark")
    app.listen(port=8000)

Best Practices

Cache Configuration Tips

from catzilla import SmartCacheConfig

# Production-ready cache configuration
production_cache_config = SmartCacheConfig(
    # Memory tier - for hot data
    memory_capacity=10000,        # Adjust based on available RAM
    memory_ttl=300,              # 5 minutes for hot data

    # Redis tier - for distributed caching
    redis_enabled=True,
    redis_url="redis://localhost:6379/0",
    redis_ttl=1800,              # 30 minutes

    # Disk tier - for cold data persistence
    disk_enabled=True,
    disk_path="/var/cache/catzilla",
    disk_ttl=86400,              # 24 hours

    # Performance optimizations
    compression_enabled=True,     # Reduce memory usage
    jemalloc_enabled=True,       # Optimize memory allocation
    enable_stats=True,           # Monitor performance
    auto_expire_interval=60      # Clean expired items every minute
)

Common Patterns

from catzilla import Catzilla, Request, Response, JSONResponse, SmartCache, SmartCacheConfig, cached, Path

app = Catzilla()

# Initialize cache
cache_config = SmartCacheConfig(memory_capacity=1000, memory_ttl=300)
cache = SmartCache(cache_config)

# Pattern 1: Function-level caching for expensive computations
@cached(ttl=600, key_prefix="fibonacci")
def fibonacci(n: int) -> int:
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# Pattern 2: Manual caching for external API calls
def get_weather(city: str):
    cache_key = f"weather:{city}"

    # Try cache first
    weather, found = cache.get(cache_key)
    if found:
        return weather

    # Fetch from API (simulated)
    weather = {"city": city, "temp": 72, "condition": "sunny"}

    # Cache for 30 minutes
    cache.set(cache_key, weather, ttl=1800)

    return weather

# Pattern 3: Cache invalidation
def update_user(user_id: int, data: dict):
    # Update database (simulated)
    print(f"Updating user {user_id} with {data}")

    # Invalidate related cache entries
    cache.delete(f"user:{user_id}")
    cache.delete(f"user:{user_id}:posts")
    cache.delete(f"user:{user_id}:profile")

@app.get("/patterns/fibonacci/{n}")
def test_fibonacci(
    request: Request,
    n: int = Path(..., ge=1, le=50, description="Fibonacci sequence position")
) -> Response:
    """Test cached fibonacci calculation"""
    result = fibonacci(n)
    return JSONResponse({"n": n, "fibonacci": result})

@app.get("/patterns/weather/{city}")
def test_weather(
    request: Request,
    city: str = Path(..., min_length=2, max_length=50, description="City name for weather lookup")
) -> Response:
    """Test weather API with caching"""
    weather = get_weather(city)
    return JSONResponse(weather)

@app.post("/patterns/update-user/{user_id}")
def test_cache_invalidation(
    request: Request,
    user_id: int = Path(..., ge=1, description="User ID to update")
) -> Response:
    """Test cache invalidation pattern"""
    data = request.json()
    update_user(user_id, data)
    return JSONResponse({"message": f"User {user_id} updated and cache invalidated"})

if __name__ == "__main__":
    print("🚀 Starting cache patterns demo...")
    print("Try:")
    print("  GET  http://localhost:8000/patterns/fibonacci/30")
    print("  GET  http://localhost:8000/patterns/weather/London")
    print("  POST http://localhost:8000/patterns/update-user/123")
    app.listen(port=8000)

Troubleshooting

Common issues and solutions:

Low Hit Ratio: - Check TTL values (too short?) - Analyze key patterns for consistency - Monitor memory capacity limits

High Memory Usage: - Enable compression - Reduce memory_capacity - Implement better key expiration

Slow Performance: - Enable jemalloc - Check disk I/O for disk tier - Monitor Redis connection pool

from catzilla import Catzilla, Request, Response, JSONResponse, SmartCache, SmartCacheConfig

app = Catzilla()

# Initialize cache with debugging enabled
cache_config = SmartCacheConfig(
    memory_capacity=1000,
    memory_ttl=300,
    enable_stats=True,
    compression_enabled=True,
    jemalloc_enabled=True
)
cache = SmartCache(cache_config)

def debug_cache_health():
    """Debug cache performance"""
    stats = cache.get_stats()
    health = cache.health_check()

    print(f"Hit Ratio: {stats.hit_ratio:.2%}")
    print(f"Memory Usage: {stats.memory_usage:.2f}MB")
    print(f"Operations/sec: {getattr(stats, 'ops_per_second', 'N/A')}")
    print(f"Health Status: {health}")

    recommendations = []
    if stats.hit_ratio < 0.8:
        recommendations.append("Consider increasing TTL values")

    if stats.memory_usage > 100:
        recommendations.append("Consider enabling compression or reducing capacity")

    return {
        "stats": {
            "hit_ratio": stats.hit_ratio,
            "memory_usage": stats.memory_usage,
            "cache_size": stats.size,
            "health": health
        },
        "recommendations": recommendations
    }

@app.get("/cache/debug")
def cache_debug_endpoint(request: Request) -> Response:
    """Debug cache health endpoint"""
    debug_info = debug_cache_health()

    return JSONResponse({
        "cache_debug": debug_info,
        "troubleshooting_tips": [
            "Low hit ratio: Increase TTL or warm more data",
            "High memory usage: Enable compression",
            "Slow performance: Enable jemalloc optimization",
            "Connection issues: Check Redis connectivity"
        ]
    })

if __name__ == "__main__":
    print("🚀 Starting cache debugging demo...")
    print("Try: http://localhost:8000/cache/debug")
    app.listen(port=8000)