1
/
5

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

こんにちは、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ダッシュボードの内容がこちらです。

続きはこちら

株式会社ZOZOでは一緒に働く仲間を募集しています
3 いいね!
3 いいね!
同じタグの記事
今週のランキング
株式会社ZOZOからお誘い
この話題に共感したら、メンバーと話してみませんか?