MongoDBのSharding機能で遊ぶ

MongoDBを使っているので、自分でも組めなければと思い勉強中。オライリーでスケーリングMongoDBが電子書籍で売っていたので迷わず購入。
とりあえずはReplicaSetをせずに1台のSharding構成でいってみましょう。

まず、Shardingというのはなにか?というと

  1. データを複数サーバへ分割する
  2. データの分割はMongoDBがよしなにやってくれる
  3. 分割したデータは状況に応じて各Shardを移動する
  4. MongoDB利用者(アプリ)は複数サーバを意識しなくてよい

ということです。
レプリケーションが同じデータのコピーを複数DBへ持つのに対して、Shardingは違うデータを複数DBへ持ちます。よって、普通の運用はSharding+ReplicaSet(レプリケーション)の構成となります。

で、今回の構成は下のようにしました。これをAWSのマイクロインスタンス1台で組んでいます。新規アカウントで申し込むとマイクロインスタンスで750時間/月無料なので大変お得。私見ですが、物理サーバでMongoDBを使うのはよっぽどお金があるところでないとキツイかなと思います。
その理由は下の構成図を見ていただければ一目瞭然。
f:id:hs_hachi:20120226173005p:plain
必要なもの多いですね。ReplicaSetがあると更に増えます。簡単な説明は以下のとおりです。

  • mongos
    • ユーザはmongos経由でDBへアクセスします。各Shardにアクセスしまとめたデータを返してくれます。
  • Configサーバ(ここではmongocと呼称)
    • Shardの設定等を保持しています。中身はただのmongodです。
  • mongod
    • 実際のデータやindexを保持しています。mongod自体はShardingされていることは意識しません。

インストール

$ sudo wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.0.2.tgz
$ tar xzfv mongodb-linux-x86_64-2.0.2.tgz
$ mv mongodb-linux-x86_64-2.0.2 mongodb
$ sudo mkdir -p /data/mongoc1/log /data/mongoc2/log /data/mongoc3/log /data/mongod1/log /data/mongod2/log /data/mongod3/log /data/mongos1/log
$ sudo chown -R `id -u` /data/db/

mongodの起動

mongod --shardsvr --dbpath /data/mongod1 --logpath /data/mongod1/log/mongod1.log --port 27031 --fork
mongod --shardsvr --dbpath /data/mongod2 --logpath /data/mongod2/log/mongod2.log --port 27032 --fork
mongod --shardsvr --dbpath /data/mongod3 --logpath /data/mongod3/log/mongod3.log --port 27033 --fork

--shardsvrオプションでShardingするよという指定になります。この場合はデフォルトポートは27018を使います。今回は、複数サーバを立てるので--portでポート番号を指定します。--logpathはログファイルの場所を指定してます。何かあればここのログをチェック。--forkでバックグランド起動です。

mongocの起動

mongod --configsvr --dbpath /data/mongoc1 --logpath /data/mongoc1/log/mongoc1.log --port 27011 --fork
mongod --configsvr --dbpath /data/mongoc2 --logpath /data/mongoc2/log/mongoc2.log --port 27012 --fork
mongod --configsvr --dbpath /data/mongoc3 --logpath /data/mongoc3/log/mongoc3.log --port 27013 --fork

mongocもmongodと同じです。パラメータ--cofigsvrに変わってるぐらいですね。簡単簡単。

mongosの起動

mongos --configdb localhost:27011,localhost:27012,localhost:27013 --logpath /data/mongos1/log/mongos1.log --chunkSize 1 --port 27021 --fork

mongosは上2つと異なります。--configdbのオプションでmongocの指定を行います。ここでのポイントはmongocは1もしくは3台にする必要があります。理由は2台だとテスト環境では多いし、本番では少なすぎるらしいからです。かと言って、4台とかにするとmongocの相互関係が複雑になるみたいで、この数が適切とのこと。
また、--chunkSizeを指定してます。chunkとはデータの塊です。Shardingでデータが移動します。デフォルトは64MBとのことですが、ここではデータが移動するところを見たいので、1MBを指定します。

各プロセスが立ち上がったあと、

$ mongo localhost:27021
mongos>

と出ればつながりました。ただ、これはまだMongoDBのインストールをしただけです。ここからがShardingの設定。

$ mongo localhost:27021
mongos> use admin
mongos> db.runCommand({"addShard":"localhost:27031"})
mongos> db.runCommand({"addShard":"localhost:27032"})
mongos> db.runCommand({"addShard":"localhost:27033"})

db.runCommand〜でmongodをShardに追加しています。
ちゃんとShardに追加されたかな?と確認するには

mongos> db.printShardingStatus()
--- Sharding Status --- 
  sharding version: { "_id" : 1, "version" : 3 }
  shards:
	{  "_id" : "shard0000",  "host" : "localhost:27031" }
	{  "_id" : "shard0002",  "host" : "localhost:27033" }

shardsのところにサーバが出てくるのでこれでOKです。
Shardingを行うかどうかはコレクション単位でできるので、その指定を行います。

mongos> db.adminCommand({"enableSharding":"blog"})
mongos> db.adminCommand({"shardCollection":"blog.user",key:{"userId":1}, unique:true});
mongos>use blog
for(var i=1001; i < 1000000; i++){
     var user = {
          userId : i,
          name : "userName"+i,
          createAt : new Date().getTime()
     }
     db.user.save(user);
}

