ECS のSidekiqワーカタスクをSidekiqのキュー内の滞留ジョブ数でオートスケールする

id:kizashi1122 です。お久しぶりです。

前提

弊社では Rails 製のサービスを運用しています。非同期ジョブの処理には Sidekiq を使っています。 そしてそのジョブを処理するワーカは AWS ECS で Fargate で動かしています。

ワーカは役割ごとにいくつかのECSサービスにわけています。そのサービス中には CPU の使用率に応じてスケールするようにしているサービスもあります。

このあたりが参考になります。

docs.aws.amazon.com

ECSServiceAverageCPUUtilization というメトリクスをつかって、例えば CPU 使用率が 80% に維持するようにスケールするように設定することができます。 この ECSServiceAverageCPUUtilization というメトリクスは予め定義されているメトリクスとなります。

ここまではわかりやすい話です。 よくあるオートスケーリング設定です。

課題

しかしまた別のECSサービスでは、CPU 使用率ではなくて、Sidekiq の滞留ジョブ数でスケールしたいなと思いました。溜まってるんだからたくさんのワーカで処理しなきゃと思うのは自然な発想です。しかし当然、滞留ジョブ数というメトリクスがあるわけもないので困ります。

そうだカスタムメトリクスがあるじゃないか!

おさらい

つまり、

  • なんらかの仕組みでカスタムメトリクスに Sidekiq の滞留ジョブ数を定期的につっこむ
  • そのメトリクスを参照してオートスケールする

ということができればいいわけですね。

カスタムメトリクスに Sidekiq の滞留ジョブ数を定期的につっこむ

ここについては

  • Sidekiq の滞留ジョブ数を取得する
  • それらを CloudWatch に送りつける

の処理にわかれることになります。

Sidekiq の滞留ジョブ数を取得する

sidekiq の API を使えば滞留しているジョブの数やレイテンシーをとってくることができます。

require 'sidekiq/api'

def collect_sidekiq_stats
  Sidekiq::Queue.all.each_with_object({}) do |q, hash|
    hash[q.name] = { latency: q.latency, size: q.size }
  end
end

こんなイメージです。

それらを CloudWatch に送りつける

こんな感じでしょうか。

require "aws-sdk-cloudwatch"

#
# cloudwatch にメトリックデータを送信する
#
def put_metric_data(hash)
  client = Aws::CloudWatch::Client.new

  hash.each do |q_name, info|
    metric_data = {
      namespace:   "SidekiqJobMetric",
      metric_data: [
        {
          metric_name: "Retention",
          dimensions:  [{
                          name:  "Environment",
                          value: "environment_here"
                        }, {
                          name:  "QueueName",
                          value: q_name
                        }],
          timestamp:   Time.zone.now.utc,
          value:       info[:size],
          unit:        "Count"
        },
      ]
    }
    res = client.put_metric_data(metric_data)
  end
end

そして、こんな感じでつなげると。

put_metric_data(collect_sidekiq_stats)

これでカスタムメトリクスを送りつけることができるので、参照してオートスケールの判断材料に使うことができます。

ここについては省略します。 このあたりの情報が参考になるかもしれません。

aws.amazon.com

ちなみに SQS と SQSワーカの関係だと SQS の ApproximateNumberOfMessagesVisible を使いたくなりますがこれは自分で投げつけたメトリクスではないのですが、事前定義メトリクスではなくカスタムメトリクスになりますね。

課題

うちのプロジェクトではこれを Rake タスクにまとめて、5分おきに起動しているのですが、Rake なのでどうにも重くていけません。 Sidekiq の API が呼び出せればいいので、Rails は読み込む必要はありません。 となると Lambda でさくっと定期実行するのがいいかなと思っています。

実現できればまたブログ記事にしたいと思います。