【Rails】SideKiqを用いて非同期処理(バックグラウンドジョブ)を実行する
今回、Rails のプロジェクトで、バックグラウンド処理を実装したくなり、SideKiq を用いて実装を行ったので、備忘録として残しておく。
- ジョブ管理
- Sidekiq とは
- Redis をインストールする
- Sidekiq を実装する
- (おまけ)AWS EC2へのデプロイ
- (おまけ)redis と sidekiq を別コンテナで起動する
- まとめ
- 関連記事
ジョブ管理
Rails アプリケーションで非同期的に処理を実行したい場合などがある。
例えば、以下のように、API 経由でメールを送信する場合。
def send_email # recieve request mail.send() # 処理が終わるまで待機する head(:ok) && return end
同期処理の場合、mail.send()
の処理が終わるまで、レスポンスを返すことができず、最悪の場合、リクエストタイムアウトになってしまう。こういう場合に、ジョブ管理を使うと、ジョブをキューに登録する処理だけ実行し、(並列処理をすることで)レスポンスを即座に返すことができる。
Rails5 には、ジョブ管理のフレームワークである Active Job がある。 ただし、Rails 自身が提供するのは、ジョブをメモリに保持するインプロセスのキューイングシステムだけであり、再起動などするとジョブは全て失われてしまう。
そのため、(Railsアプリケーションの起動とは別に)ジョブを処理するためにライブラリ固有のキューイングサービスを起動しておく必要がある。
詳細は、以下の公式ドキュメントを参考にして欲しい。
今回、Active Job とアダプタとして Sidekiq を使ってバックグラウンドジョブを実行することもできたが、Sidekiq 単体でも同様の処理が実行できたため、Sidekiq 単体での実装にすることとした。
Sidekiq とは
Simple, efficient background processing for Ruby.
Sidekiq はスレッドを使用して、同じプロセスで同時に多くのジョブを処理することができる。
Sidekiq を使うためには Client、Redis、Server の 3 つが必要となる。
- Client:Railsアプリケーションサーバー(ジョブを登録する)
- Redis:ジョブをキューイングするために必要(ジョブ管理をする)
- Server:並列処理をする(ジョブを実行する)ために、Railsアプリケーションとは別プロセスとして起動しておく(
bundle exec sidekiq
で起動することができる)
Redis をインストールする
今回、開発環境を Docker としていたので、ruby の環境下に Redis を追加することにした。
FROM ruby:2.6.3-alpine ENV LANG C.UTF-8 ENV WORKSPACE=/app # 基本モジュールインストール RUN apk update && \ apk add --no-cache tzdata libxml2-dev curl-dev make gcc libc-dev build-base libxslt-dev g++ mariadb-dev less git && \ rm -rf /usr/local/bundle/cache/* /usr/local/share/.cache/* /var/cache/* /tmp/* && \ apk del libxml2-dev curl-dev make gcc libc-dev g++ # node + yarn # python2はnodeインストール時の依存関係 RUN apk add --no-cache alpine-sdk \ nodejs-current \ nodejs-npm \ yarn \ python2 # install Redis RUN apk add --update --no-cache redis RUN mkdir -p $WORKSPACE WORKDIR $WORKSPACE ADD Gemfile $WORKSPACE/Gemfile ADD Gemfile.lock $WORKSPACE/Gemfile.lock RUN bundle install COPY . $WORKSPACE
Redis を起動する
$ redis-server --appendonly yes --daemonize yes
Sidekiq を実装する
1. gemを追加する
gem 'sidekiq' gem 'sinatra', require: false # ダッシュボードのため
bundle install
を実行して、gem をインストールする。
2. 起動時の設定を追加する
サーバー側とクライアント側の2種類の設定を追記しておく。
Sidekiq.configure_server do |config| config.redis = { url: 'redis://localhost:6379' } end Sidekiq.configure_client do |config| config.redis = { url: 'redis://localhost:6379' } end
3. 設定ファイルを作成する
:verbose: false :pidfile: ./tmp/pids/sidekiq.pid :logfile: ./log/sidekiq.log :concurrency: 10 :queues: - default
4. Woker作成(ジョブを登録する) 今回は、Testという名前のジョブを作成する。
$ bundle exec rails g sidekiq:worker Test create app/workers/testd_worker.rb create test/workers/test_worker_test.rb
class TestWorker < ApplicationController include Sidekiq::Worker def perform() p 'Hello SideKiq' end end
5. sidekiqを起動する
$ bundle exec sidekiq -C config/sidekiq.yml
6. ジョブを実行してみる Controller などから呼び出して実行することもできるが、試験的に Railsコンソールで実行してみる。(sidekiq を起動しいるシェルとは別に新しいシェルで実行する。)
$ bundle exec rails c > TestWorker.perform_async()
sidekiq 側で、「Hello SideKiq」が表示され、ジョブを実行することが確認できた。
7. 管理画面を追加する
require 'sidekiq/web' mount Sidekiq::Web, at: "/sidekiq"
再度、Rails サーバーを起動して、http://localhost:3000/sidekiq にアクセスすると、管理画面が表示される。
(おまけ)AWS EC2へのデプロイ
最後に、諸々の機能を実装して、本番のAWSにデプロイする際に、追加で実施した処理について残しておく。
Redis のインストール
$ amazon-linux-extras 6 postgresql10 available [ =10 =stable ] 8 redis4.0 available [ =4.0.5 =4.0.10 =stable ] 9 R3.4 available [ =3.4.3 =stable ] $ sudo amazon-linux-extras install redis4.0
Redis と Sidekiq のデーモン化
今回、Redis と Sidekiq のデーモン化は、前回紹介した Supervisor で行うことにした。 (前回の記事:【Linux】Supervisor でプロセス制御(Rails + Puma / Capistrano) 参照)
それぞれの設定ファイルを以下のように準備する。
[program:redis] command=/bin/redis-server /etc/redis.conf autostart=true autorestart=true user=redis
[program:sidekiq] directory=/srv/monoai-ranger-manager/current command=/bin/bash -c "/opt/rbenv/shims/bundle exec sidekiq -e production -C config/sidekiq.yml" autostart=true autorestart=true user=ec2-user startsecs=10 stdout_logfile=/srv/monoai-ranger-manager/shared/log/sidekiq.stderr.log logfile_maxbytes=1MB logfile_backups=10 redirect_stderr=true
(注意)
-e production
のオプションを付けないと、default が development になっているので、本番環境で環境変数の不一致が発生しているエラーが発生した。
supervisord を再起動させて、実行させる。
$ supervisorctl reload Restarted supervisord $ supervisorctl status redis RUNNING pid 17430, uptime 0:00:13 sidekiq RUNNING pid 17431, uptime 0:00:13
Capistrano 設定追加
以前は、Rails アプリケーションのみであったが、今回 Redis と Sidekiq が増えたので、Deploy 前後で全てのプロセスを再起動するように変更した。これで、deploy
コマンドのみでプロセスの再起動まで完結するようにできた。
~~ 省略 ~~ namespace :console do namespace :all do desc 'start service all' task :start do on roles(:app) do |_host| execute 'supervisorctl start all' end end desc 'stop service all' task :stop do on roles(:app) do |_host| execute 'supervisorctl stop all' end end end end before 'deploy:starting', 'console:all:stop' after 'deploy:finished', 'console:all:start'
(おまけ)redis と sidekiq を別コンテナで起動する
Sidekiqは、Railsで非同期処理を行うためのライブラリであり、実行の際にはRailsのプロセスとは別に実行する必要がある。負荷を分散するために、SidekiqをRailsアプリケーションとは別のコンテナで動かしたいことがあるかもしれない。その場合は、以下の設定にすることで、簡単に実現することができる。
version: '3' services: db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: password TZ: Asia/Tokyo ports: - 3306:3306 command: --default-authentication-plugin=mysql_native_password volumes: - mysql-data:/var/lib/mysql redis: image: redis:alpine ports: - 6379:6379 app: &app_base build: context: . volumes: - .:/app - gem-data:/app/vendor/bundle - log-data:/app/log ports: - 3000:3000 depends_on: - db - redis stdin_open: true tty: true sidekiq: <<: *app_base command: ["bundle", "exec", "sidekiq", "-C", "config/sidekiq.yml"] ports: - 3001:3001 volumes: mysql-data: gem-data: log-data:
また、以下のライブラリを追加する。
〜省略〜 gem 'redis-namespace'
SideKiq の設定を以下のように変更する。
Sidekiq.configure_server do |config| config.redis = { url: 'redis://redis:6379', namespace: 'sidekiq' } end Sidekiq.configure_client do |config| config.redis = { url: 'redis://redis:6379', namespace: 'sidekiq' } end
これで、コンテナを分離することができた。 (実際に、コンテナを分離することで、ログなどがそれぞれのコンテナに記載されてしまうので、正直ログを確認するのが面倒になってしまったので、そこあたりの設定をしっかりとしていきたい。)
まとめ
今回は、SideKiq を用いてジョブ管理をすることができた。定期処理や日時指定での実行も可能のため、今後も使っていきたいと思えた。
それでは、ステキな開発ライフを。