resqueとRails

19th Jan, 2010 | ruby rails resque

前回 の続き(だがあんまり書くことなかった)

Railsとの話の前に、前回書き忘れてしまったのだけど、resqueには、1日1回実行する、と言ったスケジューリングの機能はない。スケジュール機能は別のそういう機能を持ったソフトウェアに任せる(代表例: cron)か、自分で作る必要がある。また、resque-scheduler といresqueのプラグインタイプの物もある。現在どの方法が良さそうかか評価中なのでそのうち書く。

さて、Railsとの連携だが、resque自体がそもそもGitHubのRailsシステム用に作られたという経緯から、もちろん非常に親和性が高い。たとえば、worker毎にRailsのEnvironmentが一回ロードされるだけなので余計な資源を食わなかったり、RailsアプリのWeb UIから非同期な処理の扱いなども簡単にできる。

さて、インストール

./script/plugin install git://github.com/defunkt/resque

gemでインストールする方法ある。詳しくは http://github.com/defunkt/resque 参照。

設定

これも特に難しいところはない。前回も書いたように必要になるのは Redis サーバを指定するところのみ。作るファイルは3つ。全部上記READMEに書いてある。

  1. config/initializers/resque.rb
  2. lib/tasks/resque.rake
  3. config/resque.yml

Workerの起動

インストールが完了するとRailsのrake taskに以下の二つが追加される。

  • rake resque:work                           # Start a Resque worker
  • rake resque:workers                        # Start multiple Resque workers

このコマンド経由で実行するとRailsの環境を読み込んだWorkerが起動し、Jobの中でRailsとまったく同じ環境でプログラミングすることができる。

optionとかは単体での起動と同じ。

例: QUEUE=default rake resque:work

非同期処理

Web系アプリではレスポンスの速さがとても大事なので、処理に時間がかかる部分は、とりあえずレスポンスだけユーザに返してしまって、残りの処理は裏でやるということがよく行われる。resqueを使うとこの処理を簡単に行うことができる。と言ってもresque側で何か特別な非同期用の仕組が用意されているわけではなく、Web側のアプリでは、単に Resque.enqueue を呼ぶだけでの話。そして、Jobの中で再度元のクラスのmethodを呼ぶということで簡単に実現できる。この辺、変に凝った仕組ではなくとても簡単でいい。

まとめ

なんか色々なことが簡単にできてしまって、何かを見落としているのではないかという気分になるresque。まだ実戦投入してないので、その後見えてくることも多いと思うので、解り次第また何か書く。

追記: 実戦投入した



GitHub製Resqueを使用したRubyでのバックグラウンド処理(バッチ処理)

18th Jan, 2010 | ruby resque

そこそこの規模のWebシステムになってくるとバックグランド処理(batch処理)は欠かせないものになってくる。メールの送信、データの日次、月次、年次処理、削除(フラグ)データのpurgeやバックアップ、等々いろいろな物が出てくる。

現在はBackgrounDRbを使っているが、いろいろといまいちなので今回Resqueを評価してみた。ちょっと触った段階での第一印象をメモ。

まず、バッチ処理系で評価のポイントになってくる部分はなんだろうかと考えてみると、なんと言っても見通しのよさと異常系の処理だと思う。画面系と違い、バッチ処理は「見えにくい」ところで実行されるので、その二つが特に大事になってくる。「知らないうちに止まっていました」では困るのがバッチ処理。

たとえば、

  • 異常時の処理
    • 無視?
    • 管理者に通知?
    • リトライ?
  • 復旧処理
    • タスクの削除
    • (問題を修復後)リトライ
  • 状態の監視
    • いくつのJobが残っているか
    • 失敗したJobの数はいくつか
     

そして重要なのは、上記の処理を任意の粒度で実行でき、それぞれが独立して動くこと。一つのタスクが失敗した場合に、色々な他のタスクもコケる、では困る。

これに加えて複数サーバでの並列処理や、二重化、管理機能、Queueの扱いやすさ(優先度や並列実行)など、Batch処理管理としての基本機能が考慮ポイントになる。それと、今回の自分のニーズとしてはRuby on Railsとの親和性の高さも大事になってくる。これらを踏まえて評価してみた。

