TT Lab
はじめる
学ぶ 学習パス コース

リーダーが二人いた

切り離し、両側に書き、元に戻す

TT Labで続きを見る

目標

3つのノードのうち1つを残りから切り離してから戻すことで、同じ瞬間にリーダーが2人になる状態と、 そのとき各側に書いた値の運命を、自分の目で見ます。

なぜ重要なのか

分散システムの事故は、たいてい「障害」ではなく、不完全な情報から来ます。切り離されたリーダーは、 自分が切り離されたことを知りません。彼が知っているのは「最近、応答が来ない」だけで、Raftのリーダーには、 自分から退く仕組みがありません。そのため、彼は引き続きリーダーのように振る舞い、クライアントの値を受け取り、 okを返します。同じ瞬間に反対側では、過半数が集まって、より高い任期の新しいリーダーを選びます。

このとき、リーダーは本当に2人です。Raftが防ぐのは、リーダーが2人いる状態ではなく、同じ任期にリーダーが 2人いる状態で、少数派側のリーダーは、過半数の確認を得られないので、何も確定できません。 回復する瞬間に、彼の未確定のエントリは、静かに消えます。このラボが残す問いは1つです。 その間にokを受け取ったクライアントは、どうなるのか。

ステップ

  1. 3つのファイルを/root/raft/に置いて、3つのノードを起動し、/root/raft/leader.jsonに書きます。
  2. /root/raft/net.pyに、should_deliver(src, dst, cut)を書きます。
  3. 値alphaを書いて確定させ、/root/raft/before.jsonに書きます。
  4. リーダーと残りの2つが互いを切断するようにして、/root/raft/isolated.jsonに書きます。
  5. 新しいリーダーが選ばれるのを待って、/root/raft/two-leaders.jsonに書きます。
  6. 古いリーダーにghostを書いて、/root/raft/minority.jsonに書きます。
  7. 新しいリーダーにrealを書いて、/root/raft/majority.jsonに書きます。
  8. 切断を解除して、結果を/root/raft/verdict.jsonに書きます。

参考

3つのノードを立ち上げる

/opt/lab/raft/のnode.py、rules-for-partition.py、log-for-partition.pyを、/root/raft/のnode.py、raftrules.py、raftlog.pyとして配置し、3つのノードを起動した後、選ばれたリーダーを/root/raft/leader.jsonに書いてください。

前の2つのラボで書いたルールを、完成版として用意してあります。今回のラボであなたが書くコードは、分断のスイッチ1つだけで、残りは手で実験する時間です。leader.jsonには、leaderとtermを書いてください。

線を切る代わりにメッセージを拒否する

/root/raft/net.pyに、should_deliver(src, dst, cut)を書いてください。cutに含まれている相手のメッセージは、やり取りしません。

このPodには、ファイアウォールをかける権限がありません。そのため、「線を切る」代わりに、ノードが特定の相手のリクエストを拒否するようにします。相手にとっては、応答が来ないことと区別できないので、合意アルゴリズムの立場からは、本物の分断と同じです。dstは自分自身で、cutは自分が切断した相手のリストです。curl -s -XPOST -d '{"peers":[2,3]}' 127.0.0.1:5001/cutでかけて、/statusのcutで確認してください。

分断の前に値を1つ確定させる

リーダーに値alphaを書いて、3つのノードすべてでcommitが1になるのを確認した後、/root/raft/before.jsonに、value、commit、committed_onを書いてください。

後で何が捨てられて何が残るのかを見分けるには、分断の前に確定された値が1つ必要です。committed_onは、その値をコミットしたノードの数です。書き込みは、curl -s -XPOST -d '{"value":"alpha"}' 127.0.0.1:<리더포트>/clientです(プレースホルダーはリーダーのポートです)。

リーダーを切り離す

リーダーと残りの2つが互いを切断するようにして、切断した直後の古いリーダーの状態を、/root/raft/isolated.jsonに、old_leader、state_after_cut、cutとして書いてください。

両側にかけて初めて本物の分断です。片方だけにかけると、一方向は依然として届きます。切断されたリーダーが、自分から退くかを見てください。Raftのリーダーには、自分を降格させる仕組みがありません。より大きな任期を見るまでは、自分がリーダーだと信じ続けます。

同じ瞬間にリーダーが2人いる

過半数側で新しいリーダーが選ばれるのを待ってから、2人のリーダーの番号と任期を、/root/raft/two-leaders.jsonに、old_leader、old_term、new_leader、new_termとして書いてください。

ここが、このコースのタイトルです。3つのノードの/statusを並べて見ると、2つのノードが同時にleaderと答えます。ところが、任期が異なります。Raftが防ぐのは「リーダーが2人いる状態」ではなく、「同じ任期にリーダーが2人いる状態」で、その区別が、このアルゴリズムの安全性が立脚している場所です。

少数派側に書いた値

切断された古いリーダーに値ghostを書いて、/root/raft/minority.jsonに、accepted、committed、log_len、commitを書いてください。

切断されたリーダーも、値を受け取りはします。自分のログに書き込んで、クライアントにokを返します。ところが、過半数の確認が来ないので、commitは1で止まっています。受け取ったものと確定したものの距離が、ここで目に見えます。この距離を知らないクライアントがokを成功と読むと、事故が起きます。

過半数側に書いた値

新しいリーダーに値realを書いて、/root/raft/majority.jsonに、index、committed、commit、same_index_as_ghostを書いてください。

2つの値が同じ位置番号を受け取るというのが要点です。ログのインデックス2を巡って、ghostとrealが争っていて、1つの位置には1つしか残れません。どちらが残るかは、すでに決まっています。過半数の確認を得たほうです。

回復: 何が捨てられるのか

3つのノードの切断をすべて解除して、古いリーダーのログが整理されるのを確認した後、/root/raft/verdict.jsonに、survived、discarded、old_leader_state_after、ghost_was_committed、acknowledged_to_clientを書いてください。

切断の解除は、curl -s -XPOST -d '{"peers":[]}' 127.0.0.1:<포트>/cutです(プレースホルダーはポートです)。古いリーダーは、より大きな任期のハートビートを見た瞬間に退き、その後、自分のログのインデックス2が、新しいリーダーのもので上書きされます。最後の2つの項目が、このラボの教訓です。その値は一度もコミットされたことがないのに、クライアントはokを受け取りました。