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

状態はどこかに置くしかない — 共有ストアは何を引き受けるのか

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

前回、2台目を置いた時点で3つの問題が始まると見ました。今回はそのうち「状態の置き場」を取り出し、1か所にまとめるとどうなるかを確かめます。

まず結論

割れてしまう状態を1か所にまとめると、台をまたいで引き継げるようになります。ただしそれは問題を消したのではなく、「どこが落ちたら誰が止まるか」の形を変えただけです。

実測では、共有ストアを1つ止めた瞬間にアプリの2プロセスが両方とも 500 を返しました。アプリは1つも落ちていません。全員が運命を共にする点を、新しく作っていた形です。

? 読む前に予測

セッションを共有ストア1台に置いたうえで、そのストアだけを停止したとき、アプリのプロセスはどうなるでしょうか。

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

抽象原理

1か所にまとめる、という素直な答え

前回、2台目を置いた時点で「状態の置き場」が問題になると見ました。素直な答えは1つです。割れてしまうなら、割れない場所を1つ作る。

この発想はうまくいきます。台ごとにメモリに持てば台の数だけ表ができますが、外に1つ置けばどの台からも同じ表が見え、台を足しても引いても割れません。

ただし、起きたことを正確に言い直してください。状態を持つ義務が消えたのではありません。場所を移しただけです。移した先は、全員が同じものを見るために1つでなければならない。全員がその1つに依存する形を、意図的に作ったことになります。

Google SRE Book は、分散計算で歩調を揃える barrier という仕掛けについてこう書いています。

The barrier could be implemented by a single coordinator process, but this implementation adds a single point of failure that is usually unacceptable.

題材は barrier であってセッションの保管ではありません。しかし構造は同じです。共有する1実体を置くと、そこに単一障害点が加わる。「加わる(adds)」という動詞が正確で、消えたのではなく増えています。

具体実物

実測: 引き継げるようになる。そして両方が止まる

アプリ層『状態を持たないHTTPとCookie/セッション』では、sid=abc を2プロセスへ送ると、ログインを処理した 3902 だけが利用者を返し、3903 は session=null を返しました。同じ実験を、表の置き場だけ共有ストアに替えて行います。

(a) 共有ストアがある状態(macOS / Node v24.21.0 / redis:8-alpine、2026-09-18、1回)コマンド出力
$ curl -s -c ck.txt http://127.0.0.1:3912/login
logged in on 3912; store reply=+OK
$ curl -s -b ck.txt http://127.0.0.1:3912/whoami
port=3912 sid=abc session={"user":"kazu","loginPort":3912}
$ curl -s -b ck.txt http://127.0.0.1:3913/whoami
port=3913 sid=abc session={"user":"kazu","loginPort":3912}

最終行が要点です。ログインを処理していない 3913 が、3912 で作られたセッションを返しています。 既存レッスンの同じ位置は session=null でした。

では、そのストアだけを止めます。アプリの2プロセスには触れません。

(b) 共有ストアだけを停止した直後(同じ通しで1回)コマンド出力
$ docker stop h1-redis
h1-redis
$ curl -s -b ck.txt http://127.0.0.1:3912/whoami
port=3912 sid=abc STORE_ERROR=connect ECONNREFUSED 127.0.0.1:6399
$ curl -s -b ck.txt http://127.0.0.1:3913/whoami
port=3913 sid=abc STORE_ERROR=connect ECONNREFUSED 127.0.0.1:6399
$ curl -s http://127.0.0.1:3912/login
port=3912 STORE_ERROR=connect ECONNREFUSED 127.0.0.1:6399
$ curl -s -b ck.txt -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1:3912/whoami
HTTP 500
$ curl -s -b ck.txt -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1:3913/whoami
HTTP 500

Node の2プロセスは1つも落ちていません。 TCP は待ち受けており、HTTP の応答も返っています。返ったのが接続拒否ではなく 500 であることが、その証拠です。読み書きの両方が失敗し、ログインし直すこともできません。ストアを戻すと両方が回復し、今度は 3913 で書いたものを 3912 が読めました。

✓ ここまでの確認

共有ストアを停止した実測で、2プロセスが HTTP 500 を返したことは何を示していますか。

抽象原理

消したのではなく、巻き込まれ方を取り替えた

