Closed15

100日チャレンジ day13 (Raft 分散合意シミュレータ)

riddle_tecriddle_tec

昨日
https://zenn.dev/gin_nazo/scraps/18e06e899e1420


https://blog.framinal.life/entry/2025/04/14/154104

100日チャレンジに感化されたので、アレンジして自分でもやってみます。

やりたいこと

  • 世の中のさまざまなドメインの簡易実装をつくり、バックエンドの実装に慣れる(dbスキーマ設計や、関数の分割、使いやすいインターフェイスの切り方に慣れる
  • 設計力(これはシステムのオーバービューを先に自分で作ってaiに依頼できるようにする
  • 生成aiをつかったバイブコーティングになれる
  • 実際にやったことはzennのスクラップにまとめ、成果はzennのブログにまとめる(アプリ自体の公開は必須ではないかコードはgithubにおく)

できたもの

https://github.com/lirlia/100day_challenge_backend/tree/main/day13_raft_simulator

riddle_tecriddle_tec

Raft についてさっと理解できるアプリをつくる!

riddle_tecriddle_tec

承知しました。ご回答ありがとうございます。頂いた内容を反映させた最終的な仕様を以下にまとめます。

Day 13: Raft 分散合意シミュレータ 仕様

  1. 目的:
    • Raft アルゴリズムの基本動作(リーダー選出、ログ複製、コミットメント)を視覚的に理解する。
    • クラスター構成の動的な変更(ノードの停止、再開、追加、削除)が Raft の動作に与える影響を観察する。
  2. プラットフォーム:
    • クライアントサイド完結 (Web ブラウザ上)。バックエンド API やデータベースは使用しない。
  3. 初期状態:
    • 3つの Raft ノードが配置された状態でシミュレーションを開始する。
  4. 主要機能と UI:
    • 盤面: ノードを配置するメインの表示領域。
    • ノード表示:
      • 円または四角形で表現。
      • 状態(Follower, Candidate, Leader, Stopped)に応じて色分けする。
      • 各ノードの現在の Term 番号と、保持しているログ(コミット済みかも区別)を表示する。ログエントリは { term: number, command: string } (例: { term: 1, command: "CMD 1" }) とする。
    • ノード操作 (カーソル主体):
      • 移動: ノードを盤面上でドラッグ&ドロップして自由に配置できる。
      • 停止/再開: ノードをクリックするとコンテキストメニュー(またはトグルボタン)が表示され、「停止」「再開」を切り替えられる。停止中のノードは視覚的に区別する(例: 灰色表示)。
      • 追加: 盤面の空きスペースを右クリックするか、「ノード追加」ボタンをクリックすると、新しいノードが盤面に追加される。
      • 削除: ノードをクリックした際のコンテキストメニューに「削除」を追加する。
      • コマンド送信: リーダーノードをクリックした際のコンテキストメニューに「コマンド送信」を追加。選択すると、新しい一意のコマンド(例: "CMD 2", "CMD 3")が生成され、リーダーのログに追加されて複製プロセスが開始される。
    • シミュレーション制御:
      • 「開始/一時停止」「リセット」「1ステップ進む」ボタンを配置する。
    • イベントログ:
      • 画面の一部(例: 下部やサイドバー)に、シミュレーション中に発生した主要なイベント(リーダー選出開始、投票、リーダー決定、ログ複製要求/応答、コミット、ノードの追加/削除/停止/再開)の履歴を表示する。
    • RPC 可視化:
      • ノード間で RequestVote や AppendEntries の RPC が送受信される際に、ノード間に矢印を表示して視覚的に表現する。
    • (オプション) ネットワークシミュレーション:
      • 将来的には、ノード間のメッセージ遅延やロスをシミュレートする機能を追加することも検討する(初期実装では必須ではない)。
  5. 技術スタック:
    • フレームワーク: Next.js (App Router)
    • 言語: TypeScript
    • スタイリング: Tailwind CSS
    • 状態管理: React (useState, useEffect, useContext 等)
    • ドラッグ&ドロップ: react-dnd やそれに類するライブラリの利用を検討する。
    • Raft ロジック: lib/raft/ ディレクトリなどに TypeScript で実装する。
riddle_tecriddle_tec

作業計画を提示します。クライアントサイド完結の実装方針に基づき、以下のステップで進めます。

  1. プロジェクト初期化:
    • day13_raft_simulator ディレクトリ作成、template コピー。
    • package.jsonname 変更。
    • README.md に確定仕様を記述。
    • 不要ファイル (prisma ディレクトリ, .env) 削除。
    • package.json から Prisma 依存関係削除。
    • npm install を実行して依存関係を整理。
  2. 基本レイアウトと型定義:
    • app/(pages)/raft/page.tsx を作成し、基本的なページレイアウト(盤面エリア、制御ボタンエリア、イベントログエリア)を Tailwind CSS で作成。
    • Raft のコアな型定義 (NodeState, LogEntry, RPC メッセージ型 など) を lib/types/raft.ts に作成。
  3. Raft ノードクラス実装:
    • lib/raft/node.tsRaftNode クラス(またはそれに準ずるもの)を作成。
    • 状態 (term, state, votedFor, logs, commitIndex, lastApplied) を保持。
    • タイマー管理 (electionTimeout, heartbeatTimeout)。
    • RPC ハンドラ (handleRequestVote, handleAppendEntries) の基本骨格を実装。
    • 状態遷移ロジック (follower -> candidate, candidate -> leader など) の基本骨格を実装。
  4. シミュレーション管理クラス実装:
    • lib/raft/simulation.tsRaftSimulation クラスを作成。
    • ノード群 (RaftNode[]) を管理。
    • ノード間のメッセージング(RPC 呼び出し)機構を実装。
    • シミュレーションのステップ実行 (tick) ロジック(タイマー更新、メッセージ処理)を実装。
    • ノード操作 (add, remove, stop, resume, clientRequest) のメソッドを実装。
  5. UI コンポーネント実装 (表示):
    • components/raft/Node.tsx を作成し、ノードの状態、ログを視覚的に表示。
    • app/(pages)/raft/page.tsxRaftSimulation の状態を useState で管理し、盤面にノードを描画。
    • components/raft/EventLog.tsx を作成し、イベント履歴を表示。
    • RPC の矢印表示コンポーネントを実装。
  6. UI コンポーネント実装 (操作):
    • 制御ボタン (開始/停止、リセット、ステップ実行) を実装し、RaftSimulation のメソッドに接続。
    • ドラッグ&ドロップライブラリ (react-dnd など) を導入し、ノード移動を実装。
    • ノードクリック時のコンテキストメニュー(またはボタン)と、それに対応する操作(停止/再開/削除/コマンド送信)を実装。
    • ノード追加ボタンを実装。
  7. Raft ロジック詳細実装と連携:
    • RaftNode クラスの RPC ハンドラ、状態遷移ロジックを詳細に実装(投票ロジック、ログ複製ロジック、コミットメント更新など)。
    • RaftSimulationRaftNode を連携させ、メッセージングやタイマーが正しく動作するようにする。
    • クライアントコマンド受付からログ複製、コミットまでのフローを実装。
    • ノード構成変更時の Raft の挙動(構成変更ログエントリの追加とコミット)を実装。
  8. テストとデバッグ:
    • 実際に UI を操作しながら、リーダー選出、ログ複製、ノード操作などが仕様通りに動作するか確認。
    • 開発者ツールや console.log を活用してデバッグ。
  9. (オプション) ネットワークシミュレーション実装:
    • 必要に応じて、メッセージ遅延やロスのシミュレーション機能を追加。

この計画で進めます。

riddle_tecriddle_tec

なんか今日の gemini がアホなのか全然指示をまもってくれない

riddle_tecriddle_tec

Raftにおいて、各ノードがフォロワー (Follower) または 候補者 (Candidate) になるかどうかは、以下のような明確なルールに基づいて決定されます。

基本的なルール:

Raftではノードは常に3つの状態のうちどれかを取ります:
• リーダー (Leader)
• 候補者 (Candidate)
• フォロワー (Follower)

状態遷移の条件は、主に ハートビート(heartbeat) と タイムアウト(timeout) によって決定されます。

状態遷移ルールの詳細:

① フォロワー → 候補者 (Follower → Candidate)

フォロワーが一定時間(選挙タイムアウト)以内にリーダーまたは候補者からハートビートまたはRPCメッセージを受信しない場合:
• 選挙タイムアウト(election timeout)経過後、自動的に候補者に遷移する
• 候補者になった時点で 現在の任期(term)を +1 し、自分自身に投票する
• その後、他ノードに投票要求(RequestVote RPC)を送信する

「選挙タイムアウト」は各ノードでランダムに設定され(例: 150–300ミリ秒)、これにより複数ノードが同時に候補者になる確率を減らします。

② 候補者 → フォロワー (Candidate → Follower)

候補者が以下のいずれかの場合にフォロワーに戻ります。
• 他ノードから 有効なリーダー(自分と同じか自分より新しいtermを持つリーダー)を示すメッセージを受け取った場合
→ そのリーダーを認めてフォロワーに戻る
• 他ノードからより新しい任期(term)のRPCを受け取った場合(投票要求やログエントリーの受信)
→ 自分のtermを更新しフォロワーに戻る
• 選挙に負けた場合(自分より多くの票を獲得したノードが現れた場合)
→ フォロワーとして復帰する

③ 候補者 → リーダー (Candidate → Leader)

候補者の状態で 過半数の投票を得た 場合は、リーダーに昇格します。
• リーダーになった瞬間から、即座に各ノードに定期的なハートビートを送信し始めます。

④ フォロワーのままでいる (Follower維持)

フォロワーは以下の場合にそのままフォロワーでいます。
• リーダーまたは候補者から定期的にメッセージを受信し続ける限り、フォロワーとして留まり続けます。
• ハートビートを受け取り続けている間は、選挙タイムアウトは都度リセットされるため、自動的に候補者にはなりません。

まとめ(簡略版):

現在の状態 状態遷移の条件 遷移後の状態
フォロワー タイムアウト発生 候補者になる
候補者 過半数の投票を獲得 リーダーになる
候補者 より新しいtermのメッセージ受信、または有効なリーダー認識、または選挙に敗北 フォロワーになる
フォロワー 有効なリーダーからのハートビート受信継続 フォロワーのまま維持

このルールに従って、Raftはリーダー選出の安全性とライブネスを確保します。

riddle_tecriddle_tec

法人がMPL 2.0(Mozilla Public License 2.0)ライセンスのソフトウェアを商業利用する場合、以下のポイントに気をつければ、安心して使うことができます。

✅ MPL 2.0の基本的な特徴

MPLは「ファイル単位のコピーレフト」です。
つまり、MPLライセンスのコードが含まれているファイルを改変した場合、そのファイルだけは公開義務があるというスタイル。

⚖️ 商用利用で気をつけるポイント

  1. MPLコードを改変した場合、そのファイルだけ公開義務がある
    • 例えば、foo.c というMPLのファイルを編集したら、その foo.c だけは ソースコードとして公開しなければならない。
    • 他の独自ファイル(例えば main.cpp や yourlib.js など)には公開義務は一切ありません。

✅「ファイル単位」のみが対象。MPLコードと独自コードを別ファイルに分けていれば、自社資産を守れます。

  1. 静的リンク・動的リンクの扱い
    • 静的リンクしても MPLでは「ファイル単位」なので、リンクされた他のファイルには影響なし。
    • つまり、静的リンクであっても、改変されたMPLファイルのみ公開すればOK。

GPLとは違って、リンクだけで派生物扱いにはなりません。

  1. バイナリ配布時にも、改変MPLファイルのソースを添付 or 公開する必要がある
    • 社内配布やクローズドでも「第三者配布」扱いになる場合は、該当ファイルのソース開示が必要です。
    • ソースを「同梱」する or 「ダウンロードURLを提供」すればOK(ライセンス的には柔軟です)。

  1. ライセンス表示義務がある
    • 自社アプリや製品にMPLコードを含めた場合、
    • ライセンス表示(例: LICENSE.txt にMPL 2.0の文言と元の著作権者を記載)
    • 使用したMPLコードの著作権表示を残す

通常のOSS利用と同じで、表示義務を守れば問題なし。

  1. 再ライセンスは不可(MPLのままで配布)
    • MPLコードを含んだファイルは、MPLのままで再配布する必要があります。
    • ただし、自社コードは好きなライセンスでOK(MPLとは分離できるから)。

👩‍💼 法人での実務的な注意点まとめ

項目 内容 実務対応
コード改変 改変したファイルはソース公開必須 MPLコードと自社コードは別ファイル管理が安全
バイナリ配布 改変があればソースも提供 配布物にソース/URLを添付
表示義務 著作権表示、MPLライセンス表記 NOTICEやLICENSE.txtに記載
商用利用可否 完全にOK 条件を守れば、販売・提供しても合法
リンク時の影響 静的リンクでも制限なし GPLよりはるかに緩いので使いやすい

✨ 特に便利な点

MPLは商用で最も扱いやすいオープンライセンスの1つです。
GPLのように「全部公開しなきゃいけない!」という制約もなく、
Apache/MITより少しだけ制約があるくらい。

🔐 法務チェックリスト(法人向け)
• 改変したMPLファイルはソースコード提供可能?
• MPLライセンスの文言と著作権表示を保持している?
• 自社コードとMPLコードを分けて管理している?
• 製品に含める際にライセンス一覧にMPLコードを明示している?

何かMPLのコードで具体的に使おうとしているものがあれば、それに即してもう少し実務的に落とし込むこともできますよ!

riddle_tecriddle_tec

Raftライブラリ比較: etcd-io/raft vs hashicorp/raft

🔍 概要比較

観点 etcd-io/raft hashicorp/raft
開発元 CNCF (etcdチーム) HashiCorp (Consulなど)
主用途 etcd等の分散KVS向け アプリ組み込み型の分散管理
抽象度 ローレベル(制御自由) ハイレベル(使いやすさ重視)
依存関係 少ない(軽量) BoltDB等の内蔵ストレージ使用
学習コスト 高め(内部理解必要) 低め(すぐ使える)
柔軟性 非常に高い(自前実装OK) プラグイン式の拡張可能
ドキュメント 少なめ(ソース読む前提) 比較的充実
商用実績 etcd/Kubernetesなど Consul/Nomadなど

💡 向いているユースケース

✅ etcd-io/raft が向いているケース

  • etcdやKubernetesのような本格的な分散KVSや分散制御システムを構築したい
  • 通信・永続化・スナップショットなどを自前で実装/制御したい
  • Raftプロトコルの学習や研究目的にも向いている

🧪 → “素材”として使いたいエンジニア向け


✅ hashicorp/raft が向いているケース

  • アプリケーションに簡単にRaftを組み込みたい
  • 短期でプロトタイピングや社内ツールのリーダー選出やステート管理をやりたい
  • Consulのような構成管理ツールを模倣したい

🧰 → “道具”として即使いたいエンジニア向け


✍️ ざっくり判断チャート

このスクラップは4ヶ月前にクローズされました