Tecnologias/03 - Filas/01 - Kafka8 min
Armazenamento, ISR e KRaft
Conceito
Esta nota abre o motor: como a partição vive em disco, como a replicação decide o que está seguro, e o que o KRaft substituiu quando o ZooKeeper saiu no Kafka 4.0.
A partição em disco: segmentos
Uma partição é um diretório, e dentro dele arquivos de segmento:
/var/lib/kafka/pedidos-0/
├── 00000000000000000000.log ← as mensagens (append-only)
├── 00000000000000000000.index ← offset → posição no .log (ESPARSO)
├── 00000000000000000000.timeindex ← tempo → offset (ESPARSO)
├── 00000000000000368421.log ← segmento ativo (o único aberto para escrita)
├── 00000000000000368421.index
└── leader-epoch-checkpoint
- O nome do arquivo é o offset base do segmento. Achar o segmento de um offset é uma busca binária em nomes de arquivo.
- Novo segmento nasce a cada
segment.bytes(1 GB, padrão) ousegment.ms. - Retenção apaga segmentos inteiros, nunca mensagens individuais — é por isso que apagar é barato, e por isso a retenção real é sempre "um pouco mais" que o configurado.
Índice esparso: por que a busca é rápida e a RAM é pouca
O .index não tem uma entrada por mensagem. Ele tem uma a cada index.interval.bytes
(4 KB, padrão):
buscar offset 368_500:
1. busca binária nos nomes de arquivo → segmento 368421
2. busca binária no .index → "offset 368480 está no byte 12.288"
3. leitura sequencial do .log a partir do byte 12.288 até achar 368500
Índice denso custaria gigabytes de RAM; o esparso custa megabytes e paga com uma varredura
curtíssima e sequencial. O .timeindex faz o mesmo para offsetsForTimes — que é o que
permite "me dê tudo a partir de ontem às 14h" (3. Partições, Offsets e Consumer Groups).
Por que é rápido: page cache e zero-copy
O Kafka não tem cache próprio. Ele grava em arquivo e deixa o page cache do SO trabalhar:
produtor → write() → page cache → (o SO decide quando gravar no disco)
consumidor recente → lê do page cache: nunca toca o disco
E na entrega, sendfile() copia do page cache direto para o socket, sem passar pelo
espaço de usuário:
sem zero-copy: disco → kernel → JVM → kernel → NIC (4 cópias, 2 troca de contexto)
com sendfile: disco → kernel → NIC (zero cópias em user space)
Consequências práticas que surpreendem:
- Dê RAM ao SO, não à JVM. Heap de 6 GB é suficiente para a maioria dos brokers; o resto da RAM deve ficar livre para page cache. Heap gigante é desperdício e mais GC.
- TLS e compressão no broker anulam parte do zero-copy (é preciso processar os bytes em user space). É um custo real de ligar criptografia em trânsito.
Replicação: leader, follower, ISR
partição pedidos-0, replication.factor=3
├── broker 1: LEADER (recebe toda escrita e leitura)
├── broker 2: follower ← faz fetch do leader, como um consumidor
└── broker 3: follower
ISR (In-Sync Replicas) = leader + followers que estão "em dia"
(dentro de replica.lag.time.max.ms, padrão 30 s)
E os dois marcadores que governam a visibilidade:
| Marcador | Significa |
|---|---|
| LEO (Log End Offset) | o fim do log de cada réplica |
| HW (High Watermark) | o maior offset replicado em todas as ISR |
Consumidor só lê até o High Watermark. Mensagem que está no leader mas ainda não foi replicada é invisível — é assim que o Kafka evita que você leia algo que uma falha de leader pode desfazer.
A matriz de durabilidade sai daí:
acks |
O produtor espera | Perde mensagem? |
|---|---|---|
0 |
nada | sim, sem aviso |
1 |
o leader gravou | sim, se o leader cair antes de replicar |
all |
todas as ISR gravaram | não, se min.insync.replicas ≥ 2 |
E a combinação canônica:
replication.factor = 3
min.insync.replicas = 2 # com acks=all: 2 réplicas devem confirmar
acks = all # (no produtor)
unclean.leader.election.enable = false
Com isso: tolera um broker fora sem perder escrita e sem parar. Com
min.insync.replicas = 3, perder um broker para a escrita (NotEnoughReplicas) — mais
seguro, menos disponível (Availability vs Consistency).
unclean.leader.election.enable = true perde dados por projetoEle permite que uma réplica fora do ISR (atrasada) se torne leader quando todas as
ISR estão fora. O cluster volta a aceitar escrita, e tudo que existia além do log dessa
réplica desaparece. É escolher disponibilidade sacrificando durabilidade — e o
padrão false está certo para quase todo mundo.
Leader epoch: o detalhe que corrigiu divergência silenciosa
Cada troca de leader incrementa um epoch. Um follower que volta pergunta "qual era o fim do log no epoch N?" antes de truncar o próprio log. Sem isso (Kafka antigo), havia cenários de divergência silenciosa entre réplicas após falhas sucessivas. É o tipo de correção invisível que faz um sistema distribuído ser confiável.
KRaft: o log de metadados
Antes do 4.0, o ZooKeeper guardava metadados (tópicos, partições, ISR, ACLs) e elegia o controller. O KRaft substitui isso por um log Raft interno:
tópico interno __cluster_metadata (replicado por Raft entre os controllers)
├── controller ativo = o LÍDER Raft
└── controllers em espera = seguidores, com o estado já em memória
Ganhos concretos:
| ZooKeeper (≤3.x) | KRaft (4.0+) | |
|---|---|---|
| Sistemas a operar | dois | um |
| Failover de controller | recarregava metadados do ZK (lento) | seguidor já tem o estado: quase instantâneo |
| Escala de partições | ~200 mil por cluster | milhões |
| Propagação de metadados | notificações (watches) | os brokers consomem o log |
Os brokers agora acompanham metadados como se fossem consumidores de um tópico — o Kafka
passou a usar a própria abstração para se gerenciar. Se você encontrar documentação com
zookeeper.connect, ela é anterior ao 4.0 e não se aplica mais (2. Kafka).
Tiered storage
Segmentos antigos são movidos para object storage (S3, GCS), mantendo só os recentes em disco local:
disco local: últimas horas/dias (leitura quente, zero-copy)
S3: semanas/meses (leitura fria, sob demanda)
Isso desacopla retenção de custo de disco local e é o que torna viável guardar meses de log — o pré-requisito prático para tratar o Kafka como fonte da verdade e não só como transporte.
Trade-offs
Page cache é brilhante e frágil ao padrão de acesso. Consumidor em tempo real lê da RAM. Um consumidor que faz replay de 3 dias atrás varre o disco e suja o page cache, degradando os consumidores em tempo real. Mitigações: réplicas dedicadas a replay, follower fetching, ou tiered storage.
Segmento grande é eficiente e granular demais para retenção. Com
segment.bytes = 1 GB e pouco volume, um segmento pode levar dias para fechar — e a
retenção só age em segmento fechado. Em tópicos de baixo volume com retenção curta,
reduza segment.ms.
min.insync.replicas é o botão que ninguém quer configurar e todos precisam. É
literalmente onde você escolhe entre "perder mensagem" e "parar de aceitar mensagem". Não
existe resposta universal; existe resposta por tópico.
O trade-off central: o Kafka obtém throughput extremo delegando ao SO (arquivo sequencial, page cache,
sendfile) e ao operador (ISR, acks, retenção). É simples por dentro e exigente por fora: as decisões que outros sistemas tomam para você, aqui são configuração explícita — e cada uma delas é uma decisão de durabilidade.
Exemplo prático
Configuração por criticidade do tópico
# Tópico crítico (financeiro): durabilidade acima de tudo
kafka-topics.sh --create --topic pagamentos --partitions 12 \
--replication-factor 3 \
--config min.insync.replicas=2 \
--config retention.ms=2592000000 \
--config compression.type=zstd \
--config unclean.leader.election.enable=false
# Tópico de estado (compactado): o último valor de cada chave, para sempre
kafka-topics.sh --create --topic perfis --partitions 6 --replication-factor 3 \
--config cleanup.policy=compact \
--config min.cleanable.dirty.ratio=0.5 \
--config delete.retention.ms=86400000 # janela para tombstones serem vistos
# Tópico de telemetria: volume alto, perder é aceitável
kafka-topics.sh --create --topic metricas --partitions 24 --replication-factor 2 \
--config min.insync.replicas=1 \
--config retention.ms=86400000 \
--config segment.ms=3600000
Verificar saúde de replicação
# partições sub-replicadas: o alerta nº 1 de um cluster Kafka
kafka-topics.sh --bootstrap-server kafka:9092 --describe --under-replicated-partitions
# vazio = saudável. Qualquer linha = uma réplica saiu do ISR.
# partições sem leader: indisponibilidade real
kafka-topics.sh --bootstrap-server kafka:9092 --describe --unavailable-partitions
# quorum de metadados do KRaft
kafka-metadata-quorum.sh --bootstrap-server kafka:9092 describe --status
kafka-metadata-quorum.sh --bootstrap-server kafka:9092 describe --replication
Métricas JMX que valem alerta:
| Métrica | Alerta |
|---|---|
UnderReplicatedPartitions |
> 0 por mais de alguns minutos |
OfflinePartitionsCount |
> 0 (sempre) |
ActiveControllerCount |
≠ 1 no cluster (0 = sem controller; 2 = split brain) |
IsrShrinksPerSec |
picos frequentes = broker ou disco com problema |
RequestHandlerAvgIdlePercent |
< 30% = brokers saturados |
MaxLag / consumer lag |
crescendo (3. Partições, Offsets e Consumer Groups) |
Como isso falha em produção
| Sintoma | Causa |
|---|---|
UnderReplicatedPartitions > 0 |
broker lento/fora, disco saturado, rede entre brokers |
NotEnoughReplicasException no produtor |
ISR abaixo de min.insync.replicas: o tópico está protegendo o dado |
| Mensagem perdida após falha de broker | acks=1, ou unclean.leader.election ligado |
| Latência dos consumidores em tempo real degradou | alguém iniciou um replay e sujou o page cache |
| Disco cheio mesmo com retenção curta | segmento ativo nunca fecha (segment.ms alto e volume baixo) |
| Tombstone "não apagou" o registro | delete.retention.ms expirou antes do consumidor ler |
| Broker com GC longo | heap grande demais; devolva RAM ao page cache |
| Cluster sem controller ativo | quorum KRaft sem maioria (menos de 2 de 3 controllers) |
Aprofundamento aplicado
Réplicas de dados e quorum de metadados
O quorum de controllers mantém decisões de metadados no KRaft. Réplicas das partições mantêm registros dos tópicos. Um controller saudável não significa que todas as partições possuam réplicas suficientes para aceitar escritas com a garantia exigida.
Cenário: fator de replicação 3, acks=all e min.insync.replicas=2.
Se só uma réplica permanece no ISR, o produtor deve lidar com falha de escrita nessa
configuração. Relaxar a exigência recupera disponibilidade ao alterar o risco aceito.
Referência: replicação.
Diagnóstico por cadeia
Ao ver aumento de latência no produtor, compare partições afetadas, ISR, armazenamento, rede e erros. Ao ver atraso de consumo, verifique se ele nasce no processamento do cliente ou na disponibilidade dos dados. “O Kafka está lento” junta problemas distintos.
Exercício: represente perda de um broker e perda da maioria de controllers. Resultado esperado: explicar qual responsabilidade é afetada em cada caso, sem inferir que dois quorums diferentes são o mesmo mecanismo.
Relacionado
- 2. Kafka
- 3. Partições, Offsets e Consumer Groups
- 4. Garantias de Entrega e Idempotência
- 6. Kafka Streams e Connect
- 5. Kafka vs Fila Tradicional
- Availability vs Consistency
- Kinesis
Parte de MOC Kafka · Design (Kafka docs)