ドキュメント指向データベース「MongoDB」 | サイバーエージェント 公式エンジニアブログ
皆様、はじめまして。
アメーバ事業本部と言うところでサービス開発のプログラマをしている津田と申します。

先日、M.S.氏より、当エンジニアブログにて「技術に関して書くように」とお達しが下りました。
同じ技術者として、尊敬の気持ちを通り越し、畏怖の念すら抱いているM.S.氏に
「技術」に関して書けと言われるとは、もはや路頭に迷った子羊の気分です。

どうにか違う話題に出来ないかとごまかしてはみたものの、
やはり「技術」に関して書くようにと念を押されてしまいました。
路頭に迷っていても致し方ないので、今後サービスに利用する予定で検証を行っていた
「MongoDB」と言うドキュメント指向データベースに関して紹介させて頂きます。

■MongoDBとは
ここ数年注目を浴びている「NOSQL(NotOnlySQL)」のデータストアの一つ
KVSとは異なり、スキーマレスではあるが、カラムに近い概念がある
トランザクションの管理や、JOIN処理が出来ないことなどを除けば、
大きな遜色なくRDBと同じように利用が出来るデータベースである

個人的にMongoDBの位置づけでは
RDBとKVSの中間で、KVSほどの高速性や、容易性はないものの、
RDBほどの煩雑性が少なく、使い勝手がいいと言ったイメージ

また、シングルポイントレスに構成ができたり、自動データ分散機能など、スケーラビリティを
確保できるのもアプリケーションエンジニアとして非常に使いやすいのではないだろうか

■インストール方法など
さて、インストール方法や、単体での起動設定などを紹介をしようと思ったのだが、
これらの情報は昨今のNOSQLブームとともに非常に色々な箇所で紹介されているのでそちらを参照していただければと思う
一言だけ言っておくと、インストールと言う作業はほぼ無くバイナリーのtarを展開するだけで利用が可能だ

今回は実際に利用することの多そうな、ReplicaSets + Sharding構成に関しての初期設定方法を簡単に紹介する

■Sharding(自動データ分散)と Replica Sets(非同期レプリケーション)の設定
最終的には以下の図のような構成を作る
$さすらいのブロガー✿?

○Replica Setsとは
1-7台で構成されるPrimary-Secondary構成のことで、自動でフェイルオーバーやリカバリーする機能を兼ね備えている
このReplicaSetsを利用することでシングルポイントレスな構成が可能になる

○Shardingとは
データの分散配置機能のことで、データを複数のインスタンス(サーバ)に自動で分けて配置することができる
Shardingを行うことで負荷の分散や、ディスク領域の分散が図れる

○ReplicaSets+Sharding
両者を組み合わせることで、自動でFailoverをし、ノードを追加するだけで自動でデータが分散する非常にスケーラブルなデータストアが出来上がる

■設定方法
$さすらいのブロガー✿?

今回は上記の図のように、3台からなるReplicaSetsを3個組み合わせて3nodeによるShardingを行う
これは公式ドキュメントでも推奨構成となっている
※テスト環境のような場所では1台で構成させることも可能
(1つのサーバ内に複数のインスタンスを立ち上げる)

簡単に手順を紹介すると以下のようになる
1.ReplicaSets(3台構成)を3組作成
2.各ReplicaSetsの設定
3.Shardingのメタ情報を保存するConfigサーバを起動
4.ShardingしたMongoDBに接続するためのmongosサーバを起動
5.Shardingの設定

順を追って詳しく見ていく

1.ReplicaSets(3台構成)

mongodbの起動時のパラメータに「--replSet set0x」を追加
Shardingのメンバーにもなるため、sharding用のパラメータ「--shardsrv」も追加
他の有用なパラメータと組み合わせると実際には以下のようなパラメータになる
(他のパラメータの説明は割愛)


./mongod \
    --replSet set01 \
    --shardsvr \
    --rest \
    --dbpath /data/mongodb \
    --logpath /var/logs/mongodb/mongo.log \
    --logappend \
    --pidfilepath /data/mongodb/mongo.pid \
    --directoryperdb \
    --fork


