Ouço muito a pergunta "EventBridge ou SQS?". Quase sempre ela está mal formulada.

Na maioria dos fluxos com mais de um consumidor, os dois trabalham juntos: o EventBridge decide para onde o evento vai, e o SQS segura esse evento até alguém poder processar. Roteamento e durabilidade são problemas diferentes, então um não substitui o outro.

Este artigo mostra onde cada peça entra, o que cada uma resolve sozinha e quais erros aparecem quando alguém usa as duas sem saber por quê.

1.0 O que cada serviço realmente resolve

Comparação entre o papel do EventBridge (roteamento) e do SQS (durabilidade) em um fluxo de eventos

Os dois aparecem juntos em quase toda discussão porque ambos recebem mensagens. Mas o trabalho de cada um é diferente: o EventBridge decide para onde a informação vai, e o SQS guarda a informação até alguém processá-la.

1.1 EventBridge

O EventBridge é um barramento de eventos. Você publica um evento uma vez, e ele avalia as regras contra o conteúdo desse evento para decidir quais alvos recebem. O produtor não sabe quantos consumidores existem nem quem são. Para incluir um consumidor novo, basta criar uma regra. O código de quem publica não muda.

O que ele faz bem:

  • Entregar o mesmo evento a vários alvos
  • Filtrar por conteúdo com event patterns, usando campos como source, detail-type ou valores dentro de detail
  • Receber eventos de serviços AWS e de parceiros SaaS sem código de integração
  • Arquivar eventos e reenviá-los depois (archive e replay)

O que ele não faz:

  • Garantir ordem entre eventos
  • Segurar um evento por tempo indeterminado. Por padrão, ele tenta entregar ao alvo por até 24 horas e até 185 vezes. Esgotadas as tentativas, se o alvo não tem DLQ, o evento é descartado.
  • Controlar o ritmo do consumidor. Ele empurra o evento para o alvo, então quem precisa aguentar o volume é o alvo.

1.2 SQS

O SQS é uma fila. A mensagem fica guardada até um consumidor processá-la e apagá-la de forma explícita. Se o consumidor cair no meio do processamento, a mensagem volta a ficar visível depois do visibility timeout, e outro consumidor pode pegá-la.

O que ele faz bem:

  • Absorver picos. A fila cresce durante o pico, e o consumidor processa no ritmo que aguenta.
  • Limitar a concorrência do consumidor (no Lambda, com MaximumConcurrency)
  • Reter mensagens por até 14 dias (o padrão é 4)
  • Mover para uma DLQ as mensagens que falham de forma persistente, depois de maxReceiveCount tentativas
  • Manter a ordem das mensagens, quando você usa FIFO

O que ele não faz:

  • Rotear. Quem publica escolhe a fila de destino.
  • Entregar a mesma mensagem a vários consumidores. Consumidores na mesma fila competem pelas mensagens. Para broadcast, o caminho é uma fila por assinante, com SNS ou EventBridge na frente.
  • Garantir entrega única na fila padrão. Ela é at-least-once, então o consumidor precisa ser idempotente. A FIFO tem deduplicação, mas só dentro de uma janela de tempo.

Falando em FIFO, estou pensando em fazer uma série de artigos voltados a fundamentos da computação, coisas como Filas, Pilhas, Listas, Programação Orientada a Objetos, entre outros... O que vocês acham?

1.3 Quando usar cada um

Vale reparar nos sentidos do fluxo: a regra do EventBridge empurra o evento para a fila, mas quem lê a fila é o Lambda, que faz polling pelo event source mapping.

Para decidir, pergunte duas coisas: quem precisa receber esse evento? E quem precisa segurá-lo até estar pronto para processar?

Necessidade Use Por quê
Um evento para vários consumidores que não se conhecem EventBridge O produtor só publica. Cada consumidor cria a própria regra.
Absorver picos antes do processamento SQS A fila segura o excesso, e o consumidor anda no próprio ritmo.
Processar em ordem estrita SQS FIFO Só a FIFO garante ordem dentro de um grupo de mensagens.
Rotear pelo conteúdo do evento EventBridge Event patterns decidem o alvo sem código.
Reprocessar se o consumidor cair no meio SQS A mensagem permanece na fila até ser apagada.
Consumidor rápido, de baixo volume, sem necessidade de controlar o ritmo EventBridge direto para a Lambda O Lambda já guarda o evento numa fila interna e tenta de novo. Uma fila SQS na frente seria mais uma peça sem ganho.
Um único consumidor, sem roteamento SQS O EventBridge seria mais uma peça para manter, sem trazer ganho.

