O Amazon SQS no Node.js permite desacoplar serviços, absorver picos de tráfego e executar tarefas assíncronas sem administrar um broker. Produtores enviam mensagens para uma fila; consumidores recebem lotes, processam e excluem somente depois de concluir o trabalho.
Esse modelo parece simples, mas exige decisões sobre Standard ou FIFO, visibility timeout, long polling, retries, dead-letter queue, idempotência, tamanho das mensagens e observabilidade. Uma configuração incorreta pode gerar duplicatas, reprocessamento infinito ou mensagens invisíveis por tempo excessivo.
Neste guia, você aprenderá a usar o AWS SDK para JavaScript v3, criar produtor e consumidor, trabalhar com long polling, alterar visibility timeout, processar lotes, usar FIFO, DLQ, criptografia, IAM e shutdown gracioso.
O que é Amazon SQS?
A documentação oficial do Amazon SQS descreve o serviço como uma fila hospedada, segura, durável e altamente disponível. Mensagens são armazenadas de forma redundante e consumidores podem escalar horizontalmente.
O SDK atual para Node.js está documentado no cliente SQS do AWS SDK v3.
Standard versus FIFO
Filas Standard oferecem alto throughput e entrega pelo menos uma vez. A ordem é geralmente preservada, mas não garantida de forma absoluta. Duplicatas são possíveis.
Filas FIFO oferecem ordenação dentro de cada message group e deduplicação gerenciada. Mesmo assim, a aplicação deve ser idempotente, pois efeitos externos podem ocorrer antes de uma falha.
Instalação
npm install @aws-sdk/client-sqsCrie o cliente:
import { SQSClient } from '@aws-sdk/client-sqs';
export const sqs = new SQSClient({
region: process.env.AWS_REGION ?? 'sa-east-1'
});Em EC2, ECS, EKS ou Lambda, prefira credenciais temporárias fornecidas por roles. Não grave access key no código.
Enviando uma mensagem
import { SendMessageCommand } from '@aws-sdk/client-sqs';
await sqs.send(new SendMessageCommand({
QueueUrl: process.env.ORDERS_QUEUE_URL,
MessageBody: JSON.stringify({
type: 'order.created',
messageId: crypto.randomUUID(),
orderId: 'order-42',
occurredAt: new Date().toISOString()
}),
MessageAttributes: {
eventType: {
DataType: 'String',
StringValue: 'order.created'
}
}
}));Message attributes ajudam filtros e observabilidade, mas possuem limites. Evite duplicar o payload inteiro.
Consumindo com long polling
import { ReceiveMessageCommand } from '@aws-sdk/client-sqs';
const response = await sqs.send(new ReceiveMessageCommand({
QueueUrl: process.env.ORDERS_QUEUE_URL,
MaxNumberOfMessages: 10,
WaitTimeSeconds: 20,
VisibilityTimeout: 60,
MessageAttributeNames: ['All'],
AttributeNames: ['All']
}));
for (const message of response.Messages ?? []) {
await processMessage(message);
}WaitTimeSeconds ativa long polling. O servidor aguarda mensagens por até vinte segundos, reduzindo chamadas vazias e custo.
Visibility timeout
Quando uma mensagem é recebida, ela continua na fila, mas fica invisível por um período. Se o consumidor não a excluir antes do timeout, ela volta a ficar disponível.
Defina um valor maior que a duração normal do processamento, mas não exagerado. Se o worker falhar, um timeout muito longo atrasa o retry.
Excluindo após sucesso
import { DeleteMessageCommand } from '@aws-sdk/client-sqs';
await sqs.send(new DeleteMessageCommand({
QueueUrl: process.env.ORDERS_QUEUE_URL,
ReceiptHandle: message.ReceiptHandle
}));Exclua somente depois que todos os efeitos necessários forem concluídos. O ReceiptHandle muda a cada recebimento.
Processamento idempotente
async function processMessage(message) {
const event = EventSchema.parse(JSON.parse(message.Body));
if (await inbox.exists(event.messageId)) {
await deleteMessage(message);
return;
}
await database.transaction(async tx => {
await handleEvent(tx, event);
await inbox.insert(tx, event.messageId);
});
await deleteMessage(message);
}Veja Idempotência em APIs Node.js e Outbox Pattern no Node.js.
Alterando o visibility timeout
Processos longos podem estender a invisibilidade:
import { ChangeMessageVisibilityCommand } from '@aws-sdk/client-sqs';
await sqs.send(new ChangeMessageVisibilityCommand({
QueueUrl,
ReceiptHandle: message.ReceiptHandle,
VisibilityTimeout: 120
}));Use um heartbeat com limite máximo. Se a operação travar, não continue estendendo indefinidamente.
Loop de consumo
let stopping = false;
while (!stopping) {
try {
const messages = await receiveBatch();
await Promise.all(messages.map(processSafely));
} catch (error) {
logger.error({ error }, 'Falha no polling SQS');
await sleepWithJitter(2000);
}
}Limite concorrência. Um lote de dez mensagens multiplicado por dezenas de workers pode saturar banco ou API externa.
Concorrência controlada
import pLimit from 'p-limit';
const limit = pLimit(8);
await Promise.all(
messages.map(message => limit(() => processSafely(message)))
);O limite deve considerar CPU, conexões de banco e rate limits.
DeleteMessageBatch
Para reduzir chamadas, exclua em lote:
import { DeleteMessageBatchCommand } from '@aws-sdk/client-sqs';
await sqs.send(new DeleteMessageBatchCommand({
QueueUrl,
Entries: successful.map((message, index) => ({
Id: String(index),
ReceiptHandle: message.ReceiptHandle
}))
}));O resultado pode conter falhas parciais mesmo quando a chamada HTTP retorna sucesso. Inspecione Failed.
SendMessageBatch
import { SendMessageBatchCommand } from '@aws-sdk/client-sqs';
const result = await sqs.send(new SendMessageBatchCommand({
QueueUrl,
Entries: events.map((event, index) => ({
Id: String(index),
MessageBody: JSON.stringify(event)
}))
}));
if (result.Failed?.length) {
throw new Error(`Falharam ${result.Failed.length} mensagens`);
}Dead-letter queue
Configure uma fila de mensagens mortas com maxReceiveCount. Depois de várias tentativas, a mensagem vai para a DLQ em vez de circular para sempre.
Defina processo de triagem, alerta e redrive. Uma DLQ sem monitoramento apenas esconde erros.
Retries
Não faça retries agressivos dentro do consumidor e também deixe a mensagem retornar imediatamente. Escolha uma estratégia. Para falhas transitórias curtas, retry local com backoff pode ser útil. Para falhas prolongadas, deixe o visibility timeout expirar ou altere-o.
Consulte Retry com Backoff no Node.js.
Delay queues
Uma mensagem pode ficar invisível inicialmente:
await sqs.send(new SendMessageCommand({
QueueUrl,
DelaySeconds: 30,
MessageBody: JSON.stringify(job)
}));SQS não é um agendador de longo prazo. Para datas distantes e regras complexas, use EventBridge Scheduler ou outro sistema apropriado.
FIFO
await sqs.send(new SendMessageCommand({
QueueUrl: process.env.ORDERS_FIFO_URL,
MessageBody: JSON.stringify(event),
MessageGroupId: event.customerId,
MessageDeduplicationId: event.messageId
}));Mensagens do mesmo group são processadas em ordem. Escolha uma chave que permita paralelismo. Um único group transforma a fila em processamento serial.
Deduplicação FIFO
A deduplicação do SQS possui janela limitada. Ela não substitui uma tabela de idempotência de negócio. Se um evento reaparecer depois da janela, ainda pode ser processado.
Mensagens grandes
SQS possui limite de tamanho. Para dados maiores, armazene o conteúdo no S3 e envie um ponteiro:
{
"bucket": "orders-payloads",
"key": "events/evt-42.json",
"checksum": "sha256:..."
}Gerencie expiração, criptografia, autorização e limpeza do objeto.
Validação
Valide imediatamente:
const payload = JSON.parse(message.Body);
const event = OrderCreatedSchema.parse(payload);Mensagens inválidas podem ir para DLQ com contexto seguro. Não registre dados pessoais completos.
Criptografia
Ative server-side encryption com chave gerenciada pelo SQS ou AWS KMS. TLS protege trânsito; SSE protege armazenamento. Se o payload contém dados extremamente sensíveis, considere criptografia na aplicação.
IAM mínimo
O produtor normalmente precisa:
sqs:SendMessage;- acesso apenas à fila específica.
O consumidor precisa:
sqs:ReceiveMessage;sqs:DeleteMessage;sqs:ChangeMessageVisibility;sqs:GetQueueAttributes.
Não conceda sqs:* para todas as filas.
Outbox no produtor
Se o produtor grava no banco e envia SQS, pode ocorrer inconsistência. Grave uma outbox na mesma transação e publique com um worker. Isso evita pedido salvo sem mensagem ou mensagem enviada sem pedido.
Observabilidade
Monitore métricas do CloudWatch e da aplicação:
- ApproximateNumberOfMessagesVisible;
- ApproximateAgeOfOldestMessage;
- mensagens em voo;
- taxa de envio e exclusão;
- falhas por tipo;
- tempo de processamento;
- DLQ;
- tentativas por mensagem.
Veja Métricas Prometheus no Node.js.
Tracing
Inclua trace ID ou correlation ID em message attributes e inicie um novo span no consumidor. Mensageria cria uma fronteira assíncrona; não dependa apenas de AsyncLocalStorage.
Consulte OpenTelemetry no Node.js.
Shutdown gracioso
process.once('SIGTERM', () => {
stopping = true;
});Pare de receber novas mensagens, aguarde as atuais até um limite e encerre. Se o processo morrer, mensagens não excluídas voltarão após o visibility timeout.
Veja Graceful Shutdown no Node.js.
Testes locais
Use LocalStack ou um ambiente AWS de testes. Cubra:
- mensagem válida;
- duplicata;
- falha transitória;
- visibility timeout;
- DLQ;
- lote com falha parcial;
- FIFO e ordenação;
- shutdown durante processamento.
SQS ou RabbitMQ?
SQS reduz operação e escala automaticamente. RabbitMQ oferece exchanges, routing keys, plugins e controle mais direto. Consulte RabbitMQ com Node.js.
SQS ou Kafka?
SQS é uma fila de trabalho. Kafka mantém um log particionado, permite replay e múltiplos consumer groups. Veja Kafka com Node.js.
Erros comuns
- Excluir antes de concluir: mensagem é perdida.
- Visibility curto: processamento duplicado.
- Visibility longo: retry demora.
- Sem idempotência: efeitos duplicados.
- DLQ sem alerta: falhas ficam escondidas.
- Concorrência sem limite: banco satura.
- Ignorar falhas parciais: lote perde mensagens.
- Credenciais estáticas: risco de segurança.
Conclusão
O Amazon SQS no Node.js oferece uma fila durável e gerenciada para desacoplar serviços e processar tarefas assíncronas. O SDK v3 fornece comandos para envio, recebimento, exclusão, lotes e visibility timeout.
Use long polling, idempotência, DLQ, concorrência controlada e IAM mínimo. Monitore idade da fila e falhas, trate resultados parciais e finalize workers com segurança. Com essas práticas, SQS absorve picos e falhas sem transformar mensagens em um ciclo infinito de retries.


