o problema
Uma empresa de internet de porte gera muito mais dado de log do que dado “real”. Cada clique vira registro, e também os dezenas de itens da página que não foram clicados. O paper cita a ordem de grandeza da época: a China Mobile coletava de 5 a 8 TB de registros de chamada por dia, o Facebook reunia quase 6 TB de eventos de atividade. Até então esse material era assunto de analytics offline. O que mudou é que ele passou a alimentar a produção: relevância de busca, recomendação por popularidade e coocorrência, segmentação de anúncio, defesa contra spam e scraping, feed de atividade. Isso exige o mesmo dado com atraso de segundos, não de horas.
As duas famílias de sistema disponíveis erravam por lados opostos. Os agregadores de log — Scribe, o Data Highway do Yahoo, o Flume da Cloudera — despejavam tudo em HDFS para consumo offline, vazavam detalhe de implementação para o consumidor (os “minute files”) e empurravam dado para quem lia, no modelo push. Os sistemas de mensageria corporativa (Websphere MQ, implementações de JMS, ActiveMQ, RabbitMQ) ofereciam garantia de entrega cara demais para pageview: transação entre filas, ack individual e fora de ordem. Pior: o JMS não tinha API para o produtor agrupar mensagens, o que custava um roundtrip TCP/IP por mensagem, e o desempenho desses sistemas degradava justamente quando a fila crescia — que é o estado normal de um consumidor de data warehouse que carrega em lote.
a ideia
Parar de tratar mensagem como item de fila a ser removido depois de entregue, e tratar cada partição de um topic como um arquivo de log ao qual só se acrescenta no fim. A posição de leitura deixa de ser estado do servidor e vira um número inteiro que o cliente carrega. Consumidor puxa em vez de receber empurrado, no ritmo que aguenta. E o dado morre por idade, não por consumo.
como funciona
Um topic é dividido em partições; cada broker guarda uma ou mais. A partição é um log lógico implementado como um conjunto de arquivos de segmento de tamanho parecido (por exemplo, 1 GB). Publicar é acrescentar ao último segmento. O flush para disco acontece depois de N mensagens ou de um intervalo, e a mensagem só fica visível ao consumidor depois do flush.
Não existe id de mensagem: o endereço é o offset. Os ids crescem mas não são consecutivos — o próximo é o atual mais o comprimento da mensagem. Cada broker mantém em memória uma lista ordenada com o offset da primeira mensagem de cada segmento; com ela localiza o arquivo e devolve os bytes. O pull carrega offset inicial e quantidade aceitável de bytes, tipicamente centenas de kilobytes por requisição.
Duas escolhas de I/O sustentam o resto. O Kafka não guarda mensagem em cache no processo: usa o page cache do sistema operacional, o que evita double buffering, mantém o cache quente mesmo se o processo reinicia e deixa a heap praticamente vazia. E usa sendfile para mandar bytes do arquivo direto para o socket, cortando 2 das 4 cópias e 1 das 2 chamadas de sistema do caminho tradicional.
A coordenação é descentralizada, sem nó master. Consumidores formam grupos: dentro do grupo cada mensagem vai para um só consumidor, entre grupos cada um recebe a cópia inteira. Zookeeper guarda os registros de broker, de consumidor e de ownership como caminhos efêmeros, e o registro de offset como persistente. Quando um broker ou consumidor entra ou sai, o watcher dispara o rebalance: o consumidor lê os registros, ordena partições e consumidores, calcula |PT|/|CT| e pega deterministicamente o seu pedaço. Se dois tentarem a mesma partição, quem chegou depois solta tudo, espera e repete — na prática estabiliza em poucas tentativas. Cada mensagem carrega um CRC no log, e mensagens com CRC inconsistente são removidas na recuperação.
o que isso custou
A garantia é at-least-once. Se um consumidor cai sem shutdown limpo, quem assume as partições pode reprocessar o que passou do último offset gravado no Zookeeper; a deduplicação é problema da aplicação. Os autores afirmam a escolha: two-phase commit não valia o preço. A ordem existe dentro de uma partição e não existe entre partições.
Na versão descrita, não havia replicação. Broker fora do ar significa mensagem não consumida indisponível; disco destruído significa mensagem perdida para sempre. O produtor também não esperava ack — foi assim que o benchmark chegou a 50.000 mensagens por segundo com lote de 1 e 400.000 com lote de 50, saturando o link de 1 Gb. O paper reconhece na mesma frase que sem ack não há garantia de recebimento, e trata isso como troca aceitável para log.
O benchmark é de uma máquina contra outra, um produtor, um consumidor, 10 milhões de mensagens de 200 bytes. No consumo, 22.000 mensagens por segundo contra pouco mais de um quarto disso nos concorrentes, explicado em parte pelo formato: 9 bytes de overhead por mensagem no Kafka contra 144 no ActiveMQ. Os próprios autores fecham a seção dizendo que ActiveMQ e RabbitMQ têm mais recursos e que o ponto é o ganho possível quando o sistema é especializado.
Há ainda custos estruturais. Retenção por tempo — 7 dias no LinkedIn — quer dizer que consumidor atrasado perde dado em silêncio. E como uma partição é lida por um único consumidor do grupo, é preciso superparticionar o topic desde o começo para ter folga de paralelismo.
onde isso aparece hoje
O próprio paper aponta o que faltava, e as duas lacunas viraram o roteiro do projeto: replicação entre brokers, com escolha entre síncrona e assíncrona, e processamento de stream sobre partição por chave de join — a base do que hoje se chama Kafka Streams. O detalhe de operação que o LinkedIn já usava, particionar semanticamente para que mensagens da mesma chave caiam no mesmo consumidor, continua sendo como se monta pipeline de agregação.
O modelo de log durável e replayable virou infraestrutura padrão de dado em tempo real, com todas as questões de janela, atraso e ordem que ele deixa em aberto sendo tratadas depois em The Dataflow Model. A operação do Kafka em nuvem, com os problemas de multi-tenancy e elasticidade que o paper de 2011 nem tinha como enxergar, aparece em Kora, e o papel dele como espinha dorsal de uma arquitetura inteira está descrito em Real-time Data Infrastructure at Uber.