architect/_archive/2025-11-26-cleanup/cifra/archive/2025-11-10-restructure-v2/CACHING_STRATEGY.md

CIFRA Caching Strategy

Версия: 1.0.0
Дата: 2025-11-10


3 Cache Levels

L1: Request Cache (In-Memory)    0.1ms
     miss
L2: Redis (Application)          1-5ms
     miss
L3: Database (PostgreSQL)        10-50ms

Cache Key Naming

cifra:{tenant_id}:{entity}:{id}:{version}

Examples:
- cifra:acme:Contact:uuid-123:v1
- cifra:acme:Deal:list:stage=won:page=1:v2
- cifra:global:settings:theme:v1

TTL Strategy

CACHE_TTL = {
    'User': 300,        # 5 min (часто меняется)
    'Contact': 600,     # 10 min
    'Deal': 600,        # 10 min  
    'Product': 3600,    # 1 hour (редко меняется)
    'Category': 86400,  # 24 hours (почти не меняется)
    'Settings': 86400,  # 24 hours
}

Caching Decorator

from cifra.cache import cached

@cached(ttl=600, key_prefix="contact")
async def get_contact(db, contact_id):
    """Get contact with caching"""
    return await db.query(Contact).get(contact_id)

# Usage
contact = await get_contact(db, uuid)  # DB query
contact = await get_contact(db, uuid)  # From cache!

Cache Invalidation

# Automatic on update
class Contact(Base):
    @event.listens_for(Contact, 'after_update')
    def invalidate_cache(mapper, connection, target):
        cache.delete(f"contact:*{target.id}*")

# Manual
await cache.delete("contact:uuid-123")
await cache.delete_pattern("contact:*")

Cache Strategies

1. Cache-Aside (Lazy Loading)

async def get_user(user_id):
    # Try cache
    user = await cache.get(f"user:{user_id}")
    if user:
        return user

    # Query DB
    user = await db.query(User).get(user_id)

    # Save to cache
    await cache.set(f"user:{user_id}", user, ttl=600)
    return user

2. Write-Through

async def update_user(user_id, data):
    # Update DB
    user = await db.query(User).get(user_id)
    user.update(data)
    await db.commit()

    # Update cache
    await cache.set(f"user:{user_id}", user, ttl=600)
    return user

3. Write-Behind

async def update_user(user_id, data):
    # Update cache immediately
    user = await cache.get(f"user:{user_id}")
    user.update(data)
    await cache.set(f"user:{user_id}", user)

    # Schedule DB update (async)
    await task_queue.enqueue('update_user_in_db', user_id, data)
    return user

Cache Warming

# At application startup
async def warm_cache():
    # Load all categories
    categories = await db.query(Category).all()
    for cat in categories:
        await cache.set(f"category:{cat.id}", cat, ttl=86400)

    # Load settings
    settings = await db.query(Settings).all()
    await cache.set("settings:all", settings, ttl=86400)

Cache Stampede Prevention

async def get_with_lock(key):
    # Try cache
    value = await cache.get(key)
    if value:
        return value

    # Try get lock
    lock_key = f"{key}:lock"
    acquired = await cache.set(lock_key, 1, nx=True, ex=10)

    if acquired:
        # First one - query DB
        value = await db.query(...)
        await cache.set(key, value, ex=600)
        await cache.delete(lock_key)
        return value
    else:
        # Someone else querying - wait
        await asyncio.sleep(0.1)
        return await get_with_lock(key)

Monitoring

# Cache hit rate
cache_hits = redis.get("stats:cache:hits")
cache_misses = redis.get("stats:cache:misses")
hit_rate = cache_hits / (cache_hits + cache_misses)

# Target: > 80% hit rate

Документация: https://docs.cifra.io/caching