1 points par GN⁺ 2025-12-09 | 1 commentaires | Partager sur WhatsApp
  • Jepsen a vérifié la durabilité et la cohérence du système de messagerie distribuée NATS JetStream dans divers environnements de défaillance
  • Les résultats des tests montrent qu’en cas de corruption de fichiers (.blk, snapshot) et de simulation de panne d’alimentation, on observe des pertes de données et des épisodes de split-brain
  • JetStream exécute fsync toutes les 2 minutes par défaut, si bien que des messages récemment acquittés peuvent rester non écrits sur disque
  • Une seule panne de système d’exploitation peut déjà provoquer des pertes de données et une incohérence de réplication
  • Jepsen recommande soit de changer la valeur par défaut de fsync en always, soit de documenter explicitement le risque de perte de données

1. Contexte

  • NATS est un système de streaming populaire permettant de publier et de s’abonner à des messages sous forme de flux
    • JetStream utilise l’algorithme de consensus Raft pour répliquer les données et garantit une livraison au moins une fois (at-least-once)
  • La documentation de JetStream affirme offrir une cohérence Linearizable et une disponibilité constante, mais selon le théorème CAP, ces deux propriétés ne peuvent pas être assurées simultanément
  • Selon la documentation NATS, un flux à 3 nœuds peut tolérer la perte d’1 serveur, et un flux à 5 nœuds peut tolérer celle de 2 serveurs
  • Un message est considéré comme « stocké avec succès » dès que le serveur a acquitté (acknowledge) la requête publish
  • La cohérence des données requiert un quorum (majorité) de nœuds, et dans un cluster à 5 nœuds il faut au minimum 3 nœuds opérationnels pour pouvoir stocker de nouveaux messages

2. Conception des tests

  • Jepsen a exécuté les tests avec le client JNATS 2.24.0 dans des conteneurs Debian 12 LXC
    • Certains tests utilisent l’image officielle NATS Docker dans un environnement Antithesis
  • Un flux JetStream unique (réplication 5) a été configuré, puis des arrêts forcés de processus, plantages, partitions réseau, pertes de paquets et corruptions de fichiers ont été injectés
  • Une simulation de panne d’alimentation a été réalisée avec le système de fichiers LazyFS pour provoquer la perte d’écritures non validées par fsync
  • Chaque processus publie un message unique, puis vérifie la présence des messages acquittés sur tous les nœuds à la fin du test
  • Si un message n’existe que sur certains nœuds, il est classé comme divergence (incohérence de réplication)

3. Principaux résultats