さて、実際にResqueを触ったみた印象だが、「これ系のソフトウェアは使い方が難しい」という前提で、気合を入れて調査を始めたのだが、Resqueは非常に簡単だった。シンプルに物を考え、ソフトウェアを作るって大切だな、と改めて強く思った。(注: シンプルに考える != 機能を削りまくる)

まず、Resque でのJob管理がどういう構成になるのかを自分の理解したところを書いてみる。(ResqueのREADMEをまとめただけ)

Resqueの構成例:

単純にこれだけだ。この例では、Worker 1はQueue Aを専門で処理。Worker 2はQueue Bを優先的に処理し、Queue BのJobがなくなったら、Queue C, Queue Aの順で実行する。

また、Jobは以下のルールで処理されていく:

  • workerは自分が担当するqueueの中から優先度の高いものから優先的に処理する。
  • queueに登録されたjobは登録された順にworkerで実行される。
  • 1つのworkerは1つずつjobを実行する。
  • workerが実際のrubyプロセスで、Queueの中から一つずつjobを実行する。
  • queueはrubyのコードで定義され、jobはredisに格納される。
  • 複数のworkerが同じqueueを担当することができる。この場合でも当然1つのjobは全体で一回しか実行されない。
  • 複数の物理サーバでworkerを動かすことができる。
  • workerの実行は任意のマシンでコマンドを実行するだけ。複数台に跨っていようが、1台で何回に分けてworkerを生成してもいい。
  • workerの追加(実行)、削除(停止)は動的に好きなタイミングでできる。
  • queueの作成はrubyコードで定義する。
  • jobの作成とqueueへの登録もrubyコードで行う。
ほぼすべてのことがRubyのコードだけで定義できるのがかなりの魅力。別途設定ファイルを書いたり、Redisをいじったりする必要がないのがとてもいい。

実際に少し動かしてみる

まずシンプルにRails抜きで、Resque単体で見てみよう。

Resqueはqueueを保存するためにバックエンドにRedisを使う。今回は、ResqueとRuby関連はローカルのMac上に置き、redisはVMWareのFreeBSD上に置いた(参照: 仮想環境の勧め)。

redisサーバのインストールとサーバの起動

FreeBSDではパッケージ化されているので、databases/redisをインストールするだけでOK。他の*NIX系システムでもだいたい同じだろう。

redisはリクエストベースで自動的にデータベースが作られるようで、事前に何か作成しておく必要はないようだ。ただ起動するだけOK。実際に運用に入る前にはもう少しRedisのことも知っておくべきなので後で調査する予定。

関連するgemをインストール

$ gem install redis redis-namespace yajl-ruby rack resque

resqueのgitレポジトリの中にexampleが入っているのでそれを使う

$ git clone git://github.com/defunkt/resque.git

デモは、examples/demoの下に入っている。

Resqueはデフォルトではローカルホスト上にあるredisを使うことになっているので、変更したい場合には、

Resque.redis = 'redis-server.example.com:6379'

と定義する。今回のexampleでは、job.rb のrequire文の後ぐらいに書いておけばいいだろう。もちろん実際の運用では設定ファイルとかに行くべきだろう。

Resqueには、管理Webアプリが付属してるので、それを起動しておく。当然、このWebアプリは管理用なので動かさなくてもいい。

$ cd examples/demo
$ rackup config.ru

ブラウザで http://localhost:9292/ とかで管理画面が開ける。

さて、exampleのjob.rbを見てみよう。

require 'resque'
Resque.redis = 'redis.madoro.org:6379' # Redisサーバの設定

module Demo
  module Job
    @queue = :default

    def self.perform(params)
      sleep 1
      puts "Processed a job!"
    end
  end
end

@queue でこのJobがどのqueueを使うか、というのを定義している。:default というのがそれにあたり、これは任意の物を設定できる。(この例では:defaultだが、:defaultというもの自体には特別な意味はない)

self.performの中が実際にJobで実行される処理の本体。引数は任意の物が設定できる。ここではparamsとなっているが、任意のものを任意の個数設定できる。

このJobを作成しqueueに登録してみる。

$ irb
>> require 'job'
=> true
>>Resque.enqueue(Demo::Job, 'aaa')
=> "OK"

これで、登録が完了した。ただし、まだ1つもworkerを起動していないので、このJobはpending状態として実行はされない。

管理画面で見ると、

こんな感じになる。

次に、このQueueのためのworkerを作成し、このJobを実行してみる。

