Skip to content
StackPractices
intermediate Por Mathias Paulenko

Colas de Mensajes — Inmersión en RabbitMQ, Kafka y SQS

Referencia Detallada sobre colas de mensajes: cuándo usar RabbitMQ, Kafka o SQS. Cubre patrones, throughput, ordenamiento y consideraciones operativas.

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Visión General

Las colas de mensajes son el sistema circulatorio de la arquitectura distribuida. Te permiten desacoplar componentes, manejar picos de tráfico, procesar tareas en segundo plano y construir sistemas resilientes. Sin embargo, elegir la tecnología equivocada o usar patrones incorrectos puede llevar a pérdida de mensajes, procesamiento duplicado o cuello de botella en la capacidad de procesamiento. Esta guía compara tres sistemas principales — RabbitMQ, Kafka y AWS SQS — y cubre patrones, throughput, garantías de ordenamiento y pautas operativas.

Cuándo Usar

Usa esta guía cuando:

  • Necesitas elegir una solución de colas de mensajes para tu arquitectura
  • Estás experimentando pérdida de mensajes, retraso o problemas de capacidad de procesamiento
  • Necesitas decidir entre entrega al menos una vez, exactamente una vez o en orden

Solución

Comparación de Tecnologías

CaracterísticaRabbitMQKafkaAWS SQS
ModeloCola tradicional (push)Log distribuido (pull)Cola administrada (pull)
OrdenamientoPor cola, no globalPor partición, secuencialFIFO opcional; best-effort por defecto
Throughput~50K msgs/sec por nodo~1M+ msgs/sec por cluster~300 msgs/sec (FIFO); ~3000 (estándar)
PersistenciaMemoria/disco configurablePersistencia compulsiva en discoGestionada por AWS
Modelo de ConsumoMúltiples consumidores compitenMúltiples consumidores leen particionesMúltiples consumidores compiten
Integración CloudAuto-alojado o administradoAuto-alojado o administrado (Confluent, MSK)Servicio AWS nativo
Cuándo UsarEnrutamiento complejo, RPC asíncrono, traducción de protocoloStreaming de eventos, analytics, logsColas simples, serverless, sin infraestructura

Patrones Principales

# Patrón Work Queue con RabbitMQ
import pika

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.queue_declare(queue='task_queue', durable=True)

# Publicador
message = {"task": "process_payment", "order_id": "12345"}
channel.basic_publish(
    exchange='',
    routing_key='task_queue',
    body=json.dumps(message),
    properties=pika.BasicProperties(delivery_mode=2)  # persistente
)

# Consumidor
def callback(ch, method, properties, body):
    task = json.loads(body)
    process_payment(task['order_id'])
    ch.basic_ack(delivery_tag=method.delivery_tag)

channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue='task_queue', on_message_callback=callback)
channel.start_consuming()
# Consumidor Kafka con manejo de offset
from kafka import KafkaConsumer

consumer = KafkaConsumer(
    'orders',
    bootstrap_servers=['kafka1:9092', 'kafka2:9092'],
    group_id='payment-processors',
    enable_auto_commit=False,
    auto_offset_reset='earliest'
)

try:
    for message in consumer:
        process_order(message.value)
        consumer.commit()  # Commit manual después de procesamiento exitoso
except Exception as e:
    # No hacer commit — el mensaje se reprocesará
    log_error(e)
# Receptor SQS con manejo de visibilidad
import boto3

sqs = boto3.client('sqs')
queue_url = 'https://sqs.us-east-1.amazonaws.com/123456789/orders-queue'

response = sqs.receive_message(
    QueueUrl=queue_url,
    MaxNumberOfMessages=10,
    WaitTimeSeconds=20,
    VisibilityTimeout=300  # 5 minutos para procesar
)

for message in response.get('Messages', []):
    try:
        process_message(message['Body'])
        sqs.delete_message(QueueUrl=queue_url, ReceiptHandle=message['ReceiptHandle'])
    except Exception:
        # El mensaje vuelve a estar visible después del timeout
        pass

Garantías de Entrega

GarantíaRabbitMQKafkaSQS
Al menos una vezSí (ack)Sí (commit de offset)Sí (por defecto)
Exactamente una vezSí (consumidor idempotente + deduplicación publisher)Sí (idempotente con exactly-once semantics)Sí (FIFO + idempotencia)
En ordenSí (una cola)Sí (por partición, una partición = un consumidor)Sí (cola FIFO)
Best-effortSí (sin ack)NoSí (cola estándar)

