Rails3 対応 MongoDB ORM、Mongoid 詳解―インストール

まずは、インストールの解説です。

$ gem install mongoid --pre

Rails3 対応の最新版は執筆時点で 2.0.0.beta.16 で、ベータ版なので、--pre オプションを使ってインストールします。2.0.0 リリースの際には、--pre を取って下さい。

そして、mongo ドライバー自体のパフォーマンスを上げるために、bson_ext をインストールしましょう。

$ gem install bson_ext

bson_ext には、boost が必要です。MongoDB のインストール時にインストールされていると思いますので、問題ないと思います。もし、インストールできなくても動きますので、気にしないで下さい。

Rails 3 でのインストール

Rails3 は Bundler を使用しており、必要な Gem を、Gemfile というファイルに書くことになります。

gem "mongoid", "2.0.0.beta.16"
gem "bson_ext", "1.0.4"

2.0.0 リリースの暁には、バージョンの部分を描き直してください。

$ bundle install vendor/bundle

で、vendor/bundle 以下に、Mongoid や依存している Gem がインストールされ、Rails が使用します。Git を使用している方は、.gitignore に vendor/bundle を記入するのを忘れないようにしましょう。

Gemfile を書き換えた後、アップデートをかけるには、

$ bundle update

とします。

設定

Mongoid の設定をするためには、次のコマンドを打ちます。

$ rails generate mongoid:config

すると、config/mongoid.yml が生成され、デフォルトの ORM が Mongoid になるので、rails generate model Foo などとした際に、Mongoid のモデルが作成できるようになります。

それでは、config/mongoid.yml を、自分の環境に合わせて書き換えましょう。

defaults: &defaults
  host: localhost
  slaves:
    - host: slave1.local
      port: 27018
    - host: slave2.local
      port: 27019
  autocreate_indexes: false
  allow_dynamic_fields: true
  include_root_in_json: false
  parameterize_keys: true
  persist_in_safe_mode: false
  raise_not_found_error: true
  reconnect_time: 3

development:
  <<: *defaults
  database: hoge_development

test:
  <<: *defaults
  database: hoge_test

# set these environment variables on your prod server
production:
  <<: *defaults
  host: <%= ENV['MONGOID_HOST'] %>
  port: <%= ENV['MONGOID_PORT'] %>
  username: <%= ENV['MONGOID_USERNAME'] %>
  password: <%= ENV['MONGOID_PASSWORD'] %>
  database: <%= ENV['MONGOID_DATABASE'] %>

host はそのままですね、接続する MongoDB が動いているホストです。
port が省略されていますが、デフォルトでは 27017 番になります。
slaves の項は、スレイブのサーバーがある時です、開発用にはないと思うので、削除したら良いです。

その他のパラメーターは以下を意味します。

autocreate_indexes デフォルトは false です。true にした場合は、モデルクラスがロードされた際に毎回インデックスが付与されます。 development や test 以外の環境では推奨されません。
allow_dynamic_fields モデルクラスにフィールドとして設定されてない属性が呼び出されたときに、オブジェクトに属性を付け加えます。デフォルトは true で、false にした場合は、フィールドとして設定されてない属性に値をセットしたときに、例外があがります。スキーマレスな MongoDB ならではの機能です。
include_root_in_json デフォルトは false です。true にした場合は、モデルで #to_json が呼ばれたときに、JSONのトップレベルにドキュメント名が付き、関連のそれぞれのトップレベルにもドキュメント名が付きます。JSON API を作る際に変更が必要になるかも知れません。
parameterize_keys キーの特殊文字SEOフレンドリーな文字へと置換します。デフォルトは true です。
persist_in_safe_mode 全てのデータベース操作をセーフモードで行ないます。MongoDB の採用している GridFS の機能で、データを保存する際、クライアントとサーバでそれぞれMD5ハッシュを生成し、一致しなかった場合は例外を上げます。デフォルトは false です。true にした場合はパフォーマンスが落ちますので気をつけて下さい。
raise_not_found_error id で検索した際に、ドキュメントがなかった場合に、Mongoid::Error::DocumentNotFound 例外を上げます。デフォルトは true です。false にした場合は、例外を上げずに nil を返します。
reconnect_time データベースとつながらない際に、再接続を試みる最大時間を設定します。デフォルトは3秒です。
skip_version_check MongoHQMongoMachine などを使用しているときに、システムのコレクションが接続を許さないせいで、認証の問題が起きた場合に true にして下さい。
ログ

Rails のデフォルトのログ機能を使いたくない場合に、独自のログ機能を設定できます。
config/application.rb

module Hoge
  class Application < Rails::Application
    ...
    config.mongoid.logger = Logger.new($stdout, :warn)
    ...
  end
end
ActiveRecord の削除

これから Mongoid を使用するので、ActiveRecord はロードする必要がありません。
config/application.rb の先頭部分を以下のように書き換えます。

#require 'rails/all'
require "action_controller/railtie"
require "action_mailer/railtie"
require "active_resource/railtie"
他言語

