1 pontos por GN⁺ 2025-12-09 | 1 comentários | Compartilhar no WhatsApp
  • A Jepsen validou a durabilidade e a consistência do sistema de mensagens distribuídas NATS JetStream em vários cenários de falha.
  • Os resultados dos testes mostraram perda de dados e ocorrência de split-brain em cenários de corrupção de arquivo (.blk, snapshot) e simulação de falha de energia.
  • O JetStream, por padrão, executa fsync a cada 2 minutos, o que pode deixar mensagens reconhecidas recentemente sem gravação em disco.
  • A perda de dados e inconsis­tência de réplica pode ocorrer mesmo com crash de apenas um único nó de OS.
  • A Jepsen recomenda alterar o padrão do NATS para fsync=always ou documentar explicitamente o risco de perda de dados.

1. Contexto

  • NATS é um sistema de streaming popular para publicação e assinatura de mensagens em fluxos
    • O JetStream usa o algoritmo de consenso Raft para replicar dados e garantir entrega pelo menos uma vez (at-least-once)
  • O JetStream afirma oferecer consistência linearizável e disponibilidade contínua, mas, pela teoria CAP, os dois requisitos não podem ser satisfeitos simultaneamente
  • De acordo com a documentação da NATS, streams de 3 nós suportam a perda de 1 servidor, e streams de 5 nós suportam a perda de 2 servidores
  • Uma mensagem é considerada armazenada com sucesso assim que o servidor envia acknowledge para a requisição de publish
  • Para consistência de dados é necessário um conjunto de nós em quórum (quorum), e em clusters de 5 nós, pelo menos 3 devem estar ativos para armazenar novas mensagens

2. Projeto dos testes

  • A Jepsen executou os testes com o cliente JNATS 2.24.0 em ambiente de contêineres Debian 12 LXC
    • Alguns testes foram feitos no ambiente Antithesis com a imagem oficial Docker da NATS
  • Foi configurado um único stream JetStream (replicação 5) e injetaram-se falhas de interrupção de processo, crash, partição de rede, perda de pacotes e corrupção de arquivo
  • Foi simulada falta de energia usando o sistema de arquivos LazyFS, para provocar perda de escritas sem fsync
  • Cada processo publica mensagens exclusivas e, ao fim do teste, verifica-se a presença das mensagens acknowledged em todos os nós
  • Quando uma mensagem existe apenas em parte dos nós, ela é classificada como divergence (divergência)

3. Principais resultados

