SLIはユーザーが体験したものの測定値であり、SLOはその測定値に設定する目標、SLAは目標を外したときに何が起きるかを定めた契約、そしてエラーバジェットは100パーセントとSLOの差を、許容される失敗リクエストの数で数えたものだ。1,000万リクエストを処理するサービスで30日間99.9パーセントのSLOなら、バジェットは1万件の失敗になる。この4つの数字が、組織の他の誰も答えられない問いへのSREの答えだ。このサービスはどれだけ信頼できるべきで、今日出荷しても安全なのか?

これは Budget for Failure の第2回だ。第1回はSREの役割を描き、その依存先で終わった。数字がなければどれも機能しない、と。この回もまだ101レベルだ。用語を定義し、AWSのSLAが実際に何を約束しているかを読み、すでに手元にあるロードバランサーのメトリクスから最初のSLIを計算し、その結果をポリシー付きのバジェットに変える。

SLI: 有効なイベントに対する良いイベントの比率

サービスレベル指標(SLI)は0から100パーセントの間の数字で、毎回同じ方法で計算される。良いイベントを有効なイベントで割ったものだ。リクエスト駆動のサービスで最も有用なSLIは次の2つ:

  • 可用性: サーバーエラーなしに応答したリクエストを、全リクエストで割ったもの。
  • レイテンシ: しきい値(たとえば300ms)より速く応答したリクエストを、全リクエストで割ったもの。

この定義には設計上の判断が2つ隠れている。1つ目は、何を有効と数えるか。存在しないURLへの404はサービスが正しく動いている証拠であり、自前のレートリミッターが返す429は議論の余地があり、500はそうではない。一度決めて書き留めておくこと。分母こそSLIがひっそりと操作される場所だからだ。2つ目は、どこで測るか。ユーザーに近いほど数字は正直になる。ロードバランサーで取ったメトリクスは、接続を拒否したターゲットのような、アプリケーションが決してログに残さない失敗を見ている。アプリケーション内部で取ったメトリクスはそれを見逃す。

平均はSLIにならない。平均レイテンシ120msは、20リクエストに1件が4秒かかっている状況と両立する。比率の形(「リクエストの97.2パーセントが300ms未満」)は、何人のユーザーが嫌な思いをしたかを教えてくれる。それこそが制限しようとしているものだ。

SLO: 目標と、それを測る期間

サービスレベル目標(SLO)はSLIに目標と時間枠を足したものだ。「リクエストの99.9パーセントが成功する、直近30日間のローリングで測定」。3つの部分すべてが必要になる。時間枠のない目標は反証不可能だ。どんなひどい1時間も、事後に長い期間を選べば平均で消せるからだ。

目標はナインズが登場する場所で、ナインズは許容ダウンタイムとして考えたほうが扱いやすい。30日間(43,200分)の枠で:

SLO30日間で許容される不良時間1,000万件あたりの許容不良リクエスト
99%7時間12分100,000
99.5%3時間36分50,000
99.9%43分12秒10,000
99.95%21分36秒5,000
99.99%4分19秒1,000
99.999%26秒100

この表は下から読んでほしい。99.99パーセントでは月に4分ほどしかない。午前3時にページされた人間がノートPCを探すのにそれ以上かかる。つまりフォーナインのSLOとは自動検知と自動復旧への約束であり、人が関わるのはその後だ。ナインがひとつ増えるごとに許容される失敗はおよそ10分の1になり、実際にはコストが大きく跳ね上がる。正しいSLOとは、それを超えてもユーザーが違いに気づかない最も低い数字であって、スライドで見栄えのする最も高い数字ではない。

100パーセントは決して正しい目標にならない。すべてのデプロイ、すべてのマイグレーション、自分で制御できないすべての依存先を禁じることになるし、ユーザーは自宅のWi-Fiやモバイル網を通してあなたに届く。あなたが運用するどんなものより、はるかに信頼性の低い経路だ。

SLA: 契約、そしてAWSが実際に負っているもの

サービスレベル合意(SLA)は、結果が紐づいたSLOを顧客と取り交わしたものだ。その結果はたいてい金だ。違いはそれだけで、だからこそSLAは内部のSLOより常に緩い。違反が返金になるずっと前に気づいて動きたいからだ。

AWSのSLAは多くの人が最初に読むSLAであり、その文言が概念を教えてくれるので精読する価値がある。典型的なWebサービスの下にある4つ:

