Filas de Mensagem
Desacoplam produtores de consumidores, absorvem picos de carga e garantem entrega de mensagens mesmo quando o consumidor está temporariamente indisponível. O intermediário (broker) assume a responsabilidade de armazenar e entregar — liberando o produtor para seguir em frente.
Intenção
Separar o momento em que o produtor gera uma mensagem do momento em que o consumidor a processa. Filas de mensagem introduzem um intermediário durável (o broker) que armazena a mensagem até que o consumidor esteja pronto para processá-la, eliminando o acoplamento temporal entre os dois lados.
Sem filas, o produtor precisa que o consumidor esteja disponível, saudável e rápido no exato momento da chamada. Com filas, o produtor entrega ao broker e segue em frente. O consumidor processa no seu próprio ritmo — podendo estar offline, em manutenção ou simplesmente mais lento que o produtor por um período. O broker absorve essa diferença.
O resultado prático é um sistema mais resiliente: falhas transientes do consumidor não causam perda de mensagens; picos de carga do produtor não se propagam diretamente para o consumidor; e múltiplos consumidores podem processar o mesmo volume em paralelo sem coordenação explícita entre eles.
Problema
Chamadas síncronas diretas entre serviços criam dependências de disponibilidade, latência e capacidade que se tornam cada vez mais custosas à medida que o sistema cresce:
- Acoplamento temporal: se o serviço de e-mail está fora do ar no momento em que o usuário finaliza o cadastro, a requisição HTTP falha e o usuário recebe um erro — mesmo que o cadastro em si tenha sido bem-sucedido. O produtor (cadastro) está acoplado à disponibilidade do consumidor (e-mail).
- Propagação de picos: uma promoção relâmpago multiplica pedidos por dez em poucos minutos. Se a geração de PDF do comprovante é síncrona, o serviço de PDF recebe o mesmo pico e pode colapsar — degradando o fluxo principal de pagamento junto.
- Velocidades assimétricas: o produtor gera eventos na velocidade do usuário; o consumidor processa na velocidade do banco ou de uma API externa mais lenta. Sem buffer, o consumidor precisa ter capacidade para o pico máximo do produtor em tempo real.
- Reprocessamento de falhas: quando um processamento falha em uma chamada síncrona, a lógica de retry fica no produtor. Com filas, o broker gerencia retentativas e o histórico de falhas é visível na Dead Letter Queue.
Como funciona
Conceitos fundamentais
Os papéis e componentes centrais de qualquer sistema de mensageria:
QUEUE (point-to-point) — cada mensagem vai para exatamente um consumer
┌──────────┐ publica ┌───────────────────┐ consome ┌────────────┐
│ Producer │─────────────►│ Broker / Queue │─────────────►│ Consumer A │
└──────────┘ │ │ └────────────┘
│ [msg1][msg2][msg3]│
┌──────────┐ publica │ │ consome ┌────────────┐
│ Producer │─────────────►│ │─────────────►│ Consumer B │
└──────────┘ └───────────────────┘ └────────────┘
Consumer A e B competem pela mesma mensagem (Competing Consumers).
msg1 → Consumer A, msg2 → Consumer B, msg3 → Consumer A.
Scale-out: adicionar Consumer C aumenta o throughput sem mudar o producer.
TOPIC / PUB-SUB — cada mensagem vai para TODOS os subscribers
┌──────────┐ publica ┌──────────┐ entrega ┌─────────────┐
│ Producer │───────────►│ Topic │───────────►│ Subscriber 1│
└──────────┘ └──────────┘ │ └─────────────┘
│ ┌─────────────┐
└─────►│ Subscriber 2│
└─────────────┘
Semântica de broadcast/evento: todos os subscribers recebem a mesma cópia.
- Producer: quem publica a mensagem no broker. Não sabe quem vai consumir nem quando.
- Consumer: quem processa a mensagem. Pode haver múltiplos consumidores competindo (fila) ou múltiplos subscribers independentes (tópico).
- Broker: o intermediário que armazena e entrega. Exemplos: RabbitMQ, Apache Kafka, Amazon SQS, Google Pub/Sub, Azure Service Bus.
- Queue (point-to-point): cada mensagem é consumida por exatamente um consumer. Semântica de trabalho distribuído — ideal para tarefas.
- Topic / Pub-Sub: mensagem publicada chega a todos os subscribers registrados. Semântica de evento — ideal para notificações e integrações.
Padrões de entrega
A semântica de entrega define o que acontece quando uma mensagem é publicada e o que o consumer pode esperar receber:
AT-MOST-ONCE (no máximo uma vez)
Producer publica → Broker armazena → Consumer recebe → ACK antes de processar
Resultado: mensagem pode se perder (se consumer cair após ACK, antes de processar).
Nunca duplicada. Fire-and-forget. Uso: métricas, logs não-críticos.
AT-LEAST-ONCE (pelo menos uma vez)
Producer publica → Broker armazena → Consumer processa → ACK após processar
Resultado: mensagem entregue ao menos uma vez.
Pode ser duplicada se consumer cair após processar mas antes do ACK.
Uso: a maioria dos sistemas. Consumer DEVE ser idempotente.
EXACTLY-ONCE (exatamente uma vez)
Sem perda, sem duplicata. Requer coordenação:
- Transactional Outbox: producer persiste no banco + tabela outbox
na mesma transação; worker lê outbox e publica no broker.
- Two-Phase Commit: coordenação distribuída entre banco e broker.
- Idempotency keys: consumer detecta e ignora duplicatas por chave única.
Uso: transações financeiras, débitos em conta. Alto custo operacional.
Regra prática: projete consumers idempotentes e use semântica at-least-once. Exactly-once é caro para implementar e operar — na maioria dos casos, idempotência no consumer resolve o problema de duplicatas com muito menos complexidade.
Dead Letter Queue (DLQ)
Quando uma mensagem falha no processamento N vezes consecutivas (limite configurável), o broker a move para uma fila separada chamada Dead Letter Queue (DLQ) em vez de continuar retentando indefinidamente.
Fila principal
[msg-ok][msg-ok][msg-veneno][msg-ok]
│
│ falha 3x (maxReceiveCount = 3)
▼
Dead Letter Queue
[msg-veneno] ← visível para monitoramento e reprocessamento manual
Benefícios:
- Evita que uma "poison message" bloqueie a fila principal indefinidamente.
- Cria visibilidade sobre mensagens que não puderam ser processadas.
- Permite reprocessamento manual após correção do bug no consumer.
- Alertas no DLQ sinalizam problemas no consumer sem afetar o fluxo principal.
RabbitMQ vs Kafka
Os dois brokers mais comuns têm filosofias fundamentalmente distintas:
RABBITMQ — broker inteligente, consumer passivo
Producer → Exchange → Binding → Queue → Consumer
│
roteamento flexível:
Direct, Topic, Fanout, Headers
- Mensagem deletada do broker após ACK do consumer.
- Broker controla qual consumer recebe qual mensagem.
- Ideal para: work queues, RPC assíncrono, roteamento complexo.
- Quando consumer está offline: mensagem fica na fila aguardando.
KAFKA — log distribuído durável, consumer inteligente
Producer → Topic (particionado) → Consumer Group
│
offset controlado pelo consumer
- Mensagem RETIDA por tempo configurável (ex.: 7 dias), não deletada.
- Consumer controla o próprio offset (posição de leitura).
- Replay: consumer pode reler mensagens antigas reposicionando o offset.
- Ideal para: event streaming, audit log, event sourcing, alto throughput.
- Particionamento: mensagens com a mesma partition key vão para a mesma
partição, garantindo ordem dentro de uma partition key.
A escolha entre os dois não é de performance apenas — é de modelo mental. RabbitMQ pensa em tarefas a executar; Kafka pensa em eventos que aconteceram. Um pedido de "gerar PDF" é uma tarefa (RabbitMQ). Um evento "pedido criado" que múltiplos serviços precisam consumir de forma independente é um evento (Kafka).
Quando usar
- Tarefas demoradas fora do fluxo HTTP: envio de e-mail, geração de PDF, redimensionamento de imagem, notificações push. O usuário não deve esperar por essas operações — elas vão para a fila e o response HTTP é imediato.
- Absorção de picos de carga: quando o producer pode gerar mensagens muito mais rápido do que o consumer consegue processar em tempo real, a fila funciona como buffer. Os consumers processam no seu ritmo sem colapsar e o backlog é consumido gradualmente.
- Comunicação entre microsserviços desacoplados: em vez de chamadas HTTP síncronas que criam dependências de disponibilidade, serviços publicam eventos no broker e outros serviços consomem de forma independente — podendo evoluir, escalar e falhar de forma isolada.
- Audit log e event sourcing: Kafka retém mensagens por tempo configurável, tornando o log de eventos o registro canônico de tudo que aconteceu no sistema. Projeções e relatórios podem ser reconstruídos a partir do offset zero.
Quando evitar
- Quando o producer precisa da resposta do consumer imediatamente: filas são assíncronas por natureza. Se o fluxo exige saber o resultado do processamento antes de continuar (ex.: validar um cartão de crédito), use RPC síncrono (REST, gRPC) — não fila.
- Volume tão baixo que a fila é overhead operacional puro: um sistema que envia 10 e-mails por dia não precisa de broker, consumer, DLQ, monitoramento de lag e alertas. Uma chamada síncrona com retry simples resolve com zero infraestrutura adicional.
- Quando exactly-once é crítico e o custo não pode ser pago: se a semântica de exactly-once é obrigatória (débito financeiro, geração de número de nota fiscal) mas a equipe não tem experiência para implementar Transactional Outbox ou idempotency keys corretamente, o risco de duplicatas silenciosas é alto. Nesse caso, avalie uma transação síncrona com idempotência no banco antes de introduzir mensageria.
Prós e contras
Prós
- Desacoplamento temporal: producer e consumer não precisam estar online ao mesmo tempo.
- Resiliência: falhas transientes do consumer não causam perda de mensagens — elas ficam no broker aguardando.
- Absorção de picos: o broker funciona como buffer entre velocidades assimétricas de produção e consumo.
- Scale-out de processamento: adicionar consumers aumenta o throughput de forma horizontal sem mudar o producer.
- Visibilidade de falhas: DLQ torna mensagens problemáticas visíveis e reprocessáveis sem perda.
Contras
- Complexidade operacional: broker, consumers, DLQ, monitoramento de lag, alertas e estratégia de retry precisam ser configurados, operados e monitorados.
- Debugging mais difícil: rastrear o caminho de uma mensagem do producer ao consumer atravessa broker, filas, offsets e logs distribuídos.
- Latência adicional: o processamento é assíncrono — o resultado não está disponível imediatamente após a publicação.
- Idempotência obrigatória: com semântica at-least-once, consumers devem ser projetados para receber duplicatas sem corromper dados.
Armadilhas comuns
1. Consumer não idempotente com at-least-once
A semântica at-least-once é a padrão na maioria dos brokers. Isso significa que duplicatas são possíveis — e vão acontecer em produção, especialmente em reinicializações de consumer, deploys e falhas de rede. Um consumer que debita R$ 100,00 de uma conta sem verificar se aquela transação já foi processada vai debitar duas vezes quando a mensagem for entregue duplicada. A idempotência deve ser projetada desde o início: uma chave única por mensagem (idempotency key) armazenada no banco e verificada antes de processar é o mecanismo mais simples e robusto.
2. Fila sem DLQ configurada
Sem DLQ, uma "poison message" — uma mensagem que o consumer nunca consegue processar com sucesso (dados corrompidos, schema inesperado, dependência externa indisponível) — entra em loop de retry indefinido. Dependendo da configuração de visibilidade do broker, ela pode bloquear o processamento de mensagens seguintes ou consumir recursos do consumer em retentativas inúteis. Toda fila de produção deve ter uma DLQ configurada antes de receber tráfego real.
3. Retry infinito antes de ter DLQ — a poison message trava o consumer
Mesmo com DLQ configurada, o maxReceiveCount (número de
tentativas antes de ir para DLQ) precisa ser calibrado. Um valor muito
alto (ex.: 100 tentativas) significa que uma poison message vai consumir
recursos e tempo antes de ir para a DLQ. Um valor muito baixo (ex.: 1)
significa que erros transientes legítimos vão para a DLQ sem ter chance
de se recuperar. O valor típico fica entre 3 e 10, com backoff
exponencial entre as tentativas.
Regra prática: configure DLQ e maxReceiveCount
antes do primeiro deploy em produção. Adicionar DLQ depois de ter mensagens
em loop é mais trabalhoso e pode exigir reprocessamento manual do backlog.
4. Tratar Kafka como fila RabbitMQ
Kafka não deleta mensagens após consumo — ele retém por tempo configurável
(padrão: 7 dias). O consumer controla sua própria posição (offset) no
log. Isso significa que se o consumer group é deletado e recriado, o
offset padrão pode ser earliest (início do log) ou
latest (apenas novas mensagens), dependendo da configuração.
Um consumer group recém-criado com auto.offset.reset=earliest
em um tópico com 7 dias de mensagens vai reprocessar tudo desde o início.
Entender offsets, consumer groups e a semântica de retenção é
pré-requisito para operar Kafka corretamente.
Arquiteturas e padrões relacionados
Event-Driven Architecture usa filas e brokers como infraestrutura central. Um broker de mensagens (Kafka, RabbitMQ) é o mecanismo de transporte dos eventos em uma EDA. A diferença está na camada conceitual: EDA define o estilo arquitetural (componentes comunicam-se por eventos, não por chamadas diretas); a fila é a implementação técnica desse estilo. É possível ter EDA com RabbitMQ, Kafka, SNS/SQS ou qualquer outro broker.
REST vs GraphQL vs gRPC define estilos de comunicação síncrona ponto-a-ponto. Filas de mensagem são o complemento assíncrono: quando o produtor não pode ou não deve aguardar a resposta do consumidor, a fila substitui a chamada direta. Em microsserviços, é comum usar gRPC ou REST para operações síncronas (consultas, validações) e filas para operações assíncronas (tarefas, notificações, propagação de eventos).
CQRS usa filas para propagar Commands e eventos entre o Write Model e o Read Model. No grau completo (bancos separados), o CommandHandler publica um evento de domínio no broker após persistir a escrita; um consumer do lado de leitura recebe o evento e atualiza o Read Model. A fila é o mecanismo que torna a consistência eventual gerenciável e auditável.
Escalabilidade Horizontal e o padrão Competing Consumers são diretamente complementares. Adicionar instâncias de um consumer que lê da mesma fila distribui o trabalho horizontalmente sem coordenação explícita — o broker garante que cada mensagem seja entregue a exatamente um consumer. Esse é um dos casos mais simples e efetivos de scale-out: o throughput de processamento cresce linearmente com o número de consumers.