-
トラブルに対応する力は、大事だけど天性のものに見られがち
- 習慣的なものでどうやってやるかを説明するのが難しい
-
訓練は以下の2つが大事
- 一般的にどうやってトラブルに対応するか
- システムに対する理解
- 一般的な手順とかに頼ってやるのは、システムがどうやって動作するかを理解して行うよりも劣る
-
トラブルシューティングのプロセスは仮説演繹法の応用
- 観察、理解 => 仮説 => 仮説の検証
-
図12-1の例
- アラートなどをきっかけに、logなどを手がかりに現状を理解する
- 現状と、システムがどのように構成されてるか、どのように操作すべきか、障害の種類などから原因を特定する
-
仮説を検証する方法には2つある
- 観察されたシステムの状態と理論を比較して証拠を見つける
- システムに変化を与えて結果を観察する
-
このどちらかを使って、根本原因が特定できるまで検証を繰り返す
- 原因が特定できたら、再発防止策を講じるか、検討を行う
- 避けるべき落とし穴4選
- 無関係の症状を注視すること、メトリクスの意味を誤解すること => 無駄な努力
- システム、入力、環境の変更の方法を誤解すること
- 何が誤っているのかについて予想だにしない理屈を思いつくこと
- 過去の問題に固執するあまり、一度起きたことを理由にもう一度起こるとか言い出したりすること
- 誤った相関関係を一生懸命に突き止めること
- 実際には偶然の一致であったり、共通の原因によるものだったりする
- 1と2を修正することは、当該のシステムについて学ぶことや分散システムにおいて共通のパターンについて習熟することが大事
- 3については、すべての障害が同様に起こるわけではないことを心に留めておく
when you hear hoofbeats(蹄の音), think of hourses not zebras.
- 同様にすべての物事は等しいことも忘れてはいけない
- 4については、相関自体は原因でないことを心に留めておく
- 例えば、停電が原因の場合の同一クラスタ内のパケロスとディスク故障
- システムが大きく複雑になって、メトリクスの数が多くなってくると他のイベントに関連して偶然に発生するイベントは避けられない
- トラブルシューティングのプロセスを辛くなくて生産的にすることができるステップがある(らしい。ほう。)
- 基本的に図12-1の流れにそって説明がすすんでいくjo
-
問題はここから始まる(アラートだったり、同僚が「システム遅い!」って言ったりすることだったり)
-
効果的なreportに含まれるべきもの
- 期待される振る舞い
- 実際の振る舞い
- どうすればその振る舞いを抑えられるか(アラートに対して具体的なアクションが書いてあるとか?)
- 参考文献:http://www.chiark.greenend.org.uk/~sgtatham/bugs-jp.html
-
reportは一貫した形式を持つべきで、バグトラッキングシステムみたいな検索可能な場所にあるべき
- Googleでは、小さいwebアプリがあってフォームに情報を入れると特定のシステムに関して自動的にバグを突き止めることができるらしい
-
一番はじめのこのstepで自分で原因を特定したり、自己修復できるツールを入れるのはいいかもしれない
-
Googleでは発生したすべての問題に対してbug issueを開くようにしてる
- 参照しやすい調査・改善ログが自動的にできる
- 人間に直接報告するよりもいい
- 受けた報告をbug issueにする手間が発生する
- 他のチームメンバーに見えない場合もある
- 問題解決の負担がたまたま問題を知った一部のメンバーに集中しがちになる
-
Probem reportを受け取ったら、それに対してどう対処するかを明確にするのが次のステップ
-
問題は深刻度に違いがある
- 特定の環境で特定のユーザだけが影響を受けるもの
- サービス全体が影響を受けるもの
-
対応は問題の影響に対して適切な程度であるべき
- 例えば、上の例の後者に対して総力を上げるのは適切だけど、前者に対してはやり過ぎ
-
問題の深刻さの評価に必要なもの
- 技術的な判断の訓練
- プレッシャー下での心の平静
-
障害が発生した時に、トラブルシューティングを始めたり、原因を探し始めたりしがちだけど、我慢しないといけない。
-
一番はじめにすべきなのは、とりあえず動くようにすること(一次対応っていう理解でいいのかな)
- 緊急時のオプションを取る
- 例えば、
- 故障したクラスタから他のクラスタへトラフィックを迂回させる
- 障害の伝搬を防ぐためにトラフィックを遮断する
- 負荷を軽減するためにサブシステムを落とす
- 緊急時のオプションを取る
-
例えていうと、怪我をしたときに出血を止めることが再優先
- 原因探しをしている間にシステムが死んだら意味が無い
-
素早く優先順位をつけることで、原因究明のための証拠(logとか)の保存が不可能になるわけではない
-
パイロットの話
- 新入りは「トラブルの際の最優先事項は飛行機を飛ばすことだ」と教えられるらしい
- トラブルシューティングはその次
-
システムにおいても同じ
- 例えば、
- データロストを引き起こすバグがあったとして、それを動かし続けて原因を探すよりは、殺してしまったほうが良い(まあ当然っぽいけど、パニックになっても同じ判断ができることが大事そう)
-
正常かどうかを判断するためには、システムの構成要素が何をしているかを調べることができないとならない
-
10章みたいにメトリクスが保存されてるのが理想
- メトリクスから調査をはじめるのは良い
-
ログはめっちゃいい道具
- いつ、なにが起きたのかを正確に理解できる
- システムのログを解析したり、ツールスタック全体のログを見たり
- GoogleではDapperっていうの使ってるらしい
-
Googleの例
- サーバに最近やり取りしたRPCのサンプルを返すエンドポイントがはえてる
- アーキテクチャ図を見なくても、どことどんなやり取りをしたかが分かる
- エラーレートのヒストグラムとか、RPCのタイプごとにレイテンシを出すエンドポイントもある
- 今の設定ファイルやデータを吐き出すエンドポイント持ってるやつもいる
- サーバに最近やり取りしたRPCのサンプルを返すエンドポイントがはえてる
-
クライアントの方にも施策が必要な場合もある
- リクエストとレスポンスを見たりみたいな場合
-
テキストログはリアルタイムでデバッグをするのに向いてる
- 対して構造化されたバイナリログは、より詳細に分析するツールを作るのを可能にする
-
ログにも複数のレベルの冗長度を持たせたら便利
-
ログが多すぎる場合にはサンプリングすればいい
-
より発展すると
- 「この操作にマッチするログを出して」というようなことを可能にする言語とかログ基盤
- システムについて理解することは何がまずいかという仮説を立てるのに役立つ
- しかし、ドメイン知識がなくとも適用できる一般的な方法がある
-
システムのコンポーネントには、よく定義されたインターフェースと期待通りに入力を出力に変形することが理想
- そこで、コンポーネント間のつながり(つまりはデータの流れ)をみてコンポーネントが正確に動いてるかを確認できる
- 出力結果が正しいことを確認するために、それぞれのステップでテストデータを流し込むのは有効に働く
- 再現可能なテストケースがあることによってデバッグを素早くできる
- 本番環境よりはステージングとかのほうが良いよね
-
分割統治法は一般的に使える解決策
- 複数のレイヤから構成されるシステムにおいては、片方の終端からもう一方の終端に向けてシステマティックに調査していくのが良い(例えば、前段から見ていくみたいな感じ)
- パイプライン的な仕組みにも向いてる
- 並外れて大きいシステムの場合には、両方向からが良い
- システムを半分に分割して、両方向から調査していく(半分調査して正常なら、もう半分を更に分割してそれを繰り返す)
- 故障したシステムはしばしば「何か」をしようとし続ける
- 何が間違っているかを理解する手助けになるもの
- システムが何をしているかを調べる
- システムがなぜそれをしているかを問う
- システムのリソースがどこに使われているかや出力がどこに出ているかを問う
-
システムには慣性がある
- コンピュータシステムは、外部からの力(設定の変更や負荷の傾向の変化)に晒されるまで、その動作を続ける傾向にある
-
システムに対する直近の変更は誤りを特定する生産的な開始地点になりうる
-
よく設計されたシステムには、新しいバージョンのデプロイや設定の変化を追跡するロギングの仕組みがある
- トラフィックをさばくサーバのバイナリからホストにインストールされたパッケージまで全部
-
システムのパフォーマンスや振る舞いをシステムの他のイベントや環境と関連付けることはモニタリングダッシュボード構築の役に立つ
- 例:図12-2みたいにデプロイの開始時間と終了時間をエラーレートと一緒に表示したグラフ
- これまで説明してきた一般的な手法は広範な問題のドメインに対して役に立つけど、特定のシステムを診断するツールやシステムを作ることが役に立つ(GoogleのSREもこれに多くの時間を割いている)
- こういったツールは特性のシステムに特化してるけど、同じ轍を踏まないようにサービスやチーム間での共通点を見つけることも大事
-
原因の可能性のリストが思いついたらすぐに、どの要因が根本原因かどうかを探らなければならない
-
実験的な方法を使って仮説の真偽を確かめる
- 例:以下のような仮説を立てたとする
- アプリケーションサーバとデータベースサーバ間のネットワーク障害
- もしくは、データベースへの接続エラー
- この場合、アプリケーションサーバがやっているのと同じ方法でデータベースへの接続を試してみると2つめの仮説を反証することができるし、データベースサーバにpingを打つことで1つ目の仮説が反証できる(ネットワークのトポロジーやファイヤーウォールとかの要素に左右はされるけど)
- コードに従って一つ一つ再現していけば問題にたどり着くかもしれない
- 例:以下のような仮説を立てたとする
-
テストを設計する際に心に留めておくべきこと
- 理想的なテストには排反な事象があるべき
- これによって問題の集合を分割できる(実際難しい)
- 明確なことを最初に
- 可能性を減らしていくようにテストを実施する
- サーバの最近の設定の変更を追う前に、麻しん間のネットワーク接続の問題を見る
- 実験は誤った結果を導き出すことがある
- ファイヤーウォールによってIPアドレスで制限がかかっている場合などに、手元からpingを打っても失敗するなど
- テストの実行によって、将来のテスト結果を変えてしまう副作用引き起こす可能性があること
- プロセスに多くのCPUを与えることは、オペレーションを素早くするけどデータ競合を引き起こすかもしれない
- 冗長なロギングによってレイテンシの問題を悪くしたりするかもしれない
- テストには決定的でなくて単に示唆的なものもある
- 競合状態やデッドロックを適切なタイミングで再現可能な方法で引き起こすのは難しい
- 理想的なテストには排反な事象があるべき
-
どのような考えを持ったのか、どのテストを走らせたのか、どのような結果を見たのかについて注意を払え
- 特に複雑で対処に時間がかかるものに対応してる場合に心に留めておくと有効
- システムに対して変更を与えてテストを行う場合(プロセスに対して与えるリソースを増やしたり)、システマティックでドキュメント化された方法で変更を加えるべき
- こうすることでテスト前の状況に戻しやすくなる
-
「悪い」結果とは、期待した影響が出ていない実験結果のこと
-
悪い結果は無視したり軽視すべきではない
- 自分が間違っていると認識することには価値がある
- チームにおいて一見合理的な2つの設計があるときに、片方はもう一方のほうが良いのでは無いかという曖昧な問いを解決しないといけない
-
悪い結果が出た実験は決定的である
-
プロダクトや設計、パフォーマンスの限界に関して確実なことを知ることができる
-
実験や設計に価値があるのかを決定する手助けになる
-
例:ロック競合によって障害になるまでに8,000コネクションが必要な場合に、800コネクションまでしか捌けないあるWebサーバを使うかどうかを判断する場合
- Webサーバの評価を行うことになった後続のチームは、ドキュメント化された上記の実験結果があった場合に、これをスタート地点にできる
- 800コネクション以下でも良いのかとかロック競合の問題はすでに解決されたのかとか
- Webサーバの評価を行うことになった後続のチームは、ドキュメント化された上記の実験結果があった場合に、これをスタート地点にできる
-
悪い結果が直接的に他の人の実験に影響を与えることができない場合でも、追加のデータを集めれば、他の人が実験を選択する手助けができたり罠にハマることをさける手助けになったりする
- マイクロベンチマーク、ドキュメント化されたアンチパターン、プロジェクト反省会とかがこれに該当する
-
「悪い結果が他人を助けることもあるんやで」
-
-
ツールや手法は実験のあとも残るし今後の課題を教えてくれる
- ベンチマークツールとかが典型的な例
- 多くのWeb管理者がapache benchが生成した結果に助けられてるでしょ?
- 例え最初の結果がめちゃくちゃ悪いものだったとしても
- 再現可能な実験を可能にするツールを作ることには間接的な利益もある
- 設定の変更を試すことを簡単にするスクリプトを書くことで次のプロジェクトで忘れたりすることを防げる
- 例:DBサーバをSSDで作ったり、インデックスを張ったり
- 設定の変更を試すことを簡単にするスクリプトを書くことで次のプロジェクトで忘れたりすることを防げる
-
悪い結果を公開することがデータドリブンな文化を改善する
- 悪い結果に対して説明を加えることで、メトリクスに対するバイアスを減少させたり、不確実性をどのように受け入れるかに対する例を提供する
- すべてを公開することで、他の人がそうすることを奨励することにもなるし、同様の職種の人がより素早く学習することができるようになる
-
結果を公開する
-
結果を公開すれば、他の人が同様の検証を設計して実行する必要が無くなる
-
検証が失敗したと認知しやすいがために、悪い結果を報告することは避けるのが一般的だしそうしたいもの
-
検証の中には不遇なものがあって、そういうのはレビュー段階でお蔵入りにされがち
-
多くの人が悪い結果は進歩ではないと考えているために報告されていない検証が多い
-
設計、アルゴリズム、チームのワークフローに至るまで自分が手掛けたことをみんなに話すべき
- 悪い結果もリスクテイキングの一つであること、すべてのよく設計された検証にはメリットがあることを認識して同僚を勇気づけよ
- 失敗について言及されていない設計ドキュメント、パフォーマンスレビュー、エッセイは疑ってかかるべき
- こういうのは大抵の場合、大幅に内容がフィルターされてるか、厳密な手法が用いられていない(気をつけよう)
- 驚くべき結果を発見したら他の人が驚かないようにするために結果を公開しよう
-
-
原因を一つに特定できていたら理想
-
次にやることはそれが実際に原因であることを証明すること
-
ある要因が問題を引き起こしたことを再現して確実に証明することは本番環境においては困難
- たいていの場合は有り得そうな原因を発見できるだけ(理由は以下)
- システムは複雑:たくさんの原因でない要素があるけど、複数が絡まり合って問題を引き起こす
- 本番環境において問題を再現することは選択肢には無い:システムを障害が発生する状態にすることの複雑さ、さらなるダウンタイムは許容されない。コストはかかるけど本番相当の環境を用意することで挑戦できるようになる
- たいていの場合は有り得そうな原因を発見できるだけ(理由は以下)
-
原因を特定したら以下のことを書くべき
- システムにおいて何が間違っていたのか
- どのように問題を発見したのか
- どのように問題を修復したのか
- どのように再発を防止するのか
- App Engineの例
-
クライアントがレイテンシ、CPU利用率、トラフィックをさばくのに必要なプロセス数の急激な増加を報告
- クライアントには関連しそうな直近の変更はない
- アプリケーションへのトラフィックの増加も見られない(図12-3)
- クライアントはApp Engine側の変更を疑った
-
調査の結果、レイテンシがある一定の規模で増加していることがわかった(図12-4)
-
CPU利用率とかも同時に4倍くらいになってた(図12-5、12-6)
-
Let's troubleshooting!!!
-
典型的に、レイテンシやリソース利用料の急激な増加は、トラフィックの急激な増加や設定の変更が原因であることが多い
-
このへんで心が折れました
-
-
トラブルシューティングを容易かつ素早くする方法はたくさんある
-
一番基本となることは、
- 各コンポーネントに対する可観測性の確保(white-box metrixと構造化ログ)
- コンポーネント間のインターフェースがわかりやすく、観測しやすいようにシステムを設計する
-
システム全体から決まった方法で情報を得られるようにすることによって、上流のコンポーネントのどのログエントリが下流のコンポーネントのログエントリに対応するかを明確にする必要性を減らし、結果的に調査と復旧にかかる時間が短くなる
-
コードや環境の変化による問題はトラブルシューティングを必要とする
-
単純化して、制御して、変化を記録することでトラブルシューティングの必要性を減らせるし、もし問題が起きたときにも対処が簡単になる
- トラブルシューティングのプロセスが新入りにとって明確で理解できるようになるように、各ステップについて見てきた
- 運や経験に頼らずにトラブルシューティングをシステマティックにできるようにすることでサービスのリカバリタイムの上限を定めてユーザの体験もあげれるでしょう