サービスと条件月間稼働率のコミットメント未達時のクレジット
EC2、2つ以上のAZにまたがるインスタンス(リージョンレベル)99.99%10%、99.0%未満で30%、95.0%未満で100%
EC2、単一インスタンス(インスタンスレベル)99.5%10%、99.0%未満で30%、95.0%未満で100%
Elastic Load Balancing、2つ以上のAZにまたがるロードバランサー99.99%10%、99.0%未満で30%、95.0%未満で100%
RDS、Multi-AZ DBインスタンスまたはクラスター99.95%10%、99.0%未満で25%、95.0%未満で100%
S3 Standard99.9%10%、99.0%未満で25%、95.0%未満で100%

これらの文書には、パーセンテージより重要なことが3つある。

救済はサービスクレジットだ。 S3 Standardがある月に99.5パーセントまで落ちたなら、あなたは証拠を添えて申請したうえで、その月の該当リージョンにおけるS3料金の10パーセントを将来の請求に充当する資格を得る。失った売上も、自分のSLAの違約金も、潰れた週末も計算には入らない。SLAは価格条件だ。保険ではなく、サービスがどう振る舞うかの予測でもない。

コミットメントはあなたのアーキテクチャを条件とする。 EC2の99.99パーセントは、稼働中のすべてのインスタンスがリージョン内の2つ以上のアベイラビリティゾーンに配置されている場合にのみ適用される。1つのゾーンで動かすなら99.5パーセントのインスタンスレベルのコミットメントになり、これは月におよそ3.6時間のダウンタイムを許容する。SLAには、AWSがあなたに期待する設計が書き込まれている。

「利用不能」には狭い法的定義がある。 各SLAは、1分単位か5分間隔かといった独自の測定方法と、独自の除外事項を定めている。遅いことは利用不能ではない。依存しているあるAPIのエラー率が上がっても、まったく数えられないかもしれない。

すでにあるメトリクスから作る最初のSLI

サービスがApplication Load Balancerの後ろにあるなら、CloudWatchはすでに可用性SLIを持っている。AWS/ApplicationELB名前空間の3つのメトリクスで足りる。RequestCount、HTTPCode_Target_5XX_Count(あなたのコードが返したエラー)、そしてHTTPCode_ELB_5XX_Count(たとえば健全なターゲットがひとつも応答しなかったときなど、ロードバランサー自身が生成したエラー)だ。これをsli.jsonとして保存する:

[
  { "Id": "req", "ReturnData": false,
    "MetricStat": { "Stat": "Sum", "Period": 86400,
      "Metric": { "Namespace": "AWS/ApplicationELB", "MetricName": "RequestCount",
        "Dimensions": [{ "Name": "LoadBalancer", "Value": "app/checkout/0123456789abcdef" }] } } },
  { "Id": "t5xx", "ReturnData": false,
    "MetricStat": { "Stat": "Sum", "Period": 86400,
      "Metric": { "Namespace": "AWS/ApplicationELB", "MetricName": "HTTPCode_Target_5XX_Count",
        "Dimensions": [{ "Name": "LoadBalancer", "Value": "app/checkout/0123456789abcdef" }] } } },
  { "Id": "e5xx", "ReturnData": false,
    "MetricStat": { "Stat": "Sum", "Period": 86400,
      "Metric": { "Namespace": "AWS/ApplicationELB", "MetricName": "HTTPCode_ELB_5XX_Count",
        "Dimensions": [{ "Name": "LoadBalancer", "Value": "app/checkout/0123456789abcdef" }] } } },
  { "Id": "sli", "Label": "availability_percent",
    "Expression": "100 * (req - FILL(t5xx, 0) - FILL(e5xx, 0)) / req" }
]
aws cloudwatch get-metric-data \
  --start-time "$(date -u -v-30d +%Y-%m-%dT00:00:00Z)" \
  --end-time "$(date -u +%Y-%m-%dT00:00:00Z)" \
  --metric-data-queries file://sli.json \
  --query 'MetricDataResults[0].[Timestamps,Values]'

これで直近30日間の日ごとの可用性が1つずつ返ってくる(date -vの構文はmacOSのもので、Linuxではdate -u -d '30 days ago'を使う)。FILL(…, 0)が重要だ。CloudWatchはエラーがゼロだった期間についてエラーメトリクスのデータポイントを発行しないため、fillがないと完璧な1日が100パーセントではなく欠損データとして返ってくる。

レイテンシについては、同じ名前空間にTargetResponseTimeがあり、CloudWatchのパーセンタイルランク統計がそれを直接比率の形に変えてくれる。PR(:0.3)は300ms以下で完了したリクエストの割合を返す。

エラーバジェット: SLOが使わせてくれるもの

