trilha

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 POST que 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


Parte de MOC Kafka · kafka.apache.org

Buscar

Busca por título, seção e texto das notas