miinventario-v2/docs/97-adr/ADR-0002-procesamiento-asincrono.md
rckrdmrd 1a53b5c4d3 [MIINVENTARIO] feat: Initial commit - Sistema de inventario con análisis de video IA
- Backend NestJS con módulos de autenticación, inventario, créditos
- Frontend React con dashboard y componentes UI
- Base de datos PostgreSQL con migraciones
- Tests E2E configurados
- Configuración de Docker y deployment

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

6.0 KiB

ADR-0002: Procesamiento Asincrono


id: ADR-0002 type: ADR status: Aceptado created_date: 2026-01-10 updated_date: 2026-01-10 decision_date: 2026-01-10 deciders: ["Tech Lead"]

Estado

Aceptado


Contexto

El procesamiento de video con IA para deteccion de productos:

  • Toma entre 30 segundos y 3 minutos
  • Depende de APIs externas (OpenAI, Claude)
  • Puede fallar por timeout o errores de red
  • Necesita reintentos y manejo de errores

Procesar sincronamente en la request HTTP:

  • Causaria timeouts
  • Bloquearia recursos del servidor
  • Proporcionaria mala experiencia de usuario

Decision

Implementar procesamiento asincrono con Bull/Redis para:

  1. Job Queue

    • Cada video subido crea un job en la cola
    • Workers procesan jobs en background
    • Jobs tienen estados: PENDING, PROCESSING, DONE, FAILED
  2. Notificaciones

    • Push notification cuando el resultado esta listo
    • Polling opcional para clientes sin push
  3. Reintentos

    • Jobs fallidos se reintentan automaticamente
    • Backoff exponencial para APIs externas
    • Max 3 reintentos por job
  4. Monitoreo

    • Dashboard de Bull para ver estado de jobs
    • Alertas por tasa de error alta
    • Metricas de tiempo de procesamiento

Opciones Consideradas

Opcion A: Procesamiento Sincrono

Aspecto Valor
Complejidad Baja
UX Mala (esperar 1-3 min)
Escalabilidad Baja
Manejo errores Dificil

Descartada: Inaceptable para UX y escalabilidad.

Opcion B: AWS SQS + Lambda

Aspecto Valor
Complejidad Alta
Escalabilidad Muy alta
Costo Variable
Vendor lock-in Alto

Descartada: Sobre-ingenieria para MVP, lock-in.

Opcion C: Bull + Redis (Seleccionada)

Aspecto Valor
Complejidad Media
Escalabilidad Alta
Costo Bajo (Redis ya existe)
Control Total

Seleccionada: Balance ideal para MVP con path de escalamiento.


Consecuencias

Positivas

  • UX fluida: Usuario no espera, recibe notificacion
  • Resiliente: Reintentos automaticos
  • Escalable: Agregar workers segun demanda
  • Observable: Dashboard, metricas, logs
  • Flexible: Prioridades, delays, dependencias

Negativas

  • Complejidad: Otro componente (Redis)
  • Eventual consistency: Resultado no inmediato
  • Debugging: Mas dificil seguir el flujo
  • Infraestructura: Redis debe ser HA en produccion

Arquitectura

┌─────────────────────────────────────────────────────────────────┐
│                    PROCESAMIENTO ASINCRONO                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐  │
│  │  API     │───▶│  Queue   │───▶│  Worker  │───▶│ Notifier │  │
│  │ (NestJS) │    │  (Bull)  │    │ (Bull)   │    │  (FCM)   │  │
│  └──────────┘    └──────────┘    └──────────┘    └──────────┘  │
│       │               │               │               │         │
│       ▼               ▼               ▼               ▼         │
│   Crear job       Redis          Procesar IA      Push notif   │
│   status=PENDING  almacena       actualizar DB    al usuario   │
│                   jobs           status=DONE                    │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Implementacion

Definicion del Job

// jobs/inventory-processing.processor.ts
@Processor('inventory')
export class InventoryProcessor {
  @Process('process-video')
  async processVideo(job: Job<ProcessVideoData>) {
    const { sessionId, videoId } = job.data;

    try {
      // 1. Actualizar status
      await this.updateStatus(sessionId, 'PROCESSING');

      // 2. Descargar video de S3
      const video = await this.s3Service.download(videoId);

      // 3. Extraer frames
      const frames = await this.extractFrames(video);

      // 4. Procesar con IA
      const detections = await this.iaService.detectProducts(frames);

      // 5. Consolidar resultados
      const items = await this.consolidate(detections);

      // 6. Guardar resultados
      await this.saveResults(sessionId, items);

      // 7. Calcular COGS
      await this.calculateCOGS(sessionId, frames.length);

      // 8. Actualizar status
      await this.updateStatus(sessionId, 'DONE');

      // 9. Notificar usuario
      await this.notify(sessionId);

    } catch (error) {
      await this.updateStatus(sessionId, 'FAILED');
      throw error; // Bull reintentara
    }
  }
}

Configuracion de Cola

// queue.module.ts
BullModule.registerQueue({
  name: 'inventory',
  defaultJobOptions: {
    attempts: 3,
    backoff: {
      type: 'exponential',
      delay: 5000, // 5s, 10s, 20s
    },
    removeOnComplete: 100,
    removeOnFail: 50,
  },
});

Metricas

Metrica Objetivo
Tiempo promedio < 2 min
Tasa de exito > 95%
Jobs en cola < 100
Latencia cola < 30s

Referencias


Ultima Actualizacion: 2026-01-10