Skip to main content
Raft
株式会社プリファードインフラストラクチャー
2014 年 3 月 13 日
自己紹介
 久保田展行 (@nobu_k)
 Preferred Infrastructure America, Inc. 取締役
 本日の NG ワード「いつアメリカ行くの?」
 MessagePack for C,C++ メンテナ
 分散システム、 DB 、検索エンジン
 最近 golang に夢中
2
今日の話: Raft
 Raft とは
 複製されたログを管理するためのコンセンサス ( 合意 ) アルゴリズ
ム
 Raft はわかりやすさを重視して作られた
 既存のアルゴリズムは難しすぎて正しく実装するのが困難
 もしくは、難しい部分を簡単にしようとして安全ではなくなったり
 この先生きのこるには分散システムの理解が必須
 コンセンサスは安全な分散システムを構築する上で必須のトピック
 ツールとして使うにしても、中身や特性は理解しておくべき
 資料として使えるように字が多めになっておりますご了承ください3
スライドの流れ
 Raft の前に
 コンセンサスとは
 Paxos とその問題点
 Raft
 Raft の概要
 アルゴリズムの説明
 運用のために必要な機能の説明
 評価の話は・・・なしの方向で・・・w
4
コンセンサスとは
5
コンセンサス ( 合意 ) とは
 コンセンサスとは
 プロセスの集合が 1 つの値において合意を取ること
 分散システムにおける重要な問題の 1 つ
6
どれか 1 つを選択
BB
AA
CC DD
EE
つけ
麺
つけ
麺
ロンアールロンアール
ロンアールロンアール
ロンアールロンアール
Red BullRed Bull
「お昼どうする?」
コンセンサスアルゴリズムはなぜ必要か
 分散システムで高可用性 (High Availability) を実現するため
 高可用性を実現する方法は「冗長化」ただ 1 つ
 ハードウェア障害は防げない
7
どうシステムを冗長化するのか
 プロセスを State Machine だと考える
 プロセスは決まった状態の集合を持っている
 外部からの入力 ( 命令 ) と状態遷移関数により状態が変わる
 Replicated State Machine
 冗長化した全プロセスが同じ State Machine として動く
 同じ入力を同じ順序で全プロセスに与え、同じ状態を維持

コマンド列をログとして同じ順序で複製する問題としてとらえる
8
なぜレプリケーションにコンセンサスが必要なのか
 同じ命令を同じ順序で届けるため
 「 n 番目にどの命令を実行するか」という合意を取る
 ナイーブに実装すると障害時に次のような問題が起こる
 n 番目に同じ命令を実行してない or 命令の実行順序が異なる

プロセス停止、メッセージの喪失、到達タイミングの差などにより
 プロセスが停止すると状態が巻き戻る
 ネットワークが分断したときに 2 つの独立したグループができる
 プロセスの構成 ( メンバ ) を変更できない
 実は超絶に難しい問題
 ではどうやって実装するのか
 Paxos
9
( ザックリ )Paxos
10
Paxos とは
 最強のコンセンサスアルゴリズム
 安全性
 難しさ
 Raft と一緒に 50 分で説明できるレベルじゃない
 詳しくは 2012/7/5 の PFI セミナーを参照してください!
 http://www.slideshare.net/pfi/paxos-13615514
 コンセンサスの話ももう少し詳しく紹介しています
11
Paxos の問題点
 理論的には問題はない ( と思う )
 停止保証がないのは?

→ そもそも非同期システムにおいて 1 個以上のプロセスが障害を起
こす可能性がある状況で停止保証があるコンセンサスアルゴリズム
は存在しない
 ではなにが問題なのか
 理論も実装も理解しづらいのが問題
 どう言うレベルで理解しづらい?
 熟練リサーチャーが解説を求められると固まるレベル
 理解しづらいのは問題なのか?
 大問題
