Nimm an der Hanzo-Beta teil und erhalte 50 % auf jeden bezahlten Tarif

Raft abschaffen: vom ZooKeeper-Konsens zu Lux Quasar, nativ

Wie Hanzo Datastore die NuRaft-Engine unter ClickHouse Keeper durch Lux Quasar ersetzt — postquantenfest, Konsens ohne Leader, direkt in den C++-Server gelinkt — und die ZooKeeper-API behält, die die Replikation spricht.

Auf dieser Seite

Hanzo Datastore koordiniert replizierte Tabellen heute so, wie ClickHouse es tut: ein eingebetteter, ZooKeeper-kompatibler Keeper, der Raft im Serverprozess laufen lässt. Es funktioniert. Es ist auch die letzte klassische, leader-basierte, präquante Komponente im Stack. Dieser Beitrag erzählt, wie wir sie beseitigen — und warum „ZK töten“ nicht heißt, die Replikation neu zu schreiben.

Keeper entflechten: API gegen Engine

Der Reflex beim Hören von „ZooKeeper ersetzen“ ist, sich vorzustellen, jedes Replicated*MergeTree anfassen zu müssen. Das musst du nicht, denn ClickHouse Keeper sind schon zwei trennbare Dinge, in einen Daemon geflochten:

  1. Die ZooKeeper-API — ein linearisierbarer Schlüssel-Wert-Baum mit sequenziellen Knoten, Versionen und Watches. Im Quelltext ist das KeeperStorage, umhüllt von KeeperStateMachine (src/Coordination/KeeperStateMachine.h, class IKeeperStateMachine : public nuraft::state_machine). Das ist der Vertrag. Jede replizierte Tabelle, jede system.zookeeper-Abfrage, jede Leader-Wahl spricht ihn.

  2. Die Konsens-Engine — das, was einen Strom von Schreibanfragen nimmt, ein Quorum von Knoten auf ihre Reihenfolge einigen lässt und dann commit(log_idx, data) auf der Zustandsmaschine in dieser vereinbarten Reihenfolge aufruft. Heute ist das NuRaft: KeeperServer::launchRaftServer (src/Coordination/KeeperServer.cpp) baut einen nuraft::raft_server, einen asio_service, einen KeeperStateManager und einen KeeperLogStore.

Die saubere Beobachtung — die von Rich Hickey — ist, dass KeeperStateMachine schon gegen die generische Konsensschnittstelle geschrieben ist (nuraft::state_machine: pre_commit, commit, rollback, Snapshots). Sie weiß nicht und es ist ihr egal, dass Raft darunter liegt. Die Engine ist ein Wert, den du tauschen kannst, keine Stelle, die an die API geschweißt ist.

Also: behalt die Zustandsmaschine, tausch die Engine aus. Die ZooKeeper-API, von der die Replikation abhängt, bleibt unberührt. Was stirbt, sind NuRaft, die Leader-Wahl und die Pflicht, ein separates Raft-Quorum zu betreiben.

Was an ihre Stelle tritt

Die Engine wird Quasar — der leaderlose, DAG-basierte, postquanten Konsens von Lux. Quasars Aufgabe ist hier identisch mit der von Raft: sich auf die Reihenfolge eines Logs einigen und signalisieren, wann ein Eintrag final ist. Der Unterschied liegt darin, wie die Einigung entsteht — kein einzelner Leader, auf den umgeschaltet wird, ML-DSA-Signaturen statt klassischer, und eine Finalität über eine Konfidenzschwelle statt einer Mehrheit einer festen Amtszeit.

Entscheidend: Quasar ist kein Go-Dienst, zu dem wir einen Prozess starten. Es liefert eine native C-Engine mit einem C++-fertigen ABI (libluxconsensus). Der Datastore-Server ist C++. Die Engine linkt also in denselben Prozess — kein Sidecar, kein Netzsprung zu einem Koordinator, keine zweite Sprache zur Laufzeit.

Die Naht ist bewiesen — an der echten Zustandsmaschine

Das ist keine Skizze, und es ist kein Modell mehr. Wir haben ein Programm gebaut, das die echte ClickHouse-Serverbibliothek linkt (libdbms.a) und die produktive KeeperStateMachine<KeeperMemoryStorage> treibt — genau die Klasse, über die ReplicatedMergeTree koordiniert — vollständig auf Quasar, mit NuRaft außerhalb des Ordnungspfads. Es lebt im Datastore-Baum unter src/Coordination/examples/keeper_quasar_poc.cpp und wird auf einem echten Linux-Host gebaut und ausgeführt (aarch64, clang-21).

Der Ablauf folgt treu, wie der Dispatcher NuRaft heute treibt — zwei Phasen, preprocess, wenn eine Anfrage angehängt wird, und commit, wenn der Konsens ihre Reihenfolge festschreibt:

