michangarrito/docs/02-integraciones/INT-013-redis-cache.md
rckrdmrd 2c916e75e5 [SIMCO-V4] feat: Agregar documentación SaaS, ADRs e integraciones
Nuevas Épicas (MCH-029 a MCH-033):
- Infraestructura SaaS multi-tenant
- Auth Social (OAuth2)
- Auditoría Empresarial
- Feature Flags
- Onboarding Wizard

Nuevas Integraciones (INT-010 a INT-014):
- Email Providers (SendGrid, Mailgun, SES)
- Storage Cloud (S3, GCS, Azure)
- OAuth Social
- Redis Cache
- Webhooks Outbound

Nuevos ADRs (0004 a 0011):
- Notifications Realtime
- Feature Flags Strategy
- Storage Abstraction
- Webhook Retry Strategy
- Audit Log Retention
- Rate Limiting
- OAuth Social Implementation
- Email Multi-provider

Actualizados:
- MASTER_INVENTORY.yml
- CONTEXT-MAP.yml
- HERENCIA-SIMCO.md
- Mapas de documentación

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 01:43:15 -06:00

414 lines
8.4 KiB
Markdown

---
id: INT-013
type: Integration
title: "Redis Cache"
provider: "Redis"
status: Planificado
integration_type: "infrastructure"
created_at: 2026-01-10
updated_at: 2026-01-10
simco_version: "4.0.1"
tags:
- redis
- cache
- queue
- bullmq
- infrastructure
---
# INT-013: Redis Cache
## Metadata
| Campo | Valor |
|-------|-------|
| **Codigo** | INT-013 |
| **Proveedor** | Redis |
| **Tipo** | Infraestructura |
| **Estado** | Planificado |
| **Multi-tenant** | Si |
| **Epic Relacionada** | MCH-029 |
| **Owner** | Backend Team |
---
## 1. Descripcion
Redis como servicio de cache y queue para mejorar rendimiento y procesar tareas en background. Utilizado para cache de sesiones, configuracion de tenants, y colas de trabajo con BullMQ.
**Casos de uso principales:**
- Cache de sesiones JWT
- Cache de configuracion de tenant
- Cache de feature flags
- Queue para emails, webhooks, notificaciones
- Rate limiting counters
- Pub/Sub para eventos real-time
---
## 2. Credenciales Requeridas
### Variables de Entorno
| Variable | Descripcion | Tipo | Obligatorio |
|----------|-------------|------|-------------|
| `REDIS_HOST` | Host del servidor Redis | string | SI |
| `REDIS_PORT` | Puerto (default 6379) | number | SI |
| `REDIS_PASSWORD` | Password (si auth habilitado) | string | NO |
| `REDIS_DB` | Database number (0-15) | number | NO |
| `REDIS_TLS` | Usar TLS | boolean | NO |
| `REDIS_URL` | URL completa (alternativa) | string | NO |
### Ejemplo de .env
```env
# Redis Configuration
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
# O usar URL completa
# REDIS_URL=redis://:password@host:6379/0
# Redis Cloud (produccion)
# REDIS_URL=rediss://:password@redis-12345.cloud.redislabs.com:12345
```
---
## 3. Configuracion NestJS
### Redis Module
```typescript
// redis.module.ts
import { Module, Global } from '@nestjs/common';
import { Redis } from 'ioredis';
@Global()
@Module({
providers: [
{
provide: 'REDIS_CLIENT',
useFactory: () => {
return new Redis({
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT),
password: process.env.REDIS_PASSWORD,
db: parseInt(process.env.REDIS_DB || '0'),
});
},
},
],
exports: ['REDIS_CLIENT'],
})
export class RedisModule {}
```
### Cache Service
```typescript
// cache.service.ts
@Injectable()
export class CacheService {
constructor(@Inject('REDIS_CLIENT') private readonly redis: Redis) {}
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: any, ttlSeconds?: number): Promise<void> {
const data = JSON.stringify(value);
if (ttlSeconds) {
await this.redis.setex(key, ttlSeconds, data);
} else {
await this.redis.set(key, data);
}
}
async del(key: string): Promise<void> {
await this.redis.del(key);
}
async invalidatePattern(pattern: string): Promise<void> {
const keys = await this.redis.keys(pattern);
if (keys.length > 0) {
await this.redis.del(...keys);
}
}
}
```
---
## 4. BullMQ Queues
### Configuracion
```typescript
// queue.module.ts
import { BullModule } from '@nestjs/bullmq';
@Module({
imports: [
BullModule.forRoot({
connection: {
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT),
password: process.env.REDIS_PASSWORD,
},
}),
BullModule.registerQueue(
{ name: 'email' },
{ name: 'webhooks' },
{ name: 'notifications' },
{ name: 'cleanup' },
),
],
})
export class QueueModule {}
```
### Processor
```typescript
// email.processor.ts
@Processor('email')
export class EmailProcessor {
@Process('send')
async handleSend(job: Job<EmailJobData>) {
const { to, template, variables } = job.data;
await this.emailService.send(to, template, variables);
}
}
```
### Agregar a Queue
```typescript
// Agregar job
await this.emailQueue.add('send', {
to: 'user@example.com',
template: 'welcome',
variables: { name: 'John' },
}, {
attempts: 3,
backoff: {
type: 'exponential',
delay: 1000,
},
});
```
---
## 5. Estructura de Keys
### Convenciones de Naming
```
{prefix}:{scope}:{identifier}:{type}
Ejemplos:
- mch:session:{userId}
- mch:tenant:{tenantId}:config
- mch:tenant:{tenantId}:flags
- mch:rate:{tenantId}:{endpoint}:count
- mch:cache:products:{productId}
```
### Keys por Funcionalidad
| Prefijo | TTL | Descripcion |
|---------|-----|-------------|
| mch:session:* | 24h | Sesiones de usuario |
| mch:tenant:*:config | 1h | Config de tenant |
| mch:tenant:*:flags | 5m | Feature flags |
| mch:rate:* | 1m | Rate limiting |
| mch:cache:* | 15m | Cache general |
---
## 6. Rate Limiting
### Implementacion Token Bucket
```typescript
@Injectable()
export class RateLimitService {
async isAllowed(
tenantId: string,
endpoint: string,
limit: number,
windowSeconds: number,
): Promise<{ allowed: boolean; remaining: number; resetAt: Date }> {
const key = `mch:rate:${tenantId}:${endpoint}`;
const now = Date.now();
const windowStart = now - (windowSeconds * 1000);
// Remover entradas viejas
await this.redis.zremrangebyscore(key, 0, windowStart);
// Contar requests en ventana
const count = await this.redis.zcard(key);
if (count >= limit) {
const oldestEntry = await this.redis.zrange(key, 0, 0, 'WITHSCORES');
const resetAt = new Date(parseInt(oldestEntry[1]) + (windowSeconds * 1000));
return { allowed: false, remaining: 0, resetAt };
}
// Agregar request actual
await this.redis.zadd(key, now, `${now}`);
await this.redis.expire(key, windowSeconds);
return {
allowed: true,
remaining: limit - count - 1,
resetAt: new Date(now + (windowSeconds * 1000)),
};
}
}
```
---
## 7. Multi-tenant
### Aislamiento por Prefijo
```typescript
class TenantCacheService {
private getKey(tenantId: string, key: string): string {
return `mch:tenant:${tenantId}:${key}`;
}
async getTenantConfig(tenantId: string): Promise<TenantConfig> {
const key = this.getKey(tenantId, 'config');
const cached = await this.cache.get<TenantConfig>(key);
if (cached) return cached;
const config = await this.db.getTenantConfig(tenantId);
await this.cache.set(key, config, 3600); // 1 hora
return config;
}
async invalidateTenantCache(tenantId: string): Promise<void> {
await this.cache.invalidatePattern(`mch:tenant:${tenantId}:*`);
}
}
```
---
## 8. Health Check
```typescript
// redis.health.ts
@Injectable()
export class RedisHealthIndicator extends HealthIndicator {
constructor(@Inject('REDIS_CLIENT') private readonly redis: Redis) {
super();
}
async isHealthy(key: string): Promise<HealthIndicatorResult> {
try {
await this.redis.ping();
return this.getStatus(key, true);
} catch (error) {
return this.getStatus(key, false, { error: error.message });
}
}
}
```
### Endpoint
```typescript
@Get('health')
@HealthCheck()
async check() {
return this.health.check([
() => this.redis.isHealthy('redis'),
]);
}
```
---
## 9. Monitoreo
### Metricas
| Metrica | Descripcion | Alerta |
|---------|-------------|--------|
| redis_connected | Conexion activa | false |
| redis_memory_bytes | Memoria usada | > 80% max |
| redis_keys_total | Total de keys | - |
| redis_cache_hits | Cache hits | - |
| redis_cache_misses | Cache misses | ratio < 80% |
| redis_queue_length | Jobs en queue | > 1000 |
### Comandos de Monitoreo
```bash
# Info general
redis-cli INFO
# Memory
redis-cli INFO memory
# Keys por patron
redis-cli KEYS "mch:tenant:*" | wc -l
# Queue length
redis-cli LLEN bull:email:wait
```
---
## 10. Testing
### Docker Compose
```yaml
services:
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
```
### Mock para Tests
```typescript
// test/mocks/redis.mock.ts
export const mockRedis = {
get: jest.fn(),
set: jest.fn(),
setex: jest.fn(),
del: jest.fn(),
keys: jest.fn().mockResolvedValue([]),
ping: jest.fn().mockResolvedValue('PONG'),
};
```
---
## 11. Referencias
- [Redis Documentation](https://redis.io/docs/)
- [ioredis](https://github.com/redis/ioredis)
- [BullMQ](https://docs.bullmq.io/)
- [NestJS Caching](https://docs.nestjs.com/techniques/caching)
---
**Ultima actualizacion:** 2026-01-10
**Autor:** Backend Team