WEARにおけるSLOを用いた信頼性改善の取り組み

ogp

こんにちは、WEAR部バックエンドブロックの小山とSREブロックの繁谷です。

WEARでは日々システムの信頼性を向上させるため改善に取り組んでいます。今回はその中でもSLOに基づいた改善について紹介いたします。

WEARリプレイスの歩み

WEARでは2019年から本格的にリプレイスを開始しましたが、当初は専属のSREはおらずインフラ構築など緊急度の高いものをバックエンドのエンジニアや、プロダクト横断のSREが担っていました。

WEARのSREとして活動に割ける時間も短かったためSLI(Service Level Indicator)1やSLO(Service Level Objective)2の指標もありませんでした。WEARにおけるリプレイスの変遷についてはこちらのスライドに詳しく載せられているため、ご興味のある方は是非ご覧ください。

WEARの組織における課題

WEARでは2021年4月に専属のSREが発足しましたが、それ以前は以下の課題を抱えていました。

  • SREはインフラ構築担当、バックエンドはアプリケーション開発担当と分断していた
  • SLOの策定とそれの達成に向けた改善ができていなかったため、漠然とエラー解消やレイテンシ改善の対応をしていた
  • SLOに基づいて、客観的に信頼性改善と機能開発のエンジニアリングリソースを配分できていなかった

本記事の本題である、WEARにおける従来の信頼性改善への取り組みについては以下で紹介します。

WEARバックエンドの改善の歩み

2020年にリプレイス環境ではじめてAPIがリリースされました。リプレイス環境のアプリケーションのモニタリングはDatadogとSentryで行っています。

当時、APIのレイテンシは50〜100ミリ秒のレスポンスを目標としていて、API実装者が責任を持ってモニタリングし目標を満たすよう改善に努めていました。5xxエラーについてはSentryで検知したらアラートをSlackに飛ばしてチーム全体でエラーの原因を調査していました。

リプレイスを進める中で、リプレイス前の旧環境で稼働しているバッチに起因するクエリタイムアウトが頻発し、バッチがサービス全体の可用性、レイテンシに影響を与えているということが分かりました。

バッチリプレイス定例

バックエンドではサービス全体に影響を与えているバッチにスコープを絞って、クエリタイムアウトの原因調査と改修またはリプレイス対応を共有するバッチリプレイス定例を隔週で行っていました。

当時、WEARのバックエンド組織は運用改善チームとサービス開発チームの2チームに分かれていて、バッチリプレイスは、主に運用改善チームがリソースを割いて対応していました。

調査と改善対応については、Datadogのメトリクスを追ったり、こちらで紹介されているSQL Serverのブロッキング発生時の情報を基にDBアーキテクトと課題ベースで連携して対応を進めていました。

このように従来はバックエンド主体で改善に取り組んでいました。そして、WEAR専属のSREチームが発足してSLOが策定されたことで、徐々にチーム横断でSLOをベースとした信頼性改善への取り組みをはじめることになります。

WEARにおけるSRE

SREチームができてからすぐにSREとしての動きができていたわけではなく、SREチーム内でも各々がSREへの認識に齟齬がある状態でした。そのため、SREの定義や責務などをSRE本や各社の事例を参照しながら共通認識を持つことからはじめました。

そして今何ができていて何ができていないかをGoogle社の記事を参考にしてチームで議論し、SLOがないため信頼性を監視できておらず行為主体性も発揮できていないことを確認しました。

WEARにSREが存在しないころの動きを踏襲していたため、依頼されて動くと言った受け身の姿勢になっていました。よって、SLOを策定して信頼性を監視し客観的に信頼性改善のためのエンジニアリングリソースの配分について提案できる、行為主体性のある組織となることを目標としました。

SLI, SLOの決定

目標が決まったのでまずはSLIやSLOを決めます。

SLIやSLOの決定はとにかく素早く運用に乗せることを意識しました。Google社の発表でも述べられているように、SLOがない状態よりシンプルなものでも運用できている状態が望ましいからです。

よって、本来であればCUJ(Critical User Journey)3を検討することから始めるべきですが、一般的にSLIとして用いられシステム全体のエラーレートとレイテンシをSLIとしました。そして運用しながら改善することにしました。

WEARにおけるSLO

Google社の記事サイボウズ社の記事を参考にとにかくシンプルなSLOから導入することにしました。

目標
可用性 1か月のリクエストのうち、最低でも99.5%の割合で503以外のレスポンスを返す
レイテンシ 1か月のリクエストのうち最低でも50%は500ミリ秒以内にレスポンスを返し、99%は3000ミリ秒以内にレスポンスを返す

APIの目標としてはかなり緩いものですが、適宜調整する前提で最初は達成できるラインを設定し改善効果を実感できることを目指しました。

SLOモニターのコード化