デフォルトで Mongoid は、英語の国際化ファイルが追加されております。他の言語を追加するには、config/initializers/mongoid.rb を作成し以下の内容を追加します。

# adds Spanish
Mongoid.add_language("es")

現在のところ、以下の言語に対応しています。

  • es: スペイン語
  • fr: フランス語
  • it: イタリア語
  • pl: ポーランド語
  • pt: ポルトガル語
  • sv: スウェーデン語

これら全てを有効にしたい場合は、

# include all language that Mongoid knows about
Mongoid.add_language("*")

とします。

Rails 3 以外の設定

Mongoid.configure ブロックを使用して設定することができます。

Mongoid.configure do |config|
  dbname = "hoge_development"
  host = "localhost"
  config.master = Mongo::Connection.new.db(dbname)
  config.slaves = [
    Mongo::Connection.new(host, 27018, :slave_ok => true).db(dbname)
  ]
  config.persist_in_safe_mode = false
end

YAML ファイル、config/mongoid.yml を作成してロードすることもできます。

file_name = File.join(File.dirname(__FILE__), "..", "config", "mongoid.yml")
@settings = YAML.load(ERB.new(File.new(file_name).read).result)

Mongoid.configure do |config|
  config.from_hash(@settings[ENV['RACK_ENV']])
end


インストールについては以上になります。

Rails3 対応 MongoDB ORM、Mongoid 詳解―前説

つい先日 1.6.0 がリリースされ、MongoDB の時代がいよいよキタ!って感じです。MongoDB 自体のインストール・操作などは下記の記事を参考にしてください。


この記事では、Rails3 にふさわしい ORM として、Mongoid を紹介します。


MongoDB の Ruby ドライバーは、mongo ですが、これは素のドライバーで、Rails などと使用するときは、クラス・オブジェクトに自動でマッピングしてくれる ORM を使用したいところです。

その候補として、

があります、

この内、プロダクションで使われているのは、MongoMapper と Mongoid です。Rails2 では、MongoMapper がよく使われていて、他のプラグインの対応も良かったようですが、Rails3 においては対応が遅れ気味ですし、インターフェイスも Rails2 時代の古いものです。最大の弱点はドキュメントが貧弱なことです。

その点、Mongoid は、Rails3 対応に向けて活発に開発が進んでおり、インターフェイスも Rails3 の AcitiveRecord のベースになっている Arel 風味ですし、公式サイトでのドキュメントの充実ぶりは中々のものです。その上 Devise, CarrierWave といった Rails 定番プラグインも対応しているので、私は Rails3 向けには断然、Mongoid をオススメします。

Sinatra などの軽いフレームワークには、mongo にしか依存していない Candy がいいかもしれません。ただし、Candy は Ruby 1.9 以上対応なので気をつけてください。

というわけで、これから、公式サイトのドキュメントを翻訳し、若干の解説を行ないます。

ハンズオンで分かる MongoDB チュートリアル

前回(と言ってももう2ヶ月前か……1.5.8まででちゃってますね。(追記:丁度今日、1.6.0 安定版がリリースされました!)例によってビルドはクソ長いので注意して下さい)、「Mac OS X で MongoDB を動かす」で、Mac OS X での MongoDB インストール、起動、停止、デーモン化をしました。

今回は、付属のシェルで簡単なCRUD操作をしてみたいと思います。なので、OSやプログラミング言語は関係ありません。素の MongoDB を学ぶ目的で作成しました。SQL 知らないと ORM が満足に使えないように、今回の CRUD 操作を知っておけば、各言語用の ORM で悩むことが少なくなると思います。


なお、今回もいちいちイラッ☆とくる語りが入ります。


復習しておきましょうか、MongoDB っていうのは、

  • ドキュメント指向ストレージで
  • インデックスをサポートしていて
  • レプリケーションができて高可用性に富んでいて
  • 自動データ分割してくれて
  • 多彩な検索クエリが使えて
  • 更新がアトミックで速くて
  • Map/Reduce も使えたりしちゃったりして
  • GridFS っていう何でもつっこめるファイルシステムを使っていて
  • 商用サポートもある

といった特徴を持つNoSQLデータベースです。個人的に、Rails 3 との相性は抜群だと思いますね。


そして、MongoDB での用語を説明しておきます。

RDBMS MongoDB
データベース データベース
テーブル コレクション
レコード ドキュメント

データベースはコレクションを含み、コレクションはドキュメントを含むという関係です。

なんで用語を変えたのでしょうか? それはドキュメント指向データベースだからです! ドキュメントっつーのはアレだ、カラムとか気にしないで何でもぶっこめるっつーこった。すばらしい!が、ちょっと不安ですね〜。アレがないコレがないとかありそうですねー。まー、そこはアプリで頑張るっつーことで、次いきましょう!

では、シェルを起動します。

$ mongo
MongoDB shell version: 1.4.0
url: test
connecting to: test
type "help" for help
> 

