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) | 停止可能数 | 推奨用途 |
|---|---|---|---|
| 1 | 1 | 0 | 開発環境のみ |
| 3 | 2 | 1 | 小規模本番 |
| 5 | 3 | 2 | 中規模本番 (推奨) |
| 7 | 4 | 3 | 大規模 |
| 9 | 5 | 4 | 稀 (通信コスト増) |
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 比較
| 特徴 | Paxos | Raft | ZAB |
|---|---|---|---|
| 論文発表 | 1989 (Lamport) | 2014 (Ongaro) | 2010 (Reed) |
| 理解しやすさ | 難 | 易 | 中 |
| Leader 選出 | オプション | 必須 | 必須 |
| 主な実装 | Google Chubby | etcd / Consul | ZooKeeper |
| 強整合性 | ✓ | ✓ | ✓ |
8. 次の話
EP.09 では Merkle Tree (ハッシュツリー) を扱います。Bitcoin / Git / IPFS / DynamoDB が改ざん検出と分散同期に使う、暗号学的データ構造の実装。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。