- 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
Aucun commentaire pour le moment.