Filas e mensageria
RabbitMQ, Kafka e o que muda entre uma fila de tarefas e um log de eventos.
As decisões de mensageria são quatro: qual intermediário usar, como tratar mensagens que falham, como garantir a ordem quando ela importa e como publicar sem perder mensagens.
Os intermediários
| RabbitMQ | Kafka | Serviço gerenciado (SQS, Pub/Sub, Service Bus) | |
|---|---|---|---|
| Modelo | Fila de tarefas, com roteamento flexível | Log de eventos particionado | Fila (SQS, Service Bus) ou publicação e assinatura (Pub/Sub, SNS) |
| Mensagem depois de processada | Removida | Guardada pelo tempo configurado | Removida |
| Reler o passado | Não (salvo com streams do RabbitMQ) | Sim | Não, ou de forma limitada |
| Ordem | Por fila, com um consumidor | Por partição | Em filas FIFO, por grupo de mensagens |
| Volume | Dezenas de milhares de mensagens por segundo | Centenas de milhares a milhões por segundo | Escala automática |
| Operação | Moderada | Alta, ou um serviço gerenciado | Nenhuma |
RabbitMQ
Forte em distribuir trabalho. Oferece roteamento por tipo de mensagem, prioridades, mensagens com atraso, filas de mensagens mortas e confirmação por mensagem. É a escolha natural para tarefas em segundo plano: e-mails, processamento de arquivos, integrações.
Kafka
Forte em fluxos de eventos com alto volume e vários consumidores. Como guarda as mensagens, permite que um serviço novo leia todo o histórico e que dados sejam reprocessados. É a base de arquiteturas orientadas a eventos e de processamento de streams. O custo é a complexidade: partições, grupos de consumidores e retenção exigem planejamento, e operar um cluster próprio é trabalho especializado.
Serviços gerenciados
Para quem já está numa nuvem, SQS, Pub/Sub e Service Bus eliminam a operação e escalam sozinhos. As limitações estão nos detalhes: tamanho máximo de mensagem, tempo máximo de retenção, garantias de ordem só em modos específicos e custo por mensagem.
O banco que você já tem
Para volumes modestos, uma tabela no PostgreSQL funciona como fila, com SELECT ... FOR UPDATE SKIP LOCKED: cada consumidor pega uma linha que ninguém está processando. Bibliotecas como Graphile Worker, Oban, River e good_job seguem esse modelo. A vantagem é grande: a mensagem é gravada na mesma transação dos dados, o que elimina o problema de publicar sem perder. O limite é o volume e a carga extra no banco.
Como escolher
| Situação | Escolha |
|---|---|
| Tarefas em segundo plano num sistema pequeno ou médio | Fila no próprio banco, ou RabbitMQ |
| Distribuir trabalho entre consumidores, com novas tentativas e atrasos | RabbitMQ ou SQS |
| Vários serviços reagindo aos mesmos eventos | Kafka, ou publicação e assinatura gerenciada |
| Reprocessar eventos passados, ou alimentar análises | Kafka |
| Volume muito alto de eventos, como telemetria e cliques | Kafka |
| Já está numa nuvem e não quer operar nada | O serviço gerenciado dela |
Quando uma mensagem falha
Uma mensagem pode falhar por dois motivos diferentes, e cada um pede um tratamento:
| Tipo de falha | Exemplo | O que fazer |
|---|---|---|
| Temporária | O serviço de e-mail está fora do ar, o banco recusou a conexão | Tentar de novo, mais tarde |
| Permanente | JSON inválido, pedido que não existe, regra de negócio violada | Tentar de novo não adianta; separar a mensagem para análise |
Novas tentativas com atraso
Devolver a mensagem para a fila imediatamente faz o consumidor tentar de novo em milissegundos, enquanto o serviço externo ainda está fora do ar. As novas tentativas precisam de atraso crescente: 10 segundos, 1 minuto, 10 minutos.
Cada intermediário oferece isso de um jeito: filas de espera com tempo de expiração ou o recurso de mensagens atrasadas no RabbitMQ, o tempo de visibilidade no SQS, tópicos de nova tentativa no Kafka.
A fila de mensagens mortas
Depois de algumas tentativas, ou numa falha permanente, a mensagem vai para uma fila de mensagens mortas (dead letter queue). Ela sai do caminho das outras, fica guardada para análise e pode ser reenviada depois que o problema for corrigido.
Ordem por chave
Quando a ordem importa, como os eventos "pedido criado", "pedido pago" e "pedido cancelado" de um mesmo pedido, ela é garantida por chave:
| Intermediário | Como garantir a ordem por chave |
|---|---|
| Kafka | A chave da mensagem (por exemplo, o id do pedido) define a partição; uma partição é lida por um consumidor do grupo de cada vez |
| SQS FIFO | O MessageGroupId agrupa as mensagens; cada grupo é entregue em ordem |
| RabbitMQ | Uma fila por chave, ou o plugin de hash consistente para dividir por chave |
Mesmo com ordem por chave, prefira consumidores que tolerem desordem: guardar a versão ou a data do evento e ignorar um evento mais antigo que o estado atual. Novas tentativas e reprocessamentos sempre podem embaralhar a sequência.
Publicar sem perder mensagens
O problema mais sutil fica no produtor:
1. grava o pedido no banco ✔
2. publica "pedido criado" ✘ o intermediário estava fora do arO pedido existe, e nenhum outro serviço sabe dele. Inverter a ordem não resolve: a mensagem pode ser publicada e a gravação no banco falhar.
A solução é o padrão outbox: a mensagem é gravada numa tabela do próprio banco, na mesma transação do pedido. Um processo separado lê essa tabela e publica as mensagens, marcando cada uma como enviada. Se a publicação falhar, ela é tentada de novo. Como esse processo também pode publicar a mesma mensagem duas vezes, os consumidores continuam precisando ser idempotentes. O padrão tem um tópico próprio na trilha.
Quando não usar uma fila
| Situação | Por quê |
|---|---|
| O usuário precisa da resposta para continuar | A fila só adia; ele vai esperar do mesmo jeito, com mais complexidade |
| Volume baixo e uma única chamada simples | Uma chamada síncrona com tempo limite e nova tentativa resolve |
| Ninguém vai monitorar a fila | Mensagens mortas e atrasos acumulam sem ninguém ver |
Resumo da decisão
- Intermediário: fila no banco ou RabbitMQ para tarefas; Kafka para fluxos de eventos com vários consumidores; gerenciado se já estiver numa nuvem.
- Confirmação: só depois de processar.
- Idempotência: todo consumidor tolera a mesma mensagem duas vezes.
- Falhas: novas tentativas com atraso, limite de tentativas e fila de mensagens mortas monitorada.
- Ordem: por chave, e consumidores que toleram desordem.
- Publicação: padrão outbox para não perder mensagens.
O próximo nível mostra um consumidor do RabbitMQ nas cinco linguagens e a idempotência com o banco.
Prompts para aprofundar
Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.
Escolher o intermediário de mensagens
Preciso escolher um intermediário de mensagens para o meu sistema. Faça estas perguntas, uma por vez, e espere as respostas: - Que tipo de trabalho vai passar pelas filas: tarefas em segundo plano, eventos para vários serviços, ou os dois? - Qual é o volume esperado de mensagens por segundo? - Alguém vai precisar reler mensagens antigas? - A ordem das mensagens importa? Em que escopo? - O sistema está numa nuvem? A equipe tem experiência em operar algum intermediário? Com as respostas, compare para o meu caso uma fila no próprio banco de dados, o RabbitMQ, o Kafka e o serviço gerenciado da minha nuvem. Seja concreto sobre o que cada opção resolve e o que custa operar. Termine com a recomendação, os riscos dela e os sinais que indicariam a necessidade de mudar no futuro.
Tratar falhas, novas tentativas e mensagens mortas
Quero desenhar o tratamento de falhas dos meus consumidores de mensagens. Pergunte qual intermediário eu uso. Explique, para ele: 1. A diferença entre falha temporária e permanente, com exemplos, e como o consumidor distingue as duas. 2. Por que devolver a mensagem para a fila imediatamente é um problema. 3. Como implementar novas tentativas com atraso crescente no meu intermediário. 4. Como configurar o limite de tentativas e a fila de mensagens mortas. 5. O que é uma mensagem envenenada e como ela trava uma fila com ordem garantida. 6. Como monitorar a fila de mensagens mortas e reenviar as mensagens depois de corrigir o problema. Termine com o desenho completo, com as filas, os atrasos e os alertas.
Publicar sem perder mensagens
Quero entender por que gravar no banco e depois publicar uma mensagem pode perder dados, e como resolver. Mostre com linhas do tempo o que dá errado nas duas ordens: gravar e depois publicar, e publicar e depois gravar. Depois explique o padrão outbox: 1. A tabela de saída e o que vai nela. 2. Por que a mensagem é gravada na mesma transação dos dados. 3. Como o processo de publicação lê e marca as mensagens, e o que acontece se ele cair. 4. Por que os consumidores continuam precisando ser idempotentes. 5. As alternativas de leitura: consultar a tabela periodicamente ou capturar mudanças do banco (CDC). Pergunte qual banco e qual intermediário eu uso e mostre como ficaria no meu caso.