NoSQLデータベースを試してみる

第4回スキーマレスで柔軟に扱えるMongoDB

はじめに

今回はドキュメント指向型データベースの代表としてMongoDBを取り上げます。ドキュメント指向型データベースはRDBMSと違って、スキーマ(テーブル定義)が必要ないことが大きな特徴です。

今回も利用したコードやプログラムはgithubに置いてあるので適宜参照してください。

MongoDBの特徴

前々回前回と紹介したmemcachedやTokyoTyrantは基本的にRDBMSと組み合わせて、⁠RDBMSの弱い部分を補う」という使い方でした。しかしMongoDBは少し違っていて、JOINが行えないこととトランザクションをサポートしていないこと以外は、ほぼRDBMSと同じように扱うことができるため、⁠RDBMSの代替として使う」ことが可能です。

上述したようにMongoDBはRDBMSと違ってJOINはできませんが、代わりに基準となるオブジェクトに別のオブジェクトをあらかじめembedded(埋め込み)させておくことで、ある程度同じように扱うことができます。また、データを配列形式で保持したり、その配列形式のデータに対して検索を行ったりすることも簡単にできます。shardingによってスケールさせやすいというのも特徴です。

ただし、データの保存先ファイルはあらかじめ大きなサイズで作成される仕様のため、ディスク使用量は大きくなる傾向があります[1]⁠。

どんなところに使える?

カラムを固定できない場合に有効です。あらかじめたくさんのカラムを用意しておくことでも対応は可能ですが、マジックナンバーのようなカラムが増えてしまうと、運用しづらくなってしまいます。

また、RDBMSで開発を行っている場合、テーブルのスキーマが変更になるたびにデータベース側とアプリ側で修正を行わなければいけません。そういった場合、MongoDBであればスキーマレスなのでアプリ側のプログラムのみ修正すれば良いです。

具体的な利用シーン
  • カラムを固定出来ない場合
  • 開発時など、スキーマの変更が頻繁に行われるような場合

MongoDBを試す(1台)

それでは実際にMongoDBを利用してみましょう。まずはMongoDB(mongodサーバ)を1台のみ立ち上げて、Javascriptシェルなコマンドラインから動作を確認してみます[2]⁠。コレクションというのが聞き慣れないと思いますが、これはRDBMSのテーブルに相当します。

コマンドラインからMongoDBの基本操作を試す
MongoDB shell version: 1.4.3
url: test // 何も指定しなければtestデータベースに接続する
connecting to: test
type "help" for help
> show dbs // データベース一覧を表示する
admin
local
> db.hoge.save( { name : "太郎" } ) // hogeコレクションにデータを新規作成する
> db.hoge.find() // hogeコレクションのデータを一覧表示する
{ "_id" : ObjectId("4c27bc5ca8b636b30c43c917"), "name" : "太郎" }
> show dbs // testデータベースが自動的に作成された
admin
local
test
> show collections // testデータベース内のコレクションを一覧表示する。hogeコレクションが自動的に作成された
hoge
system.indexes

RDBMSと違って事前にテーブルを作成したり、スキーマを定義する必要はありません。それらは必要に応じて自動的に作成されます。

MongoDBを試す(sharding)

今度は、複数のmongodサーバを立ち上げて使ってみます。MongoDBでサポートされているsharding[3]を試してみましょう。mongod, config, mongosという3種類のサーバが登場します。

shardingを使うと、mongosにアクセスすることで透過的にmongodクラスタへアクセスすることができるようになります。本来は複数サーバで行うものですが、今回はテストなので同一サーバで行いました。

図1 MongoDBのsharding構成
図1 MongoDBのsharding構成
mongod, config, mongosを起動する
mongod --dbpath tmp/mongodb/a --port 10000 > tmp/shard_a.log &
mongod --dbpath tmp/mongodb/b --port 10001 > tmp/shard_b.log &
mongod --dbpath tmp/mongodb/config --port 20000 > tmp/config.log &
mongos --configdb localhost:20000 > tmp/mongos.log &

shardingの設定を行います。設定はconfigサーバに保存されます。