3.1 Perte totale de données sur NATS 2.10.22 (#6888)

  • Une perte totale du flux JetStream a été détectée avec un simple crash de processus
  • L’erreur "No matching streams for subject" apparaît, puis la situation ne se résout pas pendant plusieurs heures
  • La cause est liée à une inversion de snapshot du leader et à la suppression de l’état Raft, et le problème a été corrigé dans la version 2.10.23

3.2 Pertes de données en cas de corruption de fichiers .blk (#7549)

  • Lorsqu’un fichier .blk de JetStream subit une erreur de bit unique ou une troncature, des centaines de milliers d’écritures acquittées peuvent être perdues
    • Exemple : 679 153 pertes sur 1 367 069 messages
  • Même si seuls certains nœuds sont corrompus, des pertes massives de données et un split-brain peuvent se produire
    • Exemple : jusqu’à 78 % des messages perdus sur les nœuds n1, n3 et n5
  • NATS enquête actuellement sur ce problème

3.3 Suppression totale des données en cas de corruption de snapshot (#7556)

  • Si le fichier de snapshot sous data/jetstream/$SYS/_js_/ est corrompu, le nœud considère le flux comme orphaned et supprime toutes les données
  • La corruption d’une minorité de nœuds empêche d’atteindre le quorum et rend le flux permanemment indisponible
  • Exemple : corruption des nœuds n3 et n5 → élection de n3 comme leader et suppression complète de jepsen-stream
  • Jepsen souligne le risque qu’un nœud corrompu puisse être élu leader

3.4 Perte de données due au réglage fsync par défaut (#7564)

  • Par défaut, JetStream n’effectue un fsync que toutes les 2 minutes, alors que les messages sont acquittés immédiatement
    • En conséquence, les messages récemment acquittés peuvent ne pas avoir été persistés sur disque
  • Une panne d’alimentation ou un crash kernel peut entraîner la perte de plusieurs dizaines de secondes de messages acquittés
    • Exemple : 131 418 messages perdus sur 930 005
  • Des pannes de nœud enchaînées peuvent aussi provoquer la suppression complète du flux
  • Ce comportement est presque absent de la documentation
  • Jepsen recommande de modifier la valeur par défaut en fsync=always ou d’ajouter un avertissement explicite sur le risque de perte de données

3.5 Split-brain après un seul crash OS (#7567)

  • Une seule panne d’alimentation ou crash kernel d’un nœud peut provoquer des pertes de données et une incohérence de réplication
  • Dans une architecture leader-suiveur, si certains nœuds ont confirmé une écriture seulement en mémoire puis sont tombés en panne,
    la majorité des nœuds perd cette écriture et poursuit avec un nouvel état
  • Les tests ont observé un split-brain persistant après un seul événement de panne d’alimentation
    • Des pertes de messages acquittés sur des plages différentes ont été constatées selon le nœud
  • Jepsen cite des cas similaires chez Kafka pour souligner que ce risque existe aussi dans des systèmes basés sur Raft

4. Discussion et conclusion

  • Le problème de perte totale de données dans la version 2.10.22 est résolu en 2.10.23
  • En 2.12.1, des pertes de données et des split-brain subsistent en cas de corruption de fichiers et de crash OS
  • Une corruption des fichiers .blk ou snapshot peut provoquer des omissions de messages sur certains nœuds, ou la suppression complète du flux
  • L’intervalle de fsync par défaut, s’il reste long, crée un risque de perte de données acquittées en cas de pannes simultanées sur plusieurs nœuds
  • Jepsen propose fsync=always ou une mise en garde explicite dans la documentation
  • L’affirmation de JetStream selon laquelle il est « toujours disponible » est impossible selon CAP, ce qui implique une mise à jour de la documentation
  • Jepsen précise qu’il est possible de démontrer l’existence de bugs, mais pas d’impossibilité de sûreté

4.1 Rôle de LazyFS

  • Utilisation de LazyFS pour simuler la perte d’écritures non validées par fsync
  • Diverses erreurs de stockage, dont les écritures partiellement corrompues (torn write), sont reproduites lors d’un crash d’alimentation
  • Selon les travaux associés When Amnesia Strikes (VLDB 2024), des bugs similaires ont été signalés sur PostgreSQL, Redis, ZooKeeper, etc.

4.2 Pistes futures

  • Les pertes au niveau d’un seul consommateur, l’ordre des messages ainsi que les garanties Linearizable/Serializable n’ont pas encore été vérifiés
  • La garantie exactly-once est aussi identifiée comme sujet de recherche futur
  • Des erreurs de documentation et l’absence d’étapes de health check obligatoires lors de l’ajout/retrait de nœuds ont été trouvées (#7545)
  • Une procédure sûre de reconfiguration de cluster reste encore floue

1 commentaires

 
GN⁺ 2025-12-09
Avis sur Hacker News
  • À chaque fois que quelqu’un construit ce genre de système en sautant la théorie complexe, on voit aphyr le faire s’effondrer
    Je me demande maintenant si une IA pourrait lire la documentation d’un projet et prédire la possibilité de perte de données rien qu’à partir du discours marketing
    • hoche la tête en caressant sa longue barbe
      Les gens disent toujours que « la théorie est surestimée » ou que « hacker vaut mieux que les études », mais au final ils se tirent eux-mêmes une balle dans le pied dans un espace de problèmes déjà documenté
    • J’ai essayé de faire faire quelque chose de similaire à un LLM, et le résultat était plutôt utile
  • On a l’impression que NATS ignore le théorème CAP
    • Ça ressemble à une remarque sous-estimée
  • J’ai utilisé NATS pour du pub/sub en mémoire, et dans ce domaine c’était excellent
    Il gérait aussi très bien les détails subtils de montée en charge
    Mais je n’ai jamais utilisé la persistance, et je ne pensais pas que ce serait à ce point fragile
    C’est déconcertant que ce soit vulnérable à une corruption d’un simple bit dans un fichier
  • Côté ressources liées, Jepsen et Antithesis ont récemment publié un glossaire des systèmes distribués
    C’est une très bonne ressource de référence → Jepsen Glossary
  • Je me demandais quelle était la différence de contenu entre aphyr.com/tags/jepsen et jepsen.io/analyses
    J’ai découvert aphyr.com récemment, et j’attends beaucoup des analyses qu’on y trouve
    • Jepsen a commencé à l’origine comme une série d’articles sur un blog personnel
      Ensuite, jepsen.io est devenu un projet professionnel, exploité sérieusement depuis environ dix ans
  • Je me demande pourquoi le réglage « Lazy fsync by Default » existe
    Est-ce pour gonfler les performances dans les benchmarks ? Dans les petits clusters, ce type de réglage est souvent la cause du problème
    • Il n’améliore pas seulement la latence, mais aussi le débit (throughput)
      Beaucoup d’applications n’exigent pas une durabilité totale, donc un lazy fsync peut être utile
      En revanche, en faire la valeur par défaut est discutable
    • Je me suis toujours demandé pourquoi il fallait forcément retarder fsync
      On dirait qu’un traitement par lots (batch), comme avec TCP corking, pourrait suffire
    • C’est justement l’une des choses rendues possibles par un système distribué
      Les pannes dues à lazy fsync ne surviennent généralement pas sur la majorité des nœuds en même temps
    • C’est bien un choix fait pour gagner en performances
    • L’idée est de viser à la fois la durabilité via la réplication et la distribution, et le débit obtenu grâce à lazy fsync
  • Je recommande s2.dev comme alternative serverless à JetStream
    Avantage : prise en charge de flux illimités avec une durabilité au niveau du stockage objet
    Inconvénient : il n’y a pas encore de consumer group
    • Je me demande s’ils ont déjà exécuté des tests Jepsen
  • Le problème avec NATS, c’est qu’il ne fait un fsync que toutes les 2 minutes par défaut, tout en renvoyant immédiatement un ack
    Si plusieurs nœuds tombent en panne en même temps, cela peut entraîner une perte de données validées
    Ça rappelle le marketing « web scale » des débuts de MongoDB
    À mon avis, les valeurs par défaut devraient toujours être l’option la plus sûre
    • NATS indique clairement qu’il garantit seulement la disponibilité du cluster
      C’est justement ce que j’appréciais, car cela permettait de concevoir le système au-dessus en connaissance de cause
      Quand je l’ai utilisé en 2018, c’était performant et facile à administrer
    • La plupart des bases de données modernes n’ont pas non plus des paramètres par défaut totalement sûrs
      Par exemple, le niveau d’isolation transactionnelle par défaut de PostgreSQL est read committed
      Redis aussi fait un fsync par défaut toutes les secondes
    • Un cluster Redis ne renvoie un ack qu’après réplication sur plusieurs nœuds
      Même avec Redis standalone, on peut configurer un ack après fsync, mais à cause du buffering de l’OS, une garantie totale reste difficile
      Au final, l’important est de bien comprendre ce que signifie un ack
    • La plupart des systèmes choisissent un compromis entre vitesse et durabilité
      Si on impose uniquement des valeurs par défaut sûres, les performances chutent fortement et cela augmente la charge de réglage côté utilisateur
      Par exemple, même le niveau d’isolation par défaut de Postgres est faible et peut provoquer des race conditions
      Référence : article sur le test Hermitage
    • Le problème avec fsync, c’est qu’il n’offre que des options extrêmes
      À l’ère des SSD, les étapes intermédiaires comme le group-commit ont disparu, et désormais le coût du passage en syscall constitue le goulet d’étranglement
      Deux minutes, c’est beaucoup trop long comme intervalle (il faut aussi prendre en compte la différence entre fdatasync et fsync)
  • Honnêtement, je m’y attendais, mais pas à ce point
    Autant utiliser Redpanda
  • Je me demandais s’il serait possible d’améliorer les avertissements sur les performances de fsync dans NATS
    Je me dis qu’un batch flush à intervalle régulier pourrait augmenter la latence tout en maintenant le débit
    • Au lieu d’un intervalle fixe, il suffirait de mettre en file les écritures pendant qu’un fsync est en cours et de les traiter dans le lot suivant
      C’est similaire à la façon de regrouper des tours de Paxos
      Il faudrait démarrer le lot suivant immédiatement après la fin d’un tour