当初はSLOのモニターをCloudFormationのパブリック拡張機能Datadog社が提供している拡張を使っていました。しかし、後に互換性の問題から拡張をアップデートしづらくなりTerraformに移行しました。

以下にAWSのALBにAPIサーバのターゲットが接続されている構成のTerraformのコード例を示します。

可用性

ALBで受けた全リクエストからALBで5xxを返したものとターゲットで5xxを返したものを引いてエラーレートを計算します。

resource "datadog_service_level_objective" "api_error_rate" {
  name = "API Error Rate"

  query {
    numerator   = "sum:aws.applicationelb.request_count{name:api}.as_count() - sum:aws.applicationelb.httpcode_elb_5xx{name:api}.as_count() - sum:aws.applicationelb.httpcode_target_5xx{name:api}.as_count()"
    denominator = "sum:aws.applicationelb.request_count{name:api}.as_count()"
  }

  thresholds {
    target    = "99.5"
    timeframe = "30d"
  }

  type = "metric"
}

レイテンシ

99th percentile, 50th percentileのレイテンシを監視するモニターを作成しSLOのリソースに紐づけます。

resource "datadog_service_level_objective" "api_latency" {
  monitor_ids = [datadog_monitor.api_latency_p99.id, datadog_monitor.api_latency_p50.id]
  name        = "API Latency"

  thresholds {
    target    = "99"
    timeframe = "30d"
    warning   = "0"
  }

  type = "monitor"
}

resource "datadog_monitor" "api_latency_p99" {
  escalation_message = ""
  evaluation_delay   = "900"
  include_tags       = "true"
  locked             = "false"
  message            = "{{#is_alert}}{{name}}のレイテンシが{{threshold}}を超えました。{{/is_alert}}{{#is_recovery}}{{name}}のレイテンシが正常値に戻りました。{{/is_recovery}}"

  monitor_thresholds {
    critical = "3"
  }

  name                 = "Api Latency p99"
  new_group_delay      = "0"
  new_host_delay       = "300"
  no_data_timeframe    = "0"
  notify_audit         = "false"
  notify_no_data       = "false"
  priority             = "0"
  query                = "avg(last_1h):avg:aws.applicationelb.target_response_time.p99{name:api} > 3.0"
  renotify_interval    = "0"
  renotify_occurrences = "0"
  require_full_window  = "true"
  tags                 = []
  timeout_h            = "0"
  type                 = "query alert"
}

resource "datadog_monitor" "api_latency_p50" {
  escalation_message = ""
  evaluation_delay   = "900"
  include_tags       = "true"
  locked             = "false"
  message            = "{{#is_alert}}{{name}}のレイテンシが{{threshold}}を超えました。{{/is_alert}}{{#is_recovery}}{{name}}のレイテンシが正常値に戻りました。{{/is_recovery}}"

  monitor_thresholds {
    critical = "0.5"
  }

  name                 = "Api Latency p50"
  new_group_delay      = "0"
  new_host_delay       = "300"
  no_data_timeframe    = "0"
  notify_audit         = "false"
  notify_no_data       = "false"
  priority             = "0"
  query                = "avg(last_1h):avg:aws.applicationelb.target_response_time.p50{name:api} > 0.5"
  renotify_interval    = "0"
  renotify_occurrences = "0"
  require_full_window  = "true"
  tags                 = []
  timeout_h            = "0"
  type                 = "metric alert"
}

アラート

アラートにはエラーバジェットの枯渇とバーンレートの上昇を設定します。エラーバジェットとバーンレートの説明はSRE本に記載されているため省略します。バーンレートのターゲットはSRE本の推奨値の14.4を設定します。

エラーバジェットとバーンレートのアラートも同様にコード化します。

