Ruby Freaks Lounge

第31回RubyistのためのMongoDB入門(1)

はじめに

ここ最近、NoSQLというキーワードが注目を集めています。

リレーショナルデータベースは、一般的にスケールアウト(サーバの台数を増やして性能向上を図る手法)が難しく、特に大規模サービスにおいてパフォーマンス上のボトルネックとなりえます。また、タグやグラフ構造のようなデータは関係モデルに馴染みにくいため、それらを扱う際にはアプリケーションコードもぎこちないものになりがちです。

これらの問題を背景に、何にでもリレーショナルデータベースを使うのではなく、用途に応じてKVSなど他のデータストアを選択する流れが広まりつつあります。このムーブメントがNoSQL(Not Only SQL)と呼ばれているものです。

今回は、NoSQLなデータベースの1つであるMongoDBをご紹介します。

MongoDBとは

MongoDBは高いパフォーマンスとスケーラビリティを特徴とするドキュメント指向型データベースです。GNU AGPLv3を採用したオープンソースソフトウェアで、開発母体である10gen, Inc.によって商用サポートが提供されています。

MongoDBの代表的な機能は以下のとおりです。

  • JSONをベースとしたスキーマレスなデータモデル
  • B-treeインデックスをサポートする高速なクエリ
  • Master-Slave Replicationに加え、Replica Pairsによるフェイルオーバー構成を選択可能
  • 分散データベースを実現するSharding
  • 分散処理機構MapReduce
  • バイナリデータを効率的に扱う仮想ファイルシステムGridFS
  • ストリーミングやロギングに適したCapped Collections
  • 各種言語用のドライバが提供されている(もちろんRubyも)

MongoDBは、いわゆるNoSQLデータベースの中でも機能が豊富な部類に入ります。リレーショナルデータベースの機能性とKVSの速度を合わせ持つことを狙いとして開発されています。

図1 MongoDBの位置付け
図1 MongoDBの位置付け

しかし、MongoDBは設計上の判断によりトランザクションやJOINをサポートしていません。Atomic Operationsやスキーマ設計の工夫によりある程度解決することはできますが、本質的にリレーショナルデータベースの機能が必要となる場面では力不足です。

それでは、MongoDBに適した分野は何でしょうか。いくつか事例を見てみましょう。

The Business Insider

The Business Insiderは、一日に60万以上のPVがあるサイトのバックエンドとしてMongoDBを採用しました。理由として「スケーラブル」⁠動的言語との相性の良さ」⁠スキーマ構成の柔軟さ」などが挙げられています。1つのMongoDBサーバでCPU利用率は5%程度に収まっているとのことです。

BoxedIce

BoxedIceは、サーバ監視システムで使用するデータベースをMySQLからMongoDBに移行しました。主な理由として管理面の問題が挙げられています。MySQLのレプリケーションは特に初回の同期が遅く、将来的なスケーリングにも不安があったため、NoSQLデータベースを比較検討した結果MongoDBを採用した顛末が綴られています。

また、同社のサービスはアカウントの管理や請求処理などには引き続きMySQLを使用しているそうです。

その他の事例

他にもSourceforgeやGitHub、New York TimesなどのサービスでMongoDBが採用されています。

これらの事例から、MongoDBは性能を要求される大規模なサービスでの使用に向いていると言えそうです。

基本的な使い方

MongoDBのインストールはとても簡単です。ダウンロードページからお使いの環境に合うものをダウンロード・展開してください。今回は記事執筆時点での最新安定版であるバージョン1.2.2を使います。

% wget http://downloads.mongodb.org/linux/mongodb-linux-x86_64-1.2.2.tgz
% tar xf mongodb-linux-x86_64-1.2.2.tgz

※アーカイブ名・ディレクトリ名は、環境に合わせて適宜読み替えてください。

アーカイブを展開した中のbinディレクトリに各種実行ファイルが収められています。まずはサーバ本体のmongodを起動しましょう。--dbpathオプションに適当なディレクトリを指定してください。そこにデータベースが作られます。

