Registro / artículo
SQS y SNS: mensajería serverless sin operar nada (y sus trampas)
Tienes tres servicios, un equipo de cuatro personas y nadie tiene ganas de mantener un clúster de RabbitMQ. Necesitas desacoplar el envío de correos, procesar miniaturas y repartir eventos entre servicios. La respuesta aburrida y correcta suele ser la misma: SQS.
SQS es una cola gestionada. No hay nodos, no hay upgrades, no hay disco que monitorizar. Pagas por petición y escalas sin aprovisionar nada. Para la mayoría de equipos pequeños, eso vale más que cualquier característica avanzada.
Pero “serverless” no significa “sin reglas”. SQS tiene un modelo mental propio, y la ventana de visibilidad es la pieza que más incidentes causa cuando nadie la entiende.
La idea central: la ventana de visibilidad
En una cola tradicional, el broker te entrega el mensaje y espera tu ACK. En SQS no hay conexión abierta ni ACK: hay polling y una ventana de visibilidad.
- Tu worker llama a
ReceiveMessagey recibe un mensaje. - El mensaje queda invisible para el resto durante N segundos: la ventana de visibilidad.
- Si lo procesas bien, llamas a
DeleteMessagey desaparece. - Si no lo borras a tiempo, la ventana expira y el mensaje vuelve a estar visible para otro consumidor.
Ese diseño es brillante para un sistema distribuido sin estado: si el worker muere, no hay que detectar la conexión caída. El mensaje simplemente reaparece.
Y ese mismo diseño explica el 90% de los “SQS me duplica los mensajes”:
- La ventana por defecto son 30 segundos. Si tu tarea tarda 45, el mensaje reaparece mientras todavía lo estás procesando.
- El máximo son 12 horas. Una tarea de 13 horas no cabe.
- El mensaje se borra explícitamente.
ReceiveMessageno borra nada, aunque lo parezca.
Es como una taquilla de paquetería: te guardan el paquete durante un tiempo. Si no lo recoges, la taquilla se libera y el paquete vuelve a estar disponible para otro. La ventana de visibilidad es exactamente ese margen: el mensaje está reservado para ti, pero no para siempre.
Regla práctica: la ventana debe cubrir tu p99, no tu media. Si el p99 es 40 segundos, pon 120. Y para tareas largas, renueva la visibilidad a mitad de proceso con ChangeMessageVisibility en lugar de poner una ventana de horas que retrase la recuperación de un worker caído.
import {
SQSClient,
SendMessageCommand,
ReceiveMessageCommand,
DeleteMessageCommand,
} from "@aws-sdk/client-sqs";
const sqs = new SQSClient({ region: "eu-west-1" });
await sqs.send(
new SendMessageCommand({
QueueUrl: process.env.QUEUE_URL,
MessageBody: JSON.stringify(event),
}),
);
const { Messages = [] } = await sqs.send(
new ReceiveMessageCommand({
QueueUrl: process.env.QUEUE_URL,
MaxNumberOfMessages: 10,
WaitTimeSeconds: 20, // long polling
VisibilityTimeout: 120,
}),
);
for (const message of Messages) {
await handle(message);
await sqs.send(
new DeleteMessageCommand({
QueueUrl: process.env.QUEUE_URL,
ReceiptHandle: message.ReceiptHandle,
}),
);
}
El WaitTimeSeconds: 20 es long polling y no es un detalle menor. Con polling corto, un worker sin mensajes hace peticiones en bucle y pagas por cada una. Con long polling, la petición espera hasta 20 segundos a que llegue algo. Menos peticiones, menos coste, menos latencia.
Estándar vs FIFO: elige con los ojos abiertos
SQS tiene dos tipos de cola y no son intercambiables:
| Estándar | FIFO | |
|---|---|---|
| Orden | best-effort, no garantizado | estricto por grupo |
| Duplicados | posibles (at-least-once) | deduplicación de 5 minutos |
| Throughput | prácticamente ilimitado | limitado por cola |
| Requisito | ninguno | MessageGroupId |
| Uso típico | tareas independientes | eventos por entidad |
La cola estándar es la opción por defecto y casi siempre la correcta. Escala sin pensar y el coste es ridículo. A cambio, aceptas duplicados ocasionales y que dos mensajes puedan llegar en orden distinto.
La cola FIFO garantiza orden dentro de un MessageGroupId, no en toda la cola. Ese detalle es importante: si usas el mismo grupo para todo, acabas con un único carril y pierdes el paralelismo. Si usas order_id como grupo, cada pedido va ordenado y pedidos distintos avanzan en paralelo.
También tienes una ventana de deduplicación de cinco minutos: si reenvías el mismo MessageDeduplicationId, SQS lo ignora. Es útil, pero no sustituye a la idempotencia de tu consumidor. Cinco minutos no cubren un reintento de mañana.
Y el throughput de FIFO no es infinito: hablamos de cientos de mensajes por segundo sin batching, miles con lotes de hasta 10 mensajes. Si necesitas más, comprueba el modo de alto rendimiento y, sobre todo, haz números antes de diseñar.
DLQ: la cola de los mensajes que nadie quiere
Un mensaje que siempre falla no puede reintentarse para siempre. SQS lo resuelve con dead-letter queues y maxReceiveCount: tras N recepciones fallidas, el mensaje se mueve a la DLQ.
cola principal ──(maxReceiveCount = 5)──▶ DLQ
│
alarma + revisión
Tres reglas que se aprenden a golpes:
- La DLQ tiene que ser del mismo tipo que la cola origen. Una DLQ estándar no recibe mensajes de una cola FIFO.
- Una DLQ sin alarma es un basurero. Si nadie mira su profundidad, estás perdiendo mensajes en silencio. Alarma sobre
ApproximateNumberOfMessagesVisible. - El redrive existe, pero no es magia. Puedes devolver mensajes de la DLQ a la cola original, y volverán a fallar si la causa no está corregida.
SNS: el fan-out que SQS no tiene
Una cola sola no resuelve “esto le interesa a tres servicios”. Ahí entra SNS: un topic publica y cada suscriptor recibe su copia. El patrón clásico es fan-out SNS → varias colas SQS:
┌──▶ cola facturación ──▶ worker
order.created ──▶ SNS ─┼──▶ cola envíos ──▶ worker
└──▶ cola analítica ──▶ Lambda
Cada servicio tiene su cola, su ritmo y sus reintentos. Si analítica está caída, facturación sigue funcionando. Es desacoplamiento de verdad, sin que los servicios se conozcan.
Dos mejoras que marcan la diferencia:
- Filter policies. Sin filtros, todos los suscriptores reciben todos los eventos y pagan por procesarlos. Con una política sobre los atributos del mensaje, cada cola recibe solo lo suyo. Es la forma más barata de reducir ruido y coste.
- SNS FIFO. Si necesitas orden de extremo a extremo, un topic FIFO puede entregar a colas FIFO. Ojo: no puede entregar a colas estándar.
SNS no retiene mensajes. Si un suscriptor falla y no tiene su propia cola con reintentos, el mensaje se pierde. Por eso el patrón serio siempre es SNS con colas detrás, no SNS directo a un endpoint crítico.
Y si tu enrutamiento necesita algo más que atributos —reglas por contenido, archivado y replay, esquemas—, el siguiente escalón dentro de AWS es EventBridge. Comparte la filosofía serverless y añade lo que SNS no tiene. Para el 80% de los casos, SNS sobra.
El coste real: unos dólares (y una trampa)
Los precios públicos de referencia, que conviene comprobar en la calculadora antes de presupuestar:
| Servicio | Precio aproximado | Free tier |
|---|---|---|
| SQS estándar | 0,40 $ por millón de peticiones | 1 millón/mes |
| SQS FIFO | 0,50 $ por millón de peticiones | 1 millón/mes |
| SNS | 0,50 $ por millón de publicaciones | 1 millón/mes |
La trampa: SQS cobra por petición, no por mensaje. Un mensaje típico genera tres peticiones —enviar, recibir y borrar—. Diez millones de mensajes al mes son unos 30 millones de peticiones: del orden de 12 dólares, no de 4.
Aun así, la comparación con un broker dedicado es brutal: un clúster mínimo gestionado de Kafka arranca en cientos de dólares al mes, y un RabbitMQ gestionado, en decenas. Para volúmenes pequeños y medianos, SQS gana por goleada.
Lo que no aparece en la factura es lo que te ahorras en personas. No hay parches de seguridad del broker, ni disco, ni rebalanceos, ni “¿por qué el nodo dos está en rojo?”.
Límites que conviene tener en la cabeza
- 256 KB por mensaje. Para archivos, guarda el objeto en S3 y manda la referencia.
- 14 días de retención máxima. No es un log: no hay replay histórico. Para eso necesitas Kafka.
- 15 minutos de retardo máximo por mensaje, y solo en colas estándar. Para programar a horas concretas, usa EventBridge Scheduler.
- Sin prioridades. No hay forma de decir “esto va antes”. Si lo necesitas, es señal de que quizá quieras otro broker.
- Sin enrutamiento. SQS no enruta: una cola es una cola. El enrutamiento lo pones tú con SNS, EventBridge o código.
Cuándo NO usar SQS
- Necesitas replay o auditoría. Cuando borras, borras. Si mañana descubres un bug de cálculo, no hay forma de reprocesar los últimos días.
- Varios equipos necesitan el mismo flujo con historia independiente. El fan-out de SNS copia el mensaje, pero cada copia se borra al consumirse.
- Necesitas orden global a alto volumen. FIFO ordena por grupo y tiene techo de throughput.
- Necesitas enrutamiento complejo con miles de reglas. EventBridge o RabbitMQ encajan mejor.
- Necesitas latencias de microsegundos. SQS está pensado para desacoplar, no para ser un bus de baja latencia.
Checklist antes de poner SQS en producción
- ¿La ventana de visibilidad cubre el p99 y no la media?
- ¿Renuevas la visibilidad en tareas largas en vez de ponerla a 12 horas?
- ¿Borras el mensaje después de procesarlo?
- ¿El consumidor es idempotente, aunque uses FIFO?
- ¿Hay DLQ con
maxReceiveCounty alarma de profundidad? - ¿Usas long polling en lugar de polling corto?
- ¿Las colas FIFO tienen un
MessageGroupIdcon la granularidad correcta? - ¿El fan-out usa filter policies para no pagar por eventos que no interesan?
- ¿Hay una alarma de edad del mensaje más antiguo, no solo de cantidad?
SQS no es la herramienta más emocionante del mundo. Es la que te deja dormir. Y para un equipo pequeño, eso es una característica de primer nivel.
Si quieres comparar con las alternativas y ver cuándo conviene subir de nivel, el siguiente paso es la guía de elección.