システム障害と僕達はいかにして戦えば良いのか、障害対応について考えた

IT界隈でエンジニアしていると、よく出くわすのが障害対応です。できれば会いたくないという人が多いと思うんですが、僕はけっこう好きです。障害対応。どこに原因があるのか調査をして、バランス良くベターな対応をしたときの楽しさは、プログラミングとはまた違ったものがあります。探偵っぽい感じが面白いですよね。もちろん、障害が発生しない状況を作るのが一番です :stuck_out_tongue_closed_eyes: 

弊社では数多くのWebサービス／アプリを運営しているので、過去様々な障害対応をしてきました。その際に、解決までどんな道筋を僕がたどるのかを振り返ってまとめてみました。これが大正解なんてことはなく、人や事象によって違うとは思いますが。

なお、障害検知手法とか、サーバのコマンドとか、コードのデバッグ手法とか、具体的なことは一切出てきません。手続きと思考プロセス的な話です。

# 障害対応フローチャート

一般的な感じだと思いますが、障害報告から対応完了までのフローチャート的なものを作りました。それぞれの詳細を以降で解説していきます。

![障害対応フローチャート.png](https://qiita-image-store.s3.amazonaws.com/0/2152/3b757a3b-2ef9-ac45-f500-de538e11b5ff.png "障害対応フローチャート.png")

## 障害の連絡をする

障害を自分が発見した場合の話です。

まず、すぐ近くにいるエンジニアやチームメンバーに報告をします。どのように報告をすれば良いのかというと **結論ファースト** です。

ダメな例：「ここのオブジェクトがnilになってるかもしれなくて、どうも動いていないっぽく、もしかしたらライブラリの問題っぽいなーと思うんですが、どうしたらいいですか？　え？　本番サーバの話です」

良い例：「本番サーバで管理画面に障害があります！　A機能が動いていません。…もしかしたら、ここのオブジェクトがnilになっているのが原因かもしれません」

まず、緊急度の高い障害の報告ですよーというのを伝えるのが大事です。そして、ここでは一方的に話しすぎないようにします。情報量は多いほうが良いんですが、相手と対話をしてください。先輩エンジニアであれば、うまくヒアリングしてくれるはずです。

## 障害の連絡を受ける

障害の報告を自分がうけた場合の話です。

障害対応をするためには、なによりもまず状況の確認が大事です。障害対応は情報戦です。そして報告者がどんなに慌ててても自分は冷静になりましょう。焦っていいことなんて一つもないです。とはいえ、悠長にヒアリングする余裕はないので、まず下記の3点を確認しましょう。

- 何が問題なのか
- 障害発生前に、何か変化があったか
- 事態の緊急度

### 何が問題なのか

「◯◯が動かなくて、△△ができなくて、□□なんです！」という報告があるとします。ここで、いったい何が問題なのかを把握するのが重要です。◯◯が動かないことが問題なのか、△△ができないことが問題なのか、はたまた□□なのか、全てなのか。

意外と問題なのは一つだけで、それに対して応急処置をすればひとまず大丈夫…ということがあります。問題に対して、どんな対応をするかを判断するために、まずは問題の認識が必要ということですね。

### 何か変化があったか

障害が発生するということは、何か変化があった可能性が高いです。そこが分かれば対応もスムーズにできます。

- 新しいコードをデプロイした
- サーバの設定を変更した
- 管理画面でなにか操作をした
- 広告を開始した
- なにもしていないはず…

ただ、ここでは深追いしないように、さらっとヒアリングすることに留めます。原因特定に執着して時間をかけてしまっては、傷口が開いてしまいます。

## チームへ共有

障害対応の状況がひと通り分かったら、エンジニアチーム＋関係者へ共有をします。当然の行為ですが、これには2つのメリットがあります。

- 他のエンジニアに共有することで、解決への糸口が広がる可能性
- 事態を収束させるために、関係者全員を巻き込んで行動できる

自分がすべてを理解し、超人的に頭と手を動かすのが速ければ他人の協力は必要ないかもしれませんが、残念ながら多くの人は普通の人間です。というわけで、頭と手は多いほうが良いんです。瞬発的には。

協力してくれる人がいたら、役割分担をします。同時に動くので、やってる内容が被らないように気をつけましょう。よくやるのは、いくつか可能性をリストアップして、それぞれに担当を付けて調査するというスタイルです。

時々、妙な責任感や罪悪感から報告が遅くなったり、クローズドな空間で対応しようとしてたりするケースがありますが、そんなことをするメリットは一つもないです。自分自身の価値を下げるだけです。監視アラートもバグレポートもオープンな環境で共有できるようにしましょう。

### 事態の緊急度

緊急度によって対応の仕方が変わってきます。

#### とにかくなんとかしたい

時間をかけずに、とにかくなんとかしたいというケース。まぁ、障害が発生したらみんなそう思うんですが、そんなすぐ解決することはないので、冷静に最低限どうなっていればいいかを考えて決めます。例えば…

- トラフィックが異常に多く、Webサイトが正常に閲覧できない
 - 全てのリクエストをメンテナンス画面に飛ばして、サーバへの負荷をまずなくす
 - コスト払ってでもスケールアウトしまくって、一時対応をする
- ECサイトで実際に支払う金額がずれている
 - 購入画面をメンテナンス画面に切り替える
 - 実際に払っている金額は正しく、管理画面上だけおかしいので、何もしない

ここでの対応は、なるべく時間をかけずに、かつリスクの少ない方法を選択します。

#### 二次災害を防ぐ

応急手当をする場合、わりとなりふり構わない手法が採用されますが、それによって二次災害が起こる可能性があります。例えば、データベースに直接SQLを叩いて修正するとかです。間違って、全データを削除してしまったりすると、直すどころか被害を広げてしまいます。応急手当の方法を検討する際には、リスクが少ない方法を採用するようにしましょう。

また、急いでいても、作業時にはdry-runできるように、本番環境で実行する前にメンバーにレビューしてもらったり、テスト環境で実施したりしましょう。焦っていいことなんて本当にないのです。

## 再現性の確認

なにはともあれ再現性を確認します。

### 再現したりしなかったりする

100%の再現が確認できなかった場合は、たいてい **状態を持つ** 何かが再現条件だったりします。それはデータベースや、Webサーバプロセス、日時や閲覧環境などです。そこで、再現する／しないの区切りを付けます。

- 本番環境でのみ発生する
- 何回かに1回再現する
- ユーザによって発生する

特定の条件下で100%の再現ができる場合は、その条件の状態を持っているものに原因がある可能性が高いです。ひとまずそこを掘ってみましょう。応急手当として、状態を修正することができるならそうします。時間をかけられるなら、その状態になってしまう原因を見つけ修正するか、その状態でも正常に稼働するように修正します。

この前、隣のエンジニアがはまってたのはバックグラウンドで動いているWorkerが正常に稼働したり、しなかったりしてハマっていたんですが、原因は新しいコードを反映していないWokerプロセスが存在したというものでした。デプロイ時に、正常にWorkerの再起動がされなかったケースですね。

### 再現性が見つけられなかった

再現性がない、見つけられない場合…。つら…。そんな時は、Wikipediaにも載っている「[特異なバグ](https://ja.wikipedia.org/wiki/%E7%89%B9%E7%95%B0%E3%81%AA%E3%83%90%E3%82%B0)」を読んで心を落ち着かせましょう。僕のお気に入りは下の2つです。

- ハイゼンバグ: ハイゼンバグは、それを調査しようとすると変貌したり消えたりするバグである。
- シュレーディンバグ: シュレーディンバグは、特殊な状況下でバグが発覚してプログラマがソースを読むと、そもそも最初から動くはずがないように見え、それを境にして一切動作しなくなってしまうようなバグである。

まぁ、そんなこといっても解決なんてしないんですが、意外と原因を探る角度を変えるキッカケになったりします。再現性がない≠再現性が見つけられない…と考えても良いと思います。そして基本、1度起こった障害／バグは再現できるはずで、その条件を推定するための情報が少ないだけです。と思って前向きに取り組みます。

あとは、思い込みの前提条件を外すっていうのも一つの手ですね。こうなってるはずだ…というのを再度確認するというもの。けっこう手間がかかって確認しないということがありますが、思いつくもの全部やってダメなら、駄目だと思っていたものを全部洗い出すほうが精神的にまだマシだし、それで解決することがあります。

また、再現性が見つけられないほど低い発生度の場合、重要だけど緊急ではないというステータスだと思うので、対応すべき価値があるのであれば、腰を据えて取り組むのが良いんじゃないかと思います。また、根本的な解決を目指すのではなく、障害が発生したとしても大丈夫なようにセーフティーネットを組むというアプローチも。

## 解決への道

とりあえず対応の時間が確保でき、再現性も確認できました。あとは落ち着いて原因を探り、対処するだけです。どうやって原因を探りましょう？　僕の場合は、事象に近しい条件から1つずつ外していくように探っていく方法をよくとります。スコープを狭める感じです。

### スコープを狭める

スコープを狭めるというのは、例えば、ECサイトの価格表示がおかしいとします。表示部分が一番事象そのものなので、まず発生している条件が全商品なのかどうかを確認します。そして、特定の商品やカテゴリにのみ発生していると分かったら、価格が格納されているデータベースを確認します。正常値が記録されている場合、データベースからViewにわたる経路がおかしいと辺りが付けられます。こうすることで、見るべき範囲（スコープ）が狭まってきます。

この分割の仕方は、端から細切れにするというよりは、大きな要素単位で分断します。

![zakkuri.png](https://qiita-image-store.s3.amazonaws.com/0/2152/b43caa3f-04f7-a2ff-df2b-fdc20d00e868.png "zakkuri.png")

大きく分断するためには、要素ごとのインターフェイスを調べます。DBとWebAPIならDBの値でしょうし、WebAPIとViewであればWebAPIのJSONフォーマットの値でしょう。

どこの要素から分断するかについては、すぐ確認できるものからという、ラフな選び方でいいと思います。もしくは、心当たりのあるもの。二分探索的に見ると、真ん中から切り分けるとかでも良いのかもしれないです。確認ステップが、もしかしたら減るかも。

障害対応時には、とにかく **見なくても良い物** を増やしていくことが重要です。ただし、見なくても良いと思い込み過ぎないように、外したものは頭の片隅に残しておきましょう。時々、うっかりで見過ごしているケースもあります。

### 対応中のコミュニケーション

対応している最中は、積極的にSlackや口頭で今時分のステータスや、思ったこと・気がついたことをメンバーに共有しましょう。作業自体がバッティングする可能性を潰すというのもありますが、情報をアウトプットすることで、他のメンバーが何かに気がつく可能性が上がります。振り返って、その時の状況を思い出すきっかけにもなります。

## ドキュメントと再発防止策

障害対応が完了しました。おつかれさまです。じゃあ、ビールでも飲みに行くか！ではなく、忘れないうちにドキュメントを作成します。これは、未来の自分たちのために書きます。どのような障害が発生したのか、何が原因だったのか、どうやって解決したのか、再発防止策はなにかあるか。

このドキュメントは、基本的に関係者であれば誰でも閲覧できるような状態にします。例えば新人のエンジニアが入ってきた時に、過去の障害対応ドキュメントをさらっと読むだけでも役に立ちます。また、障害が発生した時に、似たような事例であれば解決が早まるかもしれません。

また、ありがちなのがインフラ対応を行ったもののドキュメントがないと、実施コマンドや設定ファイルの差分が分からなくなり、後日困るというパターンです。Chefなどで管理している場合には、コードと実態が離れてしまうという恐ろしいこともあります。Chefを実行すると障害が再発するという…怖いですね。

こういったドキュメントを書くというと、顛末書や障害報告書という名前になりがちなので、深くお詫びをし反省してます的な意味合いを感じ取る人がいるかもしれませんが、そんなことは書く必要がなく、たんなる事実と、その時感じたことを書けば良いのです。

### 再発防止策を考える

再発防止策を考えて、実施するのは難易度高いですよね。僕もなかなか良い再発防止策というのを実践できていませんが、以下の様なことを意識しています。

- 「気をつけます」的な精神的／人に頼ることはしない
- 自動でチェックできるような仕組みにする（最も小さなものでユニットテスト）
- オペミスであれば、そもそもそのオペレーションができないように設計変更する

### 月1くらいで全エンジニアに共有する

たとえドキュメントを作ったとしても、それを見る習慣や、きっかけがないと見られません。そうなると書いても無駄みたいな雰囲気が出てきてしまいます。そこで、部署や担当サービスをまたいで、全エンジニアで共有する場を作ると良いと思います。

例えば弊社では月に1回TGIFを開催していて、エンジニア持ち回りでLTを行います。かなりカジュアルな場なので、障害についても和やかな雰囲気でディスカッションができます。なるべく詰め会みたいな、誰も得しない場だけは作らないようにしましょう。

昔、僕が起こした障害でブルーグリーンデプロイメントをしたらMySQLへのコネクション数が一時的に倍になって接続エラーがでたことがあったんですが、その数ヶ月後くらいに別の開発チームで全く同じような障害が起きました。あの時、チーム内だけでなく全エンジニアに共有していればと反省しております。

### 報告を仕組み化する

さて、ここまでの話は、仕組みがあまり整っていないチームとしての対応でした。本来であれば、障害の報告の所からフォーマット化され、GitHubのIssueなりBacklog / Reactioなどに集約されていくのが良いと思います。

そうすれば、障害情報が一定のクオリティを保つこともできるし、ドキュメント化されずに風化するということも防ぎやすくなるでしょう。

## 障害を起こしにくいチームにする

障害は起こらないほうが当然良いわけで、起こしにくいチームにすることによって心配事やトラブルが少なくなるだけでなく、最終的にプロダクトの品質向上、ユーザ満足度につながります。プロダクトの品質向上させるQAという部隊があるのが定番だと思うんですが、弊社ではまだまだ取り組み始めたばかりです。

ここは、どういった成果が出せるのか…DevOps + QA（品質保証） + SRE（サイト信頼性エンジニアリング）といったチームの運用のコツなど。2016年に運用して知見をためていきたいところです。

とはいえ小さなチームだと、QAなんて専門部隊を作るのは難しいので、当たり前のようなことですが

- 1人で勝手にやらない
- 必ず（コード｜設計）レビューを通す
- 小さなことでも気になった所をメンバーに共有する

というのをやれれば、リカバーできないほどの障害は起きにくいんじゃないかなと思います。

# まとめ

まとめると、情報共有をして人を巻き込みつつ、頭のなかで複数の可能性を模索しながら、大きな要素で分断し、手を動かして、可能性の芽を１つずつ潰し、とにかくスコープを狭めること…です。

障害対応に限らないんですが、経験することで考える幅が広がるので、もし業務で障害が起こった場合は、「戦力にならないしな〜」とか遠慮するんではなく、積極的にからんでいきましょう。その方が楽しいです。