RailsリクエストSLI(サービスレベルインジケータ)

GitLab 14.4 で導入されました

note
このSLIはサービスモニタリングに使われます。しかし、デフォルトではステージグループのエラーバジェットには使われません。

リクエストApdex SLIとエラー率SLIはアプリケーションで定義されたSLIです。

リクエストApdexは、アプリケーションパフォーマンスの指標として、成功したリクエストの継続時間を測定します。これには、REST、GraphQL API、および通常のコントローラのエンドポイントが含まれます。

エラー率は、サーバーの誤動作の指標として、失敗したリクエストを測定します。これには REST API と通常のコントローラのエンドポイントが含まれます。

  1. gitlab_sli_rails_request_apdex_total:このカウンタは、5xx ステータスコードで応答しなかったリクエストごとにインクリメントされます。これは、遅い失敗が二重にカウントされないようにするためです。なぜなら、そのリクエストはすでにエラーSLIにカウントされているからです。

  2. gitlab_sli_rails_request_apdex_success_total:このカウンタは、エンドポイントの緊急度に応じて定義された目標持続時間よりも速く実行された成功したリクエストごとにインクリメントされます。

  3. gitlab_sli_rails_request_error_total:このカウンタは、5xx ステータスコードで応答を返したリクエストごとにインクリメントされます。

  4. gitlab_sli_rails_request_total:このカウンタはリクエスト毎にインクリメントされます。

これらのカウンタには

  1. endpoint_id:RailsコントローラまたはGrape-APIエンドポイントの識別。

  2. feature_category:コントローラまたは API エンドポイントに指定された機能カテゴリ。

リクエストApdex SLO

これらのカウンターは成功率にまとめることができます。この比率の目標は、サービスごとにサービスカタログで定義されます。この SLI が SLO を満たすには、記録された比率が以下より高くなければなりません:

例: ウェブサービスでは、少なくとも99.8%のリクエストが目標時間よりも速くなるようにします。

これらのターゲットはアラートとサービスモニタリングに使われます。これらのターゲットを考慮して持続時間を設定することで、アラートを発生させないようにします。しかし、ゴールはユーザーを満足させる目標に緊急度を設定することです。

成功した測定と失敗した測定の両方がステージグループのエラーバジェットに影響します。

リクエストの緊急度の調整

すべてのエンドポイントが同じ種類の作業を実行するわけではないので、エンドポイントごとに異なる緊急度を定義することが可能です。緊急度の低いエンドポイントは、緊急度の高いエンドポイントよりも長いリクエスト期間を持つことができます。

長時間のリクエストはインフラにとってより高価です。1つのリクエストに対応している間、スレッドはそのリクエストの間占有され続けます。スレッドはそれ以外には何も処理できません。Ruby のグローバル VM ロックにより、スレッドはロックを保持したまま、 同じ Puma ワーカープロセスが処理する他のリクエストを止めてしまうかもしれません。このリクエストは、ワーカーによって処理される他のリクエストの ノイジーネイバーになります。このため、目標持続時間の上限を 5 秒としました。

緊急度を下げる (目標継続時間を長くする)

既存のエンドポイントの緊急度は、ケースバイケースで下げることができます。以下の点に注意してください:

  1. Apdexは知覚されるパフォーマンスに関するものです。ユーザーがリクエストの結果をアクティブに待っている場合、5秒待つことは受け入れられないかもしれません。しかし、エンドポイントが多くのデータを必要とするオートメーションによって使用される場合、5秒は許容できるかもしれません。

    プロダクト・マネージャーは、エンドポイントがどのように使用されているかを特定するのに役立ちます。

  2. エンドポイントによっては、呼び出し元が指定するパラメータによって作業負荷が大きく異なることがあります。緊急度はそれらの違いに対応する必要があります。場合によっては、エンドポイントが行っていることに対して、別のアプリケーションSLIを定義することもできます。

    特定のケースでエンドポイントがno-opsに変わり、非常に高速になる場合、ターゲットを設定する際にこれらの高速リクエストを無視すべきです。例えば、MergeRequests::DraftsController が閲覧されるマージリクエスト毎にヒットされるが、レンダリングはほとんど行われない場合、エンドポイントが作業を実行できるようなターゲットを選ぶべきです。

  3. エンドポイントによって消費される依存リソースを考慮してください。もしエンドポイントがGitalyやデータベースから多くのデータをロードし、それが不満足なパフォーマンスを引き起こすのであれば、緊急度を下げることでターゲットの期間を長くするのではなく、データをロードする方法を最適化することを検討してください。

    このような場合、インフラが耐えられるのであれば、エンドポイントを SLO に適合させるために緊急度を一時的に下げることが適切かもしれません。このような場合は、イシューにリンクするコード・コメントを作成してください。

    もしエンドポイントが多くの CPU 時間を消費するのであれば、このことも考慮すべきです。この種のリクエストは、できるだけ短くするよう努力すべき、うるさい隣人です。

  4. トラフィックの特性も考慮すべきです。エンドポイントへのトラフィックが時々バーストする場合、例えばCIトラフィックが同じエンドポイントを叩くジョブの大きなバッチをスピンアップするような場合、これらのエンドポイントに5秒かかることはインフラストラクチャの観点からは容認できません。通常のトラフィックに加え、低速なリクエストの受信に対応するために、十分な速さでフリートをスケールアップすることはできません。