ほい、シェルが立ち上がりましたねー。引数省略したので、localhost の test データベースにつながったようです。
ちなみに、正式に書くと、

$ mongo localhost:27017/test

となります。外のサーバに接続するときはこの形式でつないでください。

じゃあ、ちょっくら何が入っているか覗いてみましょうか。show dbs コマンドでデータベース一覧を出します。

> show dbs
admin
local

へー、admin と local ってデータベースがあるんすねー。これは管理用っぽいから今は触らないようにしておきましょうねー。私は覗いてびっくりしましたが、皆さんも何が起きるかドキドキしながら覗くといいですよー。

つーか、test データベースにつながってるって言ったのに、testデータベースないじゃん!と気づいたあなた!お目が高い!そうなんですよー、どうやら、MongoDB はドキュメントの作成時にコレクションもデータベースも作成するっぽいんですよー。LazyLoadっぽいなー。

データベースを変更してみましょう。use を使います。

> use mysql
switched to db mysql

はい、mysql データベースに変更しました。え?ややこしい?気にすんな!

じゃあ、どんなテーブルコレクションが入ってるか調べてみましょう。show collections コマンドを使います。

> show collections
> 

はい、空でした! 当たり前っすね。じゃあ、これから CRUD を説明して行きます。意外と長丁場ですよ、覚悟して下さい!

Create

ドキュメントの作成には insert() メソッドを使います。

> db.things.insert({x: 1})

はい、これで things テーブルコレクションに x が 1 というレコードドキュメントが作成できましたよー。del タグそろそろうざいっすかね、サーセンフヒヒ
あ、コレクション名を thing じゃなくて things にしたのは、Rails風です。

コレクションが出来ているかどうかだけ確認しておきましょうか。

> show collections
things
system.indexes

おお、インデックスも作られている!インデックスサポートはマジだったか。
やはりここは、ドキュメント指向っぽいとこを見ておきましょうか。

> db.things.insert({y: "string"})

MongoDB△ なんでも入る!ガバマンっすね!……ただいま不適切な表現があったことをおわびします……

ところがこれだけじゃないんです!

> db.things.insert({x: 2, a: ["apple", "banana"]})
> db.things.insert({x: 3, b: {p: "hoge", q: "fuga"}})

中に配列やハッシュをぶち込めるんです!かっけー。このドキュメントは配列やハッシュが「エンベッド」されてるといいます。

あ、MongoDB の♪もっといいとこ見てみたい♪そぉれ、いっき♪いっき♪いっき♪いっき♪……

> for (var i = 0; i < 10; i++) db.things.insert({n: i, m: 5});

うぇ……このシェル JavaScript が使えやがる……恐ろしいな……

Retrieve

おまたせ!やっとコレクションの中身が見れますよ! ちなみに CRUD の R を Read とせずに Retrieve としたのは、MongoDB の力から言うとこちらの方が相応しいかなと思ってのことです。KVS とは違うのだよ KVS とは!

じゃあ、早速。find() メソッドで検索します。

> db.things.find()
{ "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 1 }
{ "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" }
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] }
{ "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } }
{ "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa58"), "n" : 1, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa59"), "n" : 2, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5a"), "n" : 3, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5b"), "n" : 4, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5c"), "n" : 5, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5d"), "n" : 6, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5e"), "n" : 7, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5f"), "n" : 8, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9, "m" : 5 }

すげぇ、そのまま飲み込んでるよ僕のエクスカリバー……
_id ってのは、auto increment 列みたいなもので、これがまさにアイデンティティになるわけですね。NoSQL の一種ですからね。これが主キーです。

じゃあ、今のクエリの変種をば、カーソルを使います。

> var cursor = db.things.find()
> while (cursor.hasNext()) printjson(cursor.next());
{ "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 1 }
{ "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" }
{
        "_id" : ObjectId("4c5991101546827805e1aa55"),
        "x" : 2,
        "a" : [
                "apple",
                "banana"
        ]
}
{
        "_id" : ObjectId("4c5991191546827805e1aa56"),
        "x" : 3,
        "b" : {
                "p" : "hoge",
                "q" : "fuga"
        }
}
{ "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa58"), "n" : 1, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa59"), "n" : 2, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5a"), "n" : 3, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5b"), "n" : 4, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5c"), "n" : 5, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5d"), "n" : 6, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5e"), "n" : 7, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5f"), "n" : 8, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9, "m" : 5 }

さっきの find() だけでドキュメントが表示されてたのは、シェルがいい具合に評価してくれてたみたいですね。find() で帰ってくるのはカーソルオブジェクトですね。
printjson() メソッドで表示してますが、これは BSON 使ってるからですかね。BSON というのはですね、Binary JSON の略でして、MongoDB はバイナリ形式の JSON でデータを持ってるんですね。

forEach() メソッド使って、こんなんもできますよー、

> db.things.find().forEach(printjson)
(略)

カーソルって使いでがありますねー、[] もサポートしてるんですね。

