49
31

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Sidekiq v6チューニングのためのconcurrencyパラメータ整理

Last updated at Posted at 2020-02-11

これは何?

Sidekiqには同時実行数のパラメータがあるため、それをどのようにセットするべきか決める必要があります。
これは、Ruby on Rails 5, 6, 7でSidekiq v6のconcurrencyのチューニングに必要な情報を事前調査してまとめたものです。

Sidekiq v7+向けはこちら => Sidekiq v7+チューニングのためのconcurrencyパラメータ整理

本記事はSidekiq v6を前提としています。v7からは一部デフォルト値が変更され、Capsules(カプセル)機能によっても、やや仕様変更が発生しているため、公式ドキュメントをご確認ください。

結論

  • Active Recordのpool値 - 1 = concurrency <= 50とします
    • config/database.ymlのpoolをconcurrency + 1にします
  • 最初から水平スケーリング(プロセス数増加)できるアーキテクチャとしておきたいです
  • 要求されるスループットのジョブを回してパフォーマンスを測定して決めます。理論的にはアムダールの法則Universal Scalability Lawを使って効果が飽和する手前の並列度を見積もるなどの定量的な手法もあります。

アーキテクチャ概要

アーキテクチャ.png

プロセス:スレッド=1:N
プロセス:キュー=1:N
キュー:スレッド=N:N
ワーカー:キュー=1:1
プロセス:コネクション数=1:N
プロセス:構成ファイル=1:1
コンピューター:プロセス=1:N

キューとスレッドの関係は定義できない。Sidekiq単体では、キューとスレッドはそれぞれプロセスとの紐付けの定義のみ。
つまり、1キューでの並列度は指定できない。プロセス全体の並列度(concurrency)のみ指定可能。
もう少し柔軟な設定とするには、sidekiq-throttledの利用を検討ください。

concurrency

  • 公式の説明
  • 1つのSidekiqプロセスで使用するスレッド数
  • デフォルト値: 10 (v6以下)
    • v6ソースコード
    • sidekiq -c Nで実行時に値を指定することも可能
    • 以下の0 of 10 busyの10はconcurrencyの値
ps出力結果
502 13124   775   0  7:10PM ttys008    0:02.79 sidekiq 5.2.1 application-name [0 of 10 busy]

スレッド

Sidekiqではジョブ処理ごとに1スレッドを使用している。
ソースコードレベルでは未確認だが、ジョブ起動する度にps結果のbusyの数が増えていったため。
実際の並列処理はCPUスペックに依存します。

コネクションプール

Sidekiqの文脈で言うコネクションプールはRedisコネクションプールのことで、Active Recordのものではありません。
ただし、Sidekiqプロセス内でActive Recordを利用すると、SidekiqプロセスからDB接続するためのActive Recordコネクションプールを利用します。なので、Sidekiqのconcurrencyとそれらのプールサイズは密接な関係があります。

Redisコネクションプールとconnection_pool gem

  • このgemはSidekiqがRedisのコネクションプーリングするために使用する。
  • 自分で制御したい場合は、config/initilizers/sidekiq.rbこう書く
  • コンストラクタに指定するsizeはプールするコネクション数。
  • Sidekiq内で以下のように使用される(ソースコード
sidekiq/lib/sidekiq/redis_connection.rb
module Sidekiq
  class RedisConnection
    class << self
      def create(options = {})
        # ...
        ConnectionPool.new(timeout: pool_timeout, size: size) do
          build_client(symbolized_options)
        end
      end
    end
  end
end

Active Recordコネクションプール

拡張性

拡張可能な変数としては以下のとおり。

  • スレッド(concurrency)
  • ワーカー
  • キュー
  • プロセス
  • Redisコネクションプールサイズ

スレッド(concurrency)

  • 1プロセスで50スレッド以下にした方がよいとの公式見解のため、少し余裕をもった値にしつつ、50に近づいたらプロセスを増やします(スケールアウトします)
  • 個人的には50も多過ぎるので、ビタビタに攻めるよりは最初から水平スケーリングできるようにしたいです
  • もちろん、CPUスペックやDB負荷を気にして効率の良い数を決めます
  • 起点としては、APMツールなどでI/O待ち時間の割合を測定しアムダールの法則を適用したり、異なる並列度での実験値をUniversal Scalability Lawを使ってフィッティングするなどして、効果が飽和する手前の並列度を見積もるなどの定量的な手法もあります

ワーカー

  • まずは非機能要件からではなく、仕事内容に応じてワーカーを定義していきます。1つのワーカーの責務が大きくなったら、処理ワークフローを再考してワーカーの分割を検討します。
  • 仕事内容は、引数で受け取るリソース、リトライ範囲、順序や一貫性を保証したい範囲など、非機能要件側からも境界を決めていきます。
  • 非同期なワーカーが増える場合、ワーカー同士の依存関係を主としてシステムの複雑度が上がってないかチェックしたいです。また、業務自体が複雑化しているシグナルと捉え、業務見直しのチェックポイントを最初から考慮しておくと拡張しやすくなります。

キュー

  • 待ち行列となる(待つことになる、FIFOになる)ことを考慮して、キューの分割を考慮します。例えば、頻度が少ないが来たらすぐに処理したいワーカーには専用のキューを用意し、その優先度を上げるようにします。
  • 平均処理時間の異なる2つのワーカーは別キューに分けます。なぜなら、同じ優先度の異なるキューに入れば、それぞれから1つずつ処理してくれることになり、短時間タスクが長時間タスクによって待たされることが減るからです。
  • なお、優先キューを作ることはそのワーカーの仕事内容によっては許容されないケースもあるかも知れないので注意です。例えば、1日の中では順序非依存でも、日付の前後が入れ替わる処理順では困る場合です。意外と簡単に既存仕様を破壊しかねないので気を付けたいです。

プロセス

  • キューごとの並列度、言い換えればワーカーごとの並列度はSidekiq単体では指定できません。concurrencyを共有するためです。よって、キューごとに別々の並列度を指定したい場合は、別プロセスに分割するか、sidekiq-throttledを導入することも検討します。
  • concurrencyが上限に近づくか、レイテンシーの頭打ちとなったらプロセスをスケールアウトします。
  • 最近はコンテナ単価も安いので、concurrency頑張るより水平スケーリングさせるのがいいです。
  • プロセスを増やすとDBコネクション含めて外部リソース利用も増えるため、限界値を見積もって将来に備えます。

Redisコネクションプールサイズ

  • 特別チューニングする必要はありません。
  • concurrencyよりも多く準備する必要があるが、デフォルトでconcurrencyと同数のコネクションが確保されるようになっています。

参考

49
31
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
49
31

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?