12
理解しづらいことによる問題点
 正しい Paxos が実装されない
 そもそもどう実装すると正しくなるのか分からない
 論文で語られていない詳細も多い
 結果として、勘で実装した部分が正しいか保証できない
 Multi-Paxos の実装すら同じものが存在しないレベル
 Chubby も・・・
 “There are significant gaps between the description of the Paxos
algorithm and the needs of a real-world system.... the final system will
be based on an un- proven protocol” (Paxos made live)
 コンセンサスが正しく取れていることを仮定して構築されているシ
ステムの信頼性が崩壊!
13
なぜ Paxos は難しくなってしまったのか
 手法が直感的でない
 Single-decree Paxos をベースにしているのがよろしくない?

Multi-decree への拡張方法も説明不足
 若干主観的な主張ではあるが、元論文を読むと共感できる
 実装する上で必要な情報を十分に提示できていない
 例: multi-decree Paxos でインスタンス ID はどうやって決める?
 リーダー (distinguished proposer) はどうやって決める?

別の合意の仕組みが必要
 ログ複製の文脈に限って言えば proposer が複数存在できるのは無駄
 Paxos も結局 multi-Paxos で最適化としてリーダーの存在を許す
14
Raft
15
Raft 概要:誤解を恐れずにザックリ説明すると
16
Leader
Follower
Follower
Follower
Follower
Client
Client
Client
Raft クラスタ : 1 人の Leader と複数人の Follower がいる
Client からのリクエストを Leader がシリアライズして
クラスタ内の他のプロセスに同じ順序でばらまく。
RPC で通信
Raft の特徴
 わかりやすさを重視したコンセンサスアルゴリズム
 “ ”複製されたログを管理するためのコンセンサスアルゴリズム
 Paxos と違いシステムを構築するための情報がすべて書かれている
 少し制約はあるが、汎用的にも使える
 Leader が強い権限を持つ
 すべてのリクエストは Leader を通る
 ログの管理は Leader が行う
 情報は Leader からのみ流れる
 RPC の呼び出しも Leader のみが行う
 とりあえずさっきの図は説明していないことが多すぎる
 Leader はどう選ぶ? Leader が死んだらどうする?他の障害は?
17
説明の流れ
 アルゴリズムの基本部分実装
 Leader 選出
 ログレプリケーション
 Safety の保証
 システムとして運用するために必要な機能の実装
 メンバシップ管理
 ログコンパクション
 Client とのやりとり
18
Leader 選出
強力な権限を持ったプロセスを 1 つだけ選出する
19
Raft におけるプロセスの状態
 プロセスは次の 3 つの状態を持つ: Follower, Candidate, Leader
 この状態は Replicated State Machine 内部の状態とは別
 Follower: 完全受け身の状態。通信を受け取って応答するだけ。
 Candidate: Leader になりたい人。
 Leader: ボス。すべてのリクエストは Leader 経由で行われる。
 必ず最大 1 プロセスになるように!
 起動直後はみんな Follower
20
Follower Candidate Leader
Start up
Leader と Term
 Raft 内では Term という論理時間が使われる
 Term とは特定の Leader が連続して活動していた期間
 Term には単調増加する ID が割り振られる
 各 Term は Leader の選出から始まる
 各 Term には 0 人か 1 人の Leader がいる
 Leader がいなくなったら新しい Candidate が次の Term を開始
  は Leader 選出期間
  は通常稼働中の期間
21
Term 1 Term 2 T 3 Term 4
Leader 選出開始のタイミング
 Raft ではハートビートを利用して Leader 選出の開始判断をする
 ハートビート = 空の AppendEntries RPC( 後述 )
 一定期間 Leader からハートビートが来なかったら選出開始

Election timeout: 実験では 150ms 前後に設定
 タイムアウトした Follower が Candidate となる
22
Follower Candidate Leader
Start up
Times out,
Starts election
投票開始
 Current term number
 各プロセスは current term number を持つ
 Leader 、 Candidate のリクエストは current term number を含む

Current term number は新しい値を受け取る度に更新される
 Follower は Candidate になったタイミングで、