今回はDB名"blog"の"user"コレクションをShardingします。keyがshardKeyと言われるShardingするときのキーになります。何をShardKeyにするかは非常に重要です。いくつか基準はありますが、ユーザIDなどの有限でカーディナリティが高いものがいいそうです。その後データを大量にぶち込んでみます。
チャンクが移動してるのを確認しましょう。blog.user chunksの箇所がshardごとのchunk数です。

mongos> db.printShardingStatus()
--- Sharding Status ---
  sharding version: { "_id" : 1, "version" : 3 }
  shards:
     {  "_id" : "shard0000",  "host" : "localhost:27031" }
     {  "_id" : "shard0001",  "host" : "localhost:27032" }
     {  "_id" : "shard0002",  "host" : "localhost:27033" }
  databases:
     {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
     {  "_id" : "blog",  "partitioned" : true,  "primary" : "shard0000" }
          blog.user chunks:
                    shard0000     5
                    shard0001     3
                    shard0002     13
               too many chunks to print, use verbose if you want to force print
     {  "_id" : "test",  "partitioned" : false,  "primary" : "shard0000" }

mongos> db.printShardingStatus()
--- Sharding Status ---
  sharding version: { "_id" : 1, "version" : 3 }
  shards:
     {  "_id" : "shard0000",  "host" : "localhost:27031" }
     {  "_id" : "shard0001",  "host" : "localhost:27032" }
     {  "_id" : "shard0002",  "host" : "localhost:27033" }
  databases:
     {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
     {  "_id" : "blog",  "partitioned" : true,  "primary" : "shard0000" }
          blog.user chunks:
                    shard0002     11
                    shard0000     12
                    shard0001     13
               too many chunks to print, use verbose if you want to force print
     {  "_id" : "test",  "partitioned" : false,  "primary" : "shard0000" }

mongos> db.printShardingStatus()
--- Sharding Status ---
  sharding version: { "_id" : 1, "version" : 3 }
  shards:
     {  "_id" : "shard0000",  "host" : "localhost:27031" }
     {  "_id" : "shard0001",  "host" : "localhost:27032" }
     {  "_id" : "shard0002",  "host" : "localhost:27033" }
  databases:
     {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
     {  "_id" : "blog",  "partitioned" : true,  "primary" : "shard0000" }
          blog.user chunks:
                    shard0002     15
                    shard0000     15
                    shard0001     16
               too many chunks to print, use verbose if you want to force print
     {  "_id" : "test",  "partitioned" : false,  "primary" : "shard0000" }

おお!チャンクがちゃんと移動してる。プチ感動。どのIDがどのチャンクに入っているかどうかを見るには

mongos> db.printShardingStatus(true);
--- Sharding Status ---
  sharding version: { "_id" : 1, "version" : 3 }
  shards:
     {  "_id" : "shard0000",  "host" : "localhost:27031" }
     {  "_id" : "shard0001",  "draining" : true,  "host" : "localhost:27032" }
     {  "_id" : "shard0002",  "host" : "localhost:27033" }
  databases:
     {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
     {  "_id" : "blog",  "partitioned" : true,  "primary" : "shard0000" }
          blog.user chunks:
                    shard0002     17
                    shard0000     18
                    shard0001     12
               { "userId" : { $minKey : 1 } } -->> { "userId" : 1000 } on : shard0002 { "t" : 16000, "i" : 0 }
               { "userId" : 1000 } -->> { "userId" : 8185 } on : shard0000 { "t" : 17000, "i" : 0 }
               { "userId" : 8185 } -->> { "userId" : 22509 } on : shard0002 { "t" : 18000, "i" : 0 }
               { "userId" : 22509 } -->> { "userId" : 36006 } on : shard0000 { "t" : 19000, "i" : 0 }
               { "userId" : 36006 } -->> { "userId" : 50758 } on : shard0002 { "t" : 20000, "i" : 0 }
               { "userId" : 50758 } -->> { "userId" : 66143 } on : shard0000 { "t" : 21000, "i" : 0 }
               { "userId" : 66143 } -->> { "userId" : 79530 } on : shard0000 { "t" : 15000, "i" : 1 }

とりあえずできた〜
さて、shardを削除してみよう。以下のようにすると、shardが削除できます。データは他のshardに移動します。…が最初実行した時にはデータが消えちゃったので、コマンド打つときはきちんとバックアップを取りましょう。

mongos> db.runCommand({removeShard : "shard0001"})

上のコマンドをもう一度実行すると、shardを削除しているかどうか確認できます。

// 削除中
mongos> db.runCommand({removeShard : "shard0001"})
{
	"msg" : "draining ongoing",
	"state" : "ongoing",
	"remaining" : {
		"chunks" : NumberLong(7),
		"dbs" : NumberLong(0)
	},
	"ok" : 1
}
// 削除完了
mongos> db.runCommand({removeShard : "shard0001"})
{
     "msg" : "removeshard completed successfully",
     "state" : "completed",
     "shard" : "shard0001",
     "ok" : 1
}
mongos> db.printShardingStatus()
--- Sharding Status --- 
  sharding version: { "_id" : 1, "version" : 3 }
  shards:
	{  "_id" : "shard0000",  "host" : "localhost:27031" }
	{  "_id" : "shard0002",  "host" : "localhost:27033" }
  databases:
	{  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
	{  "_id" : "blog",  "partitioned" : true,  "primary" : "shard0000" }
		blog.user chunks:
				shard0002	29
				shard0000	29
			too many chunks to print, use verbose if you want to force print
	{  "_id" : "test",  "partitioned" : false,  "primary" : "shard0000" }

駄々っ子だけど、楽しいもんだ