Sidekiqの要点まとめと動かし方


Ruby(Rails)界隈ではおそらくJobの実行基盤のデファクトはSidekiqです。SidekiqはRuby(Rails)とは異なるプロセスで動かすソフトウェアで、何らかのバックグラウンドの処理を担当するものです。

おそらく大抵の場合はRailsと共に使われると思いますが、Rails無しでも使うことはできます。ここではSidekiqの特徴や優れている点、使い方について解説します。

Sidekiqのアーキテクチャ

https://github.com/mperham/sidekiq

シンプルで効率的なRubyのためのバックグランドプロセッシングです。

何らかの処理を並列で動かしたい、HTTP Requestを受け付けるRailsのプロセスでI/O Waitを発生させたくないので、重い処理をJobにする、cronのように何かの処理を定期的に実行したい、というようなシーンで利用します。

使う場合は以下のGet startedを一通り読んでおくと良いかと思います。

https://github.com/mperham/sidekiq/wiki/Getting-Started

アーキテクチャは割とシンプルでSidekiqプロセスのデータの永続化にRedisが使われています。RedisにはどういったJobがenqueueされているかが格納されており、Redisのデータが失われない限りenqueueされたJobが消えることはありません。
(RailsのActiveJobデフォルトの設定ではqueue adapterはasyncであり、これはメモリにJobが積まれるので、Railsのプロセスを落とすと消えてしまう)

データの永続化はRedisで、そのほかはSidekiqのプロセスだけです。つまりDockerなどで実行するならばRedisのcontainerとSidekiqのcontainerだけあれば良いです。

Sidekiqの利点

シンプル

色々ありますが、まずは上記の通りアーキテクチャがシンプルです。

(おそらく)Redisの操作が同期処理なので、Sidekiqプロセスを単純に増やすだけでRedisがボトルネックにならない限りはスケール可能で、特にmaster nodeのようなものも必要ないはずです。

シンプルなAPIとRailsによるサポート

APIがシンプルで簡単に使うことができます。

class HardWorker
  include Sidekiq::Worker

  def perform(name, count)
    # do something
  end
end

HardWorker.perform_async('bob', 5)

これは公式のコードサンプルですが、このようにWorkerを定義して呼び出すだけです。簡単です。

ActiveJobで利用する

Web上を検索するとSidekiq::Workerを使うサンプルが多いですが、Railsを使う場合はおそらくActiveJobを使った方が良いです。ActiveJobのqueue adapterとしてsidekiqがサポートされているため、ActiveJobの形式でも書くことができます。

ただし、設定が少し必要です。

https://github.com/mperham/sidekiq/wiki/Using-Redis#using-an-initializer

このあたりを参照して、Sidekiq.configure_clientとserverの設定を行います。

https://github.com/mperham/sidekiq/wiki/Advanced-Options#the-sidekiq-configuration-file

このあたりを参照して、sidekiq.ymlを作成しておきます。

developmenet.rbなどを変更してqueue_adapterをsidekiqにします。(Rails6系のデフォルトはasync)

Redisを起動しておき、Rails serverとSidekiqを起動(bundle exec sidekiq -C config/sidekiq.yml)した後で、ActiveJobをperformするとJobが実行されます。

Rails serverとSidekiqはRedisに対してJobをenqueue/dequeueするので当然アクセスできる必要がありますし、configureの部分でURLの設定が合っている必要があります。sidekiq.ymlにqueueという設定がありますが、これはそのsidekiqプロセスで扱うqueueを表します。

sidekiqコマンドの-qオプションで対象のqueueを指定することもできます。

大きなプロジェクトではqueueを複数に分けて、queueごとにsidekiqのプロセス(インスタンス)を分けたいケースもあるでしょう。そういう時にはconfigを分けたり-qを切り替えたりして、扱うqueueを変えることができます。

redis-namespaceというgemを使うとSidekiq.configure_clientとserverの部分でnamespaceのオプションを付けることができます。namespaceを使うとRedisへ登録されるkeyの名称にprefixが付きます。

RedisをアプリAとアプリBで共有しているが、queueは分けてsidekiq側で扱うJobを一緒くたにしたくない、というような場合にnamespaceを使うと便利でしょう。
(ちなみに私はnamespaceが合っていないせいで、enqueueしたJobがいつまで経っても実行されないというミスをしました・・・)

WebUIが用意されている

https://github.com/mperham/sidekiq/wiki/Monitoring#web-ui

上記の通りmountするとWeb(Rails)プロセスの方でSidekiqのDashboardを見ることができます。割と簡素な作りでたまに機能が不足していると感じることもありますが、概ねやりたいことはできますし、知りたい情報も分かるので便利です。

Retry

https://github.com/mperham/sidekiq/wiki/Error-Handling#best-practices

Exponential backoffというアルゴリズムによるリトライを備えていて、デフォルトの設定では21日間に25回のリトライを行います。25回が21日間の間で等間隔に実行されるわけでは無く、2秒後、4秒後、8秒後・・・というように指数関数的に間隔が広くなっていきます。

リトライの方法は簡単で単純にJob(Worker)の中でraiseすれば失敗とみなされてリトライされます。

Jobが失敗した場合は21日以内にコードを修正してデプロイします。そうすれば新しいコードでJobが実行されるので、Jobが成功しqueueから無くなる、という感じです。
(Redisのqueueにはコードも含めてenqueueされるわけではなく、あくまでJobのコードはsidekiqプロセスで持っているものが使われます。そのため、例えばRailsのプロジェクトでActiveJobでコードを書いている場合、webだけデプロイして、sidekiq側をデプロイしていないと、古いJobで実行される、という状態が発生します)

上記のbest-practicesにも書いてありますが、何らかの方法でJobの失敗は検知できる様にしておいた方が良いでしょう。(でないと21日気づかずにJobが消えてしまう)

ちなみに失敗したJobはWebUI上で手動即時実行することもできます。

豊富なadd-on

https://github.com/mperham/sidekiq/wiki/Related-Projects

様々な関連したライブラリが存在します。

https://github.com/ondrejbartas/sidekiq-cron

など。私はこれしか使っていませんが、これでcron的なことができます。

ActiveJob + Sidekiq + sidekiq-cronで大抵の種類のJobを実現可能

一言にJobと言ってもいくつか種類があるかと思います。主に以下の様なケースがあるのではと思います。

  1. 何らかのイベントに応じて実行するJob
  2. 特定の時間後にスケジュールされたJob
  3. 定期的な実行がスケジュールされたJob

上記の1についてはActiveJobをperformすれば良いです

上記の2についてはActiveJobにset(wait_until: foo)というAPIがあるので、これを実行すれば良いです。

上記の3については前述のsidekiq-cronなどが使えるかと思います。

Jobの実行基盤は色々ありますが、上記の一部しかサポートしていなかったり、サポートしていても使いづらかったりなどありますが、Sidekiqはこの点は優れていると思います。

まとめ

他にも色々利点はあったり拡張できたりします。詳細はGitHubのWikiを見ると良いかと思います。

私はnamespaceまわりなどでトラブって少し時間がかかりましたが、ほとんどトラブルなく簡単に使えるかと思います。