Pular para o conteúdo

Papers

Kafka: a Distributed Messaging System for Log Processing

Jay Kreps, Neha Narkhede, Jun Rao · NetDB 2011, 2011

O problema: dados de log viraram parte do produto, não só de analytics

Em empresas de internet como o LinkedIn, o volume de dados de "log" — eventos de cliques, visualizações de página, curtidas, buscas, além de métricas operacionais de cada máquina — é ordens de grandeza maior que o volume de dados "reais" do produto. O que mudou é que esse fluxo deixou de servir só para relatórios offline: passou a alimentar funcionalidades em produção, como relevância de busca, recomendações, segmentação de anúncios, detecção de abuso e feeds de atividade. Isso cria uma exigência nova: os mesmos dados precisam chegar tanto a cargas de trabalho offline (carregadas em um data warehouse ou Hadoop) quanto a consumidores online com atraso de poucos segundos. Os sistemas de mensageria corporativos tradicionais (como filas compatíveis com JMS) não se encaixavam bem porque priorizavam garantias de entrega ricas e transacionais que são desnecessárias para logs — perder um evento de pageview ocasional não é grave — e não foram desenhados para o throughput exigido nem para particionar dados entre várias máquinas. Já os agregadores de log especializados que já existiam (como o Scribe do Facebook ou o Flume da Cloudera) resolviam bem a carga offline para um data warehouse, mas não ofereciam uma API de consumo em tempo real equivalente a um sistema de mensageria. O Kafka nasceu para preencher exatamente essa lacuna: uma única peça de infraestrutura capaz de atender tanto consumo offline quanto online do mesmo fluxo de dados.

Arquitetura central: topics, partitions, brokers e o modelo pull

No Kafka, um fluxo de mensagens de um determinado tipo é chamado de topic. Produtores publicam mensagens em um topic, que ficam armazenadas em um conjunto de servidores chamados brokers; consumidores se inscrevem em um ou mais topics e puxam (pull) as mensagens dos brokers, em vez de esperar que o broker as empurre (push) — escolha deliberada, já que o modelo pull permite que cada consumidor puxe dados na velocidade que consegue processar, sem ser inundado, e facilita "rebobinar" a leitura para um offset anterior. Para permitir escala horizontal, cada topic é dividido em múltiplas partitions, e cada partition é armazenada fisicamente como um log simples: um conjunto de arquivos de segmento aos quais o broker apenas faz append sequencial. Cada mensagem não tem um ID explícito — ela é identificada pelo seu offset (posição) dentro do log daquela partition, o que evita manter estruturas de índice caras. Consumidores organizados em consumer groups dividem entre si as partitions de um topic: cada partition é consumida por exatamente um consumidor daquele grupo por vez, o que elimina a necessidade de lock coordenado entre consumidores da mesma partition. A coordenação entre brokers e consumidores (quem é dono de qual partition, quais brokers estão vivos) é feita de forma descentralizada, usando o serviço de coordenação ZooKeeper, em vez de um nó mestre central.

Decisões de design 'pouco convencionais': simplicidade em troca de throughput

O paper descreve várias escolhas deliberadamente simples para maximizar throughput. Primeiro, o Kafka evita cache de mensagens na própria aplicação e se apoia no page cache do sistema operacional, já que produtor e consumidor acessam os arquivos de segmento de forma sequencial — isso evita duplicar o buffer em memória e mantém o cache 'quente' mesmo se o broker reiniciar. Segundo, para transferir dados de um arquivo em disco para a rede, o Kafka usa a chamada de sistema sendfile, que evita cópias intermediárias desnecessárias entre kernel e aplicação. Terceiro, o broker é 'stateless' em relação ao consumo: ele não guarda quais mensagens cada consumidor já leu — é o próprio consumidor que mantém seu offset. Isso simplifica o broker, mas cria um problema: como saber quando apagar uma mensagem, se o broker não sabe quem já a consumiu? A solução foi uma política de retenção baseada em tempo (por exemplo, manter mensagens por um período fixo, como sete dias), e não em confirmação de leitura — um trade-out explícito de garantia de entrega individual por simplicidade operacional. Um efeito colateral bem-vindo dessa escolha é que o consumidor pode voltar deliberadamente a um offset anterior e reprocessar dados, útil para corrigir erros de lógica na aplicação consumidora.

A garantia que o paper assume: ordem só dentro da partition, entrega 'ao menos uma vez'

O Kafka descrito no paper garante apenas entrega 'at-least-once' (ao menos uma vez): na maior parte do tempo cada mensagem chega exatamente uma vez a cada consumer group, mas se um consumidor falhar sem um desligamento limpo, o consumidor que assumir suas partitions pode reprocessar mensagens já entregues após o último offset confirmado — cabendo à aplicação implementar sua própria deduplicação se isso for um problema. Os autores justificam essa escolha dizendo que entrega 'exactly-once' normalmente exigiria commits em duas fases, custo que julgaram desnecessário para o domínio de logs. Outra garantia explícita e limitada: mensagens de uma mesma partition são entregues ao consumidor na ordem em que chegaram, mas não há nenhuma garantia de ordem entre mensagens de partitions diferentes do mesmo topic. Na época do paper, o Kafka também não tinha replicação embutida entre brokers — se um broker caísse e seu armazenamento fosse danificado permanentemente, as mensagens não consumidas daquele broker seriam perdidas; os próprios autores citam isso como trabalho futuro. Esse conjunto de escolhas ilustra o trade-off central do paper: abrir mão de garantias fortes e de recursos ricos (como transações multi-fila) em troca de throughput muito maior e uma implementação mais simples de operar em escala.

Legado: de ferramenta interna do LinkedIn a padrão de plataformas de streaming

No momento do paper, o Kafka já processava algumas centenas de gigabytes de novos dados por dia e cerca de um bilhão de mensagens por dia na infraestrutura do LinkedIn, com latência ponta a ponta de cerca de dez segundos entre a geração de um evento e sua disponibilidade para consumo offline. Os experimentos comparativos do próprio paper mostraram o Kafka publicando dezenas a centenas de milhares de mensagens por segundo (dependendo do tamanho do lote de envio), superando claramente sistemas de mensageria estabelecidos da época como ActiveMQ e RabbitMQ nos testes realizados pelos autores, em grande parte por causa do batching, do formato de armazenamento mais enxuto e do uso de sendfile. Os próprios autores apontam como próximos passos adicionar replicação nativa entre brokers e capacidades de processamento de streams (como agregações em janelas de tempo) diretamente sobre o Kafka. Esses dois pontos viriam, nos anos seguintes, a se tornar pilares do ecossistema que cresceu ao redor do projeto (replicação de partitions entre brokers e uma camada de processamento de streams), consolidando a ideia central do paper — um log distribuído, particionado e persistente como camada de transporte — como um dos padrões mais usados hoje para conectar sistemas dentro de arquiteturas orientadas a eventos.

Veja as ideias em ação

Pratique na escola de System Design os desenhos que usam este paper.