> var cursor = db.things.find();
> printjson(cursor[4]);
{ "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 }

もできます。

あと似たようなので、こんなんも。toArray() メソッドを使っています。

> var arr = db.things.find().toArray();
> arr[4]
{ "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 }

JavaScript シェルが自由すぎる……

シェルの多機能さはこれぐらいにして、ORMで発行されそうなクエリをやっていきますよー。

SELECT * FROM things WHERE y="string"

SQLで言うとこんな感じのクエリは、

> db.things.find({y: "string"})
{ "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" }

こうです。簡単ですねー。

じゃあ、SQLでいうカラム指定の場合

SELECT n FROM things WHERE m=5

は、

> db.things.find({m: 5}, {n: true})
{ "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0 }
{ "_id" : ObjectId("4c5991b41546827805e1aa58"), "n" : 1 }
{ "_id" : ObjectId("4c5991b41546827805e1aa59"), "n" : 2 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5a"), "n" : 3 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5b"), "n" : 4 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5c"), "n" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5d"), "n" : 6 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5e"), "n" : 7 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5f"), "n" : 8 }
{ "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9 }

なるほどなるほど、

db.collections.find([query[, cols]])

って感じの関数なんですねー。

一つだけ引きたい場合はどうするんでしょ?

SELECT * FROM things WHERE m=5 LIMIT 1;

的な。

> db.things.findOne({m: 5})
{ "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 }

できますねー。

エンベッドされた値で検索できるのですかね?
まずは配列でやってみましょう。

> db.things.find({a: ["apple", "banana"]})
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] }

> db.things.find({a: ["apple"]})
(ドキュメントなし)

> db.things.find({a: "apple"})
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] }

できますねー。

次はハッシュ。

> db.foo.find({b: {p: "hoge", q: "fuga"}})
{ "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } }

> db.foo.find({b: {p: "hoge"}})
(ドキュメントなし)

> db.foo.find({"b.p": "hoge"})
{ "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } }

なるほどねー。最後の "b.p" みたいなのは、ドットノーテーションと言います。


それではここからは SQL と対応するクエリを駆け足でやっていきましょう。

AND
SELECT * FROM things WHERE n=4 AND m=5;
> db.things.find({n: 4, m: 5})
{ "_id" : ObjectId("4c5991b41546827805e1aa5b"), "n" : 4, "m" : 5 }
OR → $or

※ これは MongoDB 1.5.3 からの機能となります。

SELECT * FROM things WHERE x=2 OR n=2;
> db.things.find({$or: [{ x: 1 }, {n: 2}]})
{ "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 1 }
{ "_id" : ObjectId("4c5991b41546827805e1aa59"), "n" : 2, "m" : 5 }
<, <=, >, >= → $lt, $lte, $gt, $gte
SELECT * FROM things WHERE n<2;
SELECT * FROM things WHERE n<=2;
SELECT * FROM things WHERE n>7;
SELECT * FROM things WHERE n>=7;
> db.things.find({n: {$lt: 2}})
{ "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa58"), "n" : 1, "m" : 5 }

> db.things.find({n: {$lte: 2}})
{ "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa58"), "n" : 1, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa59"), "n" : 2, "m" : 5 }

> db.things.find({n: {$gt: 7}})
{ "_id" : ObjectId("4c5991b41546827805e1aa5f"), "n" : 8, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9, "m" : 5 }

> db.things.find({n: {$gte: 7}})
{ "_id" : ObjectId("4c5991b41546827805e1aa5e"), "n" : 7, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5f"), "n" : 8, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9, "m" : 5 }
<>, != → $ne
SELECT * FROM things WHERE m <> 5;
> db.things.find({m: {$ne: 5}})
{ "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 1 }
{ "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" }
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] }
{ "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } }
IN → $in
SELECT * FROM things WHERE n IN (2, 4, 6);
> db.things.find({n: {$in: [2, 4, 6]}})
{ "_id" : ObjectId("4c5991b41546827805e1aa59"), "n" : 2, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5b"), "n" : 4, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5d"), "n" : 6, "m" : 5 }
NOT IN → $nin
SELECT * FROM things WHERE n NOT IN (2, 4, 6);
> db.things.find({n: {$nin: [2, 4, 6]}})
{ "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 1 }
{ "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" }
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] }
{ "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } }
{ "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa58"), "n" : 1, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5a"), "n" : 3, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5c"), "n" : 5, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5e"), "n" : 7, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5f"), "n" : 8, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9, "m" : 5 }
IS NULL, IS NOT NULL → $exists
SELECT * FROM WHERE x IS NOT NULL;
SELECT * FROM WHERE n IS NULL;
> db.things.find({x: {$exists: true}})
{ "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 1 }
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] }
{ "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } }

> db.things.find({n: {$exists: false}})
{ "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 1 }
{ "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" }
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] }
{ "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } }
LIMIT → limit()
SELECT * FROM things LIMIT 3;
> db.things.find().limit(3)
{ "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 1 }
{ "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" }
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] }
OFFSET → skip()
SELECT * FROM things OFFSET 4 LIMIT 3;
> db.things.find().skip(4).limit(3)
{ "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa58"), "n" : 1, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa59"), "n" : 2, "m" : 5 }
ORDER BY → sort(), $slice
SELECT * FROM things ORDER BY n LIMIT 2;
SELECT * FROM things ORDER BY n DESC LIMIT 2;
SELECT * FROM things ORDER BY n OFFSET 3 LIMIT 2;
SELECT * FROM things ORDER BY n DESC OFFSET 3 LIMIT 2;
> db.things.find().limit(2).sort({n: 1})
{ "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 1 }
{ "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" }

> db.things.find().limit(2).sort({n: -1})
{ "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5f"), "n" : 8, "m" : 5 }