1. 自身の Current term number を増やす
2. RequestVote RPC を全メンバに送信する
23
RequestVote API
 引数
 term: Candidate が観測している現在の term
 candidateId: Candidate のクラスタ内での ID
 lastLogIndex, lastLogTerm: ログレプリケーションのときに説明

Candidate が持つ情報の新しさを判定するために利用
 Follower からの返値
 term: Follower が観測している term
 voteGranted: bool 値。 true なら Follower が投票したことを表す。
24
投票と集計
 Follower は一番最初に受け取った有効リクエストに対して投票する
 ただし、後ほど安全性を保証するために条件を追加
 Follower は 1 term で 1 回までしか投票しない

これにより 1 term で 1 Leader しか選出されない
 Candidate はクラスタの半数以上から投票されたら Leader になる
 自分は自分に投票する ( これも 1 term 1 回に含まれる )
25
Follower Candidate Leader
Start up
Times out,
starts election
Receives votes from
majority of servers
投票終了
 Leader になった Candidate はハートビートを全員に送信
 Follower はハートビートを受け取ったら Leader の情報を更新
 Candidate はハートビートを受け取ったら Follower に戻る

ただし、ハートビートの current term number が古かったら拒否!

そのまま Candidate の状態を維持
26
Follower Candidate Leader
Start up
Times out,
starts election
Receives votes from
majority of servers
Discovers the current
leader
古い Leader 、 Candidate の扱い
 Current term number を使って古い Leader 、 Candidate を検出
 新しい current term number のリクエストを受け取ることで検知
 Follower が Leader より新しい current term number を持つことも

通信障害などで、旧 Leader が知らない場所で新しい Leader が誕生
 古い Leader や Candidate は、検出次第すぐに Follower に戻る
 Follower は古い Leader や Candidate からのリクエストを拒否する
27
Follower Candidate Leader
Start up
Times out,
starts election
Receives votes from
majority of servers
Discovers a process
with higher term
Discovers the current
leader or new term
投票が完了しないとき
 投票が完了しない Term もある
 複数の Candidate がほぼ同時に登場し、全員が過半数に至らない
 メッセージロスト
 そう言うときは election timeout が発生して投票をやり直し
 Live lock
 やり直し時に複数 Follower がほぼ同時に Candidate になると厄介

無限に投票がやり直される可能性もある
 解決策: Election timeout の時間をランダムにする

大体 Candidate は 1 プロセスだけになるのでうまくいく

実装では 150-300ms とした
 あくまで大体うまくいくのであって、たまに衝突は起こる

ただし、実用上問題の無いレベル!ということ
28
まとめ: Leader 選出
 Follower のルール:
 自分より term が新しい Candidate からの RequestVote に投票する
 Election timeout が発生したら Candidate になる
 Candidate のルール:
 自分に投票する
 全プロセスに対して RequestVote RPC を実行
 自分を含め過半数のプロセスから投票されたら Leader になる
 新しい Leader からの AppendEntries RPC が来たら Follower に戻る
 Election timeout が発生したら新しい term で再投票
 後で safety 実現のために新ルールが追加される
29
ログレプリケーション
コマンド列をログとして複製する
30
ログの持ち方
 ログはすべて Leader からの AppendEntries RPC により複製される
31
1
x 3←
1
x 3←
1
x 3←
1
x 3←
1
x 3←
1
y 1←
1
y 1←
1
y 1←
1
y 1←
1
y 1←
1
y 9←
1
y 9←
1
y 9←
1
y 9←
2
x 2←
2
x 2←
2
x 2←
2
x 2←
3
x 0←
3
x 0←
3
x 0←
3
x 0←
3
y 7←
3
y 7←
3
y 7←
3
x 5←
3
x 5←
3
x 5←
3
x 4←
3
x 4←
1 2 3 4 5 6 7 8 log index
leader
followers
term number
(a)
(b)
(c)
(e)
(f)
AppendEntries RPC
 引数
 term: Leader の term number
 leaderId: Leader のクラスタ内での ID
 prevLogIndex, prevLogTerm: 1 個古いエントリのバージョン情報
 entries[]: ログエントリ ( 配列 ) 、空だと heartbeat
 leaderCommit: Leader がコミット済みの index number
 返値
 term: Follower が観測している term number
 success: prevLogIndex/Term が一致したかどうか

