top of page
pedrobusko

Porque O Apache Kafka Não Precisa Do Fsync Para Ser Seguro

Atualizado: 1 de jun. de 2023

Este é um artigo traduzido originalmente publicado dia 24/04/2023 pelo Jack Vanlightly no post: "Why Apache Kafka Doesn't Need Fsync To Be Safe". Siga o Jack no LinkedIn para se manter atualizado com novas publicações.


TLDR: Apache Kafka não precisa de fsyncs para ser seguro porque inclui a recuperação em seu protocolo de replicação. É um sistema distribuído do mundo real que usa gravação de log assíncrona + recuperação com alguma segurança adicional incorporada. A gravação de log assíncrona permite fornecer desempenho robusto em uma variedade de hardware e com uma ampla variedade de cargas de trabalho.


Agora que o TLDR está pronto, vamos nos aprofundar nele.


O fato de que, por padrão, o Apache Kafka não libera gravações no disco às vezes é usado como munição contra ele. O argumento é que, se o Kafka não liberar os dados antes de reconhecer as solicitações de produção, certamente o cluster poderá perder os dados confirmados devido a travamentos e reinicializações. Parece plausível e as pessoas podem acreditar - mas estou aqui escrevendo isso hoje para explicar por que esse não é o caso.


O que é um fsync/flush?


O termo "fsync" e "flush" refere-se a uma chamada do sistema operacional (conhecida como chamada sys) que garante que os dados gravados em um arquivo sejam armazenados fisicamente na unidade de armazenamento. Ele é usado para sincronizar o estado na memória de um arquivo com seu estado no disco.

Basicamente, se os dados forem gravados, mas nenhum fsync for executado e, em seguida, o próprio computador travar, for reinicializado ou perder energia, essas alterações de arquivo não serão mantidas.

O Kafka não libera de forma síncrona - isso significa que, por padrão, nem sempre será liberado antes de reconhecer uma mensagem. Antes de explicar por que as liberações assíncronas são adequadas para o Apache Kafka, vamos ver por que elas não são seguras para o Raft.


Por que o Raft precisa de fsyncs


Não estou implicando com o Raft - eu amo o Raft! Basta verificar meu repositório do GitHub no Raft e postar no blog sobre o Flexible Raft . Vá conferir o RabbitMQ Quorum Queues , do qual tive a sorte de fazer parte enquanto estava na VMware. Basta olhar para o KRaft no Apache Kafka ! Raft é incrível e precisa de fsyncs.


Resumindo


O protocolo Raft é essencialmente um grupo de servidores onde cada servidor executa a mesma lógica e é apoiado por um log de operações persistidas no disco. Este protocolo inclui eleições de líder, detecção de falhas, replicação de dados, mudança de associação de cluster e assim por diante.


Raft é um algoritmo de replicação de máquina de estado (SMR) que consiste em um cluster de máquinas de estado localizadas em um log replicado.
Raft é um algoritmo de replicação de máquina de estado (SMR) que consiste em um cluster de máquinas de estado localizadas em um log replicado.

Um cluster Raft não pode tolerar a perda de nenhuma entrada de log que tenha sido gravada anteriormente no disco. O Raft depende do próprio log para a correção do protocolo e, portanto, se o log em um nó perder dados, ele poderá comprometer a exatidão de todo o cluster.

Se uma entrada for gravada no disco, mas o servidor travar e perder essa entrada, o Raft poderá perder as cópias desses dados armazenados em outros servidores - ou seja, perder uma entrada em um servidor pode fazer com que todos os servidores percam essa entrada! Portanto, cada servidor Raft deve fsync dos dados gravados no disco.


O porquê (para quem quiser entender mais)


A principal razão para a dependência da integridade do log do Raft é que as eleições de líder podem fazer com que os servidores excluam os dados confirmados se algum servidor do Raft perder entradas em seu log.

Raft requer quóruns majoritários para operar. Por exemplo, se temos três nós, então existem três quóruns possíveis que formam uma maioria (de 2 nós).


Três quóruns possíveis de dois nós: {n1,n2}, {n2,n3} e {n1,n3}.
Três quóruns possíveis de dois nós: {n1,n2}, {n2,n3} e {n1,n3}.

Existem dois tipos de quórum no Raft:

  • Um quórum de eleição: um grupo de nós que votam todos no mesmo nó.

  • Um quorum de replicação: um grupo de nós que hospedam uma determinada mensagem (e todas as mensagens anteriores).

Uma eleição bem-sucedida requer o seguinte:

  • A maioria dos nós para votar no mesmo nó.

  • Cada nó só deve votar em outro se tiver dados iguais ou mais recentes. Um nó sem os dados mais recentes não será eleito.

O cerne do projeto é que o Raft garante que o quórum de replicação das entradas mais recentes se sobreponha a todos os quóruns de eleição possíveis. Ou seja, pelo menos um nodo de qualquer quórum eleitoral terá os dados mais recentes e apenas o nodo com os dados mais recentes poderá vencer a eleição - assim garantimos que o novo líder tenha todos os dados.

A sobreposição de quóruns de replicação e eleição é a pedra angular do Raft.


