Tecnologias/03 - Filas/01 - Kafka6 min
Mensageria e Desacoplamento
Conceito
O problema não é "comunicação entre serviços" — HTTP resolve isso. O problema é acoplamento temporal:
Numa chamada síncrona, A só funciona se B estiver de pé neste instante.
E disponibilidade encadeada multiplica:
A → B → C → D, cada um com 99,9%
disponibilidade da cadeia = 0,999⁴ ≈ 99,6% → ~35 horas de indisponibilidade/ano
Ver Availability in Numbers. Um broker quebra a cadeia: A grava a mensagem e segue. Se B estiver fora, a mensagem espera. B processa quando voltar.
O que um broker realmente te dá
| Ganho | O que significa |
|---|---|
| Desacoplamento temporal | o produtor não espera o consumidor |
| Buffer / nivelamento | pico de 10× é absorvido em vez de derrubar (Queue-Based Load Leveling) |
| Fan-out | um evento, N consumidores, sem o produtor conhecê-los (Publisher-Subscriber) |
| Retry e isolamento de falha | um consumidor quebrado não afeta o produtor |
| Paralelismo de consumo | N workers dividem o trabalho (Competing Consumers) |
| Histórico (só em log) | reprocessar o passado, alimentar um sistema novo |
Os três usos que quase sempre são confundidos
Esta é a distinção mais útil da seção. "Mensageria" nomeia três coisas com requisitos diferentes:
1. Fila de trabalho (task queue) — "faça isto"
Um comando, um executor. Você quer ack por mensagem, retry com backoff, prioridade, DLQ,
e não quer histórico. Ex.: enviar e-mail, gerar PDF, processar upload.
→ RabbitMQ, SQS, Sidekiq, ou SKIP LOCKED no Postgres (Task Queues).
2. Notificação de evento (pub/sub) — "isto aconteceu"
Um fato, N interessados que o produtor nem conhece. Você quer fan-out e roteamento.
Ex.: PedidoPago → faturamento, estoque, e-mail, antifraude.
→ SNS, EventBridge, exchange do RabbitMQ, tópico Kafka.
3. Fluxo de dados (streaming / log) — "esta é a sequência de fatos" O log é a fonte da verdade. Você quer ordem, retenção longa, replay e processamento com estado (janelas, agregações, joins). Ex.: eventos de clique, CDC de banco, telemetria, event sourcing. → Kafka, Kinesis, Pulsar.
| Fila de trabalho | Notificação | Log / stream | |
|---|---|---|---|
| Semântica | comando | fato | sequência de fatos |
| Consumidores | 1 executa | N independentes | N independentes, com histórico |
| Mensagem após consumo | apagada | apagada | permanece até a retenção |
| Reprocessar o passado | não | não | sim (é o ponto) |
| Ordem | raramente importa | raramente importa | importa |
| Ack | por mensagem | por mensagem | por posição (offset) |
| Volume típico | milhares/s | milhares/s | milhões/s |
Escolher a ferramenta errada aqui é a causa mais comum de sofrimento com mensageria: Kafka usado como fila de trabalho (e sofrendo com ack por mensagem) ou SQS usado como log (e descobrindo que não há replay). Ver 5. Kafka vs Fila Tradicional.
Trade-offs
Você trocou uma falha visível por uma invisível. Com HTTP, quando B está fora, A
recebe 503 — feio e óbvio. Com broker, A recebe 200, o usuário vê "sucesso", e a
mensagem apodrece numa fila. Assíncrono não elimina a falha; ele adia e esconde. A
contrapartida obrigatória é monitorar lag e DLQ com o mesmo cuidado que você monitora
erro HTTP.
Consistência passa a ser eventual. O pedido está pago e o estoque ainda não baixou — por 200 ms ou por 4 horas, se o consumidor estiver parado. Toda a UI e todo o suporte precisam conviver com esse intervalo. Ver Eventual Consistency.
Depurar fica muito mais difícil. Um fluxo de 6 mensagens não aparece num stack
trace. Sem correlation_id/trace_id propagado em toda mensagem e sem tracing
distribuído, investigar incidente vira arqueologia. Isto não é opcional — é o custo de
entrada.
Entrega duplicada é a norma, não a exceção. Praticamente todo broker é at-least-once na prática, o que significa que todo consumidor precisa ser idempotente. Ver 4. Garantias de Entrega e Idempotência.
O broker é o novo ponto central. Você removeu o acoplamento entre A e B e criou uma dependência de ambos no broker. Ele precisa de replicação, monitoramento e alguém que saiba operá-lo às 3h da manhã.
O trade-off central: mensageria troca simplicidade por resiliência. Um
POSTque funciona é mais simples que qualquer broker; quando ele não funciona, é o broker que salva o pedido. Só pague esse custo onde a falha síncrona é inaceitável — não por padrão arquitetural.
Exemplo prático
O mesmo caso, síncrono e assíncrono:
❌ Síncrono, acoplado:
POST /pedidos
→ grava pedido no banco
→ chama serviço de estoque (se cair: erro 500 para o usuário)
→ chama serviço de e-mail (se lento: usuário espera pelo SMTP)
→ chama antifraude (se cair: pedido não é criado)
→ 201 Created
Disponibilidade = produto das 4. Latência = soma das 4.
✅ Assíncrono, desacoplado:
POST /pedidos
→ grava pedido no banco ─┐
→ publica PedidoCriado ─┘ na MESMA transação (outbox)
→ 201 Created (latência: só o banco)
PedidoCriado ─┬→ estoque (reserva)
├→ e-mail (confirmação)
└→ antifraude (análise)
Disponibilidade da criação = a do banco. Cada consumidor falha e recupera sozinho.
O detalhe que faz ou quebra esse desenho: gravar no banco e publicar no broker são duas operações que não compartilham transação. Se o processo morre entre elas, o pedido existe e o evento nunca saiu — ou o inverso. A solução é o padrão outbox:
BEGIN;
INSERT INTO pedidos (...) VALUES (...);
INSERT INTO outbox (topico, chave, payload) -- mesma transação!
VALUES ('pedidos.criados', :id, :json);
COMMIT;
-- um publicador separado (ou CDC/Debezium) lê a outbox e publica no Kafka
Agora a atomicidade é local (uma transação, um banco) e a publicação é at-least-once com retry — o que o consumidor já precisava tolerar de qualquer forma. É a peça que quase todo tutorial de mensageria omite e todo sistema em produção precisa. Ver 4. Garantias de Entrega e Idempotência.
Como isso falha em produção
| Sintoma | Causa |
|---|---|
| "O usuário recebeu sucesso mas nada aconteceu" | mensagem na DLQ sem ninguém olhando |
| Evento publicado, dado não gravado (ou vice-versa) | escrita dupla sem outbox |
| Fila crescendo sem parar | consumidor mais lento que o produtor — falta Back Pressure |
| Mesmo e-mail enviado 3 vezes | consumidor não idempotente + reentrega |
| Mensagem travando a fila para sempre | poison pill sem limite de retry e sem DLQ |
| Ninguém sabe por onde passou o pedido | falta de correlation_id e tracing |
Aprofundamento aplicado
Aceitar trabalho não significa concluí-lo
Uma API que responde depois de enfileirar precisa representar o estado pendente. O cliente pode receber um identificador de operação e consultar andamento. Se a resposta promete conclusão enquanto o worker ainda não executou, o contrato fica incorreto.
Quanto tempo leva para recuperar uma fila
Em uma conta ilustrativa, entram 100 tarefas/s e os workers processam 150 tarefas/s.
Um acúmulo de 10 mil tarefas exige cerca de 200 segundos para desaparecer, mantendo
essas taxas: 10000 / (150 - 100). Se entrada e saída são iguais, o acúmulo não diminui.
A conta supõe tarefas equivalentes, taxa estável e ausência de retries. Na prática, meça também idade da mensagem mais antiga e distribuição do tempo de processamento.
Exercício: desenhe o estado pendente → concluído e pendente → falhou, com consulta
pelo ID da operação. Resultado esperado: o usuário consegue distinguir aceitação,
atraso e falha; a indisponibilidade do worker não vira sucesso fictício.
Relacionado ao mecanismo: consumo e confirmação no RabbitMQ.
Relacionado
- 2. Kafka
- 5. Kafka vs Fila Tradicional
- Asynchronism
- Message Queues
- Publisher-Subscriber
- Queue-Based Load Leveling
- Eventual Consistency
- 07 - MOC Desacoplamento e Integração
Parte de MOC Kafka · kafka.apache.org