false だったら古いログの再送が必要

もしくは Leader が古くなってる
32
複製とコミットのフロー (1/2)
33
Leader
Follower
Follower
Follower
Follower
Client
1. リクエスト
2. ログに書く
( まだコミットしない )
3. AppendEntries
Raft クラスタ
複製とコミットのフロー (2/2)
34
Leader
Follower
Follower
Follower
Follower
Client
4. ログに書く
6. 過半数の follower から
返答があったらステート
マシンにリクエストを反映
( コミット )
5. Leader に返答する
7. Client に返答する
Raft クラスタ
コミット
 Leader は自分のステートマシンにコマンドを反映する
 ここからはやり直しが効かないし、ログの上書きも起こらない
 コミットしたタイミングで commitIndex を増やす
 Follower のコミットタイミング判断
 AppendEntries の leaderCommit を参照して判断

Leader がコミットしたところまでは自分もコミットする
 つまり、コミットのタイミングは Leader と Follower で異なる

後ほど問題が無いことを説明
35
異常系: Leader 障害が積み重なると・・・
1
Leader
for term 8
ログの再送・上書きによるコミット前エントリの同期・修正が必要。
一貫性チェックの仕組みと一緒に考える。
36
1 1 1 4 4 5 5 6 6 6
1 1 1 4 4 5 5 6 6
1 1 1 4
1 1 1 4 4 5 5 6 6 6
1 1 1 4 4 5 5 6 6 6
1 1 1 4 4
1 1 1 2
6
7 7
4 4
2 2 3 3 3 3 3
(a)
(b)
(c)
(d)
(e)
(f)
2 3 4 5 6 7 8 9 10 11 12
同期遅れ
未コミットのエントリを持つ
同期遅れ&未コミットエントリ持ち
ログの一貫性維持
 守りたい制約
 異なる 2 つのログにて、ある index のエントリが同じ term だったら

ログの中身は同じ

それ以前のログの内容は同じ
 AppendEntries 時にチェックを加える
 Leader: 直前のエントリの index と term number を渡す
 Follower: 自分の最新エントリが同じ index/term を持つかチェック

持ってた:自分のログにエントリを追加し AppendEntries に応答

持ってなかった: false を返しつつ・・・
– 自分の term が新しかったらそれを leader に伝える
– 自分が遅れていたら古いエントリの再送を待つ
 ヤバいケースに関しては safety のところで
37
ログの再送と上書き
 AppendEntries では Follower のログの上書きも行う
 ただし未コミットのログのみ!!

制約を守っていればコミット済みのログの書き換えは行われない
 また、 Leader は自分のログに対しては Append しか行わない
 同期遅れが原因で AppendEntries が失敗したら昔のエントリを再送
 Leader は 1 つ古いエントリについて AppendEntries を再実行

2 個前のエントリの index/term を使って 1 個前のエントリを再送
 成功したら次のエントリも再送
 失敗したら 2 個前のエントリに関して同様に再送を試みる

成功するまで続ける・・・
– 効率悪くない?→現実のケースでは大丈夫だったらしい
– 必要であれば最適化も可能だが、 keep it simple (stupid)!
38
異常系の対応
 Leader
 Follower への通信がタイムアウト:あとで再送する
 クラッシュ:未配布のエントリは消失、 Client が再送
 Follower
 クラッシュ: Leader が後で再送してくれる
 Client
 Leader への通信がタイムアウト:再送

アプリケーション側で冪等性を保証する仕組みが必要・・・
39
まとめ:ログレプリケーション
 Leader がすべてのリクエストを処理する
 異常系は基本的に再送でカバー
 Client は冪等性に注意しなければならない・・・

