Parte II — Arquitetura
Consumer Groups e Rebalance
Como um Consumer Group divide partitions entre consumers, o que acontece quando essa divisão muda, e o custo real de um rebalance.
Nesta página
O Capítulo 3 definiu Consumer Group como "o mecanismo que permite tanto dividir trabalho quanto compartilhar o mesmo dado entre sistemas independentes". Este capítulo abre essa definição: como a divisão de partitions funciona na prática, o que acontece quando o número de consumers muda, e por que rebalance é um dos tópicos mais mal compreendidos do Kafka em produção.
Como um Consumer Group divide o trabalho
Consumer Group
Um Consumer Group é um conjunto de instâncias de consumer identificadas pelo mesmo group.id. O Kafka
garante que, a qualquer momento, cada partition de um tópico assinado é atribuída a exatamente um consumer
do grupo — nunca dois consumers do mesmo grupo lendo a mesma partition simultaneamente.
Essa regra — uma partition, um consumer, por grupo — é o que torna o paralelismo de consumo previsível: se um tópico tem 6 partitions e o grupo tem 3 consumers, cada consumer processa, em média, 2 partitions. Isso também define o limite superior de paralelismo: não existe forma de um Consumer Group processar um tópico com mais paralelismo do que o número de partitions permite.
O que acontece com números diferentes de consumers
Partitions vs. consumers no mesmo Consumer Group
| Cenário | O que acontece |
|---|---|
| Partitions = Consumers | Cada consumer processa exatamente 1 partition — paralelismo máximo alcançado |
| Partitions > Consumers | Alguns consumers processam mais de uma partition — ainda paralelo, mas desbalanceado |
| Consumers > Partitions | Os consumers excedentes ficam ociosos — nenhuma partition sobra para atribuir a eles |
Consumers ociosos não são um bug — são um limite conhecido
Se um tópico tem 4 partitions e o Consumer Group escala para 6 instâncias, 2 delas simplesmente não recebem nenhuma partition e ficam ociosas, sem processar nada, até que uma das outras 4 caia ou o número de partitions aumente. Isso costuma surpreender times que escalam consumers via Kubernetes esperando ganho de throughput proporcional, sem considerar o número de partitions do tópico como teto.
O que dispara um rebalance
Rebalance
Rebalance é o processo de redistribuir as partitions de um tópico entre os consumers ativos de um grupo,
disparado sempre que a composição do grupo muda: um consumer entra, um consumer sai voluntariamente, ou um
consumer é declarado morto por não enviar heartbeat dentro do tempo configurado (session.timeout.ms).
O gatilho mais comum em produção não é uma queda abrupta de processo — é um consumer que demora demais para
processar um lote de mensagens (max.poll.interval.ms excedido) e é considerado morto pelo grupo mesmo
continuando vivo, só lento. Esse cenário gera um rebalance desnecessário que expulsa um consumer saudável, e
é uma causa clássica de "rebalances infinitos" em produção quando o processamento por mensagem é
consistentemente mais lento que o intervalo configurado.
O impacto de um rebalance
Na estratégia clássica ("eager rebalance"), todos os consumers do grupo revogam todas as suas partitions no início do rebalance, mesmo aquelas que continuarão com o mesmo consumer depois — o grupo inteiro para de processar durante a janela de reatribuição. Em grupos grandes ou com processamento pesado por partition, essa pausa pode ser perceptível.
Cooperative rebalancing reduz, mas não elimina, o impacto
Estratégias mais recentes de rebalance (cooperative sticky assignor) revogam apenas as partitions que realmente precisam mudar de dono, permitindo que consumers não afetados continuem processando durante a transição. Isso reduz a janela de impacto, mas não a elimina: as partitions que efetivamente trocam de consumer ainda passam por uma pausa de reatribuição.
Escalabilidade com Kubernetes
Escalar um Consumer Group horizontalmente (por exemplo, via HPA no Kubernetes, reagindo a consumer lag) tem uma consequência direta: cada evento de scale up ou scale down do deployment dispara um rebalance, porque o número de consumers do grupo muda. Um HPA configurado de forma agressiva (escalando e desescalando com frequência) pode gerar rebalances constantes, degradando o throughput que a escala deveria melhorar — o oposto do efeito pretendido.
Dica de entrevista
Se perguntarem sobre escalar consumers no Kubernetes, mencione que o número de partitions do tópico é o teto real de paralelismo — escalar o deployment além desse número não aumenta throughput, só produz consumers ociosos — e que HPAs devem ter cooldown adequado para não causar rebalances excessivos.
Quando isso vira problema arquitetural
- Dimensionar o número de partitions de um tópico sem considerar o número máximo de consumers que o time pretende escalar limita a escala futura sem uma migração de tópico (Capítulo 4).
- Processamento por mensagem mais lento que
max.poll.interval.msgera rebalances recorrentes, mesmo sem qualquer falha real de infraestrutura — o sintoma parece instabilidade do Kafka, mas a causa é configuração de timeout desalinhada com o tempo de processamento real. - Múltiplos Consumer Groups lendo o mesmo tópico não competem por partitions entre si — um rebalance em um grupo não afeta os demais grupos lendo o mesmo tópico.
Como aparece em entrevistas
"O que acontece quando um consumer cai?" é quase garantida em qualquer entrevista sobre Kafka em produção. A
resposta fraca é "o Kafka redistribui as partitions automaticamente". A resposta forte explica o mecanismo:
detecção via heartbeat/session.timeout.ms, rebalance redistribuindo partitions entre os consumers
restantes, e o impacto de pausa (parcial ou total, dependendo da estratégia de assignor) durante a
transição.
Relação com Java e Spring Boot
Em Spring Kafka, o group.id é configurado no @KafkaListener (ou na configuração global do
ConsumerFactory), e concurrency define quantas threads consumidoras a própria aplicação instancia
internamente para esse listener — cada thread se comporta como um membro adicional do Consumer Group do
ponto de vista do Kafka. Configurar concurrency maior que o número de partitions disponíveis para essa
instância cria threads ociosas, pelo mesmo motivo do Cenário "Consumers > Partitions" acima.
Escalando o serviço de antifraude
O antifraude-service roda com 4 réplicas no Kubernetes, consumindo um tópico com 8 partitions — paralelismo
saudável, 2 partitions por réplica. Em um Black Friday, o time aumenta para 12 réplicas esperando mais
throughput, mas o tópico continua com 8 partitions: 4 réplicas ficam ociosas, e o throughput real não muda,
porque o teto de paralelismo é o número de partitions, não o número de pods.
Resumo
Um Consumer Group garante que cada partition é processada por exatamente um consumer do grupo por vez, com o número de partitions definindo o teto de paralelismo. Rebalance redistribui partitions sempre que a composição do grupo muda — consumer entra, sai, ou é declarado morto por timeout — e tem um custo real de pausa, reduzido mas não eliminado por estratégias cooperativas. Escalar consumers além do número de partitions gera consumers ociosos, não mais throughput.
Pode vir a seguir
Prováveis follow-ups: "o que é max.poll.interval.ms e por que ele causa rebalances?", "qual a diferença
entre eager e cooperative rebalancing?" e "como você monitoraria rebalances excessivos em produção?"
(Capítulo 15).