Explicación

La elección entre colas tradicionales y logs distribuidos depende de si necesitas comunicación o registro de eventos. Las colas tradicionales (RabbitMQ, SQS) son para pasar trabajo entre componentes. Cada mensaje se consume típicamente una vez. Los logs distribuidos (Kafka) son para registrar eventos que múltiples sistemas pueden leer en diferentes velocidades.

El ordenamiento es la restricción más malentendida. Kafka mantiene orden solo dentro de una partición, no globalmente. Si necesitas ordenamiento global, debes usar una sola partición, lo que limita el throughput. SQS FIFO mantiene orden pero limita a 300 mensajes por segundo. RabbitMQ mantiene orden por cola, pero si tienes múltiples consumidores, el ordenamiento de procesamiento no está garantizado.

La idempotencia es la salvaguardia definitiva contra duplicados. Incluso con exactamente-una-vez configurado, fallos de red pueden causar re-entrega. Diseña tus consumidores para ser idempotentes: usa el message_id como clave de deduplicación, o almacena IDs procesados en Redis con TTL.

Variantes

SituaciónTecnología RecomendadaPatrón
Procesamiento de órdenesKafkaTopic de órdenes con partición por customer_id
Procesamiento de imágenesRabbitMQWork queue con prefetch=1 para balanceo de carga
Notificaciones pushSQS + LambdaTrigger Lambda por mensaje para procesamiento serverless
Reprocesamiento de eventosKafkaRebobinar offset para reprocesar un rango de eventos
RPC asíncronoRabbitMQCola de respuesta correlacionada por correlation_id
Agregación de logsKafkaTopic de logs con retención de 30 días

Lo que funciona

  1. Siempre diseña consumidores idempotentes; la re-entrega es inevitable en sistemas distribuidos
  2. Usa prefetch bajo (1-5) cuando el tiempo de procesamiento varía mucho entre mensajes
  3. Implementa dead letter queues para mensajes que fallan repetidamente; evita bucles infinitos
  4. Monitorea lag de consumidores como tu métrica principal de salud de cola
  5. Particiona por business key (customer_id, order_id) para mantener datos relacionados juntos

Errores Comunes

  1. Configurar prefetch alto con procesamiento lento; los mensajes se asignan a consumidores ocupados
  2. No establecer timeouts de visibilidad en SQS; los mensajes se re-procesan antes de que termines
  3. Particionar Kafka por timestamp en lugar de business key; los datos relacionados terminan en particiones diferentes
  4. Tratar los logs de Kafka como colas; no borrar eventos procesados de Kafka
  5. Ignorar la capacidad de procesamiento de consumidores; una cola no absorbe picos si tus consumidores son lentos

Preguntas Frecuentes

¿Cómo elijo entre RabbitMQ y Kafka?

RabbitMQ es mejor para comunicación — pasar trabajo, RPC asíncrono, traducción de protocolo, enrutamiento complejo. Kafka es mejor para registro de eventos — múltiples consumidores leyendo el mismo stream, analytics, replay, integración de datos. Si tu use case es “toma este trabajo y hazlo”, elige RabbitMQ. Si es “algo pasó, múltiples sistemas necesitan saberlo”, elige Kafka.

¿Cómo manejo ordenamiento de mensajes en una cola distribuida?

El ordenamiento global y la escalabilidad son mutuamente excluyentes. Si necesitas ambos, particiona por clave de negocio para ordenamiento local, o usa un paso de procesamiento secuencial único para lógica que requiere orden global. Para RabbitMQ, usa una sola cola y un solo consumidor (o múltiples consumidores con prefetch=1). Para Kafka, usa una sola partición (limita throughput) o acepta ordenamiento por partición.

¿Qué hacer cuando un consumidor es más lento que el productor?

Escala los consumidores horizontalmente — más instancias leyendo de la misma cola/topic. Para Kafka, asegúrate de tener suficientes particiones (una partición = un consumidor). Para RabbitMQ, agrega más consumidores (competirán por mensajes). Para SQS, usa más Lambdas o instancias de procesamiento. Si ya escaste la escala horizontal, optimiza el procesamiento: ¿estás haciendo I/O bloqueante? ¿Puedes procesar en batch? ¿Hay una base de datos lenta?

Temas Avanzados

Escenario: Sistema de Procesamiento de Ordenes con Colas

Sistema: E-commerce, 10K ordenes/hora
Stack: RabbitMQ + consumidores Node.js + DLQ