後ほど少し補足
 過半数のプロセスが書いたエントリをコミット
 コミット =Replicated State Machine に命令を反映すること
 Follower は leaderCommit からコミットすべきエントリを知る
 Leader とコミットタイミングがずれるが大丈夫
40
Safety
ログを壊さない
41
普通ならここからが本当の地獄のはずだ・・・
 今までは比較的普通のケースのみを扱った
 いよいよ障害時に耐えうるシステムを設計する
 Replicated State machine の一貫性が保てるようにするのが課題
 ところで、 Raft で問題になるのってどんなケース?
 コミット済みのログエントリが上書きされる

= 各プロセスが異なるコマンド列を実行する
 どう言う状況で起こる?
 Leader がログエントリ複製中もしくはコミット直後に死亡
 あれ、簡単・・・?
42
( 今更ながら )Raft が保証するもの : 論文の Figure 3
 Election Safety
 1 term で 1 Leader しか選択されない
 Leader Append-Only
 Leader は自分のログに対しては Append しかしない

Follower のログ ( 未コミット分 ) を書き換えることはある
 Log Matching
 ログの一貫性を維持するための性質
 Leader Completeness
 ( 若干省略 )Leader はそれまでにコミットされた全ログを持つ
 State Machine Safety
 ( 若干省略 ) 全プロセスが同じコマンドを state machine に適用する
 最終的に保証したいもの
43
Leader Completeness
 守りたい制約
 Leader はそれまでにコミットされた全ログエントリを持つ
 実現方法:選出のタイミングまでにコミットされたすべてのエント
リを持つプロセスしか Leader として選出されなければ良い
 前 Leader がコミットしたエントリは未コミット状態でも良い

あくまでコミット済みエントリを持っているだけで十分
 他のプロセスから不足分を調達するような複雑さは不要
 Leader 選出とコミットのタイミングに関してルールを追加
 2 つ合わせて初めて機能するルール
44
Leader 選出の追加ルール
 過半数の Follower からログがより新しいと認められたら OK
 Candidate の term と index number を元に判断

RequestVote RPC で渡される
 2 つのログのどちらが新しいかは次のように判定する
1. 最新のエントリの Term が異なる場合は Term が大きい方が新しい
2. Term が同じなら index number が大きい ( ログが長い ) 方が新しい
 これと次の新コミットルールを同時に守る
 その前にコミット時に起こる問題を見てみる
45
1
1 2
41 2
1 2
3
3
1 2
1 2
3
3
1 2
1 2
3
3
3
4
<<1. 2.
コミットタイミングの問題 (1/2)
 Leader S1 が死亡
 S2 にだけエントリを複製
 S5 が Term 3 の Leader に
 エントリを 1 個作る
 その後複製せずに死亡
 S1 が再度 Leader に
 S3 にエントリを複製
 2 は過半数に複製された
 この時点で 2 をコミットで
きるか?
46
1 2
1 2
1
1
1
S1
S2
S3
S4
S5
1 2
1 2
1 2
1
1
1
S1
S2
S3
S4
S5
1 2
3
1 2
1 2
1
1
1
S1
S2
S3
S4
S5
1 2
3
4
2
3
コミットタイミングの問題 (2/2)
 安全にコミットできない
 S1 が 2 をコミット
 直後に S1 が死亡する
 S2,S3 は未コミット
 S5 が Term 5 の Leader に
 全員 index=2
 Term は 3 が最新
 S2,S3,S4 が投票可能
 S1 がコミット後に死んだら
 S5 が 3 を全員に複製
 S1 が復活

エントリの不整合
47
1 2
1 2
1
1
1
S1
S2
S3
S4
S5
1 2
3
4
2
3
1 2
1 2
1
1
1
S1
S2
S3
S4
S5
1 2
3
4
2
3
S5 は Term 3 のエントリ 2 を
全員に上書きする権限を持つ
コミットタイミングの追加ルール
 まず自分の term でコミット可能なエントリを作り出す
 過半数のプロセスが自分の index/term で同じログエントリを持つ

Log Matching property より、それらのプロセスのログは同一