A maioria dos fluxos que precisam de mais de um consumidor acaba usando as duas peças: o EventBridge decide quem recebe, e o SQS segura o evento até o consumidor estar pronto. É o padrão da seção 2.0.

2.0 O padrão mais comum: EventBridge → SQS → Lambda

Por que não colocar a Lambda como alvo direto da regra do EventBridge? Funciona, e é mais protegido do que parece. O EventBridge invoca o Lambda de forma assíncrona: o evento entra numa fila interna do Lambda, que continua tentando por até 6 horas quando a função sofre throttling. Se a função der erro, são mais duas tentativas.

O que essa fila interna não oferece é controle. Você não enxerga quantos eventos estão esperando, não escolhe quantos são processados ao mesmo tempo (a não ser reservando concorrência para a função inteira) e não processa em lote. Com o SQS no meio, o backlog vira métrica (ApproximateNumberOfMessagesVisible e ApproximateAgeOfOldestMessage), a concorrência do consumidor fica no MaximumConcurrency do event source mapping, e a mensagem pode esperar até 14 dias.

Diagrama do padrão EventBridge, SQS e Lambda, com o backlog visível na fila

O desenho é simples. O que costuma dar errado é a configuração de cada peça: visibility timeout, permissões entre EventBridge e SQS, comportamento de lote na Lambda. O resto do artigo cobre isso com um projeto que reproduz cada erro de propósito.

3.0 O cenário: um evento, quatro destinos

O PedidoCriado do projeto de pedidos agora é roteado (fan-out) para três filas, uma por domínio, e direto para a Lambda que classifica o pedido, sem fila no meio:

 ┌─→ SQS Estoque → Lambda (baixa) PedidoCriado ├─→ SQS Notificação → Lambda (e-mail) → EventBridge ─────┼─→ SQS Financeiro → Lambda (cobrança) └─→ Lambda processar_pedido (sem fila) 

Regras do bus de eventos no console do EventBridge, com um evento roteado para quatro destinos

Um fluxo linear, com uma fila e um consumidor, prova pouco. Dá para fazer isso só com SQS e ninguém notaria a diferença. O fan-out é o argumento a favor do EventBridge: o mesmo evento alimenta estoque, notificação e faturamento, e nenhum desses consumidores sabe que os outros existem. Se amanhã surgir um quinto consumidor, como um serviço de detecção de fraude, ele assina o evento e pronto. O código que cria o pedido não muda uma linha.

Cada fila do projeto reproduz de propósito um erro comum.

4.0 Visibility timeout: a fila reentregando antes da hora

A fila de Estoque simula uma chamada a um sistema legado, com latência entre 3 e 15 segundos por mensagem.

# Simula latência de um sistema de estoque legado latencia = random.uniform(LATENCIA_MIN_SEGUNDOS, LATENCIA_MAX_SEGUNDOS) time.sleep(latencia) 

O visibility timeout da fila é de 180 segundos, para uma função com timeout de 30. São as 6x que a documentação do Lambda recomenda para filas que alimentam uma função.

EstoqueQueue: Type: AWS::SQS::Queue Properties: VisibilityTimeout: 180 

Validando o projeto, apareceu um detalhe que eu não tinha previsto. A primeira versão lia lotes de 5 mensagens. Com cada baixa levando até 15 segundos, o pior caso de um lote chegava a 75 segundos, bem acima do timeout de 30 da função. Troquei para BatchSize: 1. Quando cada mensagem é lenta, o tamanho do lote também entra na conta do timeout.