Arquitectura:
  API -> exchange (order.created) -> queue (order.process)
    -> consumer (validate payment)
    -> queue (order.fulfill)
    -> consumer (ship + notify)
    -> queue (order.complete)

  Dead Letter Queue: ordenes fallidas despues de 3 reintentos
    -> alerta humana + dashboard

Configuracion RabbitMQ:
  | Parametro | Valor | Razon |
  |-----------|-------|-------|
  | prefetch | 10 | Balance throughput vs fairness |
  | retry | 3 | Exponential backoff: 1s, 5s, 30s |
  | DLQ | Si | Ordenes irreprocesables |
  | TTL | 24h | Ordenes expiran si no se procesan |
  | durable | true | Sobrevive restart de broker |
  | persistent | true | Mensajes en disco |

Consumidor (Node.js):
  const amqp = require("amqplib");

  async function startConsumer() {
    const conn = await amqp.connect("amqp://rabbitmq:5672");
    const ch = await conn.createChannel();
    await ch.prefetch(10);

    ch.consume("order.process", async (msg) => {
      const order = JSON.parse(msg.content.toString());
      try {
        await processPayment(order);
        await ch.publish("", "order.fulfill",
          Buffer.from(JSON.stringify(order)));
        ch.ack(msg);
      } catch (error) {
        const attempts = msg.properties.headers["x-retry"] || 0;
        if (attempts < 3) {
          // Reintentar con backoff exponencial
          const delay = Math.pow(5, attempts) * 1000;
          ch.publish("", "order.process.retry",
            msg.content, {
              headers: { "x-retry": attempts + 1 },
              expiration: delay
            });
        } else {
          // Enviar a DLQ
          ch.publish("", "order.dlq", msg.content);
        }
        ch.ack(msg); // Siempre ack para no requeue automatico
      }
    });
  }

Idempotencia:
  - Cada orden tiene un orderId unico
  - Antes de procesar: verificar si orderId ya fue procesado
  - Tabla processed_orders: orderId + status + timestamp
  - Si ya existe, ack sin reprocesar

Metricas:
  | Metrica | Objetivo |
  |---------|----------|
  | Mensajes en cola | < 100 |
  | Tiempo de procesamiento | < 5s por orden |
  | Tasa de error | < 1% |
  | Mensajes en DLQ | < 10/dia |
  | Throughput | > 3K/hora |

Lecciones:
  - Prefetch bajo (10) evita que un consumidor monopolice la cola
  - DLQ es obligatorio: nunca pierdas mensajes irreprocesables
  - Idempotencia: procesar dos veces debe dar el mismo resultado
  - Backoff exponencial: no satures el sistema con reintentos
  - ack manual: controlas cuando un mensaje esta realmente procesado

Como monitoreo la salud de RabbitMQ?

Habilita el plugin de management: rabbitmq-plugins enable rabbitmq_management. Expone la API en :15672. Metricas clave: messages_ready (cola acumulada), messages_unacknowledged (en proceso), consumer_utilization (eficiencia). Configura alertas: messages_ready > 1000, consumer_utilization < 50%, connections > max. Usa Prometheus + rabbitmq_exporter para integrar con Grafana.

End of document. Review and update quarterly.

Troubleshooting

  • Messages are lost on restart: persist messages before acknowledging. Use write-ahead logging or replicated storage.
  • Consumer lags behind producer: scale consumers, increase prefetch, and partition the topic. Monitor lag per partition.
  • Duplicate messages: design consumers to be idempotent. Use exactly-once semantics only if the overhead is justified.
  • Ordering is wrong after scaling: preserve partition keys and avoid rebalancing during bursts. Consider a single partition when order is mandatory.
  • Queue depth grows but consumers are idle: check network partitions, consumer health, and permission issues. Restart gracefully.

Errores Comunes en Producción

  • Tratar la guía como un checklist para completar una vez en lugar de una práctica por evolucionar.
  • Adoptar cada recomendación de golpe en lugar de comenzar con un cambio medido.
  • Saltar la evaluación de madurez e imponer prácticas avanzadas a un equipo no preparado.
  • No actualizar runbooks y expectativas de guardia al introducir nuevas prácticas.
  • Ignorar datos reales de incidentes al priorizar qué partes de la guía aplicar primero.
  • No asignar un responsable que revise decisiones trimestralmente.
  • Copiar ejemplos sin adaptarlos a las herramientas y restricciones reales del equipo.
  • Olvidar medir resultados antes de agregar la siguiente mejora.