Rails+Sidetiqでお手軽定時処理 on Heroku(無料!)

Railsで定時処理をやろうと思って色々トライしました。Sidekiq+Sidetiqを使ってHerokuの無料分で動作させることができたのでその手順を紹介します。

Railsでの非同期処理

古いものから新しいものまでいろいろ選択肢があります。

そしてRails4.2で実装される(された)ActiveJobというアダプタのような仕組みがあります。

以前開発したのアプリケーションではResqueを使っていたことと、3つのgemのなかではSidekiqが一番新しいということでSidekiqを使いました。

Sidekiqでの定時処理

本来Sidekiqは非同期処理のためのgemなので、Sidekiq単体では定期的に処理を実行するような仕組みはありません。X時間後に実行とかはありますが。定時処理をするには別の方法と組み合わせる必要があります。

例えば次の2つのに代表されるgemは単体で定時処理を実現できるgemです。Sidekiqと組み合わせなくても定時処理はできますが、定期的に呼び出す処理の中でSidekiqにジョブをエンキューするという使い方もまあ可能です。

それに対して、Sidekiqに定時実行機能を追加するgemもあります。

この中では圧倒的にGitHubスター数の多いSidetiqを使ってみることにしました。

Prerequisites

  • Herokuアカウント
  • Rails環境
  • Redis

RedisのインストールとHerokuアカウントが必要ですが、ここは省略します。

実装していく

SidekiqとSidetiqの準備

インストール

インストールはGemfileに書くだけでOK。generateとかはありません。

gem 'sidekiq'
gem 'sidetiq'
$ bundle install

設定

config/initialiers/sidekiq.rbにこんな内容を書きます。Herokuに関わる設定がありますが、それは後述します。

Sidekiq.configure_server do |config|
  if Rails.env.production?
    if ENV['REDISCLOUD_URL']
      config.redis = { url: ENV['REDISCLOUD_URL'], namespace: 'sidekiq' }
    end
  else
    config.redis = { url: 'redis://localhost:6379', namespace: 'sidekiq' }
  end
end

ワーカーの定義

そしてapp/workersディレクトリ以下に適当なワーカーを作ります(例えばHardWorker)。

class HardWorker
  include Sidekiq::Worker
  include Sidetiq::Schedulable

  recurrence backfill: true do
    daily
  end

  def perform
    puts 'Doing hard work'
  end
end

recurrenceで頻度を指定し、performで処理を記述します。dailyとかmonthly、もっと複雑な指定もOKです。

ローカルで実行

$ bundle exec rake sidekiq

と実行するとワーカープロセス立ち上がります。さっきはdailyと書きましたが、初めてテストのときはminutelyとかにした上でperformにも適当なputsあたりを書いておいて確認するのが安全かも。

ここまででローカルでの実行はできました。

Web UIの追加

SidekiqはRailsアプリケーションにジョブの管理画面をマウントすることができるため、ジョブの状態を簡単に知ることができて便利です。

使うためにはまずSinatraをインストールして、routes.rbにちょちょいと書き加えます。

gem 'sinatra', '>= 1.3.0', :require => nil
# config/routes.rb
require "sidekiq/web"
require "sidetiq/web"

Sample::Application.routes.draw do
  mount Sidekiq::Web => '/sidekiq'
end

これで例えばhttp://localhost:3000/sidekiqとかで管理画面をみられます。

Herokuで実行する

Redisアドオンを追加

$ heroku addons:add rediscloud # アドオンを追加
$ heroku config:set REDIS_PROVIDER=REDISCLOUD_URL # 必要な環境変数を追加

Unicornを使う

Unicornを使ってゴニョゴニョすることで1dynoでSidekiqを実行することができます。というわけでGemfileにgem 'unicorn'を追記します。

そして、Unicornの設定ファイルを作ります。

herokuでRailsをUnicornに乗せる設定 | Workabroad.jpを参考にした上で、Sidekiqをspawnで起動させます。

# config/unicorn.rb
timeout 15
preload_app true

worker_processes 3

# whatever you had in your unicorn.rb file
@sidekiq_pid = nil

before_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
    Process.kill 'QUIT', Process.pid
  end

  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.connection.disconnect!

  if ENV['RAILS_ENV'] == 'staging' # Sidekiq関連はここ!【更新あり】
    @sidekiq_pid ||= spawn("bundle exec sidekiq -c 2")
    Rails.logger.info('Spawned sidekiq #{@sidekiq_pid}')
  end
end 


after_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT'
  end

  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.establish_connection
end

あとはこのconfigを使って起動するようにルートにProcfileを作成してデプロイすればOKです。

# Procfile
web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb

本当に使う場合はWorker Dynoを立ち上げるべきでしょうが、ちょっとした確認などのためであれば1Dynoです。無料でやってしまいましょう。

Sidetiqによる実装は本当に楽で、この記事の手順を進めるのもUnicornの設定とかを書くほうが面倒なぐらいだと思います。Sidekiq+Sidetiqいいですね。以前使っていたResque+Clockworkよりも良さそうです。