さらに、 Leader はそれらのプロセスからしか選出されない
 コミット可能なエントリができたら古いエントリから順次コミット
48
1 2
1 2
1
1
1
S1
S2
S3
S4
S5
1 2
3
4
2
3
4
4
2 をコミットする前に 3 を過半数に複製。
そうすることで、次の Leader は 3 を持つ。
この時点で 2 をコミット直後に死んでも
次の Leader も同じエントリをコミットする。
3 はもちろん 2 の後にコミット。
追加コミットルールの実現方法
 論文的には 2 つ方法があるように見える
1. 次の Client からのリクエストを待つ
 その段階で新しいエントリが作成される
 それがコミット可能になった段階で古いエントリもコミット
1. Candidate が Leader になったら no-op エントリをコミットを試みる
 no-op がコミット可能になったら古いエントリから順次コミット
 後ほど説明する read を最適化するときにも役立つ

実は論文だとそこで初めて出てくる情報
 個人的には後者の方が無難な気がした
49
補足:コミット直後の Leader 死は大丈夫か
 Term n+1 のケースだけ考えれば良い (Leader が死んだ直後なので )
 Index 2 は過半数のプロセスに複製されている
 この時点で Leader になれるのは S2 か S3 のみ

コミット済みのエントリを持っているので安心
50
1 2
1 2
1
1
1
S1
S2
S3
S4
S5
1 2
2
1 2
1 2
1
1
1
S1
S2
S3
S4
S5
1 2
2
S1 が 2 をコミットした直後死亡
まとめ: Safety
 2 つの追加ルールで Leader の障害に対応
 ログが新しい Leader を選出する
 term が変わった直後には古いエントリをコミットしない
 Safety の証明は論文を参照!
 時間が足りなかった
 基本的には新しい Leader の 1 個前の Leader がコミットしたエント
リを持ってないと仮定して矛盾を示す流れ
 その後、 State Machine Safety が満たされるのは自明
 別文献で形式手法での証明もある
 Safety proof and formal specification for Raft.

http://ramcloud.stanford.edu/~ongaro/raftproof.pdf
51
メンバーシップ管理
クラスタ構成を変更する
52
クラスタの構成変更
 運用中にたまに発生する
 故障したマシンの取り替え
 マシンの増大
 etc
 難しい点
 新構成と旧構成でそれぞれ独立して Leader が選ばれる可能性がある
 Raft の safety は単一 Leader が前提となっている
 対処方法
 一般的には 2 フェーズ以上での移行が必要
 一番実現が簡単なのは全台停止→設定変更→再起動

しかし、普通は無停止で移行したい
53
Joint consensus
 新構成と旧構成が混ざった状態を間に挟む
 旧構成: C_old={A, B, C}
 新構成: C_new={B, C, D, E, F}
 混在構成: C_old,new={A, B, C, D, E, F}
54
AA
BB
CC
DD
EE
FF
C_old
C_new
Joint consensus でのルール
1. ログは C_old,new の全プロセスに複製される
2. C_old, C_new のどちらのプロセスも Leader になれる
3. 合意 (Leader 選出、ログのコミット ) は両構成からの過半数が必要
 C_old と C_new で独立して過半数チェックを行う
55
AA
BB
CC
DD
EE
FF
C_old
C_new
紫は 3 プロセスだが
C_old,C_new ともに
過半数を占める。
構成変更フロー
1. C_old の Leader はまず C_old,new への構成へ移るように指示
 特殊なログエントリとして全 Follower に複製する
 このエントリの情報は未コミットでも反映される
 この時点ではまだ C_old 内で合意が取れれば OK
1. C_old,new がコミットされたら joint consensus へ移行
 同時に C_new に移る指示を同様の仕組みで全 Follower に通達
1. C_new がコミットされたら C_old のプロセスは消滅する
 C_old,new コミット前は、 C_old が独断でコミットしても安全
 C_new が複製され始めたら C_new が独断でコミットしても安全
