Dev Study
← 分散システムの土台
レッスン 9 / 13·約 9 分·第12期 分散システムの土台

過半数が決める — 分断しても割れないための最小の道具

リクエストがアプリケーションに届くまで
  1. ブラウザURL入力
  2. DNS名前→IP
  3. ルータ経路選択
  4. ロードバランサ振り分け
  5. Nginx受け口
  6. アプリ処理
  7. DB保存今ここ

前回までに、状態をどこに置くか・複製には遅れがあることを見ました。今回は複製された状態のどちらが正しいかを決める規則を見ます。

まず結論

更新に「過半数の同意」を課すと、分断されても両側が同時に書き進むことはなくなります。2つの過半数は必ず重なるからです。

代償は少数派側が止まることです。ただし実測で止まったのは書き込みと既定の読み取りだけで、古い値を返す読み取りは通りました。割れるのは書き込みではなく読み取りです。

? 読む前に予測

3台のクラスタが1台と2台に分断されました。1台側にいたのは分断前のリーダーです。この1台は書き込みを受け付けるでしょうか。

当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。

抽象原理

なぜ過半数なのか — 何を防ぐために入っている規則か

先にこの規則が無い世界を見ます。複製された2台が互いに連絡を取れなくなり、どちらも書き込みを受け付けたとします。両方が「自分の値が正しい」まま走り続け、状態が二重化する。これがスプリットブレインです。

過半数(クォーラム)は、これを起こさないための規則です。順序を取り違えないでください。分断で両側が動いてしまうのが先にあり、クォーラムはその回避策です。etcd 公式 FAQ はこう書いています。

This model totally avoids any possibility of split brain inconsistency

大きさの定義も同 FAQ にあります。

For a cluster with n members, quorum is (n/2)+1

整数除算です。n=3 なら 2、n=4 なら 3、n=5 なら 3。

なぜ二重化しなくなるのか。単純な数え上げです。n 台から過半数の集合を2つ取ると合計は n を超えます。同じ袋から n 個より多く取り出したのだから、少なくとも1台は両方に入っている。 そのノードは同じ term で2つの書き込みを両方承認しません。だから2つの過半数は同時に成立せず、書ける側は多くて1つです。この数え上げは本教材の説明で、公式で対応するのは先の This model totally avoids any possibility of split brain inconsistency です。FAQ は別に、奇数台を条件に during a network partition, an odd number of members guarantees that there will always be a majority partition と、多数派が残る側も述べています。

具体実物

3ノード etcd を1台ずつ止める

etcd 3.5.17 を3ノードで立て、1台ずつ止めました(Docker 28.4.0 / ホストは macOS Darwin 25.5.0、2026-09-18、--heartbeat-interval 100 --election-timeout 1000 は etcd の既定値)。

1台止めた状態(生存2台 = クォーラム2を満たす)コマンド出力
===== [1] stop ONE follower (h3-etcd3) =====
stopped h3-etcd3
--- write with 2/3 up (quorum 2 met) ---
OK
/lab
after-1-down
--- endpoint health ---
| h3-etcd1:2379 | true | 4.560333ms | |
| h3-etcd2:2379 | true | 3.407833ms | |
| h3-etcd3:2379 | false | 5.001169919s | context deadline exceeded |
Error: unhealthy cluster

(健全性の表は枠線の行を省いています。)書き込みは OK を返しています。 3台中2台でクォーラム2を満たすからです。一方、最後の Error: unhealthy cluster はクラスタが壊れた意味ではありません。 endpoint health が各エンドポイントを個別に調べ、ひとつでも応答しなければ非ゼロで終わる終了状態です。止めた1台が false なのは当然で、直前の書き込みは成功しています。

2台目を止めると、生存1台ではクォーラム2に届きません。