% cd mongodb-linux-x86_64-1.2.2/bin
% mkdir -p ~/tmp/mongodb
% ./mongod --dbpath ~/tmp/mongodb
Mon Feb 8 07:09:16 Mongo DB : starting : pid = 24059 port = 27017 dbpath = /home/ursm/tmp/mongodb master = 0 slave = 0 64-bit
Mon Feb 8 07:09:16 db version v1.2.2, pdfile version 4.5
Mon Feb 8 07:09:16 git version: 8a4fb8b1c7cb78648c55368d806ba35054f6be54
Mon Feb 8 07:09:16 sys info: Linux domU-12-31-39-06-79-A1 2.6.21.7-2.ec2.v1.2.fc8xen #1 SMP Fri Nov 20 17:48:28 EST 2009 x86_64 BOOST_LIB_VERSION=1_41
Mon Feb 8 07:09:16 waiting for connections on port 27017

別のターミナルで対話型シェルのmongoを起動します。オプションを指定しない場合はlocalhostのデフォルトポート(27017)に接続します。

% ./mongo
MongoDB shell version: 1.2.2
url: test
connecting to: test
type "help" for help
>

このシェルは実のところSpiderMonkeyをエンジンとしたJavaScriptコンソールです。計算からメソッド呼び出しまで一通りのことができてしまいます。

> a = 1 + 1
2
> 'hello'.toUpperCase() + a
HELLO2

先に進む前に、MongoDBで使われる用語について確認しておきましょう。

ドキュメント
リレーショナルデータベースのレコードに相当します。MongoDBではドキュメントをBSON(バイナリ化されたJSON)として扱います。
コレクション
リレーショナルデータベースのテーブルに相当します。複数のドキュメントが格納されます。
データベース
複数のコレクションが格納されます。

ドキュメントの作成

ドキュメントの作成にはdb.<コレクション名>.save(<オブジェクト>)を使います。該当するコレクションがない場合は自動的に作成されます。

それでは実際に、ユーザを表すドキュメントを作成してみましょう。コレクション名はusersとします。

> db.users.save({name: 'ursm'})

db.<コレクション名>.find()でドキュメントを取り出せます。

> db.users.find()
{ "_id" : ObjectId("4b6f40fb047606970896546e"), "name" : "ursm" }

MongoDBはスキーマレスなので、コレクション中に異なるキーを持つドキュメントが混在していても問題ありません。

> db.users.save({name: 'kakutani', blog: 'http://kakutani.com'})

文字列だけでなく、数値や日付、配列やドキュメントなど様々な種類のデータを格納できます。ドキュメントに値として埋め込まれたドキュメントをEmbedded Documentと呼びます。

> db.users.save({name: 'moro', tags: ['ruby', 'rails'], login_count: {today: 5, total: 25}})

ドキュメントの検索

ドキュメントを検索するにはfind()の引数で条件を指定します。名前が"ursm"に一致するドキュメントを検索してみましょう。

> db.users.find({name: 'ursm'})
{ "_id" : ObjectId("4b6f40fb047606970896546e"), "name" : "ursm" }

名前に"r"を含むドキュメントを検索します。

> db.users.find({name: /r/})
{ "_id" : ObjectId("4b6f40fb047606970896546e"), "name" : "ursm" }
{ "_id" : ObjectId("4b6f49870476069708965471"), "name" : "moro", "tags" : [ "ruby", "rails" ], "login_count" : { "today" : 5, "total" : 25 }}

"ruby"というタグが付けられたドキュメントを検索します。値が配列のキーに対して単一の値で検索すると、配列の中に値が含まれているかをチェックしてくれます。

> db.users.find({tags: 'ruby'})
{ "_id" : ObjectId("4b6f49870476069708965471"), "name" : "moro", "tags" : [ "ruby", "rails" ], "login_count" : { "today" : 5, "total" : 25 } }

Embedded Documentによる検索もできます。キー名を"."で区切って階層を表します。

> db.users.find({'login_count.today': 5})
{ "_id" : ObjectId("4b6f49870476069708965471"), "name" : "moro", "tags" : [ "ruby", "rails" ], "login_count" : { "today" : 5, "total" : 25 } }

他にも様々な機能がありますが、長くなってきたのでこのぐらいにしておきましょう。対話型シェルの使い方は「Overview - The MongoDB Interactive Shell」を、クエリの詳細は「Advanced Queries」を参照してください。

まとめ

今回は、MongoDBの概要と単体での使い方を説明しました。次回は、Rubyからの使い方を紹介します。

おすすめ記事

記事・ニュース一覧