Heroku に 30 秒でレスポンスを返さないといけないルールがあったのを忘れていたので、 急遽 Rails アプリで時間がかかる処理を非同期にすることにした。
Rails で非同期というと Resque や Sidekiq が今のところ人気だけど、 今回は Rails 4.2 で追加予定の ActiveJob を使うことにした。 バックエンドには Sidekiq。
Redis をインストール
バックエンドに使う Sidekiq は Redis が必要なので、 Vagrant + Chef + Berkshelf で構築している開発環境にインストールする。
Berksfile に
cookbook "redisio"
を追加し
rm -rf cookbooks berks vendor cookbooks
で Cookbook をインストール。
redisio のレシピを使うように Vagrantfile を
VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| # ... config.vm.provision "chef_solo" do |chef| chef.cookbooks_path = ["./chef/cookbooks", "./chef/site-cookbooks"] # ... # Redis のレシピを追加 chef.add_recipe "redisio::install" chef.add_recipe "redisio::enable" end end
という風に修正したら、vagrant provision
で chef-solo を実行。
ActiveJob と Sidekiq をインストール
ここからは vagrant ssh
で開発環境に入っての作業。
Gemfile に
gem "activejob", "4.2.0.beta1", require: "active_job/railtie" gem "sidekiq" gem "sinatra"
を追加して bundle install
。
ActiveJob は gem を直接使う。
Rails 4.1 のアプリで使いたかったので、activesuport 4.2.0.beta2 が不要な、4.2.0.beta1 を使用。
active_job/railtie
を require しないと、ActiveJob が Rails に読み込まれなかった。
あとは config/environment/development.rb で ActiveJob の設定を記述。 バックエンドで Sidekiq を使うように指定する。
Rails.application.configure do # ... # ActiveJob のバックエンドに Sidekiq を指定 config.active_job.queue_adapter = :sidekiq end
ローカルにインストールした Redis を使うので、Sidekiq の Redis 設定は省略可能。
Sidekiq の Web コンソールをマウント
ちゃんとジョブが実行されたか確認したいので、Sidekiq の Web コンソールをマウントする。 sinatra もインストールしたのは、これが理由。 Sidekiq の Web コンソールは Sinatra を別にインストールしないといけない。
config/routes.rb を次のように修正。
Rails.application.routes.draw do # ... # Sidekiq の Web コンソールをマウント mount Sidekiq::Web => "/sidekiq" end
ジョブを作成
次のコマンドでジョブを生成。
bundle exec railg generate job calc_score
すると
class CalcScoreJob < ActiveJob::Base def perform(*args) end end
みたいな雛形が app/jobs/calc_score_job.rb に生成されるので、 perform メソッドに非同期で実行したい処理を書く。 こんな感じ。
class CalcScoreJob < ActiveJob::Base def perform(*args) project_id = args[0] project = Project.find(project_id) project.calc_score # 30 秒以上かかる集計処理 end end
コントローラーでジョブを使う
コントローラーで作成したジョブを使ってみる。
ドキュメントには perform_later
を使うと書いてあったけど、
RubyGems.org からインストールした 4.2.0.beta1 にはまだ実装されていないので、NoMethodError。
代わりに古い API の enqueue
を呼び出す。
class ProjectsController < ApplicationController # ... def calc_score # 本当は # CalcScoreJob.perform_later(data) # ジョブ実行 CalcScoreJob.enqueue(@project.id) respond_to do |format| format.html { redirect_to @project } end end end
ジョブを実行
Rails プロジェクト直下に、次の内容を書いた Procfile を作成する。
web: bundle exec unicorn -p $PORT -c config/unicorn.rb worker: bundle exec sidekiq
Foreman を使って Rails サーバーと Sidekiq ワーカーを起動。
web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb worker: bundle exec sidekiq
コントローラーのアクションを呼び出してジョブを実行したら、 Sidekiq の Web コンソールで確認してみる。
ブラウザで http://localhost:8080/sidekiq
にアクセスして
Processed の数が増えていれば、ActiveJob のバックエンドとして Sidekiq がちゃんと動いたことになる。