2台止めた状態(生存1台。クライアント側タイムアウトは10秒)コマンド出力
--- write attempt with 1/3 up (quorum 2 NOT met), 10s client timeout ---
… "error":"rpc error: code = DeadlineExceeded desc = context deadline exceeded"
Error: context deadline exceeded
--- exit code: 1 ---
 
--- linearizable read attempt (default) ---
… "error":"rpc error: code = Unavailable desc = etcdserver: request timed out"
Error: context deadline exceeded
--- exit code: 1 ---
 
--- serializable (stale, local) read: does NOT need quorum ---
/lab
after-1-down
--- exit code: 0 ---

「書き込みが拒否された」のではありません。「返ってこなかった」のです。 etcd は過半数が無いと即断せず待ち、打ち切ったのはクライアント側のタイムアウトです。障害が無応答として現れるのは大きな違いです。

既定の読み取りも止まりました。 理由はサーバ側のログに出ています。

"steps":["trace[308952650] 'agreement among raft nodes before linearized reading' (duration: 2.969801585s)"]

読むだけでもノード間の合意を待つわけです。一方 --consistency=s の読み取りだけは通り、古い値を返しました。

✓ ここまでの確認

etcd 公式 FAQ の表では、4台の耐障害数は3台と同じ1台です。では4台にすると何が変わりますか。

抽象原理

台数を増やしても、増えるとは限らない

クォーラムが (n/2)+1 なら台数を増やすほど強くなりそうですが、そうではありません。etcd 公式 FAQ の表を見ます。

クラスタサイズと耐障害数(etcd 公式 FAQ の表より)
1台quorum 1 / 耐障害 0
2台quorum 2 / 耐障害 0
1台落ちても動くのは3台から
3台quorum 2 / 耐障害 1
4台quorum 3 / 耐障害 1
2台落ちても動くのは5台から
5台quorum 3 / 耐障害 2
6台quorum 4 / 耐障害 2
  • ・耐障害数が増えるのは奇数台へ上がるときだけ
  • ・2台構成の耐障害数は 0。1台構成より強くもならず、台数だけ倍になる
  • ・公式の逐語: An odd-size cluster tolerates the same number of failures as an even-size cluster but with fewer nodes

意外なのは2台構成の耐障害数が 0 である点です。2台のクォーラムは2、つまり全台の同意が要る。片方が落ちれば止まります。1台足しても可用性は上がりません。

4台も同じ構図で、耐障害数は3台と同じ1のままです。言い方に注意が要ります。「4台は3台より弱い」は不正確で、耐えられる故障台数は同じ1です。 公式の逐語はこうです。

the fault tolerance is worse since exactly the same number of nodes may fail without losing quorum but there are more nodes that can fail

悪くなるのは確率であって、耐障害数ではありません。

具体実物

1対2に分断する — 割れるのは書き込みではなく読み取り

リーダーだった h3-etcd1 だけをネットワークから切り離し、1台側と2台側に分けました(分断前は IS LEADER = true、RAFT TERM 9)。

1対2の分断(etcd 3.5.17、2026-09-18)。リーダーだったのは少数派側コマンド出力
----- MAJORITY side (h3-etcd2 + h3-etcd3): write -----
OK
rc=0
/split
WRITTEN-BY-MAJORITY
 
----- MINORITY side (h3-etcd1 alone): write -----
Error: context deadline exceeded
rc=1
----- MINORITY side: linearizable read -----
Error: context deadline exceeded
rc=1
----- MINORITY side: SERIALIZABLE (stale) read -- this DOES return, and it is STALE -----
/split
before-partition
rc=0

(クライアントのリトライ警告行は省いています。)

両側が動くことは起きませんでした。 多数派だけが書け、少数派は書き込みも既定の読み取りも返りません。これが公式の言う「スプリットブレインの可能性を完全に避ける」の実物です。

止まったほうが分断前のリーダーだった点に注目してください。少数派の raft ログには、これが1.7秒ごとに8回出ています。

"msg":"a525040065197eaa became pre-candidate at term 9"