O que acontece se você reduzir o visibility timeout para 10 segundos? O SQS entrega a mensagem ao consumidor e começa a contar. Se a mensagem não for apagada dentro desses 10 segundos, ela volta a ficar visível e pode ser entregue de novo, possivelmente enquanto o primeiro consumidor ainda está no meio do time.sleep(). Com isso, dois consumidores processam o mesmo pedido e o estoque é debitado duas vezes.

No projeto dá para reproduzir: troque o VisibilityTimeout de 180 para 10 no template, faça o deploy e compare a métrica EstoqueBaixado com o número de pedidos criados.

Métrica EstoqueBaixado comparada ao número de pedidos criados, com mensagens processadas em duplicidade

5.0 Lote com falhas: o que volta para a fila e o que some

A fila de Notificação processa lotes de até 10 mensagens por invocação. O problema aparece quando uma mensagem do lote falha.

Sem tratamento especial, o comportamento é tudo ou nada. Se uma exceção sobe do handler, o lote inteiro é considerado falho e as 10 mensagens voltam a ficar visíveis, inclusive as 9 que já tinham mandado o e-mail de confirmação ao cliente.

# Versão ingênua, evite def lambda_handler(event, context): for record in event["Records"]: enviar_notificacao(record) # se UMA lançar exceção, TODAS voltam 

A correção é a resposta parcial de lote, com o utilitário de batch do Powertools:

from aws_lambda_powertools.utilities.batch import ( BatchProcessor, EventType, process_partial_response, ) processor = BatchProcessor(event_type=EventType.SQS) def lambda_handler(event, context): return process_partial_response( event=event, record_handler=processar_registro, processor=processor, context=context, ) 

O BatchProcessor processa cada mensagem isoladamente, captura as falhas por item e devolve a lista batchItemFailures com os IDs que falharam. Só essas voltam para a fila. Se todas as mensagens do lote falharem, ele lança BatchProcessingError, e aí o lote inteiro volta.

Além do código, o event source mapping precisa da flag:

Events: SQSEvent: Type: SQS Properties: Queue: !GetAtt NotificacaoQueue.Arn BatchSize: 10 FunctionResponseTypes: [ReportBatchItemFailures] 

Trigger SQS da Lambda de notificação com Report batch item failures ativo

Sem a flag, o cenário é pior que o da versão ingênua. O handler captura as falhas e termina sem erro, e a documentação do Lambda diz que, quando a função processa o lote com sucesso, o Lambda apaga as mensagens da fila. A lista de falhas vira um valor de retorno qualquer, e as mensagens que falharam somem sem passar pela DLQ.

Resultado no Logs Insights mostrando mensagens que falharam e não voltaram para a fila

É um erro fácil de cometer porque o código parece certo, e a outra metade da correção está no template. No projeto, o consumer de notificação simula 25% de falha aleatória, o suficiente para gerar lotes mistos com frequência.

6.0 DLQ: separando erro passageiro de erro permanente

A fila de Financeiro simula outro tipo de falha: um erro de negócio permanente, como um valor de cobrança inválido, que nenhum retry corrige.

if random.random() < TAXA_ERRO_PERMANENTE: raise InvalidOperation( f"Valor de cobrança inválido para pedido {pedido_id}: {valor}" ) 

A diferença importa. Para falha passageira (timeout de rede, serviço fora do ar por alguns segundos), retry é o comportamento certo. Para falha permanente, retry só atrasa o inevitável e gasta concorrência. A fila tenta 3 vezes antes de desistir:

FinanceiroQueue: Type: AWS::SQS::Queue Properties: VisibilityTimeout: 90 # 6x o timeout de 15s da função RedrivePolicy: deadLetterTargetArn: !GetAtt FinanceiroDLQ.Arn maxReceiveCount: 3 

Depois da terceira entrega sem sucesso, o SQS move a mensagem para a DLQ sozinho. Não precisa de código.

Nessa fila, o erro mais comum é esquecer o alarme na própria DLQ:

FinanceiroDLQAlarm: Type: AWS::CloudWatch::Alarm Properties: Namespace: AWS/SQS MetricName: ApproximateNumberOfMessagesVisible Dimensions: - Name: QueueName Value: !GetAtt FinanceiroDLQ.QueueName Statistic: Maximum Period: 300 EvaluationPeriods: 1 Threshold: 0 ComparisonOperator: GreaterThanThreshold 

