Skip to content

LogPosition::is_newer_or_equal_than の定義ミスによって生じる無限ループについて #46

@yuezato

Description

@yuezato

Leaderがいつまで経っても決まらずに、無限ループとなりサービス不能となるバグがあるのでそれについて述べる。

is_newer_or_equal_than とは?

中身(定義)は次節で述べるとして、どこで使われていて、直感的な意味は何かをまず述べる。

Common::handle_message

/// 受信メッセージに対する共通的な処理を実行する.
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)
{

が(テストコードを除くと)唯一使用されている箇所である。特に次の箇所である:

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 が以下のように定義されている:

raftlog/src/log/mod.rs

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 の組:

raftlog/src/log/mod.rs

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がいつまでも決定できずクラスタが使用不能になる例について
上里の再現用ブランチを確認されたい。

修正方法 と 気になる点

修正方法については、論文の定義に直すだけで良い。

一方で、全順序とならないことを承知の上で、現在の定義を採用した可能性がある:

raftlog/src/log/mod.rs

Lines 265 to 266 in 05c3d32

/// // `a`の方がインデックスは大きいが、`b`の方が`Term`は大きい
/// // => 順序が確定できない

USENIXに公開されているバージョン https://www.usenix.org/node/184041. から
up-to-date の定義は変わらず辞書式順序のままなので、
辞書式順序版になにかミスがあることを見越して異なる定義を採用した可能性がある。

あるが、この辞書式順序はRaftアルゴリズムの根幹を支える直観と密接に関係していることと、
多少粘った程度では「辞書式順序で問題がある=論文の安全性をひっくり返す」ようなことは示せていないので、
論文の定義に戻したい。

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions