🐘

【Rails, Docker】開発環境に Sidekiq を導入する

2024/03/25に公開

概要

Rails で非同期処理を行う際に、キューイングシステムとして Sidekiq を使用することが多いかと思います。
今回は Sidekiq を docker-compose.yml を用いて開発環境に導入する方法を書いていきます。
https://github.com/sidekiq/sidekiq

環境

Gems

Gemfile.lock
rails (7.0.8)
redis (5.1.0)
sidekiq (7.2.2)
RUBY VERSION
   ruby 3.1.2p20

redis

Redis server v=7.2.3

概念図

この Sidekiq の話をするときに、注射器をイメージするとわかりやすいかと思います。

めっちゃ拙い絵で申し訳ないですが、Rails, Sidekiq, Redis の各立ち位置としては以下のような感じです。

  • Redis...注射器の容器
  • ジョブ(ActiveJob によって作成される)...注射器の中身
  • Sidekiq...押し出すやつ

導入手順

Redis コンテナを作成

docker-compose.yml に redis コンテナを追加します。

docker-compose.yml
version: '3.9'
services:
...
  redis:
    image: redis

コンテナを起動します。

docker compose up

コンテナに入り、redis が起動しているか確認します。

docker compose exec redis bash

# コンテナ内
❯ redis-server --version
Redis server v=7.2.3 sha=00000000:0 malloc=jemalloc-5.3.0 bits=64 build=844e0bcfd16d3790

redis の基本的な使い方は別記事に書いていますので参考にしてください。
https://zenn.dev/ebina_shohei/articles/12883eb0a89352

必要な gem のインストールと設定を行う

Sidekiq の設定で環境変数を設定する必要があるので、一緒にdotenv-railsもインストールしておきます。

Gemfile
gem 'sidekiq'
gem 'redis'
gem 'dotenv-rails'

config/application.rb で、キューイングサービスを sidekiq を使用するようにします。

config/application.rb
config.active_job.queue_adapter = :sidekiq

https://railsguides.jp/active_job_basics.html#バックエンドを設定する

次に、routes.rb に以下を追加することで、キューやジョブを UI で確認することができます。

routes.rb
mount Sidekiq::Web, at: '/sidekiq'

/sidekiq に遷移してみましょう。
以下の画面が表示されるはずです。

サンプルのワーカーとジョブを作成する

app/jobs/sample_job.rb
class SampleJob
  include Sidekiq::Worker

  def perform
    puts 'ジョブを実行しました!!!!!'
  end
end

動作確認

rails console を起動して、以下のコマンドで、sidekiq のキューにジョブを追加することができます。

rails console
> SampleJob.perform_async

上記コマンドを5回実行したあと、/sidekiq/busy にアクセスしてみましょう。
すると、「待機状態」が5になっています。

/sidekiq/queues/default に遷移すると、積まれているジョブの詳細が確認できます。

次に、rails アプリケーションのコンテナに入り、以下を実行します。

> bundle exec sidekiq

すると、以下のログが流れるはずです。

