Rejoignez la bêta Hanzo et obtenez 50 % de réduction sur toute offre payante

En finir avec Raft : du consensus ZooKeeper à Lux Quasar, nativement

Comment Hanzo Datastore remplace le moteur NuRaft sous ClickHouse Keeper par Lux Quasar — consensus post-quantique sans leader, lié directement dans le serveur C++ — tout en gardant l'API ZooKeeper que parle la réplication.

Sur cette page

Hanzo Datastore coordonne aujourd'hui les tables répliquées comme ClickHouse : un Keeper compatible ZooKeeper, embarqué, qui fait tourner Raft dans le processus du serveur. Cela fonctionne. C'est aussi le dernier composant classique, à leader et pré-quantique, de la pile. Cet article raconte comment nous le supprimons — et pourquoi « tuer ZK » ne veut pas dire réécrire la réplication.

Démêler Keeper : l'API face au moteur

Le réflexe, en entendant « remplacer ZooKeeper », est d'imaginer qu'il faut toucher chaque Replicated*MergeTree. Vous n'avez pas à le faire, parce que ClickHouse Keeper est déjà deux choses séparables tressées dans un seul démon :

  1. L'API ZooKeeper — un arbre clé-valeur linéarisable, avec des nœuds séquentiels, des versions et des watches. Dans les sources, c'est KeeperStorage, enveloppé par KeeperStateMachine (src/Coordination/KeeperStateMachine.h, class IKeeperStateMachine : public nuraft::state_machine). C'est le contrat. Chaque table répliquée, chaque requête system.zookeeper, chaque élection de leader le parle.

  2. Le moteur de consensus — ce qui prend un flux de requêtes d'écriture, obtient qu'un quorum de nœuds s'accorde sur leur ordre, puis appelle commit(log_idx, data) sur la machine à états dans cet ordre convenu. Aujourd'hui, c'est NuRaft : KeeperServer::launchRaftServer (src/Coordination/KeeperServer.cpp) construit un nuraft::raft_server, un asio_service, un KeeperStateManager et un KeeperLogStore.

L'observation nette — celle de Rich Hickey — est que KeeperStateMachine est déjà écrite contre l'interface de consensus générique (nuraft::state_machine : pre_commit, commit, rollback, instantanés). Elle ne sait pas, et ne se soucie pas, que Raft soit en dessous. Le moteur est une valeur que vous pouvez remplacer, pas un point soudé à l'API.

Donc : gardez la machine à états, remplacez le moteur. L'API ZooKeeper dont dépend la réplication survit intacte. Ce qui meurt, c'est NuRaft, l'élection de leader, et l'obligation de faire tourner un quorum Raft à part.

Ce qui le remplace

Le moteur devient Quasar — le consensus sans leader, fondé sur un DAG et post-quantique de Lux. Le travail de Quasar ici est identique à celui de Raft : s'accorder sur l'ordre d'un journal et signaler quand une entrée est finale. La différence est dans la manière de s'accorder — pas de leader unique vers lequel basculer, des signatures ML-DSA à la place des signatures classiques, et une finalité par seuil de confiance plutôt qu'une majorité d'un terme fixe.

Le point décisif : Quasar n'est pas un service Go vers lequel nous lançons un processus. Il livre un moteur C natif avec une ABI prête pour C++ (libluxconsensus). Le serveur Datastore est en C++. Le moteur se lie donc dans le même processus — pas de sidecar, pas de saut réseau vers un coordinateur, pas de second langage à l'exécution.

La couture est prouvée — contre la vraie machine à états

Ce n'est pas une esquisse, et ce n'est plus un modèle. Nous avons construit un programme qui lie la bibliothèque réelle du serveur ClickHouse (libdbms.a) et pilote la KeeperStateMachine<KeeperMemoryStorage> de production — la classe exacte par laquelle ReplicatedMergeTree se coordonne — entièrement sur Quasar, avec NuRaft hors du chemin d'ordonnancement. Il vit dans l'arbre Datastore, à src/Coordination/examples/keeper_quasar_poc.cpp, et il se compile et s'exécute sur un hôte Linux réel (aarch64, clang-21).

