ふくふくHukuhuku LLC
EP.08Algorithms 11分公開: 更新:

Raft:etcd・Consul・TiDB の中核を支える分散合意アルゴリズム

「複数のサーバが障害を起こしても、全員が同じ値で合意する」分散合意 Raft を Python で実装。Paxos より理解しやすく、Kubernetes / TiDB / CockroachDB が採用。

#Raft#分散合意#etcd#Kubernetes
執筆 / 監修
松尾 亮合同会社ふくふく 代表社員

データ基盤・データパイプライン構築 / BI / 生成 AI 活用支援を専門とするエンジニア (28 年)。 本記事は AI 利用ポリシーに基づき、生成 AI の補助で執筆 → 人間が監修・編集して公開しています。

プロフィール詳細
CO📔 Google Colab で開く(上から順にセルを実行)
シェア

Kubernetes は etcd の上で動き、etcd は Raft の上で動く。TiDB / CockroachDB の分散 SQL も Raft が中核。「複数のサーバが障害を起こしても、全員が同じ値に合意する」という難問 (分散合意問題) を解くのが Raft。Paxos より理解しやすく、本番採用が爆発的に増えた。

1. これで何が動いているか

  • etcd (Kubernetes の状態ストア)
  • HashiCorp Consul / Vault
  • TiDB / CockroachDB (分散 SQL の region 合意)
  • MongoDB レプリカセット (リーダー選出)
  • RabbitMQ Quorum Queues
  • Apache Kafka KRaft mode

2. 仕組みのざっくり

  • 3 状態: Follower / Candidate / Leader
  • term (任期): リーダーごとの番号。古い term の通信は無視される
  • ログ: 各ノードが順序付きで操作を記録
  • 過半数 (quorum): ノード数 N の (N/2)+1 以上が ack で commit
  • 安全性: 古い term の Leader が新しい term の commit を上書きすることはない (Leader Completeness)
Election Timeout のランダム化

Election timeout は 150-300ms の範囲で各ノードがランダム に持つ。これにより複数 Candidate が同時に立候補して票が割れる確率を下げる (Liveness の保証)。

3. Python 実装: リーダー選出 (動作確認済)

Raft の核心: Term + RequestVote + 過半数
Python
import random
class RaftNode:    def __init__(self, node_id, n_peers):        self.id = node_id        self.term = 0        self.voted_for = None        self.peers = list(range(n_peers))        self.role = 'follower'
    def request_vote(self, candidate_id, candidate_term):        '''投票要求を受け取った時の判定'''        if candidate_term > self.term and self.voted_for is None:            self.term = candidate_term            self.voted_for = candidate_id            return True        return False
    def become_candidate(self):        '''選挙開始'''        self.role = 'candidate'        self.term += 1        self.voted_for = self.id  # 自分に投票
    def collect_votes(self, peers):        votes = 1  # 自票        for peer in peers:            if peer.request_vote(self.id, self.term):                votes += 1        return votes
# シナリオ: 5 ノードで 1 台が立候補random.seed(42)nodes = [RaftNode(i, 5) for i in range(5)]candidate = nodes[0]candidate.become_candidate()votes = candidate.collect_votes(nodes[1:])
print(f'Term {candidate.term}: candidate {candidate.id} got {votes}/5 votes')print(f'過半数 (3) 以上? {votes >= 3} → 勝者は {candidate.id}')

4. ログ複製の仕組み

AppendEntries の簡略版
Python
class RaftWithLog:    def __init__(self, node_id):        self.id = node_id        self.term = 0        self.log = []  # [(term, command), ...]        self.commit_index = -1
    def append_entries(self, leader_term, prev_log_index, prev_log_term, entries, leader_commit):        '''Leader からのログ複製要求'''        # 古い term は拒否        if leader_term < self.term:            return False
        # ログの整合性チェック        if prev_log_index >= 0:            if prev_log_index >= len(self.log):                return False  # 自分のログが短い            if self.log[prev_log_index][0] != prev_log_term:                return False  # term が合わない
        # ログ追加        self.log = self.log[:prev_log_index + 1] + entries
        # コミット        if leader_commit > self.commit_index:            self.commit_index = min(leader_commit, len(self.log) - 1)
        return True
