- Authors

- Name
- Youngju Kim
- @fjvbn20031
etcdアーキテクチャ内部分析: Raft合意アルゴリズム
etcdは分散システムで重要な役割を果たす強い一貫性(strongly consistent)を保証する分散キーバリューストアです。Kubernetesのすべてのクラスター状態がetcdに保存され、その信頼性はRaft合意アルゴリズムに基づいています。
1. Raft合意アルゴリズム概要
Raftは理解しやすく設計された合意アルゴリズムで、Paxosと同等の安全性を提供しながら実装が遥かにシンプルです。
1.1 コア概念
Raftの核心は3つのサブ問題に分解されます:
- リーダー選出(Leader Election): クラスターで1つのリーダーを選出
- ログ複製(Log Replication): リーダーがログエントリをフォロワーに複製
- 安全性(Safety): コミットされたエントリが失われないことを保証
1.2 ノード状態
Raftクラスターの各ノードは3つの状態のいずれかです:
- Leader: クライアントリクエストを処理しログを複製
- Follower: リーダーのリクエストに応答する受動的役割
- Candidate: リーダー選出に参加する中間状態
1.3 Term(任期)
Raftは時間をTermという論理的単位に分割します。各Termは選出で始まり、選出に成功すればそのTerm中に1つのリーダーが存在します。Termは単調増加し、古いTermのメッセージは無視されます。
2. リーダー選出(Leader Election)詳細
2.1 選出過程
- Followerが一定時間(election timeout)リーダーからheartbeatを受信しなければCandidateに転換
- Candidateは現在のTermを増加させ自分に投票した後、他のノードにRequestVote RPCを送信
- 過半数(majority)の投票を得ればリーダーに選出
- リーダーになったら直ちに空のheartbeatを送信して権限を確立
2.2 選出タイムアウト
選出タイムアウトはランダム化されており、同時に複数のノードがCandidateになることを防止します。etcdではデフォルトで1000〜1500msの範囲です。
2.3 Pre-Voteメカニズム
etcdはネットワークパーティション復旧後の不要なリーダー交代を防止するためPre-Voteを実装しています。Candidateになる前にまずPre-Voteを要求し、過半数が同意した場合のみ実際の選出を開始します。
// etcdのraft状態遷移(簡略化)
// Follower -> PreCandidate -> Candidate -> Leader
func (r *raft) tickElection() {
r.electionElapsed++
if r.electionElapsed >= r.randomizedElectionTimeout {
r.electionElapsed = 0
r.Step(pb.Message{Type: pb.MsgHup})
}
}
2.4 Learnerノード
etcd 3.4からLearnerノードが導入されました。Learnerは投票権のない非投票メンバーで、ログ複製のみ受信します。新メンバーが既存ログに追いつくまでLearnerとして維持するとクラスター可用性に影響を与えません。
3. ログ複製(Log Replication)
3.1 ログエントリ構造
各ログエントリは以下を含みます:
- Index: ログ内の位置(単調増加)
- Term: そのエントリが生成されたTerm
- Data: ステートマシンに適用するコマンド(例: Put key=value)
3.2 複製過程
- クライアントがリーダーにリクエスト送信
- リーダーがログエントリを自身のログに追加
- リーダーがAppendEntries RPCでフォロワーに複製
- 過半数のフォロワーが確認(acknowledge)するとコミット
- コミットされたエントリをステートマシンに適用(apply)
- クライアントにレスポンス返却
3.3 ログ一貫性保証
Raftは2つの属性でログ一貫性を保証します:
- Log Matching: 同じindexとtermを持つ2つのエントリは同じコマンドを格納
- Leader Completeness: コミットされたエントリはその後のすべてのリーダーのログに存在
Leader Log: [1:1][2:1][3:2][4:2][5:3]
^-- committed up to here
Follower A: [1:1][2:1][3:2][4:2][5:3] (up to date)
Follower B: [1:1][2:1][3:2] (lagging, will catch up)
4. etcdサーバーアーキテクチャ
4.1 EtcdServer構造
EtcdServerはetcdのコア構造体で、以下のコンポーネントを調整します:
- RaftNode: Raft合意ロジック処理
- Backend: BoltDBベースストレージ
- KV Store: MVCCキーバリューストア
- Lessor: Lease管理
- Watchable Store: Watchメカニズム
4.2 リクエスト処理フロー
クライアントのPutリクエストが処理される過程:
- gRPCサーバーがリクエスト受信
- 認証と権限確認
- リクエストをRaftに提案(propose)
- Raft合意後コミット
- Applyループでコミットされたエントリをステートマシンに適用
- バックエンド(BoltDB)に永続化
- クライアントにレスポンス返却
4.3 Applyループ
ApplyループはetcdのコアループでRaftでコミットされたエントリを順番にステートマシンに適用します:
// Applyループの簡略化された構造
func (s *EtcdServer) applyAll(ep *etcdProgress, apply *apply) {
s.applySnapshot(ep, apply)
s.applyEntries(ep, apply)
// 適用完了後インデックス更新
s.setAppliedIndex(apply.snapshot.Metadata.Index)
}
4.4 Linearizable Read
etcdはデフォルトでLinearizable Readをサポートします。読み取りリクエスト時にリーダーがまだ現在のリーダーであるか確認するため過半数にheartbeatを送信します。これによりstale readを防止します。
Serializable Readオプションを使用するとリーダー確認なしでローカルから読み取り、パフォーマンスは良いですが最新データでない可能性があります。
5. キーバリューストア: BoltDBとMVCC
5.1 BoltDB(bbolt)バックエンド
etcdはBoltDB(bboltフォーク)をバックエンドストレージとして使用します。BoltDBはB+ツリーベースの組み込みキーバリューデータベースで:
- 読み取り最適化されたB+ツリー構造
- ページ単位ストレージ(4KBデフォルト)
- ACIDトランザクションサポート
- 単一writer、複数readerモデル
5.2 MVCC(Multi-Version Concurrency Control)
etcdはMVCCを使用してキーのすべてのバージョンを維持します:
- Revision: グローバル単調増加カウンター。すべてのトランザクションごとに増加
- Key Index: キー名からrevisionリストへのマッピング
- Generation: キーの作成から削除までのライフサイクル
Key: "/app/config"
Generation 1: [create:rev5] [modify:rev8] [modify:rev12] [tombstone:rev15]
Generation 2: [create:rev20] [modify:rev25]
^-- current
5.3 BoltDB内部バケット構造
etcdはBoltDBに2つの主要バケットを維持します:
- keyバケット: revisionをキーとし実際のキーバリューデータを値として格納
- metaバケット: メタデータ(consistent index、scheduled compact revisionなど)
6. Watchメカニズム
6.1 Watchable Store
etcdのWatchはキーまたはキー範囲の変更をリアルタイムでストリーミングします:
- synced watchers: 現在のrevisionまで追いついたwatcher。新イベントを即座に受信
- unsynced watchers: まだ追いついていないwatcher。ヒストリーからイベントを再生
6.2 イベント生成
MVCCストアに書き込み操作が発生すると:
- トランザクションがコミットされる時にイベント生成
- watchable storeがイベントを該当watcherに配信
- gRPCストリームを通じてクライアントに送信
6.3 Watchリカバリ
クライアントが接続切断後に再接続する際、最後に受信したrevisionからWatchを再開できます。そのrevisionが既にコンパクションされていればエラーが返され、クライアントは全データを再ロードする必要があります。
7. ネットワークとgRPC通信
7.1 ピア間通信
etcdクラスターメンバー間の通信は2つのチャネルを使用します:
- Stream: 長期接続でRaftメッセージ(heartbeat、append entriesなど)を送信
- Pipeline: 大容量データ(スナップショットなど)転送
7.2 クライアント通信
クライアントはgRPCを通じてetcdと通信します。主要サービス:
- KV: Put, Range, DeleteRange, Txn
- Watch: Watch
- Lease: LeaseGrant, LeaseRevoke, LeaseKeepAlive
- Cluster: MemberAdd, MemberRemove, MemberList
- Maintenance: Alarm, Status, Defragment, Snapshot
8. まとめ
etcdのアーキテクチャはRaft合意アルゴリズムを基盤に強い一貫性を保証しながら、BoltDBの効率的なストレージとMVCCの多バージョン管理により高いパフォーマンスを達成しています。次の記事ではBoltDBとMVCCストレージエンジンの内部構造をより深く見ていきます。