一般化します。状態の置き方を変えると変わるのは、壊れる確率ではなく「どこが落ちたら誰が止まるか」の形です。サーバ内に持てば、落ちた台の利用者だけが影響を受けますが、その人は他の台が生きていても続きを取り戻せません。共有ストアに置けば、どの台が落ちても他の台が引き継げますが、ストアが落ちればアプリが何台生きていても全員が止まります。

置き場を変えると、巻き込まれる範囲の形が入れ替わる
サーバ内に持つ台ごとに表がある
共有ストアに置く全員が同じ表を見る
アプリ1台が落ちたとき
その台の利用者だけ続きは戻らない
誰も止まらない他の台が引き継ぐ
置き場そのものが落ちたとき
台と一体なので別には起きない上の行と同じ
全員が止まるアプリは生きたまま 500
実測した状態
  • ・選んでいるのは「誰が一緒に止まるか」の分布

前回、1台のときは全部が運命を共にするので故障は全部か無かになる、と見ました。共有ストアは、その運命を共にする範囲を自分の手で引き直す操作です。

具体実物

対比例: 表を持たない方式に逃げても、戻ってくる

「共有ストアが単一障害点になるなら、表を持たなければよい」——署名付きトークンです。中身をクライアントに預け、サーバは署名の検証だけで受理します。どの台でも結果が同じで、止まる1点はありません。

この方式が失うものは、アプリ層『状態を持たないHTTPとCookie/セッション』で確認しました。RFC 7519 §7.2 の検証手順は構文・署名・クレームの取り出しまでで、発行元へ問い合わせる段がありません。だから期限が来るまで有効なままです。

ここから先が今回の話です。運用では「この利用者を今すぐ止めたい」が必ず起きます。そのとき要るのが失効リストで、これは全実体から同じ内容が見えなければ意味がありません。片方の台だけが無効と知っていても、もう片方が通します。

つまり、共有の1実体を避けようとして選んだ方式が、取り消しを要求した瞬間に共有の1実体を呼び戻します。

✓ ここまでの確認

「署名付きトークンにすれば単一障害点は無くなる」という主張の評価はどれですか。

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

サーバ内のメモリに持つ

表はそのプロセスのものです。落ちればその表だけが消え、他の台は無事です。ただし落ちた台で続きを持っていた利用者は、他の台へ回されても取り戻せません。

何が落ちたとき、誰が止まるか表
落ちたもの 止まる範囲 残るもの
アプリ1台 その台の利用者 他の台の利用者は継続
アプリ全台 全員 表も一緒に消える
具体例 B

共有ストアに置く

表は外の1か所にあり、どの台からも同じ内容が見えます。ただしその1か所へ届かないと、台がいくつ生きていても処理を完了できません。

何が落ちたとき、誰が止まるか表
落ちたもの 止まる範囲 残るもの
アプリ1台 誰も止まらない 他の台が引き継ぐ
共有ストア1台 全員 アプリは生きたまま 500

メモリに持つ方式と共有ストアに置く方式。どちらが「単一障害点を無くした」と言えますか。

それぞれで「1つ落ちると全員が止まる」ものを探し、個数ではなく位置を比べてみてください。

この章のまとめ

  • 状態を1か所にまとめると台をまたいで引き継げる。実測では 3913 が 3912 の作ったセッションを返した。
  • しかし状態を持つ義務は消えず、単一障害点が加わる。SRE Book は barrier について同じ構造を述べている。
  • 共有ストアだけを止めると、アプリ2プロセスは生きたまま両方 500 を返した。
  • 署名付きトークンは表を持たない代わりに期限前に取り消せない。失効リストを置いた瞬間、また共有の1実体が現れる。
  • 選んでいるのは壊れにくさではなく、誰が一緒に止まるか。次の手は複製だが、そこで遅れが現れる。

自分の現場で確かめる

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

環境:

理解度チェック

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

問題 1 / 3正解: 0

セッションを共有ストアに移したことで、何が変わりましたか。

用語集(3語)
単一障害点 (single point of failure)
そこが1つ止まると、依存する全体が止まる箇所。Google SRE Book はラック上位のスイッチが job の単一障害点になる例を挙げている。
共有ストア
複数の実体から同じ内容が見えるように状態を1か所に置いた保管先。置き方の呼び名であり、製品名ではない。
失効リスト
署名付きトークンを期限前に無効にするため、無効化したものを列挙して全実体から参照できるようにした表。