$ COUNT=1 VVERBOSE=true QUEUE=default rake resque:workers
(in /Users/masatomo/workspaces/resque/examples/demo)
(in /Users/masatomo/workspaces/resque/examples/demo)
*** Starting worker unknown-00-1f-5b-f3-c0-9e.home:85131:default
** [06:50:11 2010-01-18] 85131: Registered signals
** [06:50:11 2010-01-18] 85131: Checking default
** [06:50:11 2010-01-18] 85131: Found job on default
** [06:50:11 2010-01-18] 85131: got: (Job{default} | Demo::Job | ["aaa"])
** [06:50:11 2010-01-18] 85131: resque: Forked 85133 at 1263797411
** [06:50:11 2010-01-18] 85133: resque: Processing default since 1263797411
Processed a job!
** [06:50:12 2010-01-18] 85133: done: (Job{default} | Demo::Job | ["aaa"])
** [06:50:12 2010-01-18] 85131: Checking default
** [06:50:12 2010-01-18] 85131: Sleeping for 5
^C** [06:50:15 2010-01-18] 85131: Exiting...

COUNT=で起動するworkerの数、QUEUE=でこのworker(s)で実行するQUEUEを指定する。QUEUEはカンマ区切りで指定でき、書いた順がそのまま優先順位になる。

複数のサーバでworkerを実行したければ、このコマンドを複数のサーバで実行するだけ。とっても簡単に冗長化や処理分散ができる。

ちょっと、長くなってしまったので今回はこの辺で。次回はエラー処理と、Railsの連携とかを書く。



仮想環境の勧め (VMWare + FreeBSD/Jail)

17th Jan, 2010 | freebsd development

プログラマのための仮想環境の勧めに関しては、以前にも会社ブログの方に書いた。

そっちはどちらかと言うと共用サーバでの話。今回は自分自身が開発したり、ソフトウェアを評価したりするときにも仮想環境(特にJail)は便利だよ、という話。

普段、自分の開発環境はMac OS X(iMac / Macbook)なんだけど、両方ともVMWare Fusionを入れ、その上でFreeBSDを動かしている。そしてさらにそのFreeBSDの中にさらに複数の仮想環境であるJailが動いている。(ちょっとややこしい)

例えば、自宅のiMacの中のFreeBSDには以下のJailが入っている。

% ezjail-admin list          
STA JID   IP              Hostname              Root Directory
--- ----- --------------- --------------------- -------------------------
DR  1     192.168.252.204 rems.madoro.org       /usr/local/jails/rems.madoro.org
DR  2     192.168.252.205 rems3.madoro.org      /usr/local/jails/rems3.madoro.org
DR  3     192.168.252.207 puppets.madoro.org    /usr/local/jails/puppets.madoro.org
DR  4     192.168.252.206 puppetc.madoro.org    /usr/local/jails/puppetc.madoro.org
DR  5     192.168.252.208 puppetc2.madoro.org   /usr/local/jails/puppetc2.madoro.org
DR  6     192.168.252.203 pgsql84.madoro.org    /usr/local/jails/pgsql84.madoro.org
DR  7     192.168.252.202 pgsql82.madoro.org    /usr/local/jails/pgsql82.madoro.org
DR  8     192.168.252.211 mongodb.madoro.org    /usr/local/jails/mongodb.madoro.org
DR  9     192.168.252.209 mogilefs.madoro.org   /usr/local/jails/mogilefs.madoro.org
DR  10    192.168.252.212 gerrit.madoro.org     /usr/local/jails/gerrit.madoro.org
DR  11    192.168.252.201 couchdb.madoro.org    /usr/local/jails/couchdb.madoro.org
DR  12    192.168.252.210 couchdb2.madoro.org   /usr/local/jails/couchdb2.madoro.org
DR  15    192.168.252.213 redis.madoro.org      /usr/local/jails/redis.madoro.org

これだけの仮想環境がVMWare上のFreeBSDに入っている。使わなくなった仮想環境は消しているので、上にあるのは現役で使っている仮想環境のみ。オーバヘッドが大きそうだなあ、と思う人もいると思うがVMWare的に割り振っているメモリは、かなり余裕を持たせて1GB。少し前までは512MBだったがDatabase系がメモリ食いなので増やした。最近はノート型PCでも4GBとかいけるので、1GBくらいなら許容範囲だと思う。CPUやディスク負荷的には余裕。もちろん激しいことをしたら全体が遅くなるが、そういうことが必要になった場合(たとえば負荷テストとか、大規模データ処理とか)には別の環境を用意すればいいだけの話。

