Práctica: Crear un Sistema de Envío de Emails Asíncrono

Video
30 min~4 min lectura

Reproductor de video

Concepto clave

En sistemas backend modernos, el procesamiento asíncrono es esencial para manejar tareas que no requieren respuesta inmediata al usuario, como el envío de emails. Imagina un restaurante: cuando pides comida, el mesero toma tu orden (síncrono) pero la cocina la prepara en segundo plano (asíncrono). Redis, con su estructura de datos listas y el patrón productor-consumidor, permite crear colas de trabajos eficientes donde:

  • Productores: Agregan tareas a la cola (ej. "enviar email de bienvenida")
  • Consumidores: Procesan tareas en segundo plano
  • Redis: Actúa como broker confiable que almacena las tareas pendientes

Este enfoque desacopla el tiempo de respuesta del usuario del tiempo de procesamiento real, mejorando la escalabilidad y experiencia del usuario.

Cómo funciona en la práctica

Implementaremos una cola usando Redis Lists con comandos LPUSH (agregar trabajo) y BRPOP (consumir bloqueante). Paso a paso:

  1. Configuración inicial:
    import redis
    r = redis.Redis(host='localhost', port=6379, db=0)
    QUEUE_NAME = 'email_queue'
  2. Productor (cuando un usuario se registra):
    email_data = {
        'to': '[email protected]',
        'subject': 'Bienvenida',
        'body': 'Gracias por registrarte'
    }
    r.lpush(QUEUE_NAME, json.dumps(email_data))
  3. Consumidor (worker en segundo plano):
    while True:
        # BRPOP espera hasta 30 segundos por un trabajo
        result = r.brpop(QUEUE_NAME, timeout=30)
        if result:
            queue_name, email_json = result
            email_data = json.loads(email_json)
            # Lógica real de envío de email
            send_email(email_data)

La magia está en BRPOP: bloquea hasta que haya trabajo disponible, evitando polling constante.

Caso de estudio

E-commerce "TechShop" con 10,000 pedidos diarios. Problema: Los emails de confirmación demoraban 5 segundos por pedido, causando timeout en picos. Solución con Redis:

MétricaAntesDespués
Tiempo respuesta API5-8 segundos200 ms
Emails fallados15% en picos0.1%
Costo servidores8 instancias3 instancias

Implementación: Un solo productor (API de pedidos) y 5 workers consumiendo de la cola. Cada worker procesa hasta 100 emails/minuto.

Clave: Los workers son stateless y escalables horizontalmente según carga.

Errores comunes

  • No manejar fallos en workers: Si un worker crashea procesando un trabajo, este se pierde. Solución: Usar RPOPLPUSH a una lista de "procesando" y verificar timeout.
  • Colas infinitas sin monitoreo: Si los workers son más lentos que los productores, la cola crece hasta llenar memoria Redis. Implementar alertas cuando longitud > 10,000.
  • Serialización incorrecta: Enviar objetos complejos sin serializar causa errores. Usar JSON o MessagePack consistentemente.
  • Single point of failure: Redis single node sin replicación. En producción, usar Redis Cluster o al menos replica con persistencia.
  • Falta de idempotencia: Si un email se envía dos veces por retry, debe ser inocuo. Los emails deben tener ID único y verificación de duplicados.

Checklist de dominio

  1. Puedo explicar la diferencia entre procesamiento síncrono y asíncrono con una analogía del mundo real
  2. He implementado una cola productor-consumidor usando Redis Lists
  3. Sé cómo manejar fallos de workers sin perder trabajos
  4. Puedo monitorear el tamaño y throughput de una cola Redis
  5. Entiendo cuándo usar BRPOP vs RPOP en diferentes escenarios
  6. He configurado múltiples workers que consumen de la misma cola
  7. Puedo justificar por qué una cola Redis es mejor que una base de datos para este caso

Implementa un sistema de notificaciones con reintentos inteligentes

Extiende el sistema básico de emails para manejar fallos y reintentos. Sigue estos pasos:

  1. Crea un script Python que simule un productor que agrega 100 notificaciones a una cola 'notifications'
  2. Implementa un worker que consuma con BRPOP, pero que falle aleatoriamente el 20% de las veces (simula error de red)
  3. Cuando falle, mueve el trabajo a una cola 'retry' con timestamp usando RPOPLPUSH
  4. Crea un segundo worker que procese la cola 'retry' después de 60 segundos (usar timestamp)
  5. Agrega métricas: trabajos exitosos, fallados, en reintento
  6. Ejecuta 1 productor y 3 workers simultáneamente

Entrega: Código completo y captura de pantalla mostrando las métricas después de procesar todas las notificaciones.

Pistas
  • Usa time.time() para agregar timestamp a los trabajos fallados
  • Para el worker de reintentos, verifica si han pasado más de 60 segundos antes de procesar
  • Considera usar Redis Hashes para almacenar métricas de forma estructurada

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.