> db.things.find().skip(3).limit(2).sort({n: 1})
{ "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } }
{ "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 }

> db.things.find().skip(3).limit(2).sort({n: -1})
{ "_id" : ObjectId("4c5991b41546827805e1aa5d"), "n" : 6, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5c"), "n" : 5, "m" : 5 }

※ $slice は MongoDB 1.5.1 からの機能になります。

> db.things.find({}, {n: {$slice: 2}})
> db.things.find({}, {n: {$slice: -2}})
> db.things.find({}, {n: {$slice: [3, 2]}})
> db.things.find({}, {n: {$slice: [-3, 2]}})
COUNT → count()
SELECT COUNT(*) FROM things;
> db.things.find().count()
14


ここから先は MongoDB に特有の機能を紹介します。

$where

JavaScript を評価して検索します。

> db.things.find({$where: "this.n > 7"})
{ "_id" : ObjectId("4c5991b41546827805e1aa5f"), "m" : 5, "n" : 8 }
{ "_id" : ObjectId("4c5991b41546827805e1aa60"), "m" : 5, "n" : 9 }

> db.things.find("this.n > 7")
{ "_id" : ObjectId("4c5991b41546827805e1aa5f"), "m" : 5, "n" : 8 }
{ "_id" : ObjectId("4c5991b41546827805e1aa60"), "m" : 5, "n" : 9 }

> f = function() { return this.n > 7; }; db.things.find(f)
{ "_id" : ObjectId("4c5991b41546827805e1aa5f"), "m" : 5, "n" : 8 }
{ "_id" : ObjectId("4c5991b41546827805e1aa60"), "m" : 5, "n" : 9 }
$mod

字面の通りモジュロ(割り算の余り)ですねー。

> db.things.find({n: {$mod: [3, 0]}})
{ "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5a"), "n" : 3, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5d"), "n" : 6, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9, "m" : 5 }
$all

$in に似ていますが、エンベッドされた配列に対するもので、さらに、指定した配列の全ての値を含まなければなりません。

> db.things.find({a: {$all: ["apple", "banana"]}})
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] }

> db.things.find({a: {$all: ["apple"]}})
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] }

> db.things.find({a: {$all: ["apple", "nothing"]}})
(ドキュメントなし)
$size

エンベッドされた配列のサイズで検索します。

> db.things.find({a: {$size: 2}})
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] }

> db.things.find({a: {$size: 1}})
(ドキュメントなし)
$type

BSON型の型番号で検索します。
型と型番号の対応は以下のようになります。

型名 型番号
Double 1
String 2
Object 3
Array 4
Binary data 5
Object id 7
Boolean 8
Date 9
Null 10
Regular expression 11
JavaScript code 13
Symbol 14
JavaScript code with scope 15
32-bit integer 16
Timestamp 17
64-bit integer 18
Min key 255
Max key 127
> db.foo.find({y: {$type: 2}})
{ "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" }

> db.foo.find({x: {$type: 2}})
(ドキュメントなし)
正規表現

なんと!正規表現でも検索できます!

> db.things.find({y: /^str/})
{ "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" }

> db.things.find({y: /^str.*/})
{ "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" }

> db.things.find({y: /^str.*$/})
{ "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" }

なお、上記3つの例は全てインデックスを使っているそうですが、後者の2つは全ドキュメント検索するのでスピードが遅いそうです。

$elemMatch

エンベッドされた配列の中を検索します。

> db.things.insert({x: [{a: 1, b: 3}, 7, {b: 99}, {a: 11}]})

> db.things.find({x: {$elemMatch: {a: 1, b: {$gt: 1}}}})
{ "_id" : ObjectId("4c5a0f602e6af24434f87cfe"), "x" : [ { "a" : 1, "b" : 3 }, 7, { "b" : 99 }, { "a" : 11 } ] }

// ドットノーテーションでも同じことができます。
> db.things.find({"x.a": 1, "x.b": {$gt: 1}})
{ "_id" : ObjectId("4c5a0f602e6af24434f87cfe"), "x" : [ { "a" : 1, "b" : 3 }, 7, { "b" : 99 }, { "a" : 11 } ] }
$not

