mirror of
https://github.com/duthaho/claudekit.git
synced 2026-06-10 12:14:57 +03:00
7.0 KiB
7.0 KiB
Databases — Redis Patterns
Redis
When to Use
- Caching database queries or API responses
- Session storage for web applications
- Rate limiting (distributed across instances)
- Job/task queues (BullMQ, Celery)
- Pub/sub messaging between services
- Distributed locks
When NOT to Use
- Primary data storage — Redis is a cache/broker, not a database of record
- Complex queries — use PostgreSQL for relational queries
- Large blobs — use S3/R2 for file storage
- In-memory caching only — use
functools.lru_cacheorMapfor single-process caches
Python (redis-py / FastAPI)
Connection
# src/core/redis.py
import redis.asyncio as redis
pool = redis.ConnectionPool.from_url(
"redis://localhost:6379/0",
max_connections=20,
decode_responses=True,
)
async def get_redis() -> redis.Redis:
return redis.Redis(connection_pool=pool)
Cache-aside pattern
import json
from datetime import timedelta
async def get_user_cached(user_id: str, db: AsyncSession) -> User:
r = await get_redis()
cache_key = f"user:{user_id}"
# Check cache
cached = await r.get(cache_key)
if cached:
return User(**json.loads(cached))
# Cache miss — fetch from DB
user = await db.get(User, user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
# Store in cache with TTL
await r.setex(cache_key, timedelta(minutes=15), json.dumps(user.to_dict()))
return user
Cache invalidation
async def update_user(user_id: str, data: UpdateUserRequest, db: AsyncSession) -> User:
user = await db.get(User, user_id)
for key, value in data.dict(exclude_unset=True).items():
setattr(user, key, value)
await db.commit()
# Invalidate cache
r = await get_redis()
await r.delete(f"user:{user_id}")
return user
Rate limiting
from fastapi import Request, HTTPException
async def rate_limit(request: Request, limit: int = 100, window: int = 900):
r = await get_redis()
key = f"rate:{request.client.host}"
current = await r.incr(key)
if current == 1:
await r.expire(key, window)
if current > limit:
raise HTTPException(status_code=429, detail="Rate limit exceeded")
Session storage
import secrets
async def create_session(user_id: str) -> str:
r = await get_redis()
session_id = secrets.token_urlsafe(32)
await r.setex(f"session:{session_id}", timedelta(hours=24), user_id)
return session_id
async def get_session(session_id: str) -> str | None:
r = await get_redis()
return await r.get(f"session:{session_id}")
async def delete_session(session_id: str):
r = await get_redis()
await r.delete(f"session:{session_id}")
TypeScript (ioredis / NestJS / Express)
Connection
// src/core/redis.ts
import Redis from 'ioredis';
export const redis = new Redis(process.env.REDIS_URL ?? 'redis://localhost:6379', {
maxRetriesPerRequest: 3,
lazyConnect: true,
});
NestJS module
// src/cache/cache.module.ts
import { Global, Module } from '@nestjs/common';
import { CacheService } from './cache.service';
@Global()
@Module({
providers: [CacheService],
exports: [CacheService],
})
export class CacheModule {}
// src/cache/cache.service.ts
import { Injectable, OnModuleDestroy } from '@nestjs/common';
import Redis from 'ioredis';
@Injectable()
export class CacheService implements OnModuleDestroy {
private readonly redis = new Redis(process.env.REDIS_URL!);
async get<T>(key: string): Promise<T | null> {
const data = await this.redis.get(key);
return data ? JSON.parse(data) : null;
}
async set(key: string, value: unknown, ttlSeconds: number): Promise<void> {
await this.redis.setex(key, ttlSeconds, JSON.stringify(value));
}
async del(key: string): Promise<void> {
await this.redis.del(key);
}
async onModuleDestroy() {
await this.redis.quit();
}
}
Cache-aside in service
@Injectable()
export class UsersService {
constructor(
private readonly prisma: PrismaService,
private readonly cache: CacheService,
) {}
async findOne(id: string): Promise<User> {
// Check cache
const cached = await this.cache.get<User>(`user:${id}`);
if (cached) return cached;
// Cache miss
const user = await this.prisma.user.findUnique({ where: { id } });
if (!user) throw new NotFoundException(`User ${id} not found`);
// Store with 15min TTL
await this.cache.set(`user:${id}`, user, 900);
return user;
}
async update(id: string, dto: UpdateUserDto): Promise<User> {
const user = await this.prisma.user.update({ where: { id }, data: dto });
await this.cache.del(`user:${id}`); // Invalidate
return user;
}
}
Pub/Sub
Python
# Publisher
async def publish_event(channel: str, event: dict):
r = await get_redis()
await r.publish(channel, json.dumps(event))
# Subscriber
async def subscribe_events(channel: str):
r = await get_redis()
pubsub = r.pubsub()
await pubsub.subscribe(channel)
async for message in pubsub.listen():
if message['type'] == 'message':
yield json.loads(message['data'])
TypeScript
// Publisher
const pub = new Redis(process.env.REDIS_URL!);
await pub.publish('orders', JSON.stringify({ type: 'created', orderId: '123' }));
// Subscriber (separate connection required)
const sub = new Redis(process.env.REDIS_URL!);
sub.subscribe('orders');
sub.on('message', (channel, message) => {
const event = JSON.parse(message);
console.log(`[${channel}]`, event);
});
Key Naming Conventions
entity:id → user:abc123
entity:id:field → user:abc123:orders
rate:ip → rate:192.168.1.1
session:token → session:abc123def
lock:resource → lock:order-processing
queue:name → queue:email-notifications
Common Pitfalls
- Not setting TTLs. Every cache key should have an expiration. Unbounded caches exhaust memory.
- Cache stampede. When a popular key expires, many requests hit the DB simultaneously. Use distributed locks or stale-while-revalidate.
- Using the same connection for pub/sub. Subscribers can't run other commands. Use a dedicated connection.
- Storing large objects. Redis is fast for small values. Keep values under 1MB; for larger data, store a pointer to S3.
- Not handling connection failures. Redis connections drop. Use retry logic and connection pools.
- Forgetting to invalidate. When data changes, delete the cache key. Stale cache is worse than no cache.
Related Skills
caching— HTTP caching, CDN, memoization (framework-agnostic patterns)background-jobs— BullMQ/Celery use Redis as brokerfastapi— Redis integration with FastAPI dependency injectionnestjs— Redis caching module in NestJSdocker— Running Redis in Docker Compose for development