デスクトップ用途のマシンにサーバソフトウェアは入れない

デスクトップ用途のMac上に色々なサーバソフトウェアを入れるのは好きではない。Macに限らず、Windowsでもなんでも「デスクトップ用」として使ってる環境にサーバ系ソフトを色々入れるのはよろしくない。とは言え、開発環境としてはオールインワンで開発に必要なすべての物が物理的な一台に入っていると便利なことも多いので(ちょっと外出先でプログラミングとかね)、自分は仮想環境を使っている。

仮想環境のすばらしいところ

で、仮想環境のいいところは、たとえば、たった今 redis.madoro.org をResqueの評価用に作った。現在評価中なんだが、その後「やっぱりResque使わない」となったとしよう。この場合ばっさりその仮想環境を消せば、すべてがなかったことになる。他の環境がまったく汚れない。もっとすばらしいのは、独立した仮想環境にインストールすることによって、「Redisをインストールしたら、PostgreSQLが動かなくなった!」(例です)とかアホな事態が避けられること。また、仮想環境ひとつずつが独立したIP addressで動くので、port 80がかぶるから、こっちは8080で動かそう、とか本質でないことを考える時間も節約できる。アホなことで時間を使わない、のが生産性向上の第一歩。

なぜ、FreeBSDのJailなのか

こういうことをするのには、もちろんどんな仮想環境でもいいんだけど、FreeBSDのJailを勧めたいのは、軽くて早いから、早いというのは、特に、インストールやアンインストールにかける時間の面がでかい。人間的な手間も少ないし、実際に作成にかかる時間も少ない。慣れれば1分とかで新しい環境ができる。Jail以外の仮想環境も以前は使っていたけど、怠け者の自分には色々手がかかりすぎで使わなくなってしまった。たとえば、「ちょっと空いた時間にResqueの評価がしたくなった。でも環境の設定に1時間かかる」とかだと一気に冷めてしまう(自分は短気なので5分でも冷めるかも)。

なんか、だらだらと書いてしまったけど、このFreeBSD/Jailを使った環境は、自分のこの10年の中でも革新的なことだったので、つい熱くなってしまう。紹介してくれた同僚の某氏ありがとう!



nginxを使った簡単快速reverse proxy+cacheサーバ構築法

16th Jan, 2010 | nginx

ここのブログは、nginx(proxyサーバ)が外からのアクセスを受け、それを thin + sinatra (アプリケーションサーバ) と mongoDB (データベースサーバ)で処理する、というWebシステム定番の三層構造で構成している。

ただ、見てわかるようにほぼ静的なコンテンツのサイトなので、アクセス毎にアプリケーションサーバを走らせる意味がない。また、このVPSの一番安いコースにおいているので、あまり贅沢に資源を使いたくない。と言ったことから生成したhtmlをキャッシュして2度目のアクセスからはアプリケーションサーバやデータベースにアクセスしないようにしている。

Webシステムによっては、アプリケーションサーバで静的なhtmlファイルを作成し負荷の軽減をしたりするが、キャッシュファイルを自前で扱うのはvalidation等色々だるいので、このブログシステムではnginxに任せている。

今回はその設定の紹介

基本的にnginxは設定が簡単なので超オススメ。キャッシュの設定はたったこれだけ。全体の設定ファイルはこっちに置いとく。

http {
  proxy_cache_path /usr/local/nginx/cache levels=1:2 keys_zone=cache-space:4m max_size=50m inactive=120m;
  proxy_temp_path /usr/local/nginx/tmp; 

  server {
     location / {
      proxy_cache cache-space;
      proxy_cache_valid  200 302  60m;
      proxy_cache_valid  404 20m;
    }
  }
}

簡単に説明すると、まず、最初のproxy_cache_pathとproxy_temp_pathでキャッシュが格納されるディレクトリとサイズ等を指定する。詳しくはこの辺を見た方が早いが、この設定では、キャッシュ用に、メモリを4M使う、ディスクの最大サイズは50M、120分間アクセスがないキャッシュファイルは削除、の意味。

