antonio leandro

sistemas distribuídos

Kora: A Cloud-Native Event Streaming Platform for Kafka

paper · Anna Povzner, Prince Mahajan, Jason Gustafson, et al. · · ~49 min de leitura do original

a tese

para rodar kafka como serviço, o protocolo não precisou mudar — precisou mudar tudo em volta: storage em dois níveis, células por tenant, cota coordenada e uma métrica de carga tirada de teoria de filas

o que fica

  1. Com armazenamento de um nível só, mover uma réplica custa o log inteiro: o cluster reage à mudança de carga tão devagar que, quando a cópia termina, a carga já mudou de novo.
  2. Latência é sinal melhor que uso de CPU para dizer que um broker precisa de mais recursos, mas cresce exponencialmente perto da saturação — Kora modela o broker como fila G/G/1 e usa Kingman para devolver isso ao usuário como utilização, que cresce linear.
  3. Cluster dedicado, em Kora, é um cluster multi-tenant com um tenant só: a mesma abstração de cluster lógico serve os dois casos e ainda isola os serviços internos da própria Confluent.
  4. Espalhar as partições de cada tenant por todos os brokers piora tudo: no experimento com células de 6 brokers num cluster de 24, a carga caiu de 73% para 53% só por confinar cada tenant a um subconjunto.
  5. Dividir a cota de um tenant igualmente entre os brokers estrangula workload desbalanceado; com distribuição coordenada e dinâmica, a fatia de tenants dentro do SLO de banda subiu de 99% para mais de 99,9%.
  6. Replicação correta não protege contra bug próprio: corrupção no líder que apara o prefixo do log força os followers a aparar também, e é por isso que Kora audita invariantes de metadado num motor separado e propositalmente burro.

o problema

O Kafka foi desenhado para um mundo em que a máquina era sua. Cada réplica de uma partição guarda uma cópia completa do log em disco local, e a replicação roda no protocolo próprio do Kafka. Isso funciona enquanto o cluster é estático. Na nuvem não é: o workload do cliente muda sozinho, e a única alavanca contra um broker sobrecarregado é reatribuir réplicas — o que, com um nível só de armazenamento, significa copiar o log inteiro para outra máquina. Quanto mais dados retidos, mais lenta a reação, e a cópia ainda rouba recursos do mesmo workload que se queria salvar. Junte a isso o dilema de disco: disco rápido custa proporcional ao tamanho do volume, e o volume precisa caber todo o histórico.

O segundo problema é o contrato. A Confluent opera dezenas de milhares de clusters em 73 regiões de AWS, GCP e Azure, com SLA de disponibilidade de 99,95% para cluster de zona única e 99,99% para multi-zona. Os três provedores diferem em IOPS base, em poder escalar throughput independente da capacidade, em geração de CPU dentro da mesma família de VM e em como falham. O cliente não quer saber nada disso: quer banda de entrada, banda de saída e a conta.

a ideia

Kora não reescreve o Kafka. O protocolo continua o mesmo, o cliente padrão continua funcionando, e o que muda fica embaixo e em volta. São três movimentos. O armazenamento vira dois níveis, para que a unidade que se move num rebalanceamento seja pequena. A unidade que o usuário compra deixa de ser o broker e vira uma capacidade declarada — o que libera a Confluent a trocar instância e classe de disco sem quebrar promessa. E quase toda decisão operacional vira um loop de realimentação sobre telemetria: quem rebalanceia, quem detecta hardware degradado, quem audita durabilidade. O próprio paper diz que não há uma ideia central: é síntese de técnicas conhecidas, calibrada por operação.

como funciona

O metadado saiu do ZooKeeper e foi para uma partição interna com consenso baseado em Raft. O controller é o líder dessa partição; as réplicas seguem o log e já têm o estado montado para assumir na falha. O controller também virou processo separado do broker, o que permite empacotá-lo junto em cluster pequeno e dedicar instância em cluster grande — e rolar todos os brokers sem trocar de controller.