56
残りの問題
 C_new コミット時に C_old のプロセスが Leader だったとき
 Leader はその時点で退く
 C_new がコミットされるまで Leader が投票権を持たない状態が続
く
 新規プロセスが登録されるとき
 新しいプロセスはログを持ってないので同期が必要になる

同期が終わるまでクラスタ全体でコミットできなくなることも
 C_old,new の前にログ同期専用のフェーズを追加する

C++ 実装ではステージングフェーズと呼ばれている

ログの同期が終わってから C_old,new を複製

ダウンタイムが最小に
57
補足:ステージングの課題
 著者の C++ 実装を見てみた
 ステージングの情報は Leader しか持ってなさそう
 Leader がステージング中に死ぬと構成変更が実行されない

クライアント側でタイムアウトで再送すればいい気もするが・・・
– しかし、同期には時間がかかるためタイムアウトの設定は難しい
 ステージング開始前にステージング構成のログを複製すれば解決?
 って適当にやるとそれこそ Paxos の unproven 実装問題が再発
58
未解決問題
 構成変更中音信不通だった C_old のプロセスが生き返るケース
 C_old のプロセスには当然 C_new 専用のハートビートは届かない
 Election timeout が何度も発生し、ひたすら Leader 選出が続く
 Leader election 中はリクエストを受け付けられず可用性が下がる
59
AA
BB
CC
DD
EE
FF
C_new
まとめ:メンバシップ管理
 Raft はクラスタの動的な構成変更に対応している
 しかし、まだ正しさが完全に保証されていない箇所も・・・
 未解決の課題もいくつかある
 方針は立ってそうな感じ
 ただ、課題はあるとは言え運用でカバーできる面が大きい
 手を付けられない状況にはならない
60
ログコンパクション
ログ容量を減らしつつ復旧時間を短くする
61
ログの肥大化問題
 ログは無限に貯められない
 どこかで不要なログを消す必要がある
 消し方
1. Log cleaning

不要になったエントリを取り除く
– 不要 = 取り除いても現在の状態に影響がない

意味づけや実装が難しい
1. Snapshotting

Replicated State Machine のスナップショットをとる
– 分散ではなくプロセス毎に独立したスナップショット

Chubby や ZooKeeper でも取られている一般的なアプローチ
62
Replicated State Machine のスナップショット
 ハッシュテーブルを利用した簡単なインメモリ KVS を考える
 オペレーション : キーに値を設定
 内部表現 : JSON のオブジェクトみたいなもの
63
x  3 y  1 y  9 x  2 x  0 y  7 x  5
last included index: 5
last included term: 3
{
x: 0
y: 9
}
1 2 3 4 5 6 7
y  7 x  5
6 7
Index 5 の段階で取ったスナップショット
+ 残りのログ
元ログ
スナップショットとログ
 スナップショットを取ったところまでのログは消せる
 スナップショットに index/term number を含める

term number は consistency check で使われる
 スナップショットのその他の使われ方
 障害復旧後や、新規参入したプロセスを追いつかせる処理

ログだけだと復旧できないのでスナップショットを転送

InstallSnapshot RPC
64
スナップショットの取り方
 スナップショットを取っている間もシステムを停止させたくない
 Copy on Write を活用する
1. 永続データ構造みたいなのを使う

Purely functional な感じのデータ構造
1. Linux で fork を活用する (Redis のアプローチ )

スナップショットを取るプロセスを fork

元プロセスでは書き込み処理を継続可能
 頻度
 スナップショットはそれなりに重い処理
 高頻度すぎるとシステムが遅く、低すぎると復旧時間が長くなる
 ログが一定サイズになったら実行するのがシンプル
65
補足: Raft とスナップショット
 スナップショットは Raft 的には例外的なオペレーション
 唯一 Follower が能動的にスナップショットを取る
 なぜ Leader がスナップショットを取って配れないのか?
 スナップショットの配布がネットワークを使い切っちゃう可能性が
 Leader がスナップショットを取るタイミングを指示できないか?
 最適なタイミングを知るためにはシステムが若干複雑に
 結局 Follower が自分でやるのが楽
 必要な情報は全部 Follower が持ってる
