バーンレートでアラートを掛ける
一言でいうと
バーンレートでアラートを掛ければ、鳴るべきときに鳴り、静かであるべきときに静かになります。
99.9%は1か月で43分です
| SLO | 1か月(30日)の許容 |
|---|---|
| 99% | 7.2時間 |
| 99.9% | 43.2分 |
| 99.95% | 21.6分 |
| 99.99% | 4.32分 |
99.9と99.99は表記が1桁違うだけですが、10倍です。契約書に99.99%と書く前に、この表を見る必要があります。1か月で4分なら、デプロイを1回間違えただけで終わりです。
エラーバジェットは使うためにあります
99.9%を目標にしたなら、0.1%は使ってよい取り分です。使わずに残すことが良いことではありません。バジェットがずっと余っているなら、目標が低すぎるか、デプロイをあまりにもしていないかのどちらかです。
この視点がSLOの核心です。完璧を目標にせず、どこまで壊れてよいかを合意します。
バーンレート
エラー率が許容値の何倍かを、バーンレートと呼びます。
- 1倍 → 期間が終わるときに、ちょうどバジェットを使い切ります(設計どおり)
- 14.4倍 → 2.08日で使い切ります
14.4という数字は、次のように出ます。1時間に1か月分のバジェットの2%を消費する速度です。
0.02 × 30일 × 24시간 = 14.4
このコードブロックの韓国語は、順に「日」と「時間」という意味です。
この速度なら、今すぐ起こす必要があります。逆に、2倍くらいならチケットで十分です。
今のアラートはなぜ役に立たないのか
よくあるルールを1つ見てみましょう。
5분 오류율 > 1%
このコードブロックの韓国語は、「5分間のエラー率が1%を超える」という意味です。
SLOが99.9%(0.1%を許容)なのに、このルールはそこから出たものではありません。そのため、両方が起きます。
- 静かであるべきなのに鳴る: 明け方に1.2%が5分間跳ねました。バジェットはほとんど減っていないのに、人が起こされました
- 鳴るべきなのに静か: 0.5%が1か月間ずっと続きます。バジェットの5倍を使ったのに、しきい値を下回っているので、何も起きませんでした
アラート疲れは、アラートが多いから生まれるのではなく、役に立たないアラートが多いから生まれます。
バーンレートで書き直すと
오류율 > 14.4 × (1 - SLO)
このコードブロックの韓国語は、「エラー率が14.4 × (1 - SLO)を超える」という意味です。
SLOが99.9%なら、しきい値は0.0144(1.44%)です。数字だけを見ると似ているように見えますが、意味が違います。これは「1時間にバジェットの2%を消費している最中」という意味で、そのため、何をすべきかが付いてきます。
アラート名も変えます。HighErrorRateではなく、ErrorBudgetBurnFastです。名前が対応方法を決めます。
ウィンドウが1つでは足りません
- 短いウィンドウ(5分)だけ → 一瞬跳ねただけで起こされます
- 長いウィンドウ(1時間)だけ → 速く燃えていることに遅れて気づきます
そのため、2つをandで結びます。短いウィンドウと長いウィンドウが両方超えたときだけ鳴ります。瞬間的な跳ねは除かれ、本物の消費は早く捕まります。
そして、重大度を分けます。
| バーンレート | ウィンドウ | 対応 |
|---|---|---|
| 14.4 | 5分 + 1時間 | 即時ページ |
| 6 | 30分 + 6時間 | 即時ページ |
| 3 | 2時間 + 1日 | チケット |
| 1 | 6時間 + 3日 | チケット |
SLIを何で測るのか
バーンレートの計算は算数ですが、その前にある何を成功と見なすかが決まっていなければ、数字には何の意味もありません。ここで分かれるのが、3つです。
どこで測るか。サーバーログで測ると、ロードバランサーで切れたリクエストが見えず、ロードバランサーで測ると、クライアントのDNS・TLSの失敗が見えません。ユーザーが経験することに近いほど良いですが、その分、こちらが制御できない失敗も混ざります。ロードバランサーを基本にして、外から動く合成モニタリングを補助に置く組み合わせが、実務的です。
何を失敗として数えるか。5xxははっきりしていますが、4xxはあいまいです。400はたいていクライアントの誤りなので除き、429はこちらが止めたものなので含めるほうが、誠実です。404を除くと、ルーティングが壊れてすべて404になる障害を見逃します。定義を書いておき、変更するときは、過去の数値も一緒に計算し直します。
遅いことも失敗です。30秒かかって成功した応答は、ユーザーにとっては失敗です。そのため、レイテンシのSLIは、平均ではなくしきい値以内に入ったリクエストの割合で取ります。「95%のリクエストが300ms以内」ではなく、「300ms以内に入ったリクエストの割合が99%」と書けば、可用性と同じ方式でバジェットを計算できます。
sum(rate(http_request_duration_seconds_bucket{le="0.3",job="api"}[5m]))
/
sum(rate(http_request_duration_seconds_count{job="api"}[5m]))
すべてのリクエストを同じ重さで数えません。ヘルスチェックとボットのトラフィックが分母の半分なら、ユーザーが経験する失敗が数字にほとんど現れません。逆に、バッチAPI1つが毎秒数千件を送ってくると、その1つのクライアントがSLIを支配します。ユーザージャーニー単位に分けて測ることが答えですが、コストがかかるので、最低でも主要な経路1つは別に測ります。
SLOは契約ではなく、意思決定の道具です。バジェットが余れば危険な変更をしてもよく、使い切れば安定化に集中するという合意が、先にある必要があります。その合意なしに数字だけを測ると、障害のあとにその数字をめぐって争う材料になるだけです。
アラートもテストする必要があります
最もよく抜ける部分です。promtoolは、アラートルールのユニットテストに対応しています。
promtool test rules test.yml
偽の時系列を入れて、「このときこのアラートが鳴るべき」をアサートします。これがなければ、障害が起きたときに肝心なときに鳴らないアラートをデプロイすることになります。そして、それは障害の最中になって初めてわかります。
そして、鳴ってはいけないときに静かかもテストします(exp_alerts: [])。鳴るときだけテストするのは、半分です。
バジェットが尽きたら
ここに答えがなければ、SLOは飾りです。
あらかじめ合意しておくこと。
- 新機能のデプロイを止めて、安定化に使います
- 危険な変更は、バジェットが回復するまで先送りします
- 繰り返す原因を、次のスプリントの最優先に上げます
数字を決めるよりも、この合意のほうが難しく、これがなければ、数字は何の仕事もしません。
現場で
この方式を導入するときに最初にぶつかるのは、技術ではなく合意です。99.9%を書いておくのは簡単ですが、バジェットが半分残ったときに何を止めるかをあらかじめ決めることは、プロダクト側と話す必要があり、その会話は常に楽とは限りません。
そのため、最初はアラートを掛けず、観測だけをする期間を置くほうがよいです。1、2か月分の実際の消費曲線を見れば、99.9%が自分たちのサービスに合う数字なのか、それとも99.5%でも誰も文句を言わないのかが、表に出ます。守れない数字を掛けておくと、バジェットは毎月第1週に尽きてしまい、そうなると、誰もその数字を見なくなります。
そして、SLIを何で測るかは、思ったより大きな決定です。ロードバランサーで測ると、アプリケーションが死んでも5xxとして捉えられますが、クライアント側の失敗は見えず、アプリケーションで測ると、その逆になります。どこで測っているかをドキュメントに1行書いておくと、あとで数字をめぐって起きる論争が、大幅に減ります。