その他
    ホーム 技術発信 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