Tecnologias/03 - Filas/01 - Kafka6 min
Partições, Offsets e Consumer Groups
Conceito
Três conceitos, e praticamente tudo o que dá errado com Kafka em produção mora aqui.
Partição: paralelismo e ordem, no mesmo botão
A partição é o log de verdade. O tópico é só um nome para um conjunto de partições.
chave da mensagem → hash(chave) % nº de partições → partição
sem chave → distribuição por lotes entre as partições (sticky)
Duas garantias saem daí, e é vital não confundi-las:
- Ordem dentro da partição: garantida, sempre.
- Ordem entre partições: não existe. Não há relógio global.
Então "quero processar em ordem" nunca é uma configuração — é uma escolha de chave:
| Chave | O que fica ordenado | Paralelismo |
|---|---|---|
null |
nada | máximo |
cliente_id |
todos os eventos de um cliente | por cliente |
pedido_id |
o ciclo de vida de um pedido | por pedido |
| constante | tudo (ordem total) | 1 — uma partição só |
A última linha é a armadilha: ordem total em Kafka significa uma partição, ou seja, throughput de um único consumidor. Ordem global e escala são incompatíveis por construção — a mesma lição de Sequential Convoy e de ordenação global exige consenso.
Offset: a posição, não a mensagem
Cada mensagem tem um número sequencial dentro da partição. O consumidor guarda "estou no
offset 4.712 da partição 2" no tópico interno __consumer_offsets.
Isso é o que dá ao Kafka superpoderes que fila não tem:
# reprocessar tudo desde o começo
kafka-consumer-groups.sh --reset-offsets --to-earliest --group faturamento \
--topic pedidos.criados --execute
# voltar para 2 horas atrás (depois de descobrir um bug no consumidor)
kafka-consumer-groups.sh --reset-offsets --to-datetime 2026-09-08T12:00:00.000 ...
# pular uma mensagem envenenada
kafka-consumer-groups.sh --reset-offsets --shift-by 1 ...
Corrigir um bug de processamento é reposicionar um ponteiro e deixar rodar. Numa fila tradicional, o dado já foi apagado — e não há para onde voltar.
Consumer group: divisão do trabalho
A regra fundamental:
Dentro de um grupo, cada partição é atribuída a exatamente um consumidor. Um consumidor pode ter várias partições. Consumidor além do número de partições fica ocioso.
Tópico com 4 partições:
1 consumidor → ele fica com P0,P1,P2,P3
2 consumidores→ 2 partições cada
4 consumidores→ 1 cada ← paralelismo máximo
6 consumidores→ 4 trabalham, 2 PARADOS (não "ajudam")
E grupos diferentes consomem o mesmo tópico de forma independente, cada um com seus offsets — é assim que fan-out funciona (Publisher-Subscriber):
pedidos.criados ─┬→ grupo "faturamento" (offset próprio)
├→ grupo "estoque" (offset próprio)
└→ grupo "analytics" (offset próprio, 3 dias atrás, tudo bem)
Rebalance
Quando um consumidor entra, sai ou morre, o grupo redistribui partições. Historicamente isso era um stop-the-world: todos param, devolvem tudo, recebem de novo.
A evolução:
| Estratégia | Comportamento |
|---|---|
| Eager (antigo) | todos param e reatribuem tudo |
| Cooperative sticky | só as partições que mudam de dono são revogadas |
| KIP-848 (GA no Kafka 4.0) | coordenação no servidor, incremental; rebalance muito mais rápido e sem depender do consumidor mais lento |
Share groups: a exceção à regra (Kafka 4.2)
Os share groups, GA no 4.2, quebram deliberadamente a regra de "uma partição, um consumidor":
| Consumer group | Share group | |
|---|---|---|
| Consumidores por partição | 1 | vários |
| Teto de paralelismo | nº de partições | nenhum |
| Unidade de ack | offset (posição) | mensagem individual |
| Ordem | por partição | não garantida |
| Redelivery | reposicionar offset | por mensagem, com estado |
Ou seja: você escolhe por grupo se quer semântica de log (ordem, replay) ou de fila (paralelismo livre, ack individual) — no mesmo tópico. É a mudança conceitual mais relevante do Kafka desde o KRaft. Ver 5. Kafka vs Fila Tradicional e Competing Consumers.
Trade-offs
Partições demais também é problema. Cada partição custa descritores de arquivo,
memória e tempo de recuperação por broker, e mais requisições por lote. A regra é
dimensionar pelo throughput desejado (vazão alvo / vazão por consumidor) com folga
de 2–3×, não "1000 partições por garantia".
Aumentar partições quebra a ordem histórica. hash(chave) % N muda quando N muda:
mensagens antigas de um cliente estão em P1 e as novas vão para P3. Se algum consumidor
depende de ordem por chave, esse é um evento de migração, não um ajuste de capacidade.
Commit antes ou depois define a garantia. Commit antes de processar = at-most-once (pode perder). Commit depois = at-least-once (pode duplicar). Não existe terceira opção sem transação. Ver 4. Garantias de Entrega e Idempotência.
Auto-commit é conveniente e traiçoeiro. Com enable.auto.commit=true, o offset
avança em segundo plano a cada intervalo — inclusive de mensagens que você ainda não
terminou de processar. Se o processo cair no meio, elas somem. Em produção: commit
manual, sempre depois do trabalho.
O trade-off central: partição é o único botão do Kafka, e ele controla duas coisas ao mesmo tempo — quanto você paraleliza e o quanto fica ordenado. Toda decisão aqui é escolher onde nesse eixo você quer ficar. Os share groups do 4.2 finalmente separam os dois botões, para quem dispensa a ordem.
Exemplo prático
Dimensionar partições
Vazão alvo: 30.000 msg/s
Um consumidor processa: 2.000 msg/s (medido, não estimado!)
Consumidores necessários: 15
Partições (com folga 2×): 32 ← escolha
Meça o consumidor fazendo o trabalho real (incluindo a escrita no banco), não lendo para o vazio. O gargalo quase nunca é o Kafka.
Escolher a chave
// ❌ Sem chave: máximo paralelismo, zero ordem.
// Um PedidoAtualizado pode ser processado ANTES do PedidoCriado.
cl.Produce(ctx, &kgo.Record{Topic: "pedidos", Value: payload}, nil)
// ✅ Chave = a entidade cujo ciclo de vida precisa de ordem
cl.Produce(ctx, &kgo.Record{
Topic: "pedidos",
Key: []byte(pedido.ID), // todos os eventos deste pedido, na mesma partição, em ordem
Value: payload,
}, nil)
Publicar PedidoCriado e PedidoPago sem chave, ou com chaves diferentes. Eles vão
para partições diferentes, são processados por consumidores diferentes, e o PedidoPago
chega antes. O consumidor então cria um pedido "pago" sem saber o que foi comprado —
e o bug aparece em 0,1% dos casos, só sob carga.
Monitorar o que importa
kafka-consumer-groups.sh --bootstrap-server kafka:9092 \
--describe --group faturamento
# TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG CONSUMER-ID
LAG é a métrica número um de todo sistema com Kafka — mais importante que CPU, mais
importante que memória. Lag estável (mesmo que alto) é saudável. Lag crescendo é um
incidente em andamento, e o alerta deve ser sobre a derivada, não sobre o valor
absoluto.
Como isso falha em produção
| Sintoma | Causa |
|---|---|
| Metade dos consumidores ociosa | mais consumidores que partições |
| Rebalance a cada poucos minutos | processamento excede max.poll.interval.ms; reduza max.poll.records ou processe em paralelo dentro do consumidor |
| Uma partição sempre atrasada | key skew — uma chave concentra o volume |
| Mensagens reprocessadas depois de deploy | consumidor morto sem commit; esperado em at-least-once |
| Mensagens perdidas depois de deploy | auto-commit avançou offset de mensagem não processada |
| Ordem quebrada após aumentar partições | mudou o hash % N |
LAG negativo ou grupo "vazio" |
grupo sem membros (todos caíram) ou offsets resetados |
Aprofundamento aplicado
Conclusão fora de ordem e commit prematuro
Considere um consumidor comum, com processamento paralelo de registros de uma partição:
registro 10: ainda processando
registro 11: concluído
registro 12: concluído
Confirmar a posição 13 indica que, ao reiniciar, o grupo pode retomar dali. Isso pularia o registro 10 se seu efeito não terminou. O commit deve acompanhar o maior prefixo contíguo concluído; terminar um registro mais adiante não fecha o buraco anterior.
A mesma análise vale durante revogação de partições: parar novas tarefas, coordenar as existentes e confirmar apenas progresso seguro. Consumidores baseados em share groups têm outro modelo de confirmação e não devem ser confundidos com esse exemplo. Referência: consumer offsets e garantias.
Exercício: conclua 12, depois 10, depois 11. Resultado esperado: após 12 isolado, o próximo offset seguro continua 10; depois de 10, passa a 11; após 11, pode chegar a 13.
Relacionado
- 2. Kafka
- 4. Garantias de Entrega e Idempotência
- 5. Kafka vs Fila Tradicional
- Competing Consumers
- Sequential Convoy
- Idempotência e Ordenação
- Back Pressure
Parte de MOC Kafka · Consumer (Kafka docs)