--- 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(key: string): Promise { const data = await this.redis.get(key); return data ? JSON.parse(data) : null; } async set(key: string, value: any, ttlSeconds?: number): Promise { 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 { await this.redis.del(key); } async invalidatePattern(pattern: string): Promise { 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) { 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 { const key = this.getKey(tenantId, 'config'); const cached = await this.cache.get(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 { 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 { 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