ビジネス側が99.9パーセントで十分だと合意した時点で、残りの0.1パーセントは排除すべき欠陥であることをやめ、使ってよい許容量になる。枠の残り10日でサービスが99.97パーセントにあるなら、リスクを取る余地がある。マイグレーションを出荷し、クラスターをアップグレードし、フェイルオーバーテストを走らせる。99.88パーセントにあるなら余地はなく、それを確定させるために誰も議論に勝つ必要はない。毎回バジェットに手をつけずに枠を終えるサービスも勝利ではない。SLOがユーザーの必要より緩いか、チームが誰も求めていない信頼性に金を払っているかのどちらかだ。

バジェットはイベントで数えることも時間で数えることもでき、両者の振る舞いは異なる。

SLO                      99.9% over 30 days
requests in window       10,000,000
error budget             (1 - 0.999) x 10,000,000 = 10,000 failed requests

bad requests so far      6,400
budget consumed          6,400 / 10,000 = 64%
budget remaining         3,600 requests = 36%

イベントベースのバジェットは、失敗をトラフィックで重み付けする。火曜日のピークに10分間の完全停止は、日曜日の午前4時の10分間よりはるかに高くつき、これはユーザーの体験と一致する。リクエスト駆動のものには、この形を選ぶべきだ。

時間ベースのバジェットは不良な分数を数える。30日間で99.9パーセントは43分12秒だ。エンジニア以外に説明しやすく、バッチパイプラインや低トラフィックの社内ツールのような、安定したリクエストの流れがないサービスに向いている。代償として、1分は完全に不良か完全に良好かのどちらかとして数えられるため、何をもって不良な1分とするかのルール(たとえば「エラー率1パーセント超」)が必要になり、部分的な障害は見えないか丸ごと数えられるかのどちらかになる。

枠は目標と同じくらい重要だ。ローリングの30日枠は常に直近の過去を反映し、ひどい1日はちょうど30日後に計算から抜ける。ユーザー体験の追跡にはこちらのほうが合う。カレンダー枠は毎月1日にリセットされ、レポーティングや大半のSLAと揃うが、ひとつ嫌な性質がある。2日に起きたインシデントは4週間バジェットを使い続けるのに、30日に起きた同じインシデントは2日後に赦免される。

何がバジェットを使うか

ユーザーのリクエストを失敗させるものはすべて、誰が原因であれ、同じ口座から引き落とされる。それが肝心な点だ。典型的なチームが使う量のおおよそ多い順に:

  • 変更。 デプロイ、設定の反映、フィーチャーフラグ、スキーママイグレーション。大半のポストモーテム集では、変更がインシデントの最大の単一原因だ。DORAの変更失敗率を追跡しているなら、すでに最大の消費者を測っている。
  • 依存先。 リージョン単位のAPIの遅延、サードパーティの決済プロバイダー、DNSの問題。あなたの責任でなくてもあなたのバジェットだ。ユーザーは誰の責任かを気にしないからだ。
  • キャパシティと負荷。 スロットリング、コールドキャッシュ、2分遅れて反応するスケーリングポリシー。
  • 計画された作業。 メンテナンスウィンドウ、フェイルオーバーテスト、ゲームデー。意図的なゾーン退避訓練で300件のリクエストが失敗するのは、良い使い方だ。そしてバジェットがあるからこそ、その訓練は承認できる。
  • ハードウェアとプラットフォーム。 インスタンスのリタイア、ゾーンイベント。アーキテクチャがマルチAZなら、人が思うより小さい。

台帳をつけること。インシデントやリスクのある変更のたびに、どれだけバジェットを使ったかと原因を記録する。3か月分のその台帳は、どのロードマップ議論よりもよく、信頼性エンジニアリングの時間をどこに向けるべきかを教えてくれる。

日付イベント失敗リクエストバジェットに占める割合
8月3日接続プール設定の誤りを含むデプロイ、9分でロールバック3,10031%
8月11日決済プロバイダーのレイテンシ、チェックアウトでタイムアウト2,20022%
8月14日計画されたRDSフェイルオーバーテスト3503.5%
枠全体バックグラウンドのエラー、単一の原因なし7507.5%

この例では、1回のデプロイがサードパーティの障害と計画されたテストを合わせたより高くついた。投資の根拠はひとりでに書き上がる。何よりも先に、カナリア分析と自動ロールバックだ。

ポリシーこそがプロダクト