localtion / 内で、実際にどういうものをキャッシュするか決める。proxy_cache_valid には、httpのレスポンスコードと、そのレスポンスコードを返したときのコンテンツをどのくらいの時間キャッシュするかを書く。

この場合、正常処理である200または302を返した場合、そのコンテンツをnginxが60分間保持し、その後60分間、同じURLでアクセスがあった場合、アプリケーションサーバ(ここの場合はSinatra)に問い合わせずキャッシュをそのまま返す。

追記1:

     Twitterにて: 「なぜ404は20分にしているのか」というのがあったが、正直なところあまり深い意味はないが、設定したときは以下のことを考えていた。

  1. 404を返すだけなら、アプリケーションサーバの負荷はたいしたことなさそう。
  2. 404はエラーなので(相手側のエラーの場合と、実際にファイルがない場合)、何か直す必要がある場合にはすぐ反映されるように。

追記2: 

     Twitterにて: 「データ更新をしても最大60分間は実際の更新が行われないということか」というのがあった、そのままだとそうなる。でも自分は更新時の処理でCacheをクリアしている。ただし、NginxにはCacheを消すという正式な方法がまだ用意されていないのでアンオフィシャルな方法である「単純にcacheディレクトリの下のファイルをすべて消す」というので運用している(cacheディレクトリの下のディレクトリは消してはダメ)。今のところそれで問題は起きてない。追記: 部分的にcacheをpurgeする方法を書いた。

abを使って簡単なベンチマークを取ってみた

nginxのキャッシュなしの場合:

% ab -n 100 -c 20 http://blog.madoro.org/mn/14
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking blog.madoro.org (be patient).....done


Server Software:        nginx/0.8.31
Server Hostname:        blog.madoro.org
Server Port:            80

Document Path:          /mn/14
Document Length:        5844 bytes

Concurrency Level:      20
Time taken for tests:   9.049 seconds
Complete requests:      100
Failed requests:        0
Write errors:           0
Total transferred:      605444 bytes
HTML transferred:       588577 bytes
Requests per second:    11.05 [#/sec] (mean)
Time per request:       1809.781 [ms] (mean)
Time per request:       90.489 [ms] (mean, across all concurrent requests)
Transfer rate:          65.34 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:      161  174   9.2    173     202
Processing:   450 1522 301.2   1456    2268
Waiting:      224 1090 286.8   1059    1893
Total:        613 1696 306.1   1620    2454

Percentage of the requests served within a certain time (ms)
  50%   1620
  66%   1789
  75%   1808
  80%   1822
  90%   1853
  95%   2439
  98%   2449
  99%   2454
 100%   2454 (longest request)

nginxのキャッシュありの場合

% ab -n 100 -c 20 http://blog.madoro.org/mn/14
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking blog.madoro.org (be patient).....done


Server Software:        nginx/0.8.31
Server Hostname:        blog.madoro.org
Server Port:            80

Document Path:          /mn/14
Document Length:        5844 bytes

Concurrency Level:      20
Time taken for tests:   3.236 seconds
Complete requests:      100
Failed requests:        0
Write errors:           0
Total transferred:      656124 bytes
HTML transferred:       636251 bytes
Requests per second:    30.90 [#/sec] (mean)
Time per request:       647.157 [ms] (mean)
Time per request:       32.358 [ms] (mean, across all concurrent requests)
Transfer rate:          198.02 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:      162  177  10.1    174     201
Processing:   329  389  74.3    351     791
Waiting:      164  176  16.7    167     234
Total:        494  566  75.5    527     957

Percentage of the requests served within a certain time (ms)
  50%    527
  66%    599
  75%    617
  80%    628
  90%    652
  95%    667
  98%    817
  99%    957
 100%    957 (longest request)

格段にレスポンスがよくなっているのがわかる。

このような並列のアクセスだけなく、単体のアクセスのレスポンスも早くなるので、負荷が高いサイトだけでなく(ここのような)零細なサイトでも恩恵を得ることができる。最近のコンテンツは動的な部分にJavascriptを使うことが多くなっているので、このようなキャッシュがしやすくてよい。



Chrome 広告

15th Jan, 2010 | chrome

Chromeがたまーに落ちるとこれ思い出す。地下鉄のホームでやってる広告。日本でもやってるのかな?