既存のエンドポイントの緊急度を下げる場合は、スケーラビリティ・チームメンバーをレビューに参加させてください。私たちは、ログで利用可能なリクエストのレートと継続時間を使用して、推奨事項を導き出すことができます。緊急度を上げる場合と同じプロセスを使用して、サービスの SLO よりも高い期間を選んでしきい値を設定できます。

決定をサポートするデータがまだないので、エンドポイントを導入するマージリクエストでは、最長期間を設定すべきではありません。

緊急性を高める(目標期間を低く設定する)

緊急度を上げる場合、エンドポイントがリクエストを処理するフリートの SLO を満たしていることを確認する必要があります。ログの情報を使って確認できます:

  1. Kibana でこのテーブルを開きます。

  2. このテーブルには、デフォルトで最も忙しいエンドポイントの情報がロードされます。レスポンスを高速化するには、両方を追加します:

    • json.meta.caller_id.keyword用のフィルタ。
    • 興味のある識別子:

       Projects::RawController#show
      

      または

       GET /api/:version/projects/:id/snippets/:snippet_id/raw
      
  3. エンドポイントを処理するサービスの適切なパーセンタイル期間をチェックします。全体的な持続時間は、意図したターゲットよりも短くなるはずです。

  4. 全体の継続時間が意図した目標値より低い場合は、Kibana のこのグラフで時間経過に伴うピークを確認します。ここで、問題のパーセンタイルは、設定したい目標持続時間より上にピークがあってはなりません。

しきい値を下げすぎると、Apdexの劣化に対するアラートが発生する可能性があるため、マージリクエストにはスケーラビリティチームメンバーも参加してください。

緊急度の調整方法

緊急度は、エンドポイントが機能カテゴリを取得する方法と同様に指定できます。特定のターゲットを持たないエンドポイントは、デフォルトの緊急度である 1s を使用します。これらの設定が利用できます:

緊急度持続時間(秒備考
:high0.25s 
:medium0.5s 
:default1s何も指定しない場合のデフォルト。
:low5s 

Railsコントローラー

コントローラ内のすべてのアクションに対して緊急度を指定できます:

class Boards::ListsController < ApplicationController
  urgency :high
end

コントローラ内の特定のアクションに緊急度を指定することもできます:

class Boards::ListsController < ApplicationController
  urgency :high, [:index, :show]
end

カスタム RSpec matcher を使用すると、コントローラの仕様でエンドポイントの緊急度をチェックできます:

specify do
   expect(get(:index, params: request_params)).to have_request_urgency(:medium)
end

グレープのエンドポイント

API クラス全体の緊急度を指定します:

module API
  class Issues < ::API::Base
    urgency :low
  end
end

APIクラス内の特定のアクションに対しても緊急度を指定する場合:

module API
  class Issues < ::API::Base
      urgency :medium, [
        '/groups/:id/issues',
        '/groups/:id/issues_statistics'
      ]
  end
end

あるいは、エンドポイントごとに緊急度を指定することもできます:

get 'client/features', urgency: :low do
  # endpoint logic
end

カスタム RSpec matcher は、grape のエンドポイントの仕様とも互換性があります:


specify do
   expect(get(api('/avatar'), params: { email: 'public@example.com' })).to have_request_urgency(:medium)
end
caution
名前空間レベルで緊急度を指定することはできません。ディレクティブは無視されます。

エラーバジェットの帰属と所有権

この SLI は、サービス・レベル・モニタリングに使用されます。ステージグループのエラー予算に反映されます。

詳細については、カスタムSLIを定義してエラー予算に組み込むためのエピックを参照してください)。SLIのエンドポイントは、宣言された機能カテゴリに基づいてグループのエラー予算に組み込まれます。

どのエンドポイントがグループに含まれているかを知るには、グループのダッシュボードでリクエスト率を確認します。予算属性の行で、Puma Apdexログリンクは、1s または 5s の目標を満たしていないリクエスト数を示します。

ダッシュボードの内容については、ステージグループのダッシュボードを参照してください。エラー予算自体の調査については、イシュー1365を参照してください。