Jepsen: NATS 2.12.1
(jepsen.io)- Jepsen validierte die Robustheit und Konsistenz des verteilten Messaging-Systems NATS JetStream unter verschiedenen Fehlerbedingungen
- Die Testergebnisse zeigten bei Dateibeschädigung (.blk, Snapshot) und Power-Failure-Simulationen Datenverlust und Split-Brain-Zustände
- JetStream führt standardmäßig
fsyncnur alle 2 Minuten aus, sodass zuletzt bestätigte Nachrichten möglicherweise nicht auf der Festplatte gespeichert sind - Ein einzelner OS-Crash eines Nodes kann bereits zu Datenverlust und Replikatsinkonsistenz führen
- Jepsen empfiehlt für NATS, die Standardeinstellung auf
fsync=alwayszu ändern oder die Datenverlustgefahr explizit in der Dokumentation zu kennzeichnen
1. Hintergrund
- NATS ist ein beliebtes Streaming-System, das Nachrichten als Stream publiziert und abonniert
- JetStream verwendet den Raft-Konsensalgorithmus, repliziert Daten und garantiert at-least-once-Zustellung
- JetStream behauptet in der Dokumentation, Linearizable-Konsistenz und Always-Available-Verfügbarkeit zu bieten, doch laut CAP-Theorem können diese beiden Anforderungen nicht gleichzeitig erfüllt werden
- Laut NATS-Dokumentation kann ein 3-Knoten-Stream 1 und ein 5-Knoten-Stream 2 Serverausfälle tolerieren
- Eine Nachricht gilt als „erfolgreich gespeichert“, sobald der Server die
publish-Anfrage acknowledged hat - Zur Wahrung der Datenkonsistenz wird ein Quorum von mehr als der Hälfte der Nodes benötigt; in einem 5-Knoten-Cluster sind mindestens 3 Nodes erforderlich, damit neue Nachrichten gespeichert werden können
2. Testdesign
- Jepsen führte die Tests mit dem JNATS 2.24.0-Client und in einer Debian-12-LXC-Container-Umgebung durch
- Einige Tests nutzten in der Antithesis-Umgebung das offizielle NATS-Docker-Image
- Es wurde ein einzelner JetStream-Stream (Replikationsfaktor 5) aufgebaut und Prozessabbruch, Absturz, Netzwerktrennung, Paketverlust, Dateibeschädigung u. a. injiziert
- Mit dem LazyFS-Dateisystem wurde eine Power-Failure-Simulation durchgeführt, die nicht
fsync-gesicherte Schreibvorgänge verliert - Jeder Prozess veröffentlichte eindeutige Nachrichten; nach Testende wurde auf allen Nodes geprüft, ob acknowledgierte Nachrichten vorhanden sind
- Wenn Nachrichten nur auf einigen Nodes vorhanden sind, wird das als Divergence (Replikationsabweichung) klassifiziert
3. Hauptbefunde
3.1 Vollständiger Datenverlust in NATS 2.10.22 (#6888)
- Es wurde ein vollständiger Verlust eines gesamten JetStream-Streams durch einen einfachen Prozessabsturz festgestellt
- Nach dem Fehler
"No matching streams for subject"gab es über Stunden keine Wiederherstellung - Ursache war Leader-Snapshot-Inversion, Raft-Statuslöschung u. a.; behoben in Version 2.10.23
3.2 Datenverlust bei Beschädigung von .blk-Dateien (#7549)
- Bei einem einzigen Bitfehler oder Trunkierung in der JetStream-
.blk-Datei ging eine große Anzahl bestätigter Schreibvorgänge verloren- Beispiel: 679.153 von 1.367.069 gingen verloren
- Auch bei Beschädigung nur einzelner Nodes kam es zu massivem Datenverlust und Split-Brain
- Beispiel: Bis zu 78 % Nachrichtenverlust auf den Nodes
n1,n3,n5
- Beispiel: Bis zu 78 % Nachrichtenverlust auf den Nodes
- NATS untersucht den Fehler derzeit noch
3.3 Gesamtdatenverlust bei Snapshot-Dateibeschädigung (#7556)
- Wird die Snapshot-Datei unter
data/jetstream/$SYS/_js_/beschädigt, stuft der Node den Stream als orphaned ein und löscht sämtliche Daten - Bereits die Beschädigung weniger Nodes verhindert das Quorum und macht den Stream dauerhaft unbenutzbar
- Beispiel: Beschädigung der Nodes
n3,n5führte dazu, dassn3zum Leader gewählt wurde undjepsen-streamvollständig gelöscht wurde - Jepsen weist darauf hin, dass bei der Leader-Wahl ein beschädigter Node zum Leader werden kann
3.4 Datenverlust durch die Standard-fsync-Einstellung (#7564)
- JetStream führt standardmäßig nur alle 2 Minuten
fsyncaus, bestätigt Nachrichten jedoch sofort- Dadurch bleiben kürzlich bestätigte Nachrichten ungespeichert auf der Platte
- Bei Stromausfall oder Kernel-Crash gehen innerhalb von Sekundenbereichs bestätigte Nachrichten verloren
- Beispiel: 131.418 von 930.005 gingen verloren
- Auch bei aufeinanderfolgenden Ausfällen einzelner Nodes ist ein vollständiges Löschen des Streams möglich
- Das Verhalten wird in der Dokumentation kaum erwähnt
- Jepsen empfiehlt die Umstellung auf
fsync=alwaysoder einen ausdrücklichen Warnhinweis zum Datenverlust-Risiko
3.5 Split-Brain durch einen einzelnen OS-Crash (#7567)
- Ein Stromausfall oder Kernel-Crash eines einzelnen Nodes kann bereits zu Datenverlust und Replikatsinkonsistenz führen
- In einem Leader-Follower-Setup kann es passieren, dass Knoten einen Write nur im Speicher committen und bestätigen, aber bei einem Ausfall danach
verlieren mehrere Nodes diesen Write und arbeiten in einem neuen Zustand weiter - In den Tests trat nach einem einzelnen Stromausfall ein anhaltendes Split-Brain auf
- Pro Node wurden unterschiedliche Intervalle fehlender acknowledged Nachrichten beobachtet
- Jepsen verweist auf ähnliche Fälle bei Kafka und betont, dass dasselbe Risiko auch in Raft-basierten Systemen existiert
4. Diskussion und Fazit
- Das Problem des vollständigen Datenverlusts in 2.10.22 wurde in 2.10.23 behoben
- In 2.12.1 treten weiterhin Datenverlust und Split-Brain durch Dateibeschädigung und OS-Crashs auf
- Bei
.blk- und Snapshot-Dateibeschädigungen fehlen auf einzelnen Nodes Nachrichten oder der gesamte Stream wird gelöscht - Durch den langen Standard-
fsync-Intervall besteht bei gleichzeitigen Ausfällen mehrerer Nodes weiterhin das Risiko, dass acknowledged Daten verloren gehen - Jepsen empfiehlt
fsync=alwaysoder eine klare Risikodokumentation in den Unterlagen - Die Aussage „Always Available“ von JetStream ist im Rahmen des CAP-Theorems unmöglich, die Dokumentation muss angepasst werden
- Jepsen betont, dass ein Bug nachgewiesen werden kann, das Fehlen von Sicherheit jedoch nicht beweisbar ist
4.1 Rolle von LazyFS
- Mit LazyFS wurden die Verluste nicht
fsync-gesicherter Schreibvorgänge simuliert - Bei Stromausfall können so verschiedene Storage-Fehler, darunter partielle Schreibfehler (torn writes), nachgestellt werden
- In der zugehörigen Arbeit When Amnesia Strikes (VLDB 2024) werden ähnliche Bugs auch bei PostgreSQL, Redis und ZooKeeper berichtet
4.2 Nächste Schritte
- Der Verlust auf Single-Consumer-Ebene, die Reihenfolge von Nachrichten sowie die Prüfung von Linearizable/Serializable-Garantien wurden nicht durchgeführt
- Die exactly-once-Zustellung wird ebenfalls als Thema für künftige Untersuchungen genannt
- Bei Node-Hinzufügung und -Entfernung wurden Dokumentationsfehler sowie das Fehlen von Pflicht-Health-Check-Schritten festgestellt (#7545)
- Ein sicheres Verfahren zur Änderung der Cluster-Konfiguration ist weiterhin unklar
1 Kommentare
Hacker-News-Kommentare
Inzwischen frage ich mich, ob eine KI vielleicht die Projektdokumentation lesen und allein anhand der Marketingtexte ein Risiko für Datenverlust vorhersagen könnte
Die Leute sagen immer, „Theorie wird überbewertet“ oder „Hacken ist besser als schulische Ausbildung“, aber am Ende stellen sie sich im dokumentierten Problemraum selbst ein Bein
Auch die subtilen Scaling-Details hat es gut gehandhabt
Persistence habe ich allerdings nie genutzt, und ich hätte nicht gedacht, dass es so verwundbar ist
Es ist erschreckend, dass schon eine einzelne Bit-Beschädigung einer Datei problematisch sein kann
Eine sehr gute Referenz → Jepsen Glossary
Ich habe aphyr.com erst vor Kurzem entdeckt und erwarte dort viele Einsichten
Später entwickelte sich jepsen.io zu einem professionellen Projekt und wird seit etwa zehn Jahren ernsthaft betrieben
Soll damit die Benchmark-Performance erhöht werden? In kleinen Clustern ist so eine Einstellung oft die Ursache von Problemen
Viele Anwendungen brauchen keine vollständige Dauerhaftigkeit, daher kann lazy fsync nützlich sein
Ob es der Standardwert sein sollte, ist aber umstritten
So etwas müsste sich doch wie bei TCP corking mit Batching lösen lassen
Ausfälle durch lazy fsync treten auf den meisten Knoten nicht gleichzeitig auf
Vorteil: unbegrenzte Streams mit Dauerhaftigkeit auf Objekt-Storage-Niveau
Nachteil: noch keine Consumer-Group-Funktion
Wenn mehrere Knoten gleichzeitig ausfallen, kann es zum Verlust bereits committeter Daten kommen
Das erinnert an das „Web-Scale“-Marketing aus den frühen MongoDB-Tagen
Ich finde, der Standardwert sollte immer die sicherste Option sein
Genau das fand ich sogar gut, weil man darauf aufbauend sein System entwerfen konnte
Als ich es 2018 verwendet habe, war es performant und einfach zu betreiben
Bei PostgreSQL ist das Standard-Transaktionsisolationsniveau zum Beispiel read committed
Auch Redis macht standardmäßig nur einmal pro Sekunde fsync
Auch bei standalone Redis kann man Ack nach fsync konfigurieren, aber wegen OS-Buffering ist eine vollständige Garantie schwierig
Am Ende ist es wichtig, die Bedeutung eines Acks genau zu verstehen
Wenn man nur auf sichere Standardwerte besteht, leidet die Performance stark, und die Nutzer müssen alles selbst tunen
Zum Beispiel ist auch das Standard-Isolationsniveau von Postgres schwach genug, dass Race Conditions auftreten können
Siehe: Hermitage-Artikel
Im SSD-Zeitalter sind Zwischenstufen wie Group Commit verschwunden, und jetzt ist der Syscall-Wechselaufwand der Flaschenhals
Zwei Minuten sind ein viel zu langes Intervall (auch der Unterschied zwischen fdatasync und fsync sollte berücksichtigt werden)
Vermutlich wäre es besser, einfach Redpanda zu verwenden
Wenn man in festen Intervallen einen Batch-Flush ausführt, würde die Latenz zwar steigen, aber der Durchsatz könnte erhalten bleiben
Das ähnelt der Art, wie Paxos-Runden gebündelt werden
Sobald eine Runde abgeschlossen ist, sollte direkt der nächste Batch gestartet werden