否定を表すメタ演算子です。他の演算子と組み合わせて使います。

> db.things.find({n: {$not: {$mod: [2, 0]}}})
{ "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 1 }
{ "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" }
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] }
{ "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } }
{ "_id" : ObjectId("4c5991b41546827805e1aa58"), "n" : 1, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5a"), "n" : 3, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5c"), "n" : 5, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5e"), "n" : 7, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9, "m" : 5 }
{ "_id" : ObjectId("4c5a0f602e6af24434f87cfe"), "x" : [ { "a" : 1, "b" : 3 }, 7, { "b" : 99 }, { "a" : 11 } ] }

ふー、疲れた。これで Retrieve は一通り終わりです。group() もあるんですが、Map/Reduce が絡むので、また後日。
なんかこの辺でテンション下がってきました。平常運転でやろうかなと思います。

Update

更新がまた多彩なんですよねー。よっし、がんばるぞぅ、おう。みんなも頑張って読んで!

save()

最後に作成したドキュメントがややこしすぎるので、もっと簡素な奴に更新してみましょうか。

> var doc = db.things.find().toArray()[14]
{
        "_id" : ObjectId("4c5a0f602e6af24434f87cfe"),
        "x" : [
                {
                        "a" : 1,
                        "b" : 3
                },
                7,
                {
                        "b" : 99
                },
                {
                        "a" : 11
                }
        ]
}

> doc.x = {a: 1, b: 2, c: 3}
{ "a" : 1, "b" : 2, "c" : 3 }

> db.foo.save(doc)

> doc = db.things.find().toArray()[14]
{
        "_id" : ObjectId("4c5a0f602e6af24434f87cfe"),
        "x" : {
                "a" : 1,
                "b" : 2,
                "c" : 3
        }
}

はい、更新できました。簡単すね。
もちろん JavaScript 風に値をいじれます。

> doc.x.c++
3

> db.things.save(doc)

> doc = db.things.find().toArray()[14]
{
        "_id" : ObjectId("4c5a0f602e6af24434f87cfe"),
        "x" : {
                "a" : 1,
                "b" : 2,
                "c" : 4
        }
}

ORM 触ってるのと、あんまかわんないような気がしますね。

update()
db.collection.update(criteria, objNew[, upsert[, multi]])

引数を解説すると、

  • criteria - 検索条件
  • objNew - 更新オブジェクトまたは修飾子
  • upsert - update + insert の合成語。ドキュメントがあれば更新し、なければ新規に作成します。
  • multi - 検索条件に複数該当する場合に全部更新するかどうか決めます。デフォルトは最初の1レコードのみです。
> db.things.find({x: 1})
{ "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 1 }

> db.things.update({x: 1}, {x: 255})

> db.things.find({x: 1})
(ドキュメントなし)

> db.things.find({x: 255})
{ "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 255 }

できました。でも、objNew と丸ごと入れ替わっちゃうので、複雑なオブジェクトなんかの場合、使いでが悪いです。
次から説明する、修飾子と一緒に使えば、そこらへん楽になります。

$inc

値をインクリメントします。

> db.things.update({x: 255}, {$inc: {x: 1}})

> db.things.find({x: 255})
(ドキュメントなし)

> db.things.find({x: 256})
{ "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 256 }

save() の例でインクリメントしましたが、こちらはアトミックに更新することができます。分散環境を考えると便利ですね。

$set

update() を普通に使うとobjNewに破壊的に更新されてしまいますが、この $set を使うことで、差分更新ができます。

> db.things.find({n: 9})
{ "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9, "m" : 5 }

> db.things.update({n: 9}, {$set: {m: 10}})

> db.things.find({n: 9})
{ "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9, "m" : 10 }
$unset

フィールド自体を削除します。

> db.things.update({n: 9}, {$unset: {m: 1}})

> db.things.find({n: 9})
{ "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9 }
$push

配列フィールドに値を追加します。

> db.things.find({x: 2})
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] }

> db.things.update({x: 2}, {$push: {a: "peach"}})

> db.things.find({x: 2})
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "apple", "banana", "peach" ], "x" : 2 }
$pushAll

配列フィールドに配列を追加します。

> db.things.update({x: 2}, {$pushAll: {a: ["orange", "grape"]}})

> db.things.find({x: 2})
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "apple", "banana", "peach", "orange", "grape" ], "x" : 2 }
$addToSet

ワンモアセッ!……すいません、つい古き良きビリーの声が聞こえてきました。この記事書くために寝てないんすよ。許して♡
配列フィールドに値を追加するのは $push と同じなんですが、重複になるときは追加しません。

> db.things.find({x: 2})
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "apple", "banana", "peach", "orange", "grape" ], "x" : 2 }

> db.things.update({x: 2}, {$addToSet: {a: "grape"}})

> db.things.find({x: 2})
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "apple", "banana", "peach", "orange", "grape" ], "x" : 2 }

> db.things.update({x: 2}, {$addToSet: {a: "strawberry"}})