Le flux est fidèle à la façon dont le répartiteur pilote NuRaft aujourd'hui — deux phases, preprocess quand une requête est ajoutée au journal et commit quand le consensus fige son ordre :

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

Lancé contre une séquence de coordination ReplicatedMergeTree de dix requêtes — enregistrement de réplique, incréments du numéro de bloc, élection de leader — il prétraite chaque requête, les ordonne via Quasar, finalise, commit dans le KeeperStorage réel et vérifie que l'arbre résultant est exactement le bon, appliqué une fois chacun, dans l'ordre du consensus :

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

En dessous se trouve la démonstration minimale de l'ABI C (pkg/c/examples/cpp_consumer.cpp, make test-cpp), qui lie libluxconsensus et finalise un seul bloc — le même moteur, sans la dépendance ClickHouse. Les deux sont reproductibles ; l'exemple Datastore est optionnel (-DENABLE_EXAMPLES=1 -DLUXCONSENSUS_DIR=...) et ne fait jamais partie d'une compilation normale.

Posez-le sur Keeper, terme à terme :

Keeper / NuRaft aujourd'huiNatif Quasar
nuraft::raft_server ordonne le journallux_chain_t ordonne le journal
KeeperLogStore / Changelogle journal DAG de Quasar
élection de leader Raftvote de confiance sans leader
state_machine->commit(idx, data)le même appel, déclenché sur is_accepted
signatures classiques, leader à terme fixesignatures ML-DSA, pas de leader
un quorum ZK/Keeper séparé à opérerconsensus dans le processus, dans le serveur

KeeperStateMachine::commit est le point de jonction. NuRaft l'appelle aujourd'hui ; le pilote Quasar appelle la méthode identique demain. La machine à états ne peut pas faire la différence — et c'est exactement la propriété qui fait de ceci un remplacement, pas une réécriture.

Pourquoi cela compte au-delà du « post-quantique »

  • Pas de leader. Raft a un seul leader élu par terme ; sa chute bloque les écritures jusqu'à la réélection. L'accord sans leader de Quasar retire cette dépendance de tête de ligne.
  • Pas de second quorum à faire tourner. La coordination cesse d'être un système à part que vous provisionnez, surveillez et sauvegardez. C'est le serveur.
  • Post-quantique par construction. Les signatures qui protègent l'accord sont ML-DSA, pas des courbes classiques, si bien qu'un adversaire quantique futur ne peut pas forger les messages de consensus.
  • Une seule histoire de transport. Cela se compose avec le reste de la direction — TCP/HTTP natif maintenant, ZAP ensuite, et les mêmes primitives Lux que les chaînes utilisent.

Où nous en sommes, sans détour

L'architecture ci-dessus est réelle, et la preuve tourne déjà contre la KeeperStateMachine de production — pas un modèle —, compilée et au vert sur un hôte Linux réel. Ce qui reste, c'est le remplacement dans le moteur : introduire le backend de consensus Quasar à l'intérieur de KeeperServer, à côté de NuRaft, en faire le choix par défaut, puis y porter le reste de ce que fait KeeperServer — instantanés, reprise, expiration de session, reconfiguration — avec toute la suite de tests Coordination au vert. C'est du vrai travail, et nous n'allons pas vous dire que Raft a disparu le jour où nous avons prouvé le chemin de commit. Nous vous disons que la couture tient contre les vrais types, que le point de jonction est KeeperStateMachine::commit, et que toute la partie se joue là.

Quand cela atterrit, contrib/NuRaft/ et le moteur Raft quittent l'arbre. L'API ZooKeeper reste tant que la réplication la parle. C'est ainsi que l'on supprime le dernier composant pré-quantique sans toucher un seul MergeTree.

Commentaires

Connectez-vous avec votre identifiant Hanzo pour commenter.

Chargement des commentaires…

Lire la suite