※同じReplicaSetsに所属するインスタンスは同じ「replSet名」にすること以下のような設定になる

設定Host  所属replicaSet
mongo01  replSet set01
mongo02  replSet set01
mongo03  replSet set01

mongo04  replSet set02
mongo05  replSet set02
mongo06  replSet set02

mongo07  replSet set03
mongo08  replSet set03
mongo09  replSet set03



2.各ReplicaSetsの設定

インスタンスが立ち上がったら、各ReplicaSetsの設定を行う
各Replicaに所属するインスタンスのどこかに接続し、以下の設定を行うと同一Replicaに所属していれば自動で伝播する
-----------------------------------
# ./mongo --port 27018
-----------------------------------
※--shradsvrオプションをつけると起動ポートが 27018(デフォルトは27017)に変わるためポート指定が必要になる

--------------------------------------------
> config = { _id:'set01', members:
[
 {_id: 0, host:'mongo1:27018',priority:1},
 {_id: 1, host:'mongo2:27018',priority:1},
 {_id: 2, host:'mongo3:27018',priority:1}
]}
--------------------------------------------

configでreplicasetsを初期化
--------------------------------------------
> rs.initiate(config)
--------------------------------------------

以下のコマンドを実行して正常にconfigが登録されているか確認
--------------------------------------------
> rs.status()

{
 "set" : "mypage02", "date" : "Fri Feb xx 2011 10:18:40 GMT+0900 (JST)", "myState" : 1,
 "members" : [
  {"_id" : 0,"name" : "mongo01:27018","health" : 1,"state" : 1,"self" : true},
  {"_id" : 1,"name" : "mongo02:27018","health" : 1,"state" : 2,"uptime" : 44422,"lastHeartbeat" : "Fri Feb xx 2011 10:18:39 GMT+0900 (JST)"},
  {"_id" : 2,"name" : "mongo03:27018","health" : 1,"state" : 2,"uptime" : 44422,"lastHeartbeat" : "Fri Feb xx 2011 10:18:39 GMT+0900 (JST)"}
 ],
 "ok" : 1
}
--------------------------------------------
membersに該当のインスタンスが登録されており、health:1 であれば問題ない


3.configサーバの起動

○configサーバとは
shardingされた各nodeのメタ情報や、分散したデータがどのnodeに保存されているかなどの実データ外の情報を保持したサーバ
後述のmongosサーバから参照され、自動分散の際などにデータが更新される


./mongod --configsvr \
     --rest \
     --dbpath /data/mongoconf \
     --logpath /var/logs/mongoconf/mongo.log \
     --logappend \
     --pidfilepath /data/mongoconf/mongo.pid \
     --directoryperdb \
     --fork


4.mongosサーバの起動

○mongosサーバとは
shardingされた各nodeに透過的にアクセスを提供するmongodのproxyのような役割
shardingしたmongodbには必ずmongosサーバを通じてアクセスする
また実データなどは一切持っていない


./mongos --configdb \
     --logpath /var/log/mongo/mongod_router.log \
     --logappend \
     --pidfilepath /data/mongo/mongos/mongos.pid \
     --fork

5.Shardingの設定

mongosサーバに接続する
-----------------------------------
./mongo 
-----------------------------------
※mongosはスタンドアローンのmongodbと同じポートにバインドするのでポート指定が不要

admin dbにつなぐ
-----------------------------------
> use admin
-----------------------------------

shardの設定を追加
-----------------------------------
> db.runCommand(
{addshard:"shard01/mongo01:27018,mongo02:27018,mongo03:27018",name:"shard01",allowLocal:true}
);
> db.runCommand(
{addshard:"shard02/mongo04:27018,mongo05:27018,mongo06:27018",name:"shard02",allowLocal:true}
);
> db.runCommand(
{addshard:"shard03/mongo07:27018,mongo08:27018,mongo09:27018",name:"shard03",allowLocal:true}
);