Sem o alarme, as mensagens se acumulam na DLQ sem ninguém ser avisado, e o problema só aparece quando alguém nota que uma parte dos pedidos nunca gerou cobrança. Até lá, podem ser semanas de mensagens para investigar uma por uma.

Fila de Financeiro com a DLQ recebendo mensagens e o alarme em estado ALARM

7.0 Quando o EventBridge sozinho basta

Nem todo consumidor precisa de fila no meio. O processar_pedido, que classifica o pedido por faixa de valor, é rápido, tem volume baixo e não precisa de controle de ritmo. Para ele, a fila interna do Lambda dá conta, e uma fila SQS na frente seria só mais uma peça para manter.

No projeto, a quarta rota leva o PedidoCriado direto para essa função:

ProcessarPedidoFunction: Type: AWS::Serverless::Function Properties: Events: PedidoCriado: Type: EventBridgeRule Properties: EventBusName: !Ref PedidosEventBus Pattern: source: [observability-demo.pedidos] detail-type: [PedidoCriado] DeadLetterConfig: Arn: !GetAtt RuleDLQ.Arn EventInvokeConfig: MaximumRetryAttempts: 2 DestinationConfig: OnFailure: Type: SQS Destination: !GetAtt ProcessarFalhasQueue.Arn 

Sem fila no meio, essa rota precisa de duas redes de segurança, uma de cada lado da entrega.

A primeira é o DeadLetterConfig na regra, que pega falha de entrega. O EventBridge tentou invocar a função por até 24 horas e não conseguiu, por exemplo por causa de uma permissão alterada sem ninguém notar.

A segunda é o destino OnFailure no Lambda. Quando o Lambda aceita o evento na fila interna, a entrega do EventBridge está concluída. Se depois a função errar na primeira tentativa e nas duas seguintes, o Lambda descarta o evento, e a DLQ da regra não fica sabendo de nada. O OnFailure manda esse evento para a ProcessarFalhasQueue em vez de jogá-lo fora. É fácil configurar só a primeira rede e achar que a rota está protegida.

Nas rotas com fila, a divisão é parecida. A RuleDLQ também protege os três alvos SQS contra falha de entrega, e a falha de processamento fica com a DLQ de cada fila, pelo maxReceiveCount da seção 6.0.

8.0 A policy da fila, que só aparece quando falta

O fan-out não funciona sem um passo que é fácil de pular: o EventBridge precisa de permissão explícita para escrever em cada fila.

EstoqueQueuePolicy: Type: AWS::SQS::QueuePolicy Properties: Queues: [!Ref EstoqueQueue] PolicyDocument: Statement: - Effect: Allow Principal: { Service: events.amazonaws.com } Action: sqs:SendMessage Resource: !GetAtt EstoqueQueue.Arn Condition: ArnEquals: { "aws:SourceArn": !GetAtt EstoqueRule.Arn } 

Sem essa policy, a regra é criada, o alvo aparece configurado, e os eventos não chegam. O produtor não vê erro nenhum, porque o PutEvents deu certo: a falha acontece depois, na entrega. É o tipo de falha que a RuleDLQ da seção 7.0 captura. Sem ela, você só descobre quando o time de um domínio reclama que não recebe nada.

A condição ArnEquals limita a permissão àquela regra. Em bus customizado, o ARN da regra inclui o nome do bus (rule/pedidos-bus-dev/nome-da-regra), então usar !GetAtt EstoqueRule.Arn evita montar esse ARN à mão e errar o formato.

9.0 Checklist antes de ir para produção

Use esta lista como revisão do template antes do primeiro deploy em produção. Cada item é uma falha que aparece nos cenários deste artigo.

9.1 Filas SQS

  • Visibility timeout de pelo menos 6x o timeout da função consumidora, em cada fila
  • BatchSize compatível com o timeout: lote vezes o tempo de cada mensagem cabe na função
  • DLQ configurada em toda fila que alimenta um consumidor crítico
  • maxReceiveCount calibrado: baixo o bastante para não represar retry inútil, alto o bastante para sobreviver a uma falha passageira
  • Resource policy da fila liberando sqs:SendMessage só para a regra específica, com aws:SourceArn