Não existem dois quóruns de replicação e eleição que não se sobreponham.
Não existem dois quóruns de replicação e eleição que não se sobreponham.

Então, o que acontece com o Raft se os nós não fsyncarem?

A resposta é que a pedra angular dos quóruns sobrepostos está quebrada.


Um exemplo simples de perda de dados Raft sem fsync


Vamos usar um exemplo simples de um cluster Raft com algumas mensagens.

As mensagens m1, m2 e m3 foram adicionadas ao log, replicadas em sua maioria e reconhecidas aos clientes. O Raft garante que esses três não serão perdidos, desde que a maioria dos nós não seja perdida.


Exemplo de raft passo 1: tudo está bem.
Exemplo de raft passo 1: tudo está bem.

Mas então o servidor do broker 1 falha/perde energia e não executou um fsync causando a perda de m2 e m3.


Exemplo de raft passo 2: o corretor 1 perde m2 e m3.
Exemplo de raft passo 2: o corretor 1 perde m2 e m3.

O corretor 1 volta com apenas m1. O corretor 1 inicia uma eleição (ignorando a pré-votação aqui).


Exemplo de raft passo 3: o corretor 1 está de volta com apenas m1.
Exemplo de raft passo 3: o corretor 1 está de volta com apenas m1.

Agora b1 e b3 podem formar um quórum de eleição que não se sobreponha ao quórum de replicação de m2 e m3 (que agora é apenas b2).


No final, b1 e b3 votam em b1 e, portanto, b1 se torna o líder. Isso faz com que b2 exclua m2 e m3 de seu log.


Exemplo de raft passo 4: Oh não! Um quórum de eleição e replicação que não se sobrepõe!
Exemplo de raft passo 4: Oh não! Um quórum de eleição e replicação que não se sobrepõe!

O cluster Raft acabou de perder duas mensagens confirmadas.


Etapa 5 do exemplo do Raft: o Broker 2 trunca seu log para m1. O cluster acabou de perder duas entradas confirmadas.
Etapa 5 do exemplo do Raft: o Broker 2 trunca seu log para m1. O cluster acabou de perder duas entradas confirmadas.

Portanto, o vanilla Raft precisa de fsyncs para ser seguro e todas as implementações que conheço o fazem. É claro que você pode modificar o Raft para não fsync, mas precisaria de um protocolo de recuperação e alterações em quando um nó pode ser candidato a líder. Na verdade, é exatamente isso que veremos agora, mas com o protocolo de replicação Kafka.


Por que Kafka é seguro apesar de não liberar todas as mensagens?

Resumindo


A resposta é que o protocolo de replicação de dados do Kafka foi projetado para ser seguro sem fsyncs. A replicação Kafka não depende da integridade de uma partição de tópico para eleições de líder, em vez disso, depende de um armazenamento de metadados externo, como ZooKeeper ou KRaft. Uma partição de tópico pode perder algumas mensagens sem comprometer a exatidão do próprio protocolo de replicação de dados.

Para lidar com a perda de algumas mensagens em um corretor, o Kafka possui um mecanismo de recuperação ou autocorreção integrado que repara automaticamente os dados. Esse mecanismo de recuperação é o que torna o Kafka seguro sem fsyncs.


O porquê (para quem quiser entender mais)


A gravação de log assíncrona tem sido objeto de muita pesquisa na comunidade de sistemas distribuídos, com muitos exemplos do mundo real, como VSR Revisited ( 1 , 2 , 3 ) e Apache BookKeeper com o diário desativado .

Há uma desvantagem na gravação de log assíncrona com esses dois sistemas - falhas simultâneas podem causar perda de dados. Tendo executado diferentes sistemas distribuídos em produção, em alguns casos em escala muito grande, vi falhas de cluster ocorrerem, como OOMs em cascata removendo um cluster sob alta carga ou um bug que causava a falha de todos os nós quase ao mesmo tempo. O Kafka não está sujeito a esse problema porque não armazena dados não liberados em sua própria memória, mas no cache da página. Se o Kafka travar, os dados não desaparecem e, por esse motivo, o Kafka é realmente mais seguro do que o BookKeeper sem o diário - apesar de ambos usarem protocolos de recuperação.

Então, como o protocolo de replicação de dados Kafka funciona sem fsyncs? É tudo uma questão de recuperação.


Replicação e recuperação Kafka


Todos os sistemas distribuídos que gravam no armazenamento de forma assíncrona possuem um mecanismo de recuperação integrado que permite que um nó afetado recupere todos os dados que foram perdidos devido a um encerramento abrupto do nó.

Todos esses mecanismos de recuperação têm as seguintes regras em comum:

  1. O nó afetado deve recuperar os dados perdidos de um par.

  2. O nó afetado não pode ser candidato à liderança até que tenha pelo menos recuperado os dados que perdeu anteriormente.

