- ブラウザURL入力
- DNS名前→IP
- ルータ経路選択
- ロードバランサ振り分け
- Nginx受け口
- アプリ処理
- 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] stop ONE follower (h3-etcd3) =====stopped h3-etcd3--- write with 2/3 up (quorum 2 met) ---OK/labafter-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に届きません。
--- 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 ---/labafter-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 の表を見ます。
- ・耐障害数が増えるのは奇数台へ上がるときだけ
- ・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)。
----- MAJORITY side (h3-etcd2 + h3-etcd3): write -----OKrc=0/splitWRITTEN-BY-MAJORITY----- MINORITY side (h3-etcd1 alone): write -----Error: context deadline exceededrc=1----- MINORITY side: linearizable read -----Error: context deadline exceededrc=1----- MINORITY side: SERIALIZABLE (stale) read -- this DOES return, and it is STALE -----/splitbefore-partitionrc=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` の読み取りだけが成功しました。これは何を意味しますか。
過半数で決める(今回)
少数派に落ちたノードは、相手が死んだのか自分が隔離されたのか区別できません。そこで過半数の支持が取れるかだけを基準にし、取れなければ書き込みを進めません。判定は票数に委ねます。
側 書き込み 既定の読み取り consistency=s多数派2台 OK 返る 返る少数派1台 無応答 無応答 古い値
死活監視で外す(第30週「死活監視 — 壊れた実体を外す仕組み」)
ロードバランサも、応答しない実体が死んだのか遅いだけか区別できません。そこで1回の失敗では外さず、連続失敗の回数を基準にします。判定は回数に委ねます。
仕組み 観測だけで決めると 実際の根拠分散合意 自分が見た範囲 過半数の票死活監視 1回の失敗 連続失敗の回数
過半数による合意と、ロードバランサの死活監視。扱う対象は違いますが、共通する構造は何でしょうか。
どちらも「相手が応答しない」という観測から出発します。その観測だけでは何が分からないでしょうか。
この章のまとめ
- スプリットブレインは過半数を課さない設計で起きること。クォーラムはその回避策で、因果を逆にしない。
- クォーラムは etcd 公式 FAQ の定義で
(n/2)+1。2つの過半数は合計が n を超えるので必ず重なる(この数え上げは本教材の説明)。 - 実測では両側が動くことは起きなかった。リーダーの資格は過半数の支持であって過去の肩書きではない。
- ただし止まるのは書き込みと既定の読み取りだけ。
--consistency=sは通り古い値を返した。割れるのは書き込みではなく読み取り。 - 止まり方は「拒否」ではなく「無応答」。
- 2台の耐障害数は 0。4台は3台と同じ1で、落ちうるノードだけ増える(弱くなるのは確率の話)。
endpoint healthのunhealthy clusterはクラスタが書けないという意味ではない。
自分の現場で確かめる
読んだだけでは身につきません。手元の環境で実際に見てみてください。
理解度チェック
読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。
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 を上げられなかった。