これとこれの続き。この後、もう少し調査して、Resqueを実際のシステムの一部で使い始めてみたのでその感想とメモ。
前回までのあらすじ
Resqueはバックグラウンドでジョブの実行をするもので、かなりの大規模サイトでかつ更新系の処理が多そうなシステムであるGithubで開発され使われている。よくある使い方としては、「Web UIを軽く見せるため、処理の依頼だけを受け付け、実際の処理はバックグラウンドで実行」「バッチ処理などで、大量のJobをQueueに突っ込んでおいて、(複数の)workerで並列で効率よく処理」などがある。
不安なところ
Resqueの大きな特徴は、QueueをRDBMSではなくRedis上に作るところにある。Redisは、Memcacheのようにシンプルに使え、すべてのデータはメモリ上に展開されるのでとても速く、データはディスク上にも永続化されるので、何かあったときにもMemcacheと違いデータが揮発しないのが特徴のkey-value型のデータベース。
今回Resqueを導入したWebシステムは、よくあるRDBMSを使ったものなので、Resqueのために新たに別な種類のデータベースを導入することへの躊躇いが多少あった。サーバ設定の手間も増えるし、各開発者の開発環境のセットアップの手間も増える。また、常時使うデータベースのようなものが一つふえると言うことはメンテナンスや障害時の手間も増える。また、RDBMSならすぐ中を確認できるが、Queueの状況が見づらいのではないか、と言った心配もある。
正直なところ、上記の不安はまだ全部それなりに残っている。が、魅力も見えてきたのでとりあえず使ってみることにした。実際に使ってみないとわからないこともあるしね。
良さそうなところ
上記のような不安がある中で、導入に踏み切った大きな理由として、QueueをRDBMSに置かずにRedisに置く、というのが実際の運用ではかなり良さそうというのがある。
メインで使っているRDBMSと同じところにQueueを置くと、RDBMSの影響をモロに受けてしまう。せっかく重い処理をバックグラウンドでするためにResqueを使っても、そのQueueの登録部分が重くなってしまっては意味がない。Queueの部分が別DB(Redis)だとこの辺のことに影響されず「とりあえずQueueに入れる」「後はサーバの状況に合わせQueueを順番に処理する」といったことができるようになる。
後は、Resque自体の作りが非常に良さそう。例えば、 convention over configuration な感じがよくできていて、新しいQueueやJobを作るときにあまり難しいことを考えずに、Rubyのコードを書くだけで済む。
Deployに関して
Resqueには、起動スクリプトのようなものが添付されていないので、何か自分で起動させる方法を考えないといけない。Githubではgodを使っているようだ。今回うちでは普段使っているCapistranoとMonitを組み合わせてDeploy時に(再)起動とプロセスの監視をすることにした。また、Resque(のworker)は、自分のプロセスとしてRailsの環境を読み込みので、アプリケーションのコードが変わった際には再起動の必要がある。
まず、Resqueはpidを保存してくれたりしないので、monitのFAQに従い、簡単な起動用のWrapperを書きそれをmonitに監視させている。Capistranoでのdeployプロセスでは、monitのrestartコマンドを呼ぶだけでResqueのworkerも再起動してくれるようになった。
ちょっとこの辺が最初だけとは言えめんどくさい。
監視に関して
ここがまだ弱いのだけど、とりあえずResqueに添付されているWebアプリケーションを目視するという弱い方法で行っている。ここは改善予定。待て次号!
とりあえず、こんなところ。
たまには読んだ本の感想。ちょっと前の本だし、有名どころだけどお世話になったので感謝の気持ちを込めて。
プログラミング言語などの新しい技術を習得するとき、最初の数日〜数週間は本を集中して読むことにしている。Web+検索エンジンは知りたいことがわかっている場合には便利だけど、何も知らない状態ではやはりまだ本に分がある。ある程度の知識を得てからはWebに頼り、一通りわかるようになった後で再度本を漁るという感じが多い。今のところこれが最速学習法だと思っている。
重い腰を上げ、Rubyを本格的に始めたのが半年ほど前。そのときに読んだ本の中の一冊がこれ。何冊か目に開いた本だが、今思えば最初に読むべきだったと強く思う。
この本は、他の言語をそれなりに知っている人がRubyを初めて学ぶ、ということに完全にフォーカスしている点が本当に素晴らしい。「入門〜」「はじめての〜」「〜日で覚える〜」系の本は、簡単過ぎたり、(自分の知識に対して)冗長過ぎていたりするのだが、この本はそれがほとんどない。また、Ruby系の良書で「アジャイル」とか「デザインパターン」とか「Rails」とかとセットになっているのは英語含めいろいろあるのだけど、Ruby単体を学べる良書というのは意外と少ない。やっぱり何か学ぶには基礎をしっかりやるべきなので単体で学べるのは嬉しい。
それなりにプログラミングの経験があると自負しているが、最初から最後までページを飛ばすわけにはいけない作りになっている。すべてのページがとても濃い。1度読んだ後も、開発中(今も)手元に置いてしばしば手にとって復習している。
(余談。この本を読んで思い出したのが、たのしいUNIX、という本。始めてUNIXを学んだのがこれだった。それも同じような雰囲気を持った本だった。平易な文体だが勘所は抑えている入門用の本。読んだのが10年以上前で、もう売ってなさそうだけど、今でも*NIX入門者にはそれなりに通じる本だと思う。最初にどういう本を読むかって重要。)
これだけRubyがメジャーになってくると、学習の立ち上がりの部分でだらだらやってられないので、こういう本である程度の知識まで駆け登れるのは本当に素晴らしい。後発学習者のお得なところ。
(個人的な)唯一の欠点は英語版がないこと。同僚に勧められない。1.9完全対応版とか出るならぜひ英語でもお願いしたいところだ。
サポートページ: http://yugui.jp/wiki/LearningRuby
前々回、前回に引き続き、もうちょっとResqueの練習。試しに、このブログの更新情報をTwitterに流す仕組を作ってみた。twitterfeed的なことをする感じで、ブログに投稿したら、Twitterにリンクをポスト、またN時間後にポスト、という感じで。うざそうなので実際には使わないかも。
追記: resque-schdulerのcron的な使い方について別のエントリで書いた。
このN時間後に投稿の部分にresque-schedulerを使ってみた。
Twitterへ投稿するライブラリは、grackleを使ってみた。特に深い理由はない。単純にポストするだけだから何でもいいかな、という感じで、簡単そうに使えそうなのを選んだ。
とりあえずインストール。
gem install grackle
gem install resque
gem install resque-scheduler
Twitterにポストするためのクラスを作成する。このクラスは、 performメソッドを実装する。その中でtwitterに投稿するコードを書く。performの引数としてPostしたい文字列を渡すことにする。
class MyTwitter
@queue = :normal
def self.perform(status)
options = Sinatra::Application.options
client = Grackle::Client.new(:auth=>
{:type=>:basic,
:username=>options.twitter_username,
:password=>options.twitter_password})
client.statuses.update! :status=>status
end
end
Rakefileにschedulerのタスクを追加:
require 'resque/tasks'
require 'resque_scheduler/tasks'
これで、準備完了。
scheulderを起動する。
rake resque:scheduler
後はTwitterに投稿したいところで、
# すぐに投稿したい場合 (resque標準)
Resque.enqueue(MyTwitter, "post post post")
# 10時間後に (resuque-scheduler)
Resque.enqueue_in(10.hours, MyTwitter, "post post post")
# 投稿したい日付が決まっている (resuque-scheduler)
Resque.enqueue_at(Time.local(2010, 1, 31, 10, 30), MyTwitter, "post post post")
と言った感じ。とても簡単。
正確には、実行される時間ではなくて、Queueに入る時間なので実際には少し後に実行される。
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に書いてある。
- config/initializers/resque.rb
- lib/tasks/resque.rake
- 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。まだ実戦投入してないので、その後見えてくることも多いと思うので、解り次第また何か書く。
追記: 実戦投入した
そこそこの規模の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コードで行う。
実際に少し動かしてみる
まずシンプルに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の連携とかを書く。