[dotenv] Loaded .env


               m,
               `$b
          .ss,  $$:         .,d$
          `$$P,d$P'    .,md$P"'
           ,$$$$$b/md$$$P^'
         .d$$$$$$/$$$P'
         $$^' `"/$$$'       ____  _     _      _    _
         $:    ',$$:       / ___|(_) __| | ___| | _(_) __ _
         `b     :$$        \___ \| |/ _` |/ _ \ |/ / |/ _` |
                $$:         ___) | | (_| |  __/   <| | (_| |
                $$         |____/|_|\__,_|\___|_|\_\_|\__, |
              .d$$                                       |_|


2024-03-24T14:48:26.300Z pid=195 tid=397 INFO: Booted Rails 7.0.8 application in development environment
2024-03-24T14:48:26.301Z pid=195 tid=397 INFO: Running in ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [aarch64-linux]
2024-03-24T14:48:26.301Z pid=195 tid=397 INFO: See LICENSE and the LGPL-3.0 for licensing details.
2024-03-24T14:48:26.301Z pid=195 tid=397 INFO: Upgrade to Sidekiq Pro for more features and support: https://sidekiq.org
2024-03-24T14:48:26.301Z pid=195 tid=397 INFO: Sidekiq 7.2.2 connecting to Redis with options {:size=>10, :pool_name=>"internal", :url=>"redis://redis:6379/0"}
2024-03-24T14:48:26.305Z pid=195 tid=397 INFO: Sidekiq 7.2.2 connecting to Redis with options {:size=>5, :pool_name=>"default", :url=>"redis://redis:6379/0"}
2024-03-24T14:48:26.305Z pid=195 tid=397 INFO: Starting processing, hit Ctrl-C to stop
2024-03-24T14:48:26.310Z pid=195 tid=aiv class=SampleJob jid=31121d52b7b896591bd432e6 INFO: start
2024-03-24T14:48:26.327Z pid=195 tid=ai3 class=SampleJob jid=b5e8c8e04f8118472b63c5b3 INFO: start
2024-03-24T14:48:26.312Z pid=195 tid=ajf class=SampleJob jid=cc379d9beb695b3ce7b0839c INFO: start
2024-03-24T14:48:26.411Z pid=195 tid=ain class=SampleJob jid=216378612d37c5e105898656 INFO: start
2024-03-24T14:48:26.312Z pid=195 tid=ajz class=SampleJob jid=38a24d1140ec91d1ca7b2931 INFO: start
ジョブを実行しました!!!!!
2024-03-24T14:48:26.602Z pid=195 tid=ai3 class=SampleJob jid=b5e8c8e04f8118472b63c5b3 elapsed=0.274 INFO: done
ジョブを実行しました!!!!!
2024-03-24T14:48:26.604Z pid=195 tid=ajf class=SampleJob jid=cc379d9beb695b3ce7b0839c elapsed=0.292 INFO: done
ジョブを実行しました!!!!!
2024-03-24T14:48:26.604Z pid=195 tid=aiv class=SampleJob jid=31121d52b7b896591bd432e6 elapsed=0.294 INFO: done
ジョブを実行しました!!!!!
2024-03-24T14:48:26.610Z pid=195 tid=ain class=SampleJob jid=216378612d37c5e105898656 elapsed=0.199 INFO: done
ジョブを実行しました!!!!!
2024-03-24T14:48:26.611Z pid=195 tid=ajz class=SampleJob jid=38a24d1140ec91d1ca7b2931 elapsed=0.299 INFO: done

「ジョブを実行しました!!!!!」が5回出力されています。
もう一度 /sidekiq/busy を確認すると、「待機状態」が0になり、
完了が5件増えている事がわかります。

サンプルのメーラーを作成して実行してみる

メールを送信すると仮定したクラスを作成します。

実際にメールを送信するときは ActionMailer を使うことが多いと思いますが、
今回は簡易的に ActiveJob を使用して非同期処理を実行します。

app/jobs/sample_job.rb
class SampleJob
  include Sidekiq::Worker

  def perform
    SampleMailJob.perform_later
    puts 'ジョブを実行しました!!!!!'
  end
end
app/jobs/sample_mail_job.rb
class SampleMailJob < ApplicationJob

  def perform
    sleep_time = 5
    sleep sleep_time

    puts "メールを送信しました!#{sleep_time}秒かかりました"
  end
end

上記の SampleMailJob.perform_later の部分が、メール送信でいうところの、
deliver_laterメソッドで、メール送信処理を非同期的に実行できます。
https://api.rubyonrails.org/v7.1/classes/ActionMailer/MessageDelivery.html#method-i-deliver_later

ActionMaler ActveJob
非同期で処理を実行 deliver_later perform_later
同実行に処理を実行 deliver_now perform_now

再度、rails console から以下を実行してみます。

> SampleJob.perform_async

すると、Sidekiq のログから、最初にワーカーの処理が終了し、
その後にメール送信処理が実行されていることがわかります。

2024-03-24T15:14:35.277Z pid=195 tid=buj class=SampleJob jid=2a306f9393cdc21d0800b7ff INFO: start
Enqueued SampleMailJob (Job ID: 1a0156df-b900-4b04-86f2-b17fb6532890) to Sidekiq(default)
2024-03-24T15:14:35.316Z pid=195 tid=bob class=SampleMailJob jid=dbc901f10238ff28b84d2e6f INFO: start
ジョブを実行しました!!!!!
2024-03-24T15:14:35.317Z pid=195 tid=buj class=SampleJob jid=2a306f9393cdc21d0800b7ff elapsed=0.04 INFO: done
Performing SampleMailJob (Job ID: 1a0156df-b900-4b04-86f2-b17fb6532890) from Sidekiq(default) enqueued at 2024-03-24T15:14:35Z
メールを送信しました!5秒かかりました
Performed SampleMailJob (Job ID: 1a0156df-b900-4b04-86f2-b17fb6532890) from Sidekiq(default) in 5005.83ms
2024-03-24T15:14:40.349Z pid=195 tid=bob class=SampleMailJob jid=dbc901f10238ff28b84d2e6f elapsed=5.033 INFO: done

まとめ

今回は Sidekiq の開発環境への導入手順について書きました。
本番では 分離と独立性の観点から、Sidekiq は別コンテナで動かすことが多いですが、それはまた別記事でやってみようと思います。
参考:
https://zenn.dev/kos31de/articles/3f3dd49fa22d99

Discussion