- ブラウザURL入力
- DNS名前→IP
- ルータ経路選択
- ロードバランサ振り分け
- Nginx受け口
- アプリ処理今ここ
- 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 を返しました。同じ実験を、表の置き場だけ共有ストアに替えて行います。
$ curl -s -c ck.txt http://127.0.0.1:3912/loginlogged in on 3912; store reply=+OK$ curl -s -b ck.txt http://127.0.0.1:3912/whoamiport=3912 sid=abc session={"user":"kazu","loginPort":3912}$ curl -s -b ck.txt http://127.0.0.1:3913/whoamiport=3913 sid=abc session={"user":"kazu","loginPort":3912}
最終行が要点です。ログインを処理していない 3913 が、3912 で作られたセッションを返しています。 既存レッスンの同じ位置は session=null でした。
では、そのストアだけを止めます。アプリの2プロセスには触れません。
$ docker stop h1-redish1-redis$ curl -s -b ck.txt http://127.0.0.1:3912/whoamiport=3912 sid=abc STORE_ERROR=connect ECONNREFUSED 127.0.0.1:6399$ curl -s -b ck.txt http://127.0.0.1:3913/whoamiport=3913 sid=abc STORE_ERROR=connect ECONNREFUSED 127.0.0.1:6399$ curl -s http://127.0.0.1:3912/loginport=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/whoamiHTTP 500$ curl -s -b ck.txt -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1:3913/whoamiHTTP 500
Node の2プロセスは1つも落ちていません。 TCP は待ち受けており、HTTP の応答も返っています。返ったのが接続拒否ではなく 500 であることが、その証拠です。読み書きの両方が失敗し、ログインし直すこともできません。ストアを戻すと両方が回復し、今度は 3913 で書いたものを 3912 が読めました。
共有ストアを停止した実測で、2プロセスが HTTP 500 を返したことは何を示していますか。
消したのではなく、巻き込まれ方を取り替えた
一般化します。状態の置き方を変えると変わるのは、壊れる確率ではなく「どこが落ちたら誰が止まるか」の形です。サーバ内に持てば、落ちた台の利用者だけが影響を受けますが、その人は他の台が生きていても続きを取り戻せません。共有ストアに置けば、どの台が落ちても他の台が引き継げますが、ストアが落ちればアプリが何台生きていても全員が止まります。
- ・選んでいるのは「誰が一緒に止まるか」の分布
前回、1台のときは全部が運命を共にするので故障は全部か無かになる、と見ました。共有ストアは、その運命を共にする範囲を自分の手で引き直す操作です。
対比例: 表を持たない方式に逃げても、戻ってくる
「共有ストアが単一障害点になるなら、表を持たなければよい」——署名付きトークンです。中身をクライアントに預け、サーバは署名の検証だけで受理します。どの台でも結果が同じで、止まる1点はありません。
この方式が失うものは、アプリ層『状態を持たないHTTPとCookie/セッション』で確認しました。RFC 7519 §7.2 の検証手順は構文・署名・クレームの取り出しまでで、発行元へ問い合わせる段がありません。だから期限が来るまで有効なままです。
ここから先が今回の話です。運用では「この利用者を今すぐ止めたい」が必ず起きます。そのとき要るのが失効リストで、これは全実体から同じ内容が見えなければ意味がありません。片方の台だけが無効と知っていても、もう片方が通します。
つまり、共有の1実体を避けようとして選んだ方式が、取り消しを要求した瞬間に共有の1実体を呼び戻します。
「署名付きトークンにすれば単一障害点は無くなる」という主張の評価はどれですか。
サーバ内のメモリに持つ
表はそのプロセスのものです。落ちればその表だけが消え、他の台は無事です。ただし落ちた台で続きを持っていた利用者は、他の台へ回されても取り戻せません。
落ちたもの 止まる範囲 残るものアプリ1台 その台の利用者 他の台の利用者は継続アプリ全台 全員 表も一緒に消える
共有ストアに置く
表は外の1か所にあり、どの台からも同じ内容が見えます。ただしその1か所へ届かないと、台がいくつ生きていても処理を完了できません。
落ちたもの 止まる範囲 残るものアプリ1台 誰も止まらない 他の台が引き継ぐ共有ストア1台 全員 アプリは生きたまま 500
メモリに持つ方式と共有ストアに置く方式。どちらが「単一障害点を無くした」と言えますか。
それぞれで「1つ落ちると全員が止まる」ものを探し、個数ではなく位置を比べてみてください。
この章のまとめ
- 状態を1か所にまとめると台をまたいで引き継げる。実測では 3913 が 3912 の作ったセッションを返した。
- しかし状態を持つ義務は消えず、単一障害点が加わる。SRE Book は barrier について同じ構造を述べている。
- 共有ストアだけを止めると、アプリ2プロセスは生きたまま両方 500 を返した。
- 署名付きトークンは表を持たない代わりに期限前に取り消せない。失効リストを置いた瞬間、また共有の1実体が現れる。
- 選んでいるのは壊れにくさではなく、誰が一緒に止まるか。次の手は複製だが、そこで遅れが現れる。
自分の現場で確かめる
読んだだけでは身につきません。手元の環境で実際に見てみてください。
理解度チェック
読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。
セッションを共有ストアに移したことで、何が変わりましたか。
用語集(3語)
- 単一障害点 (single point of failure)
- そこが1つ止まると、依存する全体が止まる箇所。Google SRE Book はラック上位のスイッチが job の単一障害点になる例を挙げている。
- 共有ストア
- 複数の実体から同じ内容が見えるように状態を1か所に置いた保管先。置き方の呼び名であり、製品名ではない。
- 失効リスト
- 署名付きトークンを期限前に無効にするため、無効化したものを列挙して全実体から参照できるようにした表。