オブザーバビリティとは何か
もともとは制御理論の用語で、システムの外部に出てくる出力だけを見て、内部状態をどの程度把握できるかを意味します。ソフトウェアに置き換えると、こうなります。サーバーに直接入らず、コードを修正してログを増やすこともなく、現在出力されているデータだけで問題を説明できるか。
重要なのは、あらかじめ決めておいた質問に答えるだけではないという点です。応答時間が3秒を超えたら知らせてほしい、というのは事前に決めておく質問です。一方、「昨日の午後、特定の機種でだけアップロードに失敗した理由」は、あらかじめ決めておけなかった質問です。後からこうした質問を投げかけられるなら観測可能なシステムであり、投げかけられないなら、コードを修正してデプロイした後、問題が再び起きるのを待つしかありません。
言葉だけで見ると当然の話のようですが、実際にどのような状況で違いが出るのかは、サービスに当てはめて考えると実感しやすくなります。
班長ノートではなぜ必要なのか
班長ノートは、建設現場の班長とチームリーダーをつなぐアプリです。これまでチャットやSMSでやり取りしていた求人情報への応募、書類提出、勤怠記録をアプリに移します。チームリーダーが求人を掲載すると班長が応募し、提出された書類をチームリーダーが確認して不足があれば補完を依頼し、選ばれた人をチームに配置した後、出退勤と日当を記録します。
構成上重要なのはここです。ユーザーの識別と認証、書類ファイルの保管、通知、決済など、社内プラットフォームの他のマイクロサービスを利用しています。班長ノートのサーバーは独自のビジネスロジックも実行しますが、各マイクロサービスの処理の流れを組み合わせ、より複雑な機能を実現します。
では、ある状況を考えてみましょう。「昨日から書類の提出が時々できない」という報告を受けました。
問題は「時々」という点です。10回押してみると、9回は成功します。サーバーは稼働しており、CPUも正常で、エラー通知も届いていません。私たちが監視していた指標の中に、おかしなものは一つもありません。ローカルで再現しようとしても、問題なく動作します。
ログを探すにしても、どのキーワードで検索するか、どの時間帯を見るかの段階で行き詰まります。しかも、このリクエストは私たちのサーバーだけで処理されるわけではありません。認証を経てファイル保管サービスを呼び出すため、私たちのログに異常がないからといって、問題がないとは限りません。失敗したリクエストを一つ選び、それがどこまで進み、どこで止まったのかを追跡できなければなりません。しかし、その手段がなければ、できることは推測だけです。
ここで、モニタリングがなぜ不十分なのかも明らかになります。モニタリングとは、既知の問題を監視することです。ディスクがいっぱいになったら知らせてほしい、エラー率が1%を超えたら知らせてほしい。何が危険なのかをすでに把握していなければ、しきい値は設定できません。しかし、実際の障害のかなりの部分は、私たちが予想していなかった組み合わせから発生します。特定のバージョンのアプリで、特定の時間帯にだけ起きる問題には、事前にアラートを設定できません。どのような組み合わせなのかを知らなかったからです。
まとめると、モニタリングはアラームを鳴らすためのものであり、オブザーバビリティはアラームが鳴った後、あるいはアラームすら鳴らなかった問題について、その理由を見つけられるようにするものです。アラームだけあって掘り下げるデータがなければ、障害発生時に推測するしかありません。データだけ蓄積してアラームがなければ、問題が起きたことをユーザーが先に知ることになります。両方が必要です。
オブザーバビリティにおける三つの要素
メトリクスとは、数値を時間軸に沿って蓄積したものです。リクエスト数、応答時間のパーセンタイル、エラー率などが該当します。軽量で長期間保存できる一方、個別の事象は失われます。エラー率が2%だということは分かっても、誰がなぜ失敗したのかは分かりません。ラベルの組み合わせごとに時系列が生成されるため、ユーザーIDのように値の種類が多いものを入れてはいけません。
ログとは、その瞬間に何が起きたのかを記録したものです。最も詳細である一方、量が多くコストもかかります。人間が読みやすい文章だけを蓄積しておくと、後から条件を指定して検索しにくいため、キーと値を分けた形式で残すほうがよいでしょう。
トレースとは、一つのリクエストがシステムを通過した経路です。三つの中で唯一、リクエスト単位で見るデータであり、個別の事象を失うメトリクスと、流れを失うログの間を補います。
三つを別々に扱うと、それぞれが不完全です。実際には、順序を持ってつながっています。メトリクスが、失敗率が0.1%から4%に上昇したと知らせます。トレースが、失敗したリクエストの経路を開き、すべてファイルサービスの区間で止まっていることを示します。ログが、その区間でどのパラメーターを使って呼び出し、どのような応答を受けたのかを教えてくれます。何がおかしいのか、どこが問題なのか、なぜそうなったのかが、順番に絞り込まれていきます。
この三つをつなぐ接着剤が相関IDです。リクエストが入ってきたときにトレースIDを一つ生成し、通過するすべてのサービスとすべてのログ行に同じIDを付けます。そうすれば、トレースで見つけた遅い区間をクリックして、その区間のログに直接移動できます。これがなければ、三つは互いに異なるデータの山にすぎず、グラフが跳ね上がったのを見て、ログの検索画面で時間帯を目測で合わせながら探し回ることになります。それは観測というより、捜索に近いものです。
サービスが分割されるとトレーシングが必要になる
オブザーバビリティが選択肢ではなく必須になったきっかけは、システムが複数のサービスに分割されるようになってからです。
モノリシックサービスでは、例外が発生するとスタックトレースに原因がすべて含まれていました。しかし、サービスを分割すると、それが失われます。スタックトレースはサーバー間の境界を越えられず、私たちのログには「呼び出し失敗」までしか残りません。他のマイクロサービスのログを開いて確認することはできますが、問題は、その膨大な行の中からどれが私たちのリクエストなのかを特定する方法がないことです。閲覧権限があるかどうかではなく、つなぎ合わせる手段があるかどうかの問題なのです。
ここにいくつかの要因が重なります。一つの画面を表示するために五つのサービスを呼び出し、そのうち一つだけが失敗すると、応答は200で返るためエラー率にも現れません。デプロイの周期もサービスごとに異なるので、昨日まで動いていたものが動かなくなったのに、私たちはデプロイしていないということもあり得ます。失敗が私たちのコードによるものなのか、相手のサービスによるものなのかを区別できなければ、障害対応は原因追跡ではなく、互いに確認を依頼し合うだけのやり取りになってしまいます。
また、この状況ではメトリクスとログだけで対応するのは困難です。メトリクスは、サービスごとにそれぞれ正常だと示します。五つのサービスがそれぞれ200ミリ秒を使い、それが順番に積み重なってユーザーが2秒待っているとしても、個別の指標だけでは合算された状態は見えてきません。ログは一か所に集約しても、時系列の一覧と一つのリクエストの流れは異なります。呼び出しが並列に行われたり非同期に移行したりすると、時系列だけでは因果関係を把握できません。
トレーシングは、この二つにはできないことを実現します。一つのリクエストを単位として、どの呼び出しがどの呼び出しを生み出したのかを親子関係として記録し、各区間の時間も一緒に記録します。散在した事実を時間順ではなく因果関係の順に復元するのです。これにより、次のような質問に答えられるようになります。このリクエストは実際にどこを通ったのか。全体の2秒のうち、どの区間で時間を使ったのか。連鎖的な障害の中で、最も騒がしい場所ではなく、実際の発火点はどこだったのか。一つのリクエストで同じサービスを8回呼び出してはいないか。
班長ノートに当てはめると、さらに明確になります。書類を一度提出するだけで、1つのリクエストが認証を経てユーザーを識別し、ファイル保管サービスにファイルを渡し、結果を保存したうえで相手に通知を送るところまで進みます。私たちが作成したコードはその一部にすぎません。そのため、失敗が発生する場所も「班長ノートのサーバー内」ではなく、サービス間になります。書類や通知のように各マイクロサービスの処理の流れに近い機能ほど、実際の処理は私たちのプロセスの外で行われます。
トレースがなければ、私たちの側には「書類提出失敗」という一行だけが残ります。トレースがあれば、認証までは20ミリ秒で完了し、ファイル保管サービスの呼び出しで止まったことがすぐに分かります。その地点をクリックしてログを開けば、どのようなリクエストを送り、どのような応答を受けたのかが確認できます。原因を見つける作業は、どのチームの責任かを問いただす会話ではなく、どの区間でどのような応答が返ってきたのかを確認する作業になります。
プロセスが長い。 公告に応募し、チーム長が確認し、選ばれ、待機した後にチームへ配置され、出勤が確定するまで、複数の段階を経ます。途中の一つの段階がひっそりと失敗すると、ユーザー画面にはエラーすら表示されません。ただ何も起きないのです。「応募したのに何日も連絡がない」という問い合わせを受けたら、どの段階まで進み、どこで止まったのかをたどり直すことが、対応のほぼすべてになります。リクエスト単位で経路が残っているかどうかが、ここで違いを生みます。
ユーザーに聞き返すことはできません。班長ノートを使うのは、現場で働く作業員と管理者です。開発者ツールを開いてもらったり、もう一度試して結果を知らせてもらったりするのは、ほぼ不可能です。カスタマーサービスへの問い合わせを起点に原因まで把握するには、その時点で何が起きていたのかがシステムに記録されていなければなりません。
まとめ
オブザーバビリティを一文にまとめるなら、まだ予想していなかった質問を後から投げかけられるように、システムを作っておくことです。
注意すべきなのは、ツールを導入すれば観測できるようになると考えてしまうことです。ライブラリを組み込み、コレクターを起動するのは始まりにすぎません。シグナルが実際にストレージまで届いているか、届いたものを誰が見ているか、異常時に誰へ通知されるのかまでが一つのセットです。この三つのうち一つでも欠ければ、観測できないのと同じです。
私たちはコードを書くとき、正常に動作するケースをまず考えます。しかし、運用で時間を奪うのは、たまに一度だけおかしくなるケースであり、サービスが分かれていると、それがどこで発生したのかさえ分かりにくくなります。機能を一つ作るたびに、一度は考えてみる価値があります。これが午前3時に壊れたら、私は何を見て原因を探せばよいのだろうか。
Ted