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>
414 lines
8.4 KiB
Markdown
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
|