コマンド「QUEUE=resque_sample rake environment resque:work」を読み解く

Railsのコントローラで定義したメソッドの中で、長い処理は非同期で行いたいときはありませんか?例えばユーザー登録のメール送信などは、コントローラのメソッドの中でメールが送信されるまで処理を止めるのではなく、非同期にして先に「登録が完了しました。」とページを表示させてあげる。こういうのをrailsでやるのにresqueというgemが役立ちます。

このgemのチュートリアルである下記の記事を試してみました。

Hello World Resque (Railsにresqueを導入する) | hello-world.jp.net

今回は定義したキューを実行させるための下記のコマンドを掘り下げて見ていこうと思います。

QUEUE=resque_sample rake environment resque:work

ちなみに次のように書いても実行できます。QUEUEを後ろに回しただけです。

rake environment resque:work QUEUE=resque_sample

QUEUEは環境変数で、どの種類のタスクを実行するのか決めている

QUEUE=resque_sampleQUEUEという環境変数を定義して、そこにresque_sampleという文字列を格納しています。その後にrakeでタスクを走らして、そのタスク内でこの環境変数の中身を使っているわけです。

参考:Linux - シェル変数と環境変数の違いをコマンドラインで確認する - Qiita

簡単な例をあげます。 ruby_script_testとうファイルを作り、次のとおりに記述します。その後chmod 755 ruby_script_testコマンドラインから実行して、このファイルに実行権限を与えてあげましょう。

#!/usr/bin/env ruby
puts ENV['SAY']

そしてコンソールで次のように打ってください。環境変数の中身が出力されましたね。

% SAY=hello
% ./ruby_script_test
hello

rubyで記述されたファイルをrubyコマンドなしで実行できる仕組みは下記が参考になります。簡潔にまとめると、ファイルの拡張子があろうがなかろうか、1行のめシバンとよばれる#!~の記述でどのコマンドで実行すればいいかをファイル自体に記述しているからrubyコマンドなしで実行できるわけです。

ruby - rubyで作ったプログラムをターミナルからコマンドで実行したい。 - スタック・オーバーフロー

ちなみに、QUEUEresque_sampleを指定していますが、これはapp/workers/hello.rb@queue = :resque_sampleと記述したからです。@queue = :another_taskとした場合は、QUEUE=another_task rake environment resque:workと実行しなければなりません。

キューというのはタスクをやることリストとして管理するためのもので、それをいくつか名前をつけて用意できます。今回だと、resque_sampleanother_taskという2種類のキューを試したわけです。QUEUE= resque_sample rake environment resque:workとはresque_sampleというキューに登録されたタスクを消費し始めるとコマンドです。「消費し始める」が重要ですよ。このコマンドを実行していなくても、今回のチュートリアルの/helo/worldというページにアクセスすればResque.enqueueが実行されてキューにどんどん溜まっていきます。その後にQUEUE= resque_sample rake environment resque:workを実行すれば、溜まったタスクがどんどん実行されていくので、ページにアクセスしなくても実行されているように見えます。

実際にはRedisというKVSのDBに、「resque:queue:ワーカ名」の配列としてどんどんジョブが格納されていきます。そして実行が終わるたびに配列から要素が取り出されていきます。詳しくは下記を参考。

Resqueで色々やって、Redisに何が格納されているのか調べてみた - kitak.blog

environmentはモデルを読み込むために使う

rakeコマンドにenvironmentという引数を渡しています。これを渡すことによってタスク内でActive Recordのモデルなどを使用できるようになります。下記はrake user:registで実行できるタスクです。中でUserモデルを使用しています。これはtask関数でenvrionmentを渡しているからです。これを削除したらUserモデルは使えなくなってしまいます。

# lib/task/user.rake
namespace :user do
  desc "ユーザーを登録するタスクです。"
  task regist: :environment do
    User.create(name: '太郎')
  end
end

しかし、この記述がなくてもUserモデルをタスク内で使える方法があります。それが最初にあげたコマンドのrake environmentです。コマンドラインからenvironmentを渡しているのでモデルなどが使用できるようになるわけです。

参考:[rails]Rakeタスクでのtask定義の「:environment」引数について | アプレンティス プラクティス

resque:workというタスクはどこで作られる?

lib/tasks/resque.rakeというファイルを作成して、require 'resque/tasks'という記述を書きましたよね。resque/tasksを読み込むことによって自動で作られるみたいです。githubのlib/resuq/tasks.rbを見てみましょう。コメントにもこれをrequireしたらresqueタスクが作られると書いてありますよね。それとnamespace :resqueの中にtask :workが定義されていることも確認できます。

# require 'resque/tasks'
# will give you the resque tasks
require 'resque/cli'

namespace :resque do
  task :setup

  desc "Start a Resque worker"
  task :work => [ :preload, :setup ] do
    warn "DEPRECATION WARNING: Rake tasks are deprecated. Use `resque work` instead"

    opts = [
      "-q", ENV['QUEUES'] || ENV['QUEUE'] || "*",
      "-t", ENV['RESQUE_TERM_TIMEOUT'] || 4.0,
      "-d", !ENV['BACKGROUND'].nil?,
      "-p", ENV['PIDFILE'],
      "-i", ENV['INTERVAL'] || 5
    ]

    Resque::CLI.new([], opts).invoke(:work)
  end
# 略
end