Cuándo Usar Colas de Mensajes en el Desarrollo de Software
- Mauricio ECR
- Arquitectura
- 18 Apr, 2025
Las colas de mensajes son herramientas clave para construir sistemas distribuidos, escalables y tolerantes a fallos. En este artículo te comparto una guía con situaciones comunes donde su uso es altamente recomendable. Esto puede servirte como referencia rápida para decidir si una cola puede ser útil en tu arquitectura.
1. Procesamiento Asíncrono de Tareas Pesadas
Descripción de la situación
Una aplicación web necesita procesar tareas pesadas (como enviar correos, generar PDFs o hacer procesamiento de imágenes) después de una solicitud del usuario.
Dificultades
- Alta latencia si se procesa todo en la misma petición HTTP.
- Posibles timeouts en el servidor.
- Experiencia de usuario lenta y frustrante.
Por qué se solucionaría con colas de mensajes
Separar el procesamiento de la respuesta al usuario permite responder rápido y delegar la tarea a un worker. La cola actúa como puente entre el sistema que genera la tarea y el que la ejecuta.
Características típicas de la cola
- Persistencia para no perder mensajes si algo falla.
- Retries automáticos para tareas fallidas.
- Delay opcional para tareas programadas.
- Visibilidad de mensajes en procesamiento.
2. Comunicación Entre Microservicios
Descripción de la situación
Una arquitectura basada en microservicios donde varios servicios necesitan intercambiar información o coordinar acciones.
Dificultades
- El acoplamiento entre servicios crece si se comunican de forma directa (HTTP sincrónico).
- Si un servicio está caído, puede romper toda la cadena.
- Difícil escalar servicios de forma independiente.
Por qué se solucionaría con colas de mensajes
Las colas desacoplan los servicios, permitiendo que uno publique mensajes sin depender del estado del consumidor. Esto permite una comunicación más resiliente y escalable.
Características típicas de la cola
- Entrega garantizada (at-least-once).
- Soporte para múltiples consumidores.
- Escalabilidad horizontal.
- Opcional: orden garantizado de mensajes.
3. Picos de Carga Temporales
Descripción de la situación
Una aplicación recibe picos de tráfico (por ejemplo, durante una campaña de marketing o un evento en vivo).
Dificultades
- El sistema puede saturarse si intenta procesar todo al instante.
- Riesgo de perder solicitudes o fallar por falta de recursos.
Por qué se solucionaría con colas de mensajes
Las colas permiten “almacenar” las tareas y procesarlas a medida que los workers tienen capacidad. Se convierte una carga variable en una carga continua.
Características típicas de la cola
- Alta capacidad de buffer.
- Procesamiento en paralelo (workers escalables).
- Métricas para monitorear backlog.
- Integración con sistemas de auto-escalado.
4. Integración con Sistemas Externos o APIs Lentas
Descripción de la situación
Tu sistema necesita integrarse con APIs de terceros (por ejemplo, pasarelas de pago, servicios de envío, etc.) que pueden ser lentas o poco confiables.
Dificultades
- Timeouts frecuentes.
- Limitaciones de tasa (rate limiting).
- Caídas del servicio externo afectan el sistema completo.
Por qué se solucionaría con colas de mensajes
Poner las llamadas a servicios externos en una cola permite controlar el ritmo, manejar reintentos, y evitar sobrecargar al proveedor.
Características típicas de la cola
- Retries con backoff.
- Soporte para Dead Letter Queues (DLQ).
- Capacidad de definir prioridades o tasa de procesamiento.
- Persistencia y durabilidad.
5. Auditoría y Logging Centralizado
Descripción de la situación
Se requiere capturar eventos del sistema (como accesos, cambios de estado, errores) en un sistema central para auditoría o análisis.
Dificultades
- El logeo en tiempo real puede bloquear procesos principales.
- Si el sistema de auditoría cae, se pierden los eventos.
Por qué se solucionaría con colas de mensajes
Las colas permiten enviar eventos de forma asincrónica y confiable a un sistema de almacenamiento o procesamiento.
Características típicas de la cola
- Alta velocidad de escritura.
- Orden garantizado (opcional, según la necesidad).
- Múltiples consumidores (ej. para alertas, dashboards).
- Baja latencia.
6. Workflows Distribuidos (Orquestación de Procesos)
Descripción de la situación
Un proceso complejo requiere que varias acciones ocurran en orden y/o condicionalmente, como un onboarding de usuario o procesamiento de pagos.
Dificultades
- Difícil mantener el estado y coordinación entre servicios.
- Problemas de sincronización y gestión de errores.
Por qué se solucionaría con colas de mensajes
Las colas permiten implementar orquestadores que gestionan los pasos del workflow como eventos, con flexibilidad para manejar errores y lógica condicional.
Características típicas de la cola
- Soporte para enrutamiento de mensajes.
- Integración con motores de orquestación.
- Baja latencia y confiabilidad.
- Opcional: soporte para eventos tipo pub/sub.
🧪 Ejemplo: Generación Asíncrona de PDF usando una Cola
Este ejemplo representa un caso real y común: un usuario solicita la generación de un PDF. En lugar de procesarlo en la misma solicitud (lo cual puede tardar), se encola la tarea y un worker la procesa de forma asíncrona.
🧍♂️ Usuario solicita un PDF desde el Frontend
El usuario hace una solicitud para generar un PDF. Este proceso es controlado desde el frontend, donde el usuario envía su solicitud.
// Envío de solicitud desde el cliente (frontend)
// Este llamado puede estar en un botón: "Generar PDF"
fetch('/generate-pdf', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: 123 })
})
.then(res => res.json())
.then(data => {
// El usuario recibe un mensaje indicando que la tarea ha sido encolada.
console.log(data.status); // "Tarea encolada correctamente"
console.log("ID de la tarea:", data.jobId); // El ID para consultar el estado
});
🧠 Backend (API) recibe la solicitud y encola la tarea
El backend recibe la solicitud del frontend y encola la tarea en una cola de trabajo para ser procesada en segundo plano. La API responde inmediatamente al usuario con la confirmación de que la tarea se ha encolado.
# Supongamos un backend en Flask (Python)
@app.route('/generate-pdf', methods=['POST'])
def generate_pdf():
data = request.get_json()
user_id = data['userId']
# Genera un identificador único para la tarea
job_id = str(uuid.uuid4())
# Se encola una tarea para procesar luego
enqueue_task('generate_pdf', {'user_id': user_id, 'job_id': job_id})
# Responde al usuario con la confirmación de la tarea encolada
return jsonify({
'status': 'Tarea encolada correctamente',
'jobId': job_id, # ID de la tarea para que el usuario pueda consultar el estado
'message': 'Te notificaremos cuando tu PDF esté listo para descargar.'
})
¿Qué hace enqueue_task?
La función enqueue_task empuja la tarea a una cola (como Redis, RabbitMQ, AWS SQS, etc.). El jobId se guarda para poder referenciar la tarea.
def enqueue_task(task_name, data):
task = {
'name': task_name,
'data': data
}
redis.rpush('pdf_tasks', json.dumps(task)) # Ejemplo con Redis
⚙️ Worker que consume tareas y las ejecuta
El worker es un proceso que corre en segundo plano y escucha la cola para procesar las tareas en el momento adecuado. Una vez que el PDF esté generado, puede guardarlo o enviarlo al usuario.
# Un worker que corre en segundo plano y escucha la cola
def worker():
while True:
raw_task = redis.blpop('pdf_tasks', timeout=0) # Espera indefinidamente
if raw_task:
task = json.loads(raw_task[1])
handle_task(task)
def handle_task(task):
if task['name'] == 'generate_pdf':
user_id = task['data']['user_id']
job_id = task['data']['job_id']
generate_pdf_for_user(user_id, job_id)
def generate_pdf_for_user(user_id, job_id):
# Aquí iría la lógica real de generación del PDF
print(f"Generando PDF para el usuario {user_id}")
# Simulación: se genera el PDF y se guarda con el ID de tarea
filename = f"{job_id}.pdf"
with open(filename, "w") as f:
f.write(f"PDF generado para usuario {user_id}")
# Aquí podrías guardar el resultado en una BD o subirlo a un almacenamiento
# Además, actualizamos el estado de la tarea en la base de datos o en el sistema de colas
redis.set(f"job:{job_id}:status", "completado")
📥 Consulta del estado de la tarea (opcional)
El usuario puede consultar el estado de la tarea en cualquier momento utilizando el jobId que se le proporcionó cuando la tarea fue encolada. Esto permite saber si la tarea está aún en proceso o si ya ha sido completada.
@app.route('/job-status/<job_id>', methods=['GET'])
def job_status(job_id):
status = redis.get(f"job:{job_id}:status") # Recupera el estado desde Redis
return jsonify({'jobId': job_id, 'status': status or 'pendiente'})
En este ejemplo, si el jobId existe en el sistema, el usuario recibirá el estado de la tarea. De lo contrario, puede devolver el estado como “pendiente” si la tarea aún no se ha completado.
📧 Notificación cuando la tarea se complete (opcional)
Además de permitir que el usuario consulte el estado, puedes configurar una notificación para cuando el trabajo esté listo. Esto podría ser una notificación en la web, un correo electrónico, o incluso un SMS.
Ejemplo de función de notificación:
def notify_user(user_id, job_id):
# Esta función podría enviar un email, SMS o una notificación web
# Aquí simplemente imprimimos un mensaje de ejemplo
print(f"Notificando al usuario {user_id} que su PDF con jobId {job_id} está listo para descargar.")
Puedes llamar a esta función después de que la tarea haya sido procesada y el PDF esté disponible.
💡 Ventajas de este enfoque
- ✅ Respuesta inmediata: El usuario no espera bloqueado mientras se genera el PDF.
- 🕐 Asincronía: El trabajo pesado se maneja en segundo plano, sin afectar la experiencia del usuario.
- 🔔 Notificación opcional: El usuario puede ser notificado cuando la tarea esté lista.
- 🧱 Escalabilidad: Puedes agregar más workers si la carga aumenta, o priorizar tareas según la necesidad.
- 🔗 Desacoplamiento: El frontend no está directamente vinculado al procesamiento pesado.
Conclusión
Las colas no son solo una herramienta de “alto nivel empresarial”, sino una solución práctica para muchos retos comunes en el desarrollo moderno. Identificar los síntomas típicos —como latencia, acoplamiento, o pérdida de datos— puede ayudarte a decidir cuándo usarlas.