state_machine->preprocess(rfs, /*lock_mutex*/ true);     // append-time, pre-consensus (≙ nuraft pre_commit)
auto buf = IKeeperStateMachine::getZooKeeperLogEntry(rfs); // the real ZooKeeper log entry
lux_chain_add_block(chain, &blk);   // the entry rides Quasar (the slot raft_server occupies)
lux_consensus_process_vote(...);    // validators agree — no leader
lux_consensus_is_accepted(chain, id, &accepted);
state_machine->commit(height, *buf); // finality → the real KeeperStorage mutates, in order

Gegen eine ReplicatedMergeTree-Koordinationsfolge von zehn Anfragen gelaufen — Replik-Registrierung, Blocknummern-Sprünge, Leader-Wahl — verarbeitet es jede Anfrage vor, ordnet sie über Quasar, finalisiert, committet in den echten KeeperStorage und prüft, dass der entstandene Baum exakt stimmt, jedes Element einmal, in Konsensreihenfolge:

== REAL Keeper state machine reached consensus on Quasar — zero Raft, zero ZK ensemble ==

Darunter sitzt die minimale C-ABI-Demonstration (pkg/c/examples/cpp_consumer.cpp, make test-cpp), die libluxconsensus linkt und einen einzelnen Block finalisiert — dieselbe Engine, ohne die ClickHouse-Abhängigkeit. Beide sind reproduzierbar; das Datastore-Beispiel ist optional (-DENABLE_EXAMPLES=1 -DLUXCONSENSUS_DIR=...) und nie Teil eines normalen Builds.

Leg es eins zu eins auf Keeper:

Keeper / NuRaft heuteQuasar-nativ
nuraft::raft_server ordnet das Loglux_chain_t ordnet das Log
KeeperLogStore / ChangelogQuasars DAG-Log
Raft-Leader-Wahlleaderlose Konfidenzwahl
state_machine->commit(idx, data)derselbe Aufruf, ausgelöst bei is_accepted
klassische Signaturen, Leader mit fester AmtszeitML-DSA-Signaturen, kein Leader
separates ZK/Keeper-Quorum zum BetreibenKonsens im Prozess, im Server

KeeperStateMachine::commit ist die Nahtstelle. NuRaft ruft sie heute auf; der Quasar-Treiber ruft morgen dieselbe Methode auf. Die Zustandsmaschine kann den Unterschied nicht erkennen — und genau diese Eigenschaft macht daraus einen Tausch und keine Neufassung.

Warum das über „post-quantum“ hinaus zählt

  • Kein Leader. Raft hat einen gewählten Leader pro Amtszeit; sein Ausfall hält Schreibvorgänge an, bis neu gewählt wird. Quasars Einigung ohne Leader nimmt diese Head-of-Line-Abhängigkeit weg.
  • Kein zweites Quorum zum Betreiben. Koordination hört auf, ein separates System zu sein, das du bereitstellst, überwachst und sicherst. Sie ist der Server.
  • Postquantisch durch die Konstruktion. Die Signaturen, die die Einigung schützen, sind ML-DSA, keine klassischen Kurven, sodass ein künftiger Quanten-Angreifer Konsensnachrichten nicht fälschen kann.
  • Eine Transportgeschichte. Sie fügt sich in den Rest der Richtung — natives TCP/HTTP jetzt, ZAP als Nächstes, und dieselben Lux-Primitive, die die Chains nutzen.

Ehrlicher Stand

Die Architektur oben ist real, und der Beweis läuft jetzt gegen die produktive KeeperStateMachine — kein Modell —, gebaut und grün auf einem echten Linux-Host. Was bleibt, ist der Tausch in der Engine: das Quasar-Konsens-Backend innerhalb von KeeperServer neben NuRaft einführen, es zum Standard machen und dann den Rest dessen, was KeeperServer tut — Snapshots, Wiederherstellung, Sitzungsablauf, Rekonfiguration — darauf tragen, mit der ganzen Coordination-Testsuite grün. Das ist echte Arbeit, und wir werden dir nicht sagen, Raft sei weg an dem Tag, an dem wir den Commit-Pfad bewiesen haben. Wir sagen dir, dass die Naht gegen die echten Typen hält, dass die Nahtstelle KeeperStateMachine::commit ist, und dass dort das ganze Spiel liegt.

Wenn es landet, verlassen contrib/NuRaft/ und die Raft-Engine den Baum. Die ZooKeeper-API bleibt, solange die Replikation sie spricht. So beseitigst du die letzte präquante Komponente, ohne ein einziges MergeTree anzufassen.

Kommentare

Melde dich mit deiner Hanzo ID an, um zu kommentieren.

Kommentare werden geladen…

Weiterlesen