9.2 Consumidor Lambda

  • ReportBatchItemFailures ligado no event source mapping, e não só no código
  • Handler com BatchProcessor (ou lógica equivalente) devolvendo batchItemFailures
  • Consumidor idempotente, porque a fila padrão entrega pelo menos uma vez
  • MaximumConcurrency definido onde o sistema downstream não aguenta o pico

9.3 EventBridge

  • DeadLetterConfig em cada alvo da regra, principalmente quando não há fila no meio
  • Destino OnFailure (ou DLQ da função) quando a regra invoca a Lambda direto

9.4 Alarmes

  • Alarme em ApproximateNumberOfMessagesVisible de cada DLQ, incluindo a da regra
  • Alarme em ApproximateAgeOfOldestMessage das filas principais, para detectar consumidor atrasado

10.0 Projeto de exemplo

O repositório estende o observability-demo dos artigos anteriores com o fan-out completo: três filas com seus consumidores, a rota direta para a Lambda de classificação, as policies e os alarmes descritos acima. Cada consumer tem um comentário no código explicando a lição daquela fila.

GitHub logo diegobroetto / observability-demo

Demo serverless de observabilidade com CloudWatch + X-Ray (logs, metricas EMF, traces) - AWS SAM

observability-demo

Exemplo completo de observabilidade serverless com CloudWatch + X-Ray para o artigo:

"Observabilidade serverless sem surpresa no bolso: logs, métricas e traces com CloudWatch + X-Ray do jeito certo"

O que este projeto demonstra





























































Pilar Ferramenta Onde ver no código
Logs estruturados Powertools Logger + JSON nativo do Lambda
observability.py, todos os handlers
Retenção de logs AWS::Logs::LogGroup com RetentionInDays
template.yaml → LogGroups
Log level por ambiente Mappings no SAM + LOG_EVENT flag
template.yaml → Mappings
Métricas sem custo extra Embedded Metric Format (EMF) criar_pedido/handler.py
Métricas com dimensão de negócio Powertools Metrics + add_dimension processar_pedido/handler.py
Traces por subsegmento Powertools Tracer + @capture_method Todos os handlers
Anotações no X-Ray tracer.put_annotation consultar_pedido/handler.py
Alarmes em p99 AWS::CloudWatch::Alarm + ExtendedStatistic
template.yaml → Alarmes
Dashboard consolidado AWS::CloudWatch::Dashboard
template.yaml → Dashboard
Queries prontas CloudWatch Logs Insights monitoring/insights-queries.md

Endpoints






















Método Path Função Descrição
POST /pedidos CriarPedidoFunction Cria o pedido e publica PedidoCriado no EventBridge
GET /pedidos/{id}
…





Para rodar:

sam build --use-container sam deploy --config-env dev 

O README traz o passo a passo para reproduzir os três erros de propósito: reduzir o visibility timeout, remover a flag de resposta parcial e ver a DLQ recebendo mensagem.

O projeto tem limitações que vale deixar claras. Não há testes automatizados, os alarmes cobrem só a fila de Estoque e as DLQs de Financeiro, da regra e do processar_pedido, e os consumers simulam as integrações (estoque, e-mail, cobrança) em vez de chamar sistemas reais.

11.0 Conclusão

Conclusao

A pergunta "EventBridge ou SQS?" parte de uma premissa errada. O que importa é o que cada ponto do fluxo precisa: roteamento ou durabilidade. Às vezes é só um dos dois. No projeto de pedidos, com quatro consumidores, precisou dos dois.

Configure DLQ no alvo da regra do EventBridge e em cada fila SQS desde o primeiro deploy, não depois que algo já sumiu. E antes de copiar um BatchSize: 10 de algum exemplo, faça duas contas: quanto tempo o lote leva no pior caso, e o que acontece quando a nona mensagem dá certo e a décima falha.

Diego Broetto é Tech Lead na Darede e escreve sobre AWS, serverless e infraestrutura como código.

Já passou por alguma dessas pegadinhas de um jeito diferente? Comenta aqui embaixo.