票が過半数に届かず、pre-candidate のまま term 9 から上がれません。リーダーの資格は過半数の支持であって過去の肩書きではないと、ログに見えます。

山場はここです。少数派でも --consistency=s の読み取りは成功し、before-partition という分断前の値を返しました。 多数派が分断中に書いた WRITTEN-BY-MAJORITY は見えていません。

よくある誤解は「スプリットブレイン = 両側が別々に書いて衝突する」ですが、クォーラム方式では書き込みでは割れず、読み取りで割れます。 危険の所在が違うのです。

分断を戻すと、隔離されていたノードも WRITTEN-BY-MAJORITY を返しました。古い状態は捨てられます。

✓ ここまでの確認

隔離された1台側で、`--consistency=s` の読み取りだけが成功しました。これは何を意味しますか。

⇄ 2つを見比べる
具体例 A

過半数で決める(今回)

少数派に落ちたノードは、相手が死んだのか自分が隔離されたのか区別できません。そこで過半数の支持が取れるかだけを基準にし、取れなければ書き込みを進めません。判定は票数に委ねます。

1対2に分断したときの各側の挙動(実測)表
側 書き込み 既定の読み取り consistency=s
多数派2台 OK 返る 返る
少数派1台 無応答 無応答 古い値
具体例 B

死活監視で外す(第30週「死活監視 — 壊れた実体を外す仕組み」)

ロードバランサも、応答しない実体が死んだのか遅いだけか区別できません。そこで1回の失敗では外さず、連続失敗の回数を基準にします。判定は回数に委ねます。

判定を何に委ねるか表
仕組み 観測だけで決めると 実際の根拠
分散合意 自分が見た範囲 過半数の票
死活監視 1回の失敗 連続失敗の回数

過半数による合意と、ロードバランサの死活監視。扱う対象は違いますが、共通する構造は何でしょうか。

どちらも「相手が応答しない」という観測から出発します。その観測だけでは何が分からないでしょうか。

この章のまとめ

  • スプリットブレインは過半数を課さない設計で起きること。クォーラムはその回避策で、因果を逆にしない。
  • クォーラムは etcd 公式 FAQ の定義で (n/2)+1。2つの過半数は合計が n を超えるので必ず重なる(この数え上げは本教材の説明)。
  • 実測では両側が動くことは起きなかった。リーダーの資格は過半数の支持であって過去の肩書きではない。
  • ただし止まるのは書き込みと既定の読み取りだけ。--consistency=s は通り古い値を返した。割れるのは書き込みではなく読み取り。
  • 止まり方は「拒否」ではなく「無応答」。
  • 2台の耐障害数は 0。4台は3台と同じ1で、落ちうるノードだけ増える(弱くなるのは確率の話)。
  • endpoint health の unhealthy cluster はクラスタが書けないという意味ではない。

自分の現場で確かめる

読んだだけでは身につきません。手元の環境で実際に見てみてください。

環境:

理解度チェック

読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。

問題 1 / 3正解: 0

etcd 公式 FAQ が定義するクォーラムの大きさは、n 台のクラスタでいくつですか。

用語集(5語)
クォーラム(quorum)
更新の確定に同意が要るノード数。etcd 公式 FAQ の定義は (n/2)+1(整数除算)。
耐障害数(failure tolerance)
クォーラムを失わずに落ちてよいノード数。公式の表では 3台で1、4台で1、5台で2。
スプリットブレイン
分断の両側が自分を正しいとみなして書き進み、状態が二重化すること。公式 FAQ はクォーラム方式がこの可能性を完全に避けると述べる。
直列化可能(serializable)読み取り
etcd で `--consistency=s` を指定した読み取り。合意を経ず手元の状態を返すので、少数派でも成功し古い値を返す。
PreVote
選挙前に勝ち目を問う段。実測では少数派が pre-candidate を繰り返し、term を上げられなかった。