-
Notifications
You must be signed in to change notification settings - Fork 6
Description
Leaderがいつまで経っても決まらずに、無限ループとなりサービス不能となるバグがあるのでそれについて述べる。
is_newer_or_equal_than とは?
中身(定義)は次節で述べるとして、どこで使われていて、直感的な意味は何かをまず述べる。
Common::handle_message
raftlog/src/node_state/common/mod.rs
Lines 300 to 304 in 05c3d32
| /// 受信メッセージに対する共通的な処理を実行する. | |
| pub fn handle_message(&mut self, message: Message) -> HandleMessageResult<IO> { | |
| if self.local_node.role == Role::Leader | |
| && !self.config().is_known_node(&message.header().sender) | |
| { |
が(テストコードを除くと)唯一使用されている箇所である。特に次の箇所である:
raftlog/src/node_state/common/mod.rs
Lines 325 to 330 in 05c3d32
| let next_state = if let Message::RequestVoteCall(m) = message { | |
| if m.log_tail.is_newer_or_equal_than(self.history.tail()) { | |
| // 送信者(候補者)のログは十分に新しいので、その人を支持する | |
| let candidate = m.header.sender.clone(); | |
| self.transit_to_follower(candidate, Some(m.header)) | |
| } else { |
これは raft論文 https://raft.github.io/raft.pdf の Figure2 RequestVote RPCの
If votedFor is null or candidateId, and candidate’s log is at least as up-to-date as receiver’s log, grant vote
に対応しているだろう。ここで up-to-date というのは論文の5.4.1節で定義されている全順序である。詳しくは下の方で述べる。
一言で言うなれば、is_newer_or_equal_than は leader node を決定する上で鍵となる述語である。
これは、上で抜粋したコードで行っている計算からも明らかである(下は無理やり日本語にしたもの):
- 325行目
- RequestVoteを他のノード X から受け取った時に
- 326行目
- 相手のlogの最新情報が、自分のlogの最新情報と同じかそれ以上に新しい(すなわち、least as up-to-date)ならば
- 328--329行目
- Xに投票して、自分自身はfollowerになる
現在の定義
現在のraftlogでは、LogPosition上の順序 is_newer_or_equal_than が以下のように定義されている:
Lines 272 to 274 in 05c3d32
| pub fn is_newer_or_equal_than(&self, other: LogPosition) -> bool { | |
| self.prev_term >= other.prev_term && self.index >= other.index | |
| } |
Note: LogPosition は term と index の組:
Lines 233 to 241 in 05c3d32
| /// ログの特定位置を識別するためのデータ構造. | |
| #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] | |
| pub struct LogPosition { | |
| /// 一つ前のインデックスのエントリの`Term`. | |
| pub prev_term: Term, | |
| /// この位置のインデックス. | |
| pub index: LogIndex, | |
| } |
現在の順序は、 (termL, indexL) ≽ (termR, indexR) が成立するのは直積順序が成立する時、すなわち
(termL, indexL) ≽ (termR, indexR) iff termL >= termR AND indexL >= indexR
を要求している。
期待される定義
is_newer_or_equal_than は、raft論文 https://raft.github.io/raft.pdf の 5.4.1 節 の最後にあるパラグラフ:
Raft determines which of two logs is more up-to-date by comparing the index and term of the last entries in the logs. If the logs have last entries with different terms, then the log with the later term is more up-to-date. If the logs end with the same term, then whichever log is longer is more up-to-date.
に対応していると考えられる。このパラグラフが要求しているのは辞書式順序
(termL, indexL) ≽ (termR, indexR) iff termL > termR OR (termL = termR AND indexL >= indexR)
2つの定義の違いと、それによる問題点
- 現在採用している直積順序は全順序ではない(全順序2つからなる直積順序は一般に半順序)
- 一方で論文の辞書式順序は全順序(全順序2つからなる辞書式順序は一般に全順序)
全順序でない順序をLeaderの裁定基準に採用する場合には、優劣を決定できないために計算が次のステージ/Termに持ち越され、次のステージ/Termでも結局判断出来ず、これが繰り返されいつまでも決定できないということが起きうる。
実際に、無限ループとなりLeaderがいつまでも決定できずクラスタが使用不能になる例について
上里の再現用ブランチを確認されたい。
修正方法 と 気になる点
修正方法については、論文の定義に直すだけで良い。
一方で、全順序とならないことを承知の上で、現在の定義を採用した可能性がある:
Lines 265 to 266 in 05c3d32
| /// // `a`の方がインデックスは大きいが、`b`の方が`Term`は大きい | |
| /// // => 順序が確定できない |
USENIXに公開されているバージョン https://www.usenix.org/node/184041. から
up-to-date の定義は変わらず辞書式順序のままなので、
辞書式順序版になにかミスがあることを見越して異なる定義を採用した可能性がある。
あるが、この辞書式順序はRaftアルゴリズムの根幹を支える直観と密接に関係していることと、
多少粘った程度では「辞書式順序で問題がある=論文の安全性をひっくり返す」ようなことは示せていないので、
論文の定義に戻したい。