3.1 Perda total de dados do NATS 2.10.22 (#6888)

  • Foi descoberto que uma simples falha de processo pode causar o desaparecimento de todo o stream JetStream
  • Ocorreu o erro "No matching streams for subject" e ele não foi recuperado por várias horas
  • A causa foi inversão de snapshot do líder, exclusão de estado do Raft, etc., corrigida na versão 2.10.23

3.2 Perda de dados em corrupção de arquivo .blk (#7549)

  • Quando ocorre erro de 1 bit ou truncamento em arquivos .blk do JetStream, há perda de centenas de milhares de gravações reconhecidas
    • Ex.: 1.367.069 mensagens, 679.153 perdidas
  • Mesmo com a corrupção de apenas alguns nós, ocorre grande perda de dados e split-brain
    • Ex.: nós n1, n3, n5 com até 78% de mensagens perdidas
  • A NATS está investigando o problema

3.3 Perda total de dados por corrupção de snapshot (#7556)

  • Se um arquivo snapshot em data/jetstream/$SYS/_js_/ for corrompido, o nó considera o stream como órfão (orphaned) e apaga todos os dados
  • Mesmo com a corrupção de poucos nós, há indisponibilidade permanente do stream por não conseguir quórum de maioria
  • Ex.: corrupção dos nós n3, n5n3 foi eleito líder e excluiu todo o jepsen-stream
  • A Jepsen aponta o risco de que um nó corrompido vire líder durante eleição

3.4 Perda de dados com configuração padrão de fsync (#7564)

  • O JetStream faz fsync por padrão apenas a cada 2 minutos, enquanto as mensagens são reconhecidas imediatamente
    • Consequentemente, mensagens reconhecidas recentemente podem permanecer sem gravação em disco
  • Em falta de energia ou crash de kernel, ocorre perda de mensagens reconhecidas equivalente a dezenas de segundos
    • Ex.: 930.005 mensagens, 131.418 perdidas
  • Falhas consecutivas de nós individuais podem causar a exclusão completa do stream
  • Esse comportamento praticamente não é mencionado na documentação
  • A Jepsen recomenda alterar o valor padrão de fsync para fsync=always ou incluir aviso explícito sobre risco de perda de dados

3.5 Split-brain por crash único de OS (#7567)

  • A perda de dados e a divergência de réplica podem ocorrer apenas com falta de energia ou crash de kernel em um único nó
  • Em arquitetura líder-seguidor, se alguns nós confirmam uma gravação apenas com commit em memória e falham,
    a maioria dos nós perde aquela gravação e avança para um novo estado
  • Nos testes houve split-brain persistente após uma única falta de energia
    • Perda de mensagens reconhecidas em faixas diferentes por nó foi observada
  • A Jepsen cita casos semelhantes do Kafka e reforça que o mesmo risco também existe em sistemas baseados em Raft

4. Discussão e conclusão

  • O problema de perda de dados total do 2.10.22 foi resolvido no 2.10.23
  • No 2.12.1, ainda ocorrem perda de dados e split-brain por corrupção de arquivos e crash de OS
  • A corrupção de arquivos .blk e snapshot ainda pode causar perda de mensagens em nós específicos ou exclusão total do stream
  • O intervalo padrão longo de fsync mantém risco de perda de dados reconhecidos em falhas simultâneas de múltiplos nós
  • A Jepsen sugere definir fsync=always ou incluir alertas claros no documento
  • A afirmação de “sempre disponível” do JetStream é impossível pelo teorema CAP, exigindo ajuste de documentação
  • A Jepsen deixa claro que é possível provar a existência de bugs, mas não é possível provar a ausência de segurança

4.1 Papel do LazyFS

  • O LazyFS foi usado para simular perda de escritas sem fsync
  • Também permite reproduzir diferentes erros de armazenamento, como escrita parcial (torn write), em simulações de falta de energia
  • No estudo relacionado When Amnesia Strikes (VLDB 2024), erros semelhantes também foram reportados em PostgreSQL, Redis, ZooKeeper etc.

4.2 Próximos passos

  • Não foram executadas verificações de perda de mensagem por consumidor único, ordem de mensagens e garantias Linearizable/Serializable
  • A garantia de exactly-once também é tema de pesquisa futura
  • Em adição e remoção de nós foram encontrados erros de documentação e falta de etapa obrigatória de health check (#7545)
  • O procedimento seguro para reconfiguração de cluster ainda é incerto

1 comentários

 
GN⁺ 2025-12-09
Comentários do Hacker News
  • Toda vez que alguém pula a teoria complexa e constrói um sistema desses, eu vejo o aphyr desmontando tudo
    Agora fico até pensando se a IA talvez já consiga ler a documentação de um projeto e prever a possibilidade de perda de dados só pelo texto de marketing
    • Sensação de estar acariciando uma barba longa e assentindo com a cabeça
      As pessoas sempre dizem que “teoria é superestimada” ou que “hackear é melhor do que educação formal”, mas no fim acabam tropeçando no próprio espaço de problemas documentado
    • Eu também já pedi algo parecido para um LLM, e o resultado foi bem útil
  • Dá a sensação de que o NATS ignora a teoria CAP
    • Parece uma observação subestimada
  • Eu vinha usando o NATS para pub/sub em memória, e nessa parte ele foi excelente
    Também lidou bem com detalhes sutis de escalabilidade
    Mas nunca usei a persistência, e não fazia ideia de que ela podia ser tão frágil assim
    Fico surpreso por ser vulnerável até a corrupção de um único bit em arquivo
  • Como material relacionado, Jepsen e Antithesis publicaram recentemente um glossário de sistemas distribuídos
    É uma referência excelente → Jepsen Glossary
  • Fiquei curioso sobre a diferença de conteúdo entre aphyr.com/tags/jepsen e jepsen.io/analyses
    Descobri o aphyr.com recentemente e estou animado, parece ter muitos insights
    • Jepsen começou originalmente como uma série de posts em blog pessoal
      Depois, jepsen.io evoluiu para um projeto profissional e passou a ser operado para valer há cerca de 10 anos
  • Fico me perguntando por que existe a configuração “Lazy fsync by Default”
    É para melhorar desempenho em benchmark? Em clusters pequenos, esse tipo de configuração costuma ser a causa do problema
    • Não é só latência, também há ganho de throughput
      Muitas aplicações não exigem durabilidade total, então o lazy fsync pode ser útil
      Ainda assim, deixar isso como padrão é discutível
    • Sempre me perguntei por que o fsync precisaria necessariamente ser adiado
      Parece que isso poderia ser resolvido com batching, como no TCP corking
    • É uma das coisas que sistemas distribuídos tornam possíveis
      Falhas causadas por lazy fsync quase nunca acontecem ao mesmo tempo na maioria dos nós
    • É, sim, uma escolha voltada para ganho de desempenho
    • Eles querem combinar a durabilidade obtida por replicação e distribuição com o throughput conquistado pelo lazy fsync
  • Recomendo s2.dev como alternativa serverless ao JetStream
    Vantagem: oferece streams ilimitados com durabilidade no nível de object storage
    Desvantagem: ainda não tem suporte a consumer groups
    • Fico curioso se já rodaram testes do Jepsen nele
  • O problema é que o NATS, por padrão, só faz fsync a cada 2 minutos e retorna ack imediatamente
    Se vários nós falharem ao mesmo tempo, pode haver perda de dados já commitados
    Isso me lembra o marketing de “web scale” dos primeiros tempos do MongoDB
    Acho que o padrão deveria ser sempre a opção mais segura
    • O NATS deixa claro que só garante disponibilidade do cluster
      Isso até foi algo positivo para mim, porque dava para projetar o sistema em cima dessa premissa
      Quando usei em 2018, ele tinha bom desempenho e era fácil de operar
    • A maioria dos DBs modernos também não é totalmente segura por padrão
      Por exemplo, o nível padrão de isolamento de transação do PostgreSQL é read committed
      O Redis também faz fsync por padrão a cada 1 segundo
    • O cluster do Redis só retorna ack depois de replicar para múltiplos nós
      Mesmo no Redis standalone, dá para configurar ack só após fsync, mas por causa do buffering do SO ainda é difícil garantir totalmente
      No fim, o importante é entender exatamente o significado do ack
    • A maioria dos sistemas escolhe um trade-off entre velocidade e durabilidade
      Se insistirem apenas em padrões seguros, o desempenho cai muito e aumenta a carga para o usuário ter que tunar tudo manualmente
      Por exemplo, até o nível de isolamento padrão do Postgres é fraco o bastante para permitir race conditions
      Referência: post sobre o teste Hermitage
    • O problema é que o fsync só oferece opções extremas
      Na era dos SSDs, etapas intermediárias como group-commit desapareceram, e agora o gargalo é o custo da troca de syscall
      2 minutos é um intervalo longo demais (também vale considerar a diferença entre fdatasync e fsync)
  • Sinceramente, eu já esperava algo assim, mas não imaginei que seria tão grave
    Melhor simplesmente usar Redpanda
  • Fiquei pensando se daria para melhorar o aviso de desempenho do fsync no NATS
    Talvez um batch flush periódico aumentasse a latência, mas mantivesse o throughput
    • Em vez de um intervalo fixo, bastaria enfileirar as escritas enquanto o fsync estiver em andamento e processá-las junto no próximo lote
      Isso é parecido com agrupar rodadas de Paxos
      Quando uma rodada termina, a próxima leva já começa imediatamente