> db.things.find({x: 2})
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "apple", "banana", "peach", "orange", "grape", "strawberry" ], "x" : 2 }
$pop

配列フィールドの末尾を削除します。-1を指定した場合は、先頭が削除されます。

> db.things.find({x: 2})
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "apple", "banana", "peach", "orange", "grape", "strawberry" ], "x" : 2 }

> db.things.update({x: 2}, {$pop: {a: 1}})

> db.things.find({x: 2})
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "apple", "banana", "peach", "orange", "grape" ], "x" : 2 }

> db.things.update({x: 2}, {$pop: {a: -1}})

> db.things.find({x: 2})
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "banana", "peach", "orange", "grape" ], "x" : 2 }
$pull

配列フィールドから、指定した値を削除します。

> db.things.find({x: 2})
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "banana", "peach", "orange", "grape" ], "x" : 2 }

> db.things.update({x: 2}, {$pull: {a: "orange"}})

> db.things.find({x: 2})
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "banana", "peach", "grape" ], "x" : 2 }
$pullAll

$pushAll の逆ですね。配列フィールドから指定した配列の値を全部削除します。

> db.things.find({x: 2})
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "banana", "peach", "grape" ], "x" : 2 }

> db.things.update({x: 2}, {$pullAll: {a: ["peach", "grape"]}})

> db.things.find({x: 2})
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "banana" ], "x" : 2 }
$

ちょっとややこしいのですが、ポジショナルオペレーターというらしいです。検索条件でマッチした配列の要素を表します。

> db.things.insert({title: "foo", comments: [{by: "joe", votes: 3}, {by: "jane", votes: 7}]})

> db.things.find({"comments.by": "joe"})
{ "_id" : ObjectId("4c5a37442e6af24434f87d00"), "title" : "foo", "comments" : [ { "by" : "joe", "votes" : 3 }, { "by" : "jane", "votes" : 7 } ] }

> db.things.update({"comments.by": "joe"}, {$inc: {"comments.$.votes": 1}})

> db.things.find({"comments.by": "joe"})
{ "_id" : ObjectId("4c5a37442e6af24434f87d00"), "title" : "foo", "comments" : [ { "by" : "joe", "votes" : 4 }, { "by" : "jane", "votes" : 7 } ] }

なんか使いどころが難しそうですね。

以上で Update は終わりです!

Delete

いろいろ削除する前に foo コレクション全体が現状どうなっているか確認してみましょう。

> db.things.find()
{ "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } }
{ "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa58"), "n" : 1, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa59"), "n" : 2, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5a"), "n" : 3, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5b"), "n" : 4, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5c"), "n" : 5, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5d"), "n" : 6, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5e"), "n" : 7, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5f"), "n" : 8, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9}
{ "_id" : ObjectId("4c5a0f602e6af24434f87cfe"), "x" : { "a" : 1, "b" : 2, "c" : 4 } }
{ "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" }
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "banana" ], "x" : 2 }
{ "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 256 }
{ "_id" : ObjectId("4c5a37442e6af24434f87d00"), "title" : "foo", "comments" : [ { "by" : "joe", "votes" : 4 }, { "by" : "jane", "votes" : 7 } ] }

いっぱい作りましたねー。

じゃあ、まずは、x = 256 のレコードを削除してみたいと思います。

> db.things.remove({x: 256})

> db.things.find({x: 256})
(ドキュメントなし)

削除できました。

では、次に、n > 4 のドキュメントを削除してみます。

> db.things.remove({n: {$gt: 4}, $atomic: true})

> db.things.find()
{ "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } }
{ "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa58"), "n" : 1, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa59"), "n" : 2, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5a"), "n" : 3, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5b"), "n" : 4, "m" : 5 }
{ "_id" : ObjectId("4c5a0f602e6af24434f87cfe"), "x" : { "a" : 1, "b" : 2, "c" : 4 } }
{ "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" }
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "banana" ], "x" : 2 }
{ "_id" : ObjectId("4c5a37442e6af24434f87d00"), "title" : "foo", "comments" : [ { "by" : "joe", "votes" : 4 }, { "by" : "jane", "votes" : 7 } ] }

綺麗に削除できましたね。$atomic 修飾子は、その名の通りアトミックに複数レコードを削除します。自動分散機構があるもんで、気を使ってますね。

オブジェクトが得られているときには、こうやって削除します。

> doc = db.things.findOne({x: 3})
{
        "_id" : ObjectId("4c5991191546827805e1aa56"),
        "x" : 3,
        "b" : {
                "p" : "hoge",
                "q" : "fuga"
        }
}

> db.things.remove({_id: doc._id})

