ホーム DoRuby rails の model メソッドを簡単に daemon 化する方法

rails の model メソッドを簡単に daemon 化する方法

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

Rails で普段開発される方は、 ruby script/console や ruby script/runner は非常に重宝されていると思います。

これらのコマンドは、Web アプリを作成するために作った model  のメソッドを、簡単に batch や コマンドラインのインタラクティブアプリに変更することが出来、cron 化やデバッグに大変重宝します。

今回、その script/[hoge] に、新たなスクリプトを1つ追加しましたのでご紹介いたします。

今開発中のアプリで、あるバッチを数秒単位で実行する必要がでてきました。

バッチを定期実行するとき、例えば下記のようなシェルファイルを作成し、

#!/bin/sh

RUBY="/usr/local/bin/ruby"
DATE=`date +"%Y%m%d"`

cd [RAILS_ROOT]

$RUBY script/runner -e production "ModelClass.method" >> /var/tmp/model_method.${DATE}.log 2>&1 &

そのファイルを cron に登録して実行することがよくあることかと思います。

ですが cron の最小実行単位は「分」のため、秒の間隔でのアクセスは上記の

script/runner を使った方法では行えません。

通常、秒単位での実行を行いたい場合、下記のような「sleep」を用いた処理を書くのが良く行われます。

例えばこのような Rails の model ファイルを作成し、

class ModelClass <  ActiveRecord::Base
  SLEEP = 5
  class << self
    def method
      loop do
        time = Time.now.strftime("%H:%M %S")
        self.core time
        sleep SLEEP
      end
    end

    def core(time)
      open("#{RAILS_ROOT}/log/test_log.txt", "a") do |f|
        f.write(time + "\n")
      end
    end

  end
end

ターミナル上で

$ ruby script/runner -e production ModelClass.method

を実行すると、log/test_log.txt に 5秒毎に現在の時刻が書き込まれる処理が実行されます。

問題は、この処理を永続化したい場合です。

その場合、上記の処理を daemon 化する必要があります。

daemon についてはこちら

http://markun.cs.shinshu-u.ac.jp/learn/linux/h_06.html

Rubyではスクリプトを簡単にdaemon化する処理が既に用意されているのですが(http://homepage1.nifty.com/~tetsu/ruby/tool/daemon.html)、ActiveRecord の支援を受けたいと考えた自分は、「より Rails らしい」 daemon プロセスの作成法を考えてみました。

使うのは、 gem daemons です

http://daemons.rubyforge.org/

mongrel が依存している gem なので、Rails を使う多くの方々は知らず知らずのうちにインストールしていることと思われます。

そして下記の内容のファイルを作成し、[RAILS_ROOT]/script/daemon という名前で保存します。

require File.dirname(__FILE__) + '/../config/boot'
require 'rubygems'
require 'daemons'

Daemons.run(
  "#{RAILS_ROOT}/script/runner",
  :app_name => "rails_daemon",
  :dir_mode => :script,
  :dir => "../log",
  :multiple   => true,
  :keep_pid_files => false
)

オプションの意味については、詳しくは下の参考文献を参照くださいませ。

そして、ターミナル上で、実行したい処理を下記のように実行します。

$ ruby script/daemon start -- -e production ModelClass.method

(「–」だけ忘れないで下さい)

すると、ModelClass.method の中身が、「rails_daemon」というプロセス名で、daemonとして実行されます。

$ ps ax | grep rails_daemon
 1685   ??  S      0:01.90 rails_daemon                                          
 1690 s002  R+     0:00.00 grep rails_daemon

終了するときは下記になります。

$ ruby script/daemon stop

今のオプションでは、daemon はオプションを変えて複数プロセス作成できますが、上のコマンドでその全てが終了します。

上記のファイルを作成することにより、Rails の model のメソッドを、その内部を一切変更することなく、Rails 使いの方々が慣れ親しんだ文法で、daemon プロセス化することが可能になりました。

こちらの daemon は多重起動ができる設定にしているため、渡すメソッドによって、様々な処理をそれぞれ daemon プロセス化することができます。また、ARGV をうまく指定することにより、daemon プロセス名もより分かりやすいものに変えることもできるでしょう。

もっとシンプルに指定が出来る daemon を作成したい場合、例えば、

require File.dirname(__FILE__) + '/../../config/boot'
require 'rubygems'
require 'daemons'

def daemon_run(running_command, mode = "run", rails_env = "production", daemon_name = nil)
  if daemon_name.nil?
    daemon_name = running_command
  end
  argv = [mode, "--", "-e", rails_env, running_command]
  Daemons.run(
    "#{RAILS_ROOT}/script/runner",
    :ARGV => argv,
    :app_name => daemon_name,
    :dir_mode => :script,
    :dir => "../log",
    :multiple   => true,
    :keep_pid_files => false
  )
end

という内容のファイルを [RAILS_ROOT]/lib/daemons/base.rb という名前で保存し、

require File.dirname(__FILE__) + '/../../config/boot'
require "#{RAILS_ROOT}/lib/daemons/base.rb"

daemon_run("ModelClass.method", "start")

という内容のファイルを [RAILS_ROOT]/lib/daemons/daemon1.rb

という名前で保存したら、

$ ruby [RAILS_ROOT]/lib/daemons/daemon1.rb

で、daemon プロセスを作成できます。(プロセスを止めるときは、 kill する必要がありますが)

※追記

daemon の、run / start / stop が可能なようにしました。

[RAILS_ROOT]/lib/daemons/rails.rb

require File.dirname(__FILE__) + '/../../config/boot'
require 'rubygems'
require 'daemons'

def daemon_run(running_command, mode = "run", rails_env = "production", daemon_name = nil, prefix = "Prefix::")
  if daemon_name.nil?
    daemon_name = running_command
  end
  argv = [mode, "--", "-e", rails_env, running_command]
  Daemons.run(
    "#{RAILS_ROOT}/script/runner",
    :ARGV => argv,
    :app_name => prefix.to_s + daemon_name.to_s,
    :dir_mode => :script,
    :dir => "../log",
    :multiple   => true,
    :keep_pid_files => false
  )
end

[RAILS_ROOT]/lib/daemons/daemons1.rb

require File.dirname(__FILE__) + '/../../config/boot'
require "#{RAILS_ROOT}/lib/daemons/base.rb"

type = "start"
if ARGV[0] && ["start", "stop", "run"].index(ARGV[0])
  type = ARGV[0]
end

daemon_run("ModelClass.method", type)

参考文献

http://labs.unoh.net/2007/10/daemons.html

http://daemons.rubyforge.org/classes/Daemons.html#M000004

記事を共有

最近人気な記事