shardingの設定を行う
MongoDB shell version: 1.4.3
url: test
connecting to: test
type "help" for help
> db.runCommand( { addshard : "localhost:10000", allowLocal : true } ) // 10000番ポートをshardとして追加
{ “ok” : 1, “added” : “localhost:10000” }
> db.runCommand( { addshard : "localhost:10001", allowLocal : true } ) //10001番ポートをshardとして追加
{ “ok” : 1, “added” : “localhost:10001” }
> db.runCommand( { listshards : 1 } );
{
        "shards" : [
                {
                        "_id" : ObjectId("4c27cf028ce643a969355dfc"),
                        "host" : "localhost:10000"
                },
                {
                        "_id" : ObjectId("4c27cf0f8ce643a969355dfd"),
                        "host" : "localhost:10001"
                }
        ],
        "ok" : 1
}
> db.runCommand( { enablesharding : “test” } ) // testデータベースでshardingを行える状態にする
{ “ok” : 1 }
> db.rucCommand( { shardcollection : “test.hoge”, key : { name : 1 } } ) // hogeコレクションをnameでshardingする
{ “ok” : 1 }

RailsからMongoDBを利用してみる

今回はMongoMapperというライブラリを使ってRubyからMongoDBを操作します。これはActiveRecordライクにMongoDBを扱えるライブラリです。必要に応じてインストールしてください。

mongo_mapperのインストール
gem install mongo_mapper

まず、MongoDBで扱いたいモデルで設定を行う必要があります。最低限やることはMongoMapper::Documentをインクルードすることだけです。今回の例ではageとinterestの2つをキーとして設定していますが、これは必須ではありません。ただし、このようにキーとして指定することで、以下のようなメリットがあります。

  • 文字列以外の型を指定できる(IntegerやArray、Booleanなど)
  • データ作成時や更新時にvalidationを行うことができる
MongoDBを利用したモデルのコード例
class User
  include MongoMapper::Document

  key :age, Integer, :default => 0
  key :interest, Array
end

さらにコントローラー側は以下のようになります。開発時はloggerを指定して、どんなクエリが実行されているのかを見れるようにした方が良いでしょう。

MongoDBを利用したコントローラーのコード例
class MongodbController < ApplicationController
  require 'mongo_mapper'
  MongoMapper.connection = Mongo::Connection.new("localhost", 27017, :logger => Logger.new(STDOUT))
  MongoMapper::database = 'test'

  def set
    User.create(:name => "太郎1")
    User.create(:name => "太郎2", :age => 20)
    User.create(:name => "太郎3", :interest => %w!soccer baseball tennis!)
    User.create(:name => "太郎4", :interest => %w!soccer!)
  end

  def get
    p User.all(:name => /太郎/)                             # 太郎1, 太郎2, 太郎3, 太郎4
    p User.all(:age => 20)                                 # 太郎2

    # interestにsoccerを含んでいるデータ
    p User.all(:interest => “soccer”)                      # 太郎3, 太郎4
                                                                
    # interestにsoccerもしくはtennisのいづれかを含んでいるデータ
    p User.all(:interest => %w!soccer tennis!)             # 太郎3, 太郎4

    # interestにsoccerとtennisを両方とも含んでいるデータ
    p User.all(:interest => {"$all" => %w!soccer tennis!}) # 太郎4
  end
end

Array型の扱いは若干わかりにくいかもしれません。Array型のカラムに対して配列を渡して検索した場合、デフォルトでは「いずれかを含む」の意味で解釈されます。渡した配列を全て含んでいるデータが欲しければ、$allというキーワードを指定しなくてはいけないことに注意してください。

便利なメソッド

その他にも様々な便利メソッドがあります。例えばadd_to_setというメソッドを利用すると、⁠対象の配列の中にそのデータが存在しなければ追加し、存在していれば何もしない」という処理を簡単に行うことができます。

MongoDBの便利なメソッド
def add_to_set
  p User.first(:name => "太郎4") # interest: ["soccer"]
  
  User.add_to_set({:name => "太郎4"}, :interest => "tennis")
  p User.first(:name => "太郎4") # interest: ["soccer", "tennis"]
  
  User.add_to_set({:name => "太郎4"}, :interest => "soccer")
  p User.first(:name => "太郎4") # interest: ["soccer", "tennis"]
end

まとめ

MongoDBはshardingさせられるため、スケーラビリティに優れたデータベースです。さらに、データ型として配列が扱えたり、検索が柔軟に行えたりするため、様々な場面で使えるのではないでしょうか。また、データベース側とアプリ側でスキーマの整合性を気にする必要がないのも大きなメリットだと思います。

おすすめ記事

記事・ニュース一覧