Pular para o conteúdo principal
Sumário do livro

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árioO que acontece
Partitions = ConsumersCada consumer processa exatamente 1 partition — paralelismo máximo alcançado
Partitions > ConsumersAlguns consumers processam mais de uma partition — ainda paralelo, mas desbalanceado
Consumers > PartitionsOs 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).

Antes do rebalancePartition 0Partition 1Partition 2Consumer 1Consumer 2Consumer 3Depois (Consumer 2 cai)Partition 0Partition 1Partition 2Consumer 1Consumer 2Consumer 3Consumer 1 assume tambéma Partition 1
Antes: 3 consumers, 1 partition cada. O Consumer 2 cai; depois do rebalance, Consumer 1 e Consumer 3 dividem as 3 partitions entre si.

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.ms gera 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).