# シナリオ: Leader → Follower にログ複製follower = RaftWithLog(1)follower.term = 5ok = follower.append_entries(    leader_term=5, prev_log_index=-1, prev_log_term=0,    entries=[(5, 'set x=1'), (5, 'set y=2')], leader_commit=1)print(f'AppendEntries OK: {ok}, log: {follower.log}, commit: {follower.commit_index}')

5. 障害耐性の計算

ノード数過半数 (quorum)停止可能数推奨用途
110開発環境のみ
321小規模本番
532中規模本番 (推奨)
743大規模
954稀 (通信コスト増)

6. 実用ライブラリ

  • hashicorp/raft (Go): Consul / Vault で利用
  • etcd-io/raft (Go): etcd / Kubernetes で利用
  • tikv/raft-rs (Rust): TiKV / TiDB で利用
  • python-raft / raftos: Python 実装 (学習用)

7. Paxos / Raft / ZAB 比較

特徴PaxosRaftZAB
論文発表1989 (Lamport)2014 (Ongaro)2010 (Reed)
理解しやすさ難易中
Leader 選出オプション必須必須
主な実装Google Chubbyetcd / ConsulZooKeeper
強整合性✓✓✓

8. 次の話

EP.09 では Merkle Tree (ハッシュツリー) を扱います。Bitcoin / Git / IPFS / DynamoDB が改ざん検出と分散同期に使う、暗号学的データ構造の実装。

よくある質問

Raft は何を解決する?
**「分散システムで全員が同じ値に合意する」 (consensus / 合意問題)**。N 台のサーバのうち過半数が生きていれば、全員が同じ順序で同じ操作を実行することを保証する。**等価なアルゴリズム: Paxos (Lamport 1989)、Raft (Ongaro 2014)、ZAB (ZooKeeper)、Multi-Paxos**。
なぜ Paxos でなく Raft?
**Raft は「理解しやすさ」を最初から設計目標にした** (論文タイトル "In Search of an Understandable Consensus Algorithm")。Paxos は理論的に美しいが、実装の細部が論文に書かれておらず、エンジニアが正しく実装するのが困難。Raft は **リーダー選出 / ログ複製 / 安全性** の 3 つに分割し、一つずつ検証可能。
リーダー選出の流れは?
**(1) 全ノードは Follower として起動。(2) ハートビート (リーダーからの定期通信) が一定時間 (election timeout) 来ないと Candidate になる。(3) Candidate は term を +1 し、自分に投票して RequestVote を送る。(4) 過半数の票を得たら Leader に。(5) Leader は AppendEntries (ログ複製 + ハートビート) を定期送信**。
ログ複製は?
**(1) クライアントが Leader に書込み要求。(2) Leader が自分のログに追加。(3) AppendEntries で Follower にコピー。(4) 過半数が ack したら commit (= 状態機械に適用)。(5) 結果を Client に返す**。これで「過半数のノードに永続化されている」ことが保証される。
本番で使われている例は?
**etcd**: Kubernetes の状態ストア。**HashiCorp Consul**: サービスディスカバリ。**TiDB / CockroachDB**: 分散 SQL の各 region の合意。**RethinkDB / MongoDB**: レプリカセットのリーダー選出。**Apache Kafka KRaft mode**: ZooKeeper を Raft で置換。
ノード障害でどこまで耐える?
**N ノードあれば (N-1)/2 まで停止しても動作継続**。3 ノードで 1 台、5 ノードで 2 台、7 ノードで 3 台。**ネットワーク分断 (split brain) にも安全** ── 過半数を得られない側は新しい Leader を選べない (古い Leader はコミットできない)。
シェア

この記事の感想を教えてください

あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。

シリーズの外も探す:

まずは、現状を聞かせてください。

要件が固まっていなくて大丈夫です。現状診断と方針提案までを無料でお手伝いします。

無料相談フォームへ hello [at] hukuhuku [dot] co [dot] jp