El problema
Tu API recibe una petición que dispara un trabajo lento: generar un informe, redimensionar un vídeo, llamar a un ERP que tarda ocho segundos.
Hacerlo en la misma invocación tiene tres problemas simultáneos:
- El cliente espera lo que tarde el trabajo, y corre contra el timeout de integración de API Gateway.
- El pico se propaga. Si llegan mil peticiones en diez segundos, se lanzan mil invocaciones a la vez — y si detrás hay una base de datos o un tercero con cuota, se lo comen entero.
- Un fallo pierde el trabajo. Si el ERP está caído, la petición se pierde y el cliente tiene que volver a intentarlo.
Llamar directamente a otra Lambda no arregla nada: sigue sin haber sitio donde el trabajo espere.
La solución
Una cola entre quien pide y quien hace.
El productor deposita el mensaje y responde 202 en milisegundos. Uno o varios
consumidores lo recogen al ritmo que puedan. Y aquí está la propiedad que define
el patrón y que se confunde constantemente:
Una cola reparte; no duplica. Cada mensaje lo recibe un solo consumidor.
Tres funciones consumiendo la misma cola no son tres suscriptores: son tres obreros turnándose. Por eso el patrón también se llama competing consumers, y por eso escalar es simplemente añadir consumidores, no colas.
Lo que ganas:
- La cola absorbe el pico. Mil mensajes en diez segundos entran sin despeinarse; el backlog de una cola no tiene techo.
- El ritmo lo marcas tú. Con concurrencia reservada en el consumidor decides a qué velocidad se drena, protegiendo lo que hay detrás.
- Nada se pierde. Si el consumidor está caído, el mensaje espera — hasta 14 días si lo configuras.
Cuándo usarlo
- El trabajo es lento y el cliente no necesita el resultado para seguir.
- El tráfico llega a golpes y lo de detrás no escala igual de rápido.
- Quieres reintentos gratis: una cola con DLQ te los da sin escribir un bucle.
- Hay un consumidor lógico del mensaje, aunque corran diez instancias suyas.
Cuándo NO usarlo
Cómo implementarlo
- Crea la DLQ antes que la cola y dale más retención que a la de origen.
maxReceiveCountentre 3 y 5.- Visibility timeout cómodamente por encima del timeout del consumidor —no ajustado— para que un proceso lento no genere duplicados.
- Long polling a 20 segundos: menos respuestas vacías, menos latencia y menos factura.
reportBatchItemFailuresactivado, siempre.- Consumidor idempotente: la entrega es al menos una vez.
- Concurrencia reservada en el consumidor si detrás hay algo que no escala.
- Alarmas sobre la profundidad de la cola, la edad del mensaje más antiguo y la DLQ.
El código
// ── Productor: deposita y responde. No espera a nadie. ─────────────
export const pedirInforme = async (evento: APIGatewayProxyEventV2) => {
const id = crypto.randomUUID();
await sqs.send(new SendMessageCommand({
QueueUrl: process.env.COLA,
MessageBody: JSON.stringify({ id, ...JSON.parse(evento.body!) }),
}));
// 202: aceptado, todavia no hecho. El cliente consulta luego por id.
return { statusCode: 202, body: JSON.stringify({ id }) };
};
// ── Consumidor: uno de varios, compitiendo por los mensajes ────────
export const generarInforme = async (e: SQSEvent): Promise<SQSBatchResponse> => {
const fallidos: { itemIdentifier: string }[] = [];
for (const r of e.Records) {
const trabajo = JSON.parse(r.body);
try {
// Al menos una vez: este mensaje puede llegar dos veces.
if (await yaGenerado(trabajo.id)) continue;
await generar(trabajo);
} catch {
// Solo este vuelve a la cola; los otros nueve del lote no.
fallidos.push({ itemIdentifier: r.messageId });
}
}
return { batchItemFailures: fallidos };
};
// ── Infraestructura ────────────────────────────────────────────────
const dlq = new sqs.Queue(this, 'InformesDlq', {
retentionPeriod: cdk.Duration.days(14), // mas que la de origen
});
const cola = new sqs.Queue(this, 'Informes', {
retentionPeriod: cdk.Duration.days(4),
visibilityTimeout: cdk.Duration.minutes(6), // holgado sobre el timeout
deadLetterQueue: { queue: dlq, maxReceiveCount: 3 },
});
consumidor.addEventSource(new SqsEventSource(cola, {
batchSize: 10,
reportBatchItemFailures: true,
}));
Te va a morder
Coste
| Concepto | Precio aproximado |
|---|---|
| Peticiones de SQS estándar | ~0,40 USD / millón |
| Primer millón al mes | gratis |
| Almacenar mensajes | sin coste |
| Invocaciones del consumidor | las normales de Lambda |
La cola es de las piezas más baratas de AWS: no cobra por existir ni por guardar, solo por peticiones. Dos cosas mueven la factura más que el precio unitario:
Los lotes. Diez mensajes en una llamada son una petición, no diez. Si controlas el productor, batear es la optimización más rentable del patrón.
El sondeo vacío. Una cola ociosa conectada a Lambda genera ReceiveMessage
las veinticuatro horas. Long polling a 20 segundos reduce ese goteo a la
vigésima parte de lo que costaría sondeando cada segundo.
Fuentes
Garantías de entrega, desorden y comportamiento de las colas estándar: Amazon SQS standard queues. Visibility timeout, retención, retraso máximo, tamaño de lote y long polling: Amazon SQS message quotas. Mensajes en vuelo y comportamiento con short polling: Amazon SQS standard queue quotas. Regla de facturación por porciones de 64 KB: SQS pricing.