No storage, a escrita continua indo para volume local e replicando pelo protocolo do Kafka; conforme envelhece, o segmento sobe para object store e some do disco local. Os metadados do que subiu vão para outro tópico interno, que cada réplica observa para saber o que apagar e para montar a tabela de referência que serve leitura fria. O volume local só precisa segurar o log ativo.

A carga do broker é medida como fila G/G/1: o tempo de espera vira uma média móvel exponencial de um minuto, no estilo do load average do Unix, e a aproximação de Kingman converte isso em utilização, com coeficientes achados empiricamente em benchmark. Em carga baixa, cai para utilização das threads de rede e de request. O balanceador (SBC, derivado do Cruise Control) combina bytes de entrada, bytes de saída, uso de disco e essa métrica, com metas que disparam rebalanceamento separadas de metas que só são perseguidas em best-effort. Na expansão, escolhe primeiro as réplicas mais pesadas, porque a carga por réplica segue lei de potência.

Na multi-tenancy, o cluster lógico é o namespace: um interceptor no broker carimba o id do tenant na requisição no momento da autenticação. Cota por tenant existe para banda, conexão, criação de partição e CPU — esta última aproximada pelo tempo de relógio que o broker gasta processando requisições daquele tenant. Um coordenador de cota redistribui banda entre brokers conforme o consumo real, e cada broker ainda aplica backpressure quando bate no próprio limite. Cada tenant vive numa célula de brokers espalhada pelas zonas; tenant novo cai na menos carregada entre duas células sorteadas.

o que isso custou

A abstração vaza. A CKU expõe máximo em cada dimensão, mas atingir o máximo de uma exige gastar menos das outras — o cluster pode acabar antes de bater qualquer limite anunciado, e é por isso que existe a métrica de carga por cima. Rebalancear é disruptivo: muda metadado, força cliente a atualizar e reconectar, então frequência demais machuca e de menos deixa broker torto. A atribuição de carga a réplicas é heurística, e em cluster com centenas de milhares de réplicas eles nem coletam métrica nesse nível. Os microserviços são empacotados junto do broker com cerca de 80% da VM reservada para ele, e os autores admitem que há workload em que isso limita o broker — a saída oferecida é o cliente pagar mais.

A durabilidade é onde o paper é mais honesto. Há uma tabela de incidentes próprios: corrupção de storage que aparou log, divergência de metadado de tiered storage em ambiente de teste, bug de configuração dinâmica mexendo em retenção, corrida na atualização do log-start-offset apagando registro cedo demais. O backup só recupera prefixo do log; se o que se perde é o sufixo, incluindo a parte não tierizada, eles dizem que ainda não conseguem recuperar. E upgrade, antes do investimento em rolagem por zona, era fonte importante de escalação de cliente — o trabalho de tornar o protocolo robusto a upgrade segue aberto.

onde isso aparece hoje

Kora é a continuação direta do desenho de Kafka: a Distributed Messaging System for Log Processing, e mostra o que sobra dele depois de dez anos de nuvem: o log e a API sobrevivem, o resto foi trocado. Duas peças descritas aqui deixaram de ser exclusividade de serviço gerenciado — o Apache Kafka aberto aposentou o ZooKeeper em favor do metadado em tópico interno replicado por Raft, e ganhou armazenamento em camadas.

O diagnóstico de que um único broker lento no conjunto de réplicas em sincronia degrada a latência de todas as partições do lote é a mesma leitura de The Tail at Scale, com uma resposta diferente: em vez de mandar requisição redundante, tirar o nó doente do caminho crítico por demoção de liderança, que não move dado nenhum. Os números que a Confluent reporta para essas defesas: 12 casos de degradação transitória de hardware tratados automaticamente em 30 dias nos três provedores, P99 de latência da frota dividido por 3 com troca de instância e de classe de disco, e SLA multi-zona subindo de 99,95% para 99,99%.

lido na íntegra por pipeline de llm, revisado por antonio leandro antes de publicar ·