O processo de recuperação com Kafka é muito simples.

  1. Um broker com falha é reiniciado, tendo perdido um certo número de mensagens que não foram armazenadas com sucesso no disco. Digamos que o log tinha 1000 mensagens, mas perdeu as últimas dez, o que significa que o último deslocamento agora é 990.

  2. O corretor foi removido do ISR (o quórum de candidatos a líder). O intermediário começa a buscar no deslocamento 991 do líder de partição de tópico existente, incluindo as mensagens em seu próprio log. As primeiras dez mensagens recuperam as perdidas anteriormente.

  3. Depois que o corretor é alcançado, ele pode ingressar no ISR como um corretor totalmente funcional, candidato à liderança.

O ponto é que até que o corretor afetado alcance (e recupere quaisquer dados perdidos), ele não pode se tornar um líder e, portanto, não pode haver perda de dados. Esse conceito de um nó não ser candidato a líder até que tenha pelo menos recuperado o que perdeu é o princípio fundamental por trás de todos os três protocolos de recuperação que conheço.


Um exemplo de recuperação


Etapa 1 do exemplo Kafka: Tudo está bem, embora o ISR esteja reduzido a dois para torná-lo semelhante ao exemplo “Raft sem fsync”.
Etapa 1 do exemplo Kafka: Tudo está bem, embora o ISR esteja reduzido a dois para torná-lo semelhante ao exemplo “Raft sem fsync”.

Por alguma razão, digamos que o ISR inclua apenas uma maioria, como nosso exemplo do Raft. Isso significa que as mensagens confirmadas podem existir apenas em 2 brokers, como é o caso das mensagens m1, m2 e m3.

Em seguida, o servidor de b1 trava/perde energia, fazendo com que m2 e m3 sejam perdidos no corretor 1. Nesse ponto, o corretor 1 é removido do ISR e deixa de ser candidato à liderança.


Kafka exemplo 2: O servidor do broker 1 fica offline, levando consigo o cache da página e perdendo m2 e m3.
Kafka exemplo 2: O servidor do broker 1 fica offline, levando consigo o cache da página e perdendo m2 e m3.

O corretor 1 volta a ficar online, sem as mensagens m2 e m3.


Kafka exemplo passo 3: Broker 1 volta sem as mensagens não liberadas m2 e m3.
Kafka exemplo passo 3: Broker 1 volta sem as mensagens não liberadas m2 e m3.

O corretor 1 começa a buscar mensagens do líder, começando no deslocamento de m1 + 1. Assim que o corretor 1 alcança, ele é incluído no ISR novamente. Qualquer futura eleição de líder pode incluir com segurança o corretor 1.


Etapa 4 do exemplo de Kafka: o corretor 1 busca no corretor 2 até que seja alcançado e retorne ao ISR.
Etapa 4 do exemplo de Kafka: o corretor 1 busca no corretor 2 até que seja alcançado e retorne ao ISR.

Somente corretores no ISR podem se tornar líderes e todos os corretores no ISR têm o log completo - não há cenário de perda de dados equivalente como em “Raft sem fsync”.


Como lidar com falhas em todo o cluster


O Kafka pode lidar com falhas simultâneas do broker, mas a falha de energia simultânea é um problema. Se todos os agentes perderem energia simultaneamente, haverá risco de perda de dados. Todo sistema faz compensações e este é um dos do Apache Kafka. No entanto, felizmente para nós, existe uma solução simples.

Alta disponibilidade e alta durabilidade só são realmente possíveis quando os clusters estão espalhados por vários datacenters. Na nuvem, isso significa espalhar clusters em várias zonas de disponibilidade, onde cada zona é, na verdade, um data center separado fisicamente. Isso evita que desastres como incêndios ou picos de energia destruam todas as cópias dos dados e permite que um sistema permaneça disponível.

Projetos de gravação de log assíncronos como Kafka são mais duráveis ​​quando espalhados por várias zonas para evitar quedas de energia façam com que todos os corretores parem simultânea e abruptamente, o que pode levar a alguns dados irrecuperáveis. Este é um argumento comum contra o Apache Kafka, no entanto, a verdade é que as organizações que se preocupam com a durabilidade devem usar implantações de várias zonas em qualquer caso.


Resumindo


O Apache Kafka foi projetado desde o início para ser seguro sem fsyncs, incluindo a recuperação em seu protocolo de replicação. Kafka não é o único garoto no quarteirão que faz isso e oferece alguns benefícios de desempenho no mundo real. Em breve, iniciarei uma série de desempenho do Kafka, onde veremos como a gravação de log assíncrona é a arma secreta do Kafka, uma das maneiras pelas quais ele é capaz de fornecer uma taxa de transferência enorme e baixas latências com segurança em todos os tipos de hardware e cargas de trabalho.


Uma das coisas legais de ter trabalhado no RabbitMQ Core Team, ter feito parte da equipe Pulsar-as-a-service na Splunk, onde escrevi código para Apache Pulsar e Apache BookKeeper, e agora na Confluent ajudando a mover o Apache Kafka (e nosso cloud) é que eu escrevo sobre as decisões legais de design que cada sistema tomou. Eu escrevi muito sobre os componentes internos do RabbitMQ, Pulsar e BookKeeper, mas não tanto sobre o Kafka ainda. Isso está mudando e estou muito feliz por finalmente poder passar algum tempo em Kafka porque, francamente, é demais.


54 visualizações0 comentário

Commentaires


bottom of page