読者です 読者をやめる 読者になる 読者になる

平凡なエンジニアの独り言 はてなブログ出張所

ピアノをこよなく愛するエセRubyistが適当に書き綴ります

MongoDBにRubyからアクセスできるMongoRecordを使ってみている

ゲームを作れるウェブサービス - Rmakeでは、MongoDB : C++ で書かれた高速なドキュメント指向DB (ヽ( ・∀・)ノくまくまー(2009-07-02))にて紹介されているMongoDBとMongoDBにRubyからアクセスできるMongoRecordを使ってみています。使った感じが良かったので、簡単な使用例と適用方法を紹介します。

元記事を読んでMongoDBとMongoRecordのセットアップが終わっている人向けです。ただ、MongoRecordのインストールは以下のInstallationを読んでおく方が良いかもしれません。

検索関係

NoSQLなDBを使う場合、どれくらい検索機能が充実しているかが一番気になります。MongoDBはトランザクションを扱うことには向いていませんが、検索機能は充実しています。

まずは基本的な値から検索する方法について。

@hoges = HogeModel.find(:all, :conditions => {
  :foo_id => foofoo_id
})

範囲検索も以下のような書き方ができます。

# 以下のケースはnanika_value1 < nanika < nanika_value2 になります。
# また、$gteのようにeを付けると、<= になります。
@hoges = HogeModel.find(:all, :conditions => {
  :nanika => {"$gt" => nanika_value1, "$lt" => nanika_value2}
})

時間を使う場合は以下の通り。

# fugafuga.day.ago < created_atになります。
@hoges = HogeModel.find(:all, :conditions => {
  :created_at => {"$gt" => fugafuga.day.ago}
})

配列を使う場合は以下の通り。

@hoges = HogeModel.find(:all, :conditions => {
  :foo_id => {"$in" => id_array}
})

正規表現は以下の通り。

@hoges = HogeModel.find(:all, :conditions => {
  :var_name => /hogenonamae/
})

where句も使えます。JavaScriptで書くんですね。なんだかとても便利です。

_js = <<-EOS
function() {
  return this.hoge_id == 123 && this.fuga_id == 456;
}
EOS

@hoges = HogeModel.find(:all, :conditions => {"$where" => _js})

:orderやlimitも使えます。

@hoges = HogeModel.find(:all, :order => "name desc", :limit => 10)

他にも、find_by_*とかfind_or_create_by_*とか便利な機能があるようです。その他の検索方法を知りたい場合は以下を参照してください。

また、MongoRecordでは物足りない場合は、MongoRecordのベースになっているMongo-Ruby-Driverを直接さわっても良いでしょう。意外と(?)難しくありません。

生成、更新、削除

これはActiveRecordとあまり変わりありません。

@hoge = HogeModel.new({:hogehoge => hogehoge_data})
@hoge.name = "nanika"
@hoge.save

# 削除
@hoge.delete

どこに適用するか

範囲検索ができるとはいえ、NoSQLなMongoDBであることと、今のところActiveRecordのようにリッチな機能を備えているわけでもないMongoRecordを使うことを考慮に入れる必要はあります。たとえば結合を使いまくるようなリッチなモデルには適用せずに、アクセスの頻度が多くて、しかし結合をそれほど使わないモデルを置き換えるのが良いかなと考えています。

また、スキーマレスですが、JSONチックなデータ構造で保存するMongoDBでは、カラム名も行に保存されることになりますから、ファイルサイズが大きくなることが予想されます。カラム名を短く保存するように作っても良いですが、データ構造が単純な物を選択することも一つのアイディアです。元記事に書かれているとおり、Rubyオブジェクトをそのまま放り込む(もしくは圧縮してから放り込む)のも一つの手です。

これだけ見ると、複雑なモデルを利用する場合に使えなさそうに見えます。ただ、モデルが「子から見て親が一つに定まるツリー構造」で表現でき、かつ、親子をまとめて取る場合が多いのなら(つまり、子だけを取り出す必要があまりないのなら)、Ruby上のオブジェクトモデルをそのままDBに放り込めばいいのですから、複雑なモデルをMongoDBに格納してもこれといったデメリットはありません。

ちなみに、Rmakeではゲームのセーブデータなどに使っています(この機能はまだリリースしていませんが)。セーブデータへのアクセスは、ゲームIDとプレイヤーキャラクタIDから検索する単純な利用ケースしか無く、データ構造はテキスト形式でひとまとまりになっているだけなので、カラムの構造もシンプルです。他にも使っていますが、同じような要件です。最悪、すぐにMySQLに逃げられそうかつ、消滅してもそれほど問題にならなさそうなデータを対象にしています。運用実績が増えてくれば、もっといろいろなところに気兼ねなく使えるようになるでしょう。

結論

Railsで違和感なく使えることと、インストールが容易であることを考えると、MySQLの補助的な利用にとどまらず、もっと積極的な利用を検討しても良いかもしれません。元記事にあったとおり、通常の使い方をする限りではパフォーマンス的にはかなり有利なようです。また、以下の通り、MongoDBの事例は海外ではまずまず出てきているようです。

バッチ系もMapReduce機能を使えば大規模な物も処理できそう(まだ使ったことがありませんが・・・)です。運用系は抑え切れていないのですが、Auto-Sharding(サーバを追加すれば勝手にスケールしてくれる仕組み。ここは期待しているところなのですが。)などは将来に期待と言わざるを得ないようです。レプリケーション機能はあるので、それなりにはスケールするようです。この辺はもう少し具体例を聞いてみたいところです。

そういえば、Windows Vistaでも問題なく使えています。Vistaで、NetBeans+Rails+MongoDB+MySQLって感じで開発しています。結構快適です。

MongoDBが「来る」かどうかはともかく、パフォーマンスやスケーラビリティ上の理由から、NoSQLの重要性は高まっていくことでしょう。幸い、Rubyの世界では生産性をほとんど落とさずに(この容易さなら他の言語でも同様の状態だと思いますが)導入することができます。どんどん試して今のうちに試行錯誤しておく方が賢明でしょう。