66
まとめ:ログコンパクション
 ログは放っておくと際限なく増える
 定期的にスナップショットを取って古いログを削除する
 スナップショットを取るのは重い処理なので実行頻度に気をつける
67
Client とのやりとり
68
Client と Leader
 Raft とのやりとりはすべて Leader を介して行う
 Follower へのリクエストはすべて失敗

代わりに自分が知っている最新の Leader の位置を返す

Follower はハートビートなどの情報から Leader について知ってい
る
 Client はまず適当なノードにリクエストを送信
 失敗したら Leader に問い合わせ直す
 Leader へのリクエストがタイムアウトしたときも同様
69
Linearizability の実現
 Linearizability とは ( 引用 )
 “each operation appears to execute instantaneously, exactly once, at
some point between its invocation and its response”
 Write と Read で分けて考える
 Write
 Leader が 1 個 1 個実行しているためタイミングの問題は無い

Raft の RPC は冪等なので再送による問題も起こらない
 Client のリトライによる重複実行が課題

アプリケーション側でコマンドに ID を持たせて重複実行を回避
– あっさり書いてるけど地味に大変・・・
 Read
 古い情報を返さないようにする
70
Linearizability: Read
 Write と同様にログエントリにすれば解決
 しかし、状態を変更する必要が無いため高速化できる部分が多い

本来であればログに書き込む必要がない

命令のコミット処理もいらない
 最適化するといくつか問題が起こる可能性がある
 前 Leader がコミット済みだが自分がコミットしてない情報を返す

まずは Leader になった直後に自分の term で no-op をコミット

前 term までの情報はすべてコミットされるので最新情報を返せる
 自分が値を読んでる間に別の Leader が誕生

返す前に全台にハートビートを送り過半数からのレスポンスを待つ

ログに書き込むよりは低いコストで読み込める
71
まとめ: Client とのやりとり
 Client はまず適当なプロセスにリクエストを送信
 Leader じゃなかったら、教えて貰った Leader に問い合わせし直し
 Linearizability
 Write

Raft のレイヤーでは問題無い

Client は再送検知の仕組みを自分で実装する
– もしくは全命令を冪等にする
 Read

最適化しつつ古い値を返さないように工夫する
72
まとめ
73
Raft とは
 ログレプリケーションを前提に現実的な制約を付けパフォーマンス
やわかりやすさを向上させたコンセンサスアルゴリズム
 確かに分かりやすい
 Paxos の準備期間は 100 時間、 Raft は 40 時間くらい
 実装も資料もいっぱいある
 http://raftconsensus.github.io
 Paxos は特許の関係もあり公開されてる実装は少ない
 ”また、ほとんどが Paxos-based”

つまり Paxos は少し改変しないと現実では使いづらい

または、 Paxos とは何か自身を持って主張できる人が少ない
74
Paxos は無くなるのか?
 無くならないと思う
 問題になるのは Raft の時間的な制約
 ブロードキャスト時間 << election timeout << MTBF
 広域で使おうとすると難しい ( 使えなくもないとは思う )

RTT が 150ms かかる場合の election timeout はどれくらいが適切?
 Single-decree や基本的な Paxos で十分なことも多い
 例: KVS にてキー毎に独立して合意を取る

グローバルな順序付けがいらない
 Paxos は Leader が複数いても安全に動く

Leader を維持するコストがいらない ( 合意コストが上がるけど )
 時間的な制約もほとんどないに等しい
 結局は適材適所
75
ZooKeeper は無くなるのか?
 Raft は ZooKeeper じゃなくて ZAB 相当
 Raft の上に ZooKeeper 相当のアプリケーションを作ることも可能
 完全に ZooKeeper 相当では無いが、 etcd というのがある

https://github.com/coreos/etcd
 ZooKeeper( や Chubby) のコンセプト自体は不滅
 実装がどうなるかは謎!
76
Copyright © 2006-2014
Preferred Infrastructure All Right Reserved.