そもそものきっかけは、ここ半年間くらいRuby on Rails(以下RoR)で開発していることにある。
ここ半年弱ほどRoRで開発をして、それなりに満足しているのだけど、ActiveRecordに関しては色々とひっかかるところがあった。
「ActiveRecordがRoRの素晴らしいところそのものだ」と評価している人もいるが、自分の中では逆で、ActiveRecordはRoRの中でもかなりいまいちな部分。
いや、ActiveRecordというよりも、O/Rマッパーの限界なのかな。実際、ActiveRecordはO/Rマッパーの中では、いい方だと思う。今まで、自分用O/Rマッパーを作ったり、それなりの数のO/Rマッパーを見てきたけど、そういうのに比べても、確かにActiveRecordはよくできている。
でも、RoRがよくできたフレームワークなだけに、O/Rマッパー経由RDBMSを使うことに、色々な無駄を感じてしまう。
たとえば、1つのテーブルから同じ条件で検索し、複数のインスタンスを作るために、primary key(id)を検索条件として、何度も検索するとか。「そこはSQL一発で持ってこいよ」と思ってしまう。またそこからlazyに外部キーで他のModel(テーブル)のオブジェクトを持ってこようとすると、またprimary keyでの検索が走る。
一人のユーザが一つの画面を開くだけでSQLを何十回も発行することも普通に起こる。これはどう考えてもSQLの使い方として間違っている。SQLはそういう風に作られていないので、結局(RoR自体を含めた)アプリケーション側の別のレイヤーでインスタンスをCacheしたりと、どんどん複雑になっていく。
そういうのが嫌で、RDBMSとして正しく使おうと、find()で:includeとか:joinsとか使いだすと、今度はどんどんRoR(Ruby)のコードのSQL成分が増していく。全然楽しくない。
つまり、
- SQLっぽく書かずに、Ruby(ActiveRecord)っぽくきれいに書くように頑張る
- でも、ロジック的/パフォーマンス的に頑張るのが限界だった場合に、SQLっぽいコードをRubyに書くことになる
- そうこうしているうちにRubyらしい可読性の高いコードは消えSQLっぽいコードにまみれる
この流れにうんざりする。そもそも1.の段階でも脳内では普通にSQLのことを考えている。だったら最初からSQL書けよ、と思ってしまう。
結局、O/Rマッパーで、きれいに書けるところは、検索した結果でオブジェクトを作る、または、primary *key*指定で持ってきてそれを元にオブジェクトを作る。これだけ書けて、十分な性能が出るなら、RDBMSでなくても、(そこそこの機能を持った) Key Value Storageで十分なんじゃないのかな、とぼんやりと思っていた。
もう一つActiveRecordで嫌いなのが、Modelのコードにカラムの情報を持たない、というとこと。DRYのためだかなんだか知らないが、これはどうみても欠点だと思っている。Modelのコードを見ただけでどういう情報が入っているかわからない。migrationのコードはツギハギだらけで後から見るには向いていない。どういうModelか、ということを知るためには、動いているデータベースにアクセスしてスキーマ情報を見るしかない。かっこ悪い。
ってなことを、漠然と思っていた頃に、タイミングよくNoSQL(KVS)ブームが到来。「ああ、これだ」と思って、ブームに乗っかって、色々なKVSを試している中で、
「これなら最近のWeb開発の多くの部分でRDBMSを捨ててもいけるんじゃないかな」
と感じさせてくれたのがMongoDBだった。
数あるKVSの中でMongoDBの何がよかったのかと言うと、
- RoRのModelにぴったりと合いそう。上にも書いたように、KeyとValueの関係が、そのままModelのIdとInstanceの関係になりそう。
- Valueの部分はスキーマフリー&ダイナミックに作成可能なので、Ruby側のコード主導でカラムの追加をすることができる。Rubyのコードでのカラムの定義が「正」になる。もちろんDRYも守られたまま。
- SQL-likeなこともできる。where句(のようなもの)を指定したり、maxとかminとかgroup byも使えるし、やろうと思えばストアドプロシージャ(Javascript)のようなこともできる。この辺は、SQL脳の人(チーム)にとっては移行するためのハードルが低そう。
- 上記を踏まえた上で、KVSらしく、RDBMSでは厄介なスケールさせることも考えられていて、Shardみたいなこともデフォルトで用意されている。
とまあ、こんなところ。
つまり、最近のリッチなフレームワークの中では、DB側にそれほどリッチな機能はいらない。RDBMSはそのいらないリッチさがあり、そのせいでスケールさせるのが難しくなったりもしている。だったら、軽量でコンパクト、でもそれなりの機能を持って、そしてスケールしやすいMongoDBで十分なんじゃないのかな、と思った。
もしMongoDB始めてみようって人がいたら、こちらをどうぞ。
追記: CouchDB編もできたみたいです。私は、Railsは好きです。念のため。
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。まだ実戦投入してないので、その後見えてくることも多いと思うので、解り次第また何か書く。
追記: 実戦投入した
RSSリーダで読む