Entre na beta da Hanzo e ganhe 50% de desconto em qualquer plano pago

Acabando com o Raft: do consenso ZooKeeper ao Lux Quasar, nativo

Como o Hanzo Datastore troca o motor NuRaft sob o ClickHouse Keeper pelo Lux Quasar — consenso pós-quântico e sem líder, ligado direto no servidor C++ — e mantém a API ZooKeeper que a replicação fala.

Nesta página

Hanzo Datastore coordena hoje as tabelas replicadas do jeito que o ClickHouse faz: um Keeper compatível com ZooKeeper, embutido, que roda Raft dentro do processo do servidor. Funciona. Também é o último componente clássico, com líder e pré-quântico, da pilha. Este post conta como a gente mata isso — e por que «matar ZK» não significa reescrever a replicação.

Desentrelaçar o Keeper: API versus motor

O reflexo, ao ouvir «substituir o ZooKeeper», é imaginar que dá para mexer em cada Replicated*MergeTree. Você não precisa, porque o ClickHouse Keeper já são duas coisas separáveis trançadas num daemon só:

  1. A API ZooKeeper — uma árvore chave-valor linearizável, com nós sequenciais, versões e watches. No código isso é KeeperStorage, embrulhado por KeeperStateMachine (src/Coordination/KeeperStateMachine.h, class IKeeperStateMachine : public nuraft::state_machine). Esse é o contrato. Cada tabela replicada, cada consulta a system.zookeeper, cada eleição de líder fala com ele.

  2. O motor de consenso — o que pega um fluxo de pedidos de escrita, faz um quórum de nós concordar com a ordem e depois chama commit(log_idx, data) na máquina de estados nessa ordem combinada. Hoje isso é o NuRaft: KeeperServer::launchRaftServer (src/Coordination/KeeperServer.cpp) constrói um nuraft::raft_server, um asio_service, um KeeperStateManager e um KeeperLogStore.

A observação limpa — a do Rich Hickey — é que KeeperStateMachine já está escrita contra a interface de consenso genérica (nuraft::state_machine: pre_commit, commit, rollback, snapshots). Ela não sabe, nem se importa, que o Raft está embaixo. O motor é um valor que você troca, não um ponto soldado na API.

Então: fique com a máquina de estados, troque o motor. A API ZooKeeper da qual a replicação depende sobrevive intacta. O que morre é o NuRaft, a eleição de líder e a obrigação de rodar um quórum Raft separado.

O que entra no lugar

O motor vira Quasar — o consenso sem líder, baseado em DAG e pós-quântico da Lux. O trabalho do Quasar aqui é idêntico ao do Raft: concordar com a ordem de um log e avisar quando uma entrada é final. A diferença está em como se concorda — nenhum líder único para failover, assinaturas ML-DSA no lugar das clássicas, e uma finalidade por limiar de confiança em vez da maioria de um termo fixo.

O ponto decisivo: Quasar não é um serviço em Go para o qual a gente dispara um processo. Ele traz um motor C nativo com uma ABI pronta para C++ (libluxconsensus). O servidor do Datastore é C++. O motor então se liga no mesmo processo — sem sidecar, sem salto de rede até um coordenador, sem uma segunda linguagem em tempo de execução.

A emenda está provada — contra a máquina de estados real

Isso não é um rascunho, e já não é um modelo. A gente construiu um programa que liga a biblioteca real do servidor ClickHouse (libdbms.a) e dirige a KeeperStateMachine<KeeperMemoryStorage> de produção — a classe exata pela qual o ReplicatedMergeTree se coordena — inteira em cima do Quasar, com o NuRaft fora do caminho de ordenação. Ele vive na árvore do Datastore em src/Coordination/examples/keeper_quasar_poc.cpp e é compilado e executado num host Linux de verdade (aarch64, clang-21).

O fluxo é fiel a como o despachante dirige o NuRaft hoje — duas fases, preprocess quando um pedido é anexado e commit quando o consenso fecha a ordem:

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

Rodado contra uma sequência de coordenação ReplicatedMergeTree de dez pedidos — registro da réplica, incrementos do número do bloco, eleição de líder — ele pré-processa cada pedido, ordena pelo Quasar, finaliza, faz commit no KeeperStorage real e confere que a árvore resultante está exatamente correta, aplicada uma vez cada, na ordem do consenso:

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

Embaixo fica a demonstração mínima da ABI C (pkg/c/examples/cpp_consumer.cpp, make test-cpp), que liga libluxconsensus e finaliza um único bloco — o mesmo motor, sem a dependência do ClickHouse. As duas são reproduzíveis; o exemplo do Datastore é opcional (-DENABLE_EXAMPLES=1 -DLUXCONSENSUS_DIR=...) e nunca entra num build normal.

Mapeie no Keeper, um para um:

Keeper / NuRaft hojeNativo no Quasar
nuraft::raft_server ordena o loglux_chain_t ordena o log
KeeperLogStore / Changelogo log DAG do Quasar
eleição de líder do Raftvotação de confiança sem líder
state_machine->commit(idx, data)a mesma chamada, disparada em is_accepted
assinaturas clássicas, líder de termo fixoassinaturas ML-DSA, sem líder
um quórum ZK/Keeper separado para operarconsenso no processo, dentro do servidor

KeeperStateMachine::commit é o ponto de junção. O NuRaft chama isso hoje; o driver do Quasar chama o método idêntico amanhã. A máquina de estados não consegue perceber a diferença — e é exatamente essa propriedade que faz disso uma troca, e não uma reescrita.

Por que isso importa além do «pós-quântico»

  • Sem líder. O Raft tem um único líder eleito por termo; a falha dele trava as escritas até a reeleição. O acordo sem líder do Quasar tira essa dependência de cabeça de fila.
  • Sem um segundo quórum para rodar. A coordenação deixa de ser um sistema à parte que você provisiona, monitora e do qual faz backup. Ela é o servidor.
  • Pós-quântico por construção. As assinaturas que protegem o acordo são ML-DSA, não curvas clássicas, então um adversário quântico futuro não consegue forjar mensagens de consenso.
  • Uma história só de transporte. Isso se compõe com o resto da direção — TCP/HTTP nativo agora, ZAP em seguida, e as mesmas primitivas da Lux que as cadeias usam.

Status honesto

A arquitetura acima é real, e a prova agora roda contra a KeeperStateMachine de produção — não um modelo —, compilada e verde num host Linux de verdade. O que falta é a troca dentro do motor: introduzir o backend de consenso Quasar dentro do KeeperServer, ao lado do NuRaft, torná-lo o padrão e depois carregar o resto do que o KeeperServer faz — snapshots, recuperação, expiração de sessão, reconfiguração — com a suíte inteira de testes de Coordination verde. Isso é trabalho de verdade, e não vamos te dizer que o Raft sumiu no dia em que provamos o caminho de commit. Estamos te dizendo que a emenda segura contra os tipos reais, que o ponto de junção é KeeperStateMachine::commit, e que o jogo inteiro está aí.

Quando chegar, contrib/NuRaft/ e o motor Raft saem da árvore. A API ZooKeeper fica enquanto a replicação falar com ela. É assim que se mata o último componente pré-quântico sem tocar um único MergeTree.

Comentários

Entre com seu Hanzo ID para comentar.

Carregando comentários…

Leia mais