resource "datadog_monitor" "api_error_rate_error_budget" {
  name    = "API Error Rate Error Budget Alert"
  type    = "slo alert"
  message = <<EOT
{{#is_alert}}APIのエラーレートにおけるエラーバジェットが枯渇しました。{{/is_alert}}
{{#is_warning}}APIのエラーレートにおけるエラーバジェットの消化率が{{warn_threshold}}を超えました。{{/is_warning}}
Notify @slack-${local.slack_channel_name}
EOT

  query = <<EOT
error_budget("${datadog_service_level_objective.api_error_rate.id}").over("30d") > 100
EOT

  monitor_thresholds {
    critical = 100
    warning  = 70
  }

  tags = []
}

resource "datadog_monitor" "api_error_rate_burn_rate" {
  name    = "API Error Rate Burn Rate Alert"
  type    = "slo alert"
  message = <<EOT
{{#is_alert}}<!here> APIのエラーレートにおけるバーンレートが{{threshold}}を超えました。{{/is_alert}}
{{#is_recovery}}APIのエラーレートにおけるバーンレートが回復しました。{{/is_recovery}}
Notify @slack-${local.datadog_slack_channel_name}
EOT

  query = <<EOT
burn_rate("${datadog_service_level_objective.api_error_rate.id}").over("30d").long_window("6h").short_window("1h") > 14.4
EOT

  monitor_thresholds {
    critical = 14.4
  }

  tags = []
}

SLOの運用

次にSLOを運用して信頼性を改善するための会議を設計します。

会議体としては元々実施していたバッチリプレイス定例をリニューアルして、SLO定例として実施しています。従来はバックエンドチームのみでしたが、現在はSREチーム、Webフロントエンドチームも合流し、信頼性に関わるエンジニアが参加してSLOの達成率やパフォーマンスの低い箇所について議論しています。またこの場でSLOの見直しも行っています。

SLOが適切であればSLOのアラートが飛んだタイミングでデプロイを制御して改善に工数を割くのが理想的な運用と考えますが、運用しながら改善していく前提で、粗い状態で設定しているためこのようにしています。

具体的な会議の内容についてはエウレカ社のパフォーマンス定点観測会の内容を参考にさせていただきました。SLOやAPM、CPUやメモリ、AWSのコストまで信頼性に関係するメトリクスを一覧で見られるDatadogのダッシュボードを作成します。会議ではそれを眺めながら改善点を議論します。改善点が挙がればIssueにおこして進捗を管理します。

Datadogダッシュボードの内容がこちらです。

SLO

SLO

左にSLOのモニターを配置し右はAPMでエンドポイントごとのエラーレートやレイテンシ内容を表示させています。エラーバジェットが枯渇したサービスについてAPMを見ながら原因の議論ができるようにしています。

DB

DB

Datadogのデータベース モニタリングを用いてアラートよりも長期的な目線で信頼性に影響する動きがないかを確認しています。

リソース

リソース

リクエスト数とCPU使用率、メモリ使用量を確認し、長期目線でリソースの使用状況が適切かを確認しています。リソースが枯渇しそうかや、逆に過剰にリソースを確保していないかも確認しています。

コスト

コスト

コストの大きいサービス順に棒グラフで表示させ、長期的な目線でコスト状況に異変がないか確認しています。パフォーマンス定点観測会のスライドにもあるようにDatadogにメトリクスを集めて1枚のダッシュボードを眺めることで信頼性に関係する情報を得られるように設計しています。

SLOの運用で得られた効果

SLOを策定してから実際に半年間運用してみて、エンジニアに限らずビジネスのメンバーも含めてチーム横断で信頼性へ意識が向くようになってきており、効果が表れていると感じています。

WEARはフリマ機能コーディネート動画機能をはじめとする新機能のリリースを積極的に行なっています。

一方で、リプレイス前の旧環境に依存する障害、負荷も課題となっており、これらの課題に対しても改善が必要です。機能開発とシステムの信頼性改善の両方がサービスにとって重要なため、リソース配分を適切に行い、最大化されることは組織にとってとても重要です。

SLOの導入により、サービスレベルを下回る影響度であれば改善を行い、そうでなければ過剰に改善にはリソースを割かず機能開発に注力するような意思決定ができるようになったので効果があったと言えるでしょう。

今後の課題

しかし、残された今後の課題もあります。現在のSLOのターゲット値は達成できるラインで設定しているため、目標を達成したからといってユーザーが満足する訳ではありません。

SLOの主眼は究極的には顧客体験の改善であるため、ユーザーが満足している状態を保つために、CUJの策定と改善、またユーザーのインサイトを反映させたSLOの閾値を追及していくことが今後の課題です。

BizDevOpsにむけて

弊社瀬尾の記事にもありますが、我々はBizDevOpsを体現し組織として密に連携することを目指しています。我々はシステムの信頼性やSLOの状態はプロダクトの戦略において重要で、エンジニアだけが知っていれば良いということではないと考えています。実際にSLOで見ているエラーレートやレイテンシはCVR離脱率に影響があるという調査も挙がっています。

よって、ビジネスのメンバーと一緒に確度の高い戦略を打ち出すために都度SLOのサマリをレポートしています。これは始めたばかりですがビジネスのメンバーからは好感触を得ているので今後もWEARをより良くしていけると確信しています。

おわりに

WEARにおけるSLOを用いた信頼性改善の取り組みについて紹介しました。SLOに関心のある皆さんの参考になれば幸いです。

ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。

corp.zozo.com

hrmos.co

hrmos.co


  1. SLI:外部からシステムに対して期待される可用性に関して設定された目標(引用:https://newrelic.com/jp/topics/what-are-slos-slis-slas
  2. SLO:システムの可用性を特定するための主要な測定値およびメトリクス(引用:https://newrelic.com/jp/topics/what-are-slos-slis-slas
  3. CUJ:ユーザーが1つの目的を達成するために行うサービスとの一連のインタラクション(引用:https://cloud.google.com/architecture/defining-SLOs
カテゴリー