-----------------------------------

Shardingを使う設定をしたあと、実際のDBや、collectionに設定を適用させる
-----------------------------------
> db.runCommand({enablesharding:"dbname"});
{ "ok" : 1 }

> db.runCommand({shardcollection:"dbname.collectionName",key:{"_id":1}});
{ "collectionsharded" : "dbname.collectionName", "ok" : 1 }

-----------------------------------

Shardingが正常にされているか確認

mongoconfサーバのconfig DBに接続
-----------------------------------
# ./mongo --port 27019 config
-----------------------------------

-----------------------------------
> db.shards.find()
{ "_id" : "shard01", "host" : "shard01/mongo01:27018,mongo02:27018,mongo03:27018" }
{ "_id" : "shard02", "host" : "shard02/mongo04:27018,mongo05:27018,mongo06:27018" }
{ "_id" : "shard03", "host" : "shard01/mongo07:27018,mongo08:27018,mongo09:27018" }

> db.databases.find()
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
{ "_id" : "test", "partitioned" : false, "primary" : "shard01" }
{ "_id" : "dbname_01", "partitioned" : false, "primary" : "shard02" }
{ "_id" : "dbname_02", "partitioned" : true, "primary" : "shard01" }

> db.collections.find()
{ "_id" : "dbname_02.shard01", "lastmod" : "Fri Jan 16 1970 09:43:14 GMT+0900 (JST)", "dropped" : false, "key" : { "_id" : 1 }, "unique" : false }
-----------------------------------

これでMongoDBを ReplicaSetsとShardingで利用することが可能になる
前述の通り、mongosを通してsaveやfindを実行すればデータ量に応じて分散して保存がされる

■パフォーマンス検証
設定を紹介するに留まる予定だったが、一部パフォーマンス検証を行ったのでその結果を記載する

○検証環境
サーバ 6台
ReplicaSets(3台構成) x Shard 2node
OS:CentOS5.4
CPU:Xeon X3430 2.40GHz
メモリー:16~24GB
HDD:800TB SAS 15,000RPM
RAID: raid5
FS: xfs(noatime,nobarrier)

○負荷生成サーバ
OS:CentOS5.4
CPU:Xeon X3430 2.40GHz

実運用環境同様にJAVAのクライアントから
マルチスレッド(1/50/100)での Insert / Selectを繰り返す
どちらも1.5k程度のオブジェクトで試行している
(特に記載の無いものは、各50万件を3回繰り返し、秒間クエリー数の平均値を結果とした)
※Clientにはmongo-java-driver(ver2.4)を利用し、POJO Mappersとして、Morphia(ver0.99)を使っている
 どちらも公式サイトのリンクからDL可能


結果は以下のようになった
サイバーエージェント 公式エンジニアブログ


おおむね想定どおりの結果が見て取れる
ただ、ある程度の結果のばらつきがあったので、試行回数50万件ではやや足りない印象がある
また、秒間クエリー数に関してはそもそも負荷生成サーバ側のキャパシティの問題があったり、
クライアントプログラムの書き方などもの問題もあるので、もう少しチューニングが可能なように思われる

ほかにも、MongoDBの効率的なクエリーの投げ方などもあるので、今後はそちらの検証も進めていきたい


---------------------------------------------
おまけ:mongo tips
---------------------------------------------
○ログローテート:kill -10 <mongo_pid>
○mongo shell内では ctrl+r でincremental history searchが可能
○デフォルトのObjectID(_id)はタイムスタンプをlong値でもっている(先頭4byte)
○各サーバのMongoDBがデフォルト(port指定せずに)でバインドするポート
 Standalone mongod : 27017
 mongos : 27017
 shard server (mongod --shardsvr) : 27018
 config server (mongod --configsvr) : 27019
 web stats page for mongod : add 1000 to port number (28017, by default)
 
【参考文献】
mongodb公式サイト
http://www.mongodb.org/

日本語翻訳ドキュメント
http://www.mongodb.org/display/DOCSJP/Home