成功率100%の裏にある誤った分母
一言でいうと
SLIの最初の問いは、式ではなく、何を1件として数え、どんな失敗を見逃しているかです。誤って計装したデータを正確に割っても、正しいユーザー体験の指標にはなりません。
なぜ必要なのか
仮想のセルフサービスプラットフォームが、データベースの払い出しAPIを提供しています。成功すると201を返し、依存するサービスが障害なら503を返します。担当者は、成功ハンドラーの最後でだけカウンターを増やしました。正常な試行2件と失敗した試行2件を送っても、メトリクスには成功2件と全体2件しか残りません。ダッシュボードの成功率は100%ですが、実際のレスポンスの記録は50%です。このとき、しきい値を99.9%から99.99%に上げても、問題は発見できません。
続くラボは、このインシデントを再現します。偽のイメージのキャプチャではなく、Pod内でPython HTTPサーバーを実行し、201・503・504を実際に受け取ります。障害は、X-Lab-Scenarioというラボ専用のヘッダーで選びます。実際の外部データベースを止めるわけではありません。このように、本当に実行する層と、仮想に設定した原因を分けて説明して初めて、証拠の範囲がわかります。
どう動くのか
1件の単位を先に決める
Google SREのSLO実装ガイドは、ユーザーが重視する行為を基準に、測定可能な指標と目標を設計する出発点を提供します。ここでは、払い出しAPIのHTTP試行1件を単位として決めます。失敗した試行aのあとに成功したリトライbがあれば、2件です。「最終的に払い出されたので成功1件」に変えると、試行ベースの指標とユーザー作業ベースの指標が混ざります。どちらか一方が常に正しいのではなく、それぞれ別の問いに答えます。
ラボのscope.jsonは、/provisionの2xx・5xxだけを有効な試行とし、4xxとヘルスチェックの経路は除外する、仮想の契約です。本番では、この分類をそのままコピーしないでください。たとえば、サービスの容量不足のために429が発生するなら、ユーザーの過ちとして除外するのは不当かもしれません。リクエストの有効性とサービスの責任を合意し、除外する割合も観測して初めて、指標をよく見せるために変えることを防げます。
良い可用性と、良いレイテンシを分ける
ラボでは、eligibleは分母に入る試行、availableはそのうち2xx、fastはそのうち成功していて、サーバー処理時間が1000ms以下のレスポンスです。1000msは含み、1000.1msは除外します。速い503はfastではありません。遅い201はavailableですが、fastではありません。2つのSLIは、同じ分母を使いますが、分子が異なります。
| ラボのシナリオ | eligible | available | fast |
|---|---|---|---|
| 正常な201、10ms | 真 | 真 | 真 |
| 遅い201、1100ms | 真 | 真 | 偽 |
| 失敗の503、10ms | 真 | 偽 | 偽 |
| 入力エラーの400 | 偽 | 偽 | 偽 |
測定する位置も明示します。サーバーの処理時間には、クライアントのネットワーク往復や接続待ちがすべて含まれるわけではありません。このラボがサーバーの時間を測ったという事実を、ユーザー全体のレスポンス時間の証明に拡大しないでください。実際の目標がブラウザーで見えるレイテンシなら、その位置の観測も必要です。
失敗を直接送って、2つの記録を突き合わせる
Prometheusの計装の原則は、カウンター・失敗・ラベルの設計を合わせて考える根拠です。ラボのサーバーは、メトリクスにユーザーIDやリクエストIDを入れず、observed・eligible・available・fastの4種類だけを公開します。リクエストIDは、固有の出来事を追跡するログで扱い、メトリクスのラベルは限られた集合に保ちます。ユーザーが増えたときに、ユーザーごとの時系列も際限なく増える設計を避けるためです。
HTTPの再現は、レスポンス7件とobserved=7を合わせ、そのうち分母4件・可用な成功2件・速い成功1件を突き合わせます。/metricsの収集自体を、業務リクエストとして再び数えません。メトリクスが100%だという出力だけを確認するのではなく、失敗を送った事実と、失敗が分母に入った事実を結びつけます。現在のコードでもう一度実行するので、古い正常な結果ファイルだけを提出しても十分ではありません。
現場での姿
別の仮想の事例では、コレクターが同じログを2回送りました。attempt_idも内容も同じなら、収集の重複です。1回だけ分類し、重複の個数は別に残します。同じIDなのに、ステータスコードが違うなら、どちらを信じるべきかわからないため、ValueErrorで衝突を明らかにします。任意に最後の値を選ぶと、実際の失敗が成功で覆われるおそれがあります。一方、別のIDで実行したリトライは、別の試行なので、両方とも数えます。
このルールは、小さなラボ用のJSONシナリオに対するものです。本番で複数のサービスがIDを発行するなら、衝突を防ぐ範囲と保存期間も必要です。今回の課題が、分散環境での正確に1回の処理や、永続的な重複排除を実装したとは主張しません。
次に読むこと
今度は、分母が検証されたデータで、どれだけの失敗を許容するか、データが空のときに何を決められないかを読みます。続くラボでは、分類関数と実際のHTTP、重複処理、エラーバジェット、自分で設計した反例を、つなげます。