結果の伴わないバジェットはダッシュボードだ。機能させるのは、バジェットが減ってきたときに何が変わるかを記した短い書面のポリシーで、エンジニアリングとプロダクトのリーダーシップが事前に合意したものだ。Google SRE Workbookはポリシーの例を公開しており、その構造は真似る価値がある。実用的な最初の版は半ページに収まる:

  • バジェット残50パーセント超: 通常のリリースプロセス。リスクのある変更や実験は奨励される。
  • バジェット残50パーセント未満: 変更にはカナリア段階とテスト済みのロールバックが必要。本番での実験は禁止。
  • バジェット枯渇: サービスがSLOの範囲内に戻るまで機能リリースは停止。信頼性の修正、セキュリティパッチ、バジェットの回復に必要な変更だけが出る。チームの優先事項は直近のポストモーテムの上位項目。
  • 単一のインシデントがバジェットの20パーセント超を消費: ポストモーテムは必須で、高優先度のアクションアイテムを少なくとも1つ含む。
  • 意見の不一致: 名指しされた役員が決める。エスカレーション経路は必要になる前に書面にしておく。

これを成り立たせる性質が2つある。対称であること。バジェットが尽きたときにリリースを凍結するのと同じ文書が、健全なときには運用側にリリースを妨げるなと告げる。そして事前合意であること。プロダクトオーナーは平穏な週に署名したのだから、荒れた週にそれを発動するのは実行であって対立ではない。機能開発を止めうるポリシーにリーダーシップが署名しないなら、あなたはまだSLOを持っていない。持っているのはメトリクスだ。

台帳はCloudWatchに任せる

上のSLIはget-metric-dataで手計算した。走り続けるバジェットについては、CloudWatchにApplication Signals内のマネージドSLO機能がある。指標、達成目標、インターバルを定義すれば、達成率と残りのエラーバジェットを継続的に追跡してくれる。そのモデルにある3つの選択肢が、この記事に直接対応する:

  • 期間ベースかリクエストベースか。 期間ベースのSLOはインターバルを期間(たとえば1分)に分割し、それぞれを良いか悪いかで判定し、全期間に対する良い期間の割合を報告する。リクエストベースのSLOは全リクエストに対する良いリクエストを数える。上で述べた時間ベースとイベントベースのバジェットそのものだ。
  • ローリングかカレンダーか。 両方がサポートされており、トレードオフはすでに述べた通りだ。
  • SLIを何の上に組むか。 Application Signalsで計装されたサービスは標準のレイテンシと可用性のメトリクスを得る。5xx応答は障害として数えられ、4xx応答は成功として扱われる。計装していないなら、単一の時系列になる任意のCloudWatchメトリクスやメトリクス数式にSLOを設定でき、上のロードバランサーの式もそこに含まれる。

コンソールは結果を2つの数字で示す。目標に対する達成率と、残りのバジェットだ。リクエストベースのSLOでは、残りバジェットはインターバルの途中で減るだけでなく増えることもある。それまでの良いリクエストと全リクエストの比率に依存するため、静かな一週間のきれいなトラフィックが先のインシデントを薄めるからだ。

障害モードと注視すべきこと

パーセンテージの平均。 100リクエストで90パーセントの日と、100万リクエストで99.99パーセントの日を平均しても95にはならない。30日間のSLIは、全有効リクエストに対する全良好リクエストだ。分子同士、分母同士を足してから割ること。

穴のある分母。 RequestCountはロードバランサーがターゲットを選べたリクエストしか数えないため、上の式は完全停止時に被害をやや過大に見積もる。これは間違えるなら安全な方向だ。SLIはロードバランサーに届かなかったリクエストも見られない。DNSの失敗、WAFによるブロック、壊れたネットワーク上のクライアントだ。

誰も合意していないSLO。 プラットフォームチームだけで選んだ目標は、ローンチ日と衝突したときに何の権威も持たない。数字はロードマップを持つ人が持たなければならない。

SLOが多すぎる。 ユーザー向けサービスごとに1つか2つから始めること。金を稼ぐリクエスト経路の可用性とレイテンシだ。20のSLOは20のバジェットを生み、そのうち1つは常に枯渇していて、ポリシーはノイズになる。

データを見る前に目標を決める。 まず直近30日に対してクエリを走らせること。SLIを見る前に選んだSLOは願望だ。測定した履歴のやや下に設定し、それから意図的に引き締めたものが、守れる目標になる。

月末に知る。 月次レポートで読むバジェットは過去を説明することしかできない。バジェットが残っているうちに動くには消費速度のアラートが要り、サービスの消費を減らすにはそのコードの変更が要る。どちらも第3回だ。

次に読む

AWS、クラウドアーキテクチャ、SREプラクティス、CI/CDパイプライン、Linuxなどのコンサルティングは、ercanermis.comからどうぞ。

参考資料