> db.things.find()
{ "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa58"), "n" : 1, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa59"), "n" : 2, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5a"), "n" : 3, "m" : 5 }
{ "_id" : ObjectId("4c5991b41546827805e1aa5b"), "n" : 4, "m" : 5 }
{ "_id" : ObjectId("4c5a0f602e6af24434f87cfe"), "x" : { "a" : 1, "b" : 2, "c" : 4 } }
{ "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" }
{ "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "banana" ], "x" : 2 }
{ "_id" : ObjectId("4c5a37442e6af24434f87d00"), "title" : "foo", "comments" : [ { "by" : "joe", "votes" : 4 }, { "by" : "jane", "votes" : 

_id 指定ですね。


では最後に、綺麗サッパリ、全ドキュメントを削除します。

> db.things.remove({})

> db.things.find()
(ドキュメントなし)

はい、これで CRUD は終わりです。

後片付け

things コレクションを削除しましょう。

> show collections
things
system.indexes

> db.things.drop()
true

> show collections
system.indexes

データベースも削除しましょう。

> db.dropDatabase()
{ "dropped" : "mysql.$cmd", "ok" : 1 }

> show dbs
admin
local

はい、これで終了です!お疲れ様でしたー。

(アサマシ貼りたいけど、MongoDBの本、洋書でさえまだ出てないw)
(あ、今クソ貧乏なんで、この記事に感動した方は babie.tanaka@gmail.com 宛に Amazon ギフト券500円分とか贈ってもらえると助かります!)

Mac OS X で MongoDB を動かす

MongoDB は、いわゆる NoSQL データベースの一つです。
公式サイトの文言をそのまま信じるなら、

  • ドキュメント指向ストレージで
  • インデックスをサポートしていて
  • レプリケーションができて高可用性に富んでいて
  • 自動データ分割してくれて
  • 多彩な検索クエリが使えて
  • 更新がアトミックで速くて
  • Map/Reduce も使えたりしちゃったりして
  • GridFS っていう何でもつっこめるファイルシステムを使っていて
  • 商用サポートもある

といった特徴を持つプロダクトです。

私は、自動分散と、単純なKVSにはない豊富な検索クエリに惹かれて、使ってみようかと思いました。インデックスがあるのもいいですね! というか、MongoHQ ですよ! Amazon EC2 のサーバーとつなげられるらしい、自動スケールアウト管理不要サービスです。最初無料からスタートして安いんですよねー。これが決め手です。


では、インストールしましょうか。幸いなことに macports にありました。楽ちんですね。

$ sudo port install mongodb

今コマンド実行した?早速実行した?はい、君、終了〜。なんと、依存ライブラリ boost のビルドに6時間、mongodb のビルドに1時間かかってしまうのです!(MacBook (Early 2008) 調べ) 会社の人は退社間際、自宅の人は出社前・寝る前にやるのが吉ですね。


起動に必要なディレクトリとファイルを作っておきましょう。

$ sudo mkdir /var/lib/mongodb
$ sudo touch /var/log/mongodb.log

ディレクトリパスはお好みで。macports なので /opt/local/ を頭につけたい人もいるかもしれませんね。


起動は、

$ /opt/local/bin/mongod --dbpath /var/lib/mongodb --logpath /var/log/mongodb.log

ログを追記式にしたいときは、--logappend を加えて下さい。付けないと、起動毎にログがリフレッシュします。私は、開発環境なので、ディスク占有しない方がいいかなー、と思って外しました。

停止は、

$ kill -2 (プロセスID)
または、
$ kill -15 (プロセスID)

です。
マニュアルによると、SIGINT(2) か SIGTERM(15) で停止だそうです。CTRL-C とか kill -9 とかは厳禁ですよ!


Mac 起動時に自動でスタートして欲しいですよね? OS X なので launchd/launchctl で起動・停止するように設定します。
/Library/LaunchDaemons/org.mongodb.mongod.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>Label</key>
        <string>org.mongodb.mongod</string>
        <key>RunAtLoad</key>
        <true/>
        <key>ProgramArguments</key>
        <array>
                <string>/opt/local/bin/mongod</string>
                <string>--dbpath</string>
                <string>/var/lib/mongodb</string>
                <string>--logpath</string>
                <string>/var/log/mongodb.log</string>
        </array>
</dict>
</plist>

launchd/launchctl は停止時は SIGTERM を送るそうなので安心ですね。

オーナーやパーミッションも変更しておきましょう。

$ sudo chown root:wheel org.mongodb.mongod.plist
$ sudo chmod 644 org.mongodb.mongod.plist
$ ls -l org.mongodb.mongod.plist
-rw-r--r--  1 root  wheel  496  6  2 04:30 org.mongodb.mongod.plist

これで(RunAtLoad が true なので)次回Macの起動時に自動で起動されるはずです。


手動で起動・停止するのは、

$ sudo launchctl load /Library/LaunchDaemons/org.mongodb.mongod.plist
$ sudo launchctl unload /Library/LaunchDaemons/org.mongodb.mongod.plist

です。plist はフルパスじゃないといけませんよ。


うまく起動したら、チュートリアルを見ながら、クライアントインターフェイスで遊んでみましょう。

$ mongo


それでは、今日はここまで!


続き書きました → ハンズオンで分かる MongoDB チュートリアル