MongoDBでゆるふわDB体験

第5回MongoDBのシャーディングを試してみよう

はじめに

今回は、MongoDBのシャーディングについて説明します。

シャーディングとは、データを複数のサーバに分散させる機能です。今回は、最初にシャーディングのメリットを説明し、続けてシャーディングの概要、さらにシャーディングに登場する重要キーワードを解説します。2ページ目以降ではシャーディングの構築手順について解説します。

シャーディングはMongoDBの機能の中でも重要かつ複雑なもののひとつです。手元の環境で構築することが、シャーディングを理解する大きな助けになりますので、本記事を参考にぜひ構築してみてください。

シャーディングのメリット

シャーディングはMongoDBを水平スケーリングさせる機能で、以下のようなメリットをもたらします。

負荷分散による性能の向上

データを複数のサーバに分散させることにより、CPUやI/O負荷を分散させることが可能です。後述しますが、MongoDBはキーの範囲でデータを分散させます。適切なキー範囲を設定することにより、負荷を水平スケーリングさせることが可能となります。

リソース分散によるコストパフォーマンスの向上

近年、メモリやディスクの価格は大変安くなってきましたが、サイズの大きなモジュールほど価格が高く、また価格の上昇幅が大きくなることは変わりません。

メモリを例にあげますと、合計で64GBのメモリが必要になったとき、4GBメモリモジュール16枚の価格より16GBメモリモジュール4枚の価格の方が、一般的にコストがかかります。メモリもディスクも1台のサーバに積めるユニット数には制限があります。このような背景から、複数のサーバにデータを分散させることが、コストパフォーマンスの向上につながります。MongoDBではメモリが性能に直結するため、十分なサイズのメモリを確保することが推奨されています。

MongoDBのシャーディングの概要

シャーディングについて、データ分散と自動バランシングという2つの特徴を説明します。

シャードキーのレンジ(範囲)によるデータ分散

MongoDBのシャーディングはレンジパーティション方式を採用しています[1]⁠。シャードキーを指定することで、各サーバに格納されるデータの範囲が決定されます。サーバ間で重複データは持たず、1つのデータが格納されるサーバはシャードキーの範囲によって1つに決定されます。

シャーディングのイメージを図で表すと、図1のようになります。図1に出てくる用語に関して後ほど説明しますので、まずは全体的なイメージを押さえてください。

図1 シャーディングの概要図
図1 シャーディングの概要図

自動バランシング

キー範囲の調整と、調整に伴うサーバ間のデータの移動まで全部MongoDBが行う自動バランシング[2]という機能を備えています。自動バランシングにより、サーバ間のデータの偏りをユーザが意識しなくてもよいように設計されています。また、新たにサーバを追加した場合も、同様にデータの移動を行い偏りがなくなるように自動調整します。

重要キーワードの説明

シャーディングに登場するキーワードに関して説明します。

シャード

実際にデータが格納されているmongodプロセスです。1つのドキュメントは1つのシャードに格納され、シャード間でデータの複製は行いません。必須ではありませんがレプリケーション構成とすることを推奨されています。

configサーバ

シャーディングのメタデータを管理しているmongodプロセスです。単一障害点となるので、複数のconfigサーバで構成することが推奨されています。

mongosサーバ

シャーディングにおけるルーティングプロセスです。シャードとクライアントを連携させます。必要があれば複数のmongosサーバをたてることが可能です。mongodプロセスではないので、状態やデータを持っていません。

シャードキー

データを分散する範囲のキーです。複数指定もできます。キー上のどの範囲のデータがどのシャードに格納されるかは、MongoDBが管理し、データの偏りによって自動調整を行います。シャーディングにおいて、シャードキーの設計が非常に重要になります。

チャンク

シャーディングにおけるチャンクとは、分散するデータの単位です。具体的には、あるコレクションの連続した範囲のデータで、複数のドキュメントとなります。チャンクの最大サイズに達すると分割され、シャードが持っているチャンク数に応じて必要ならば他のシャードに移動されます。チャンクの最大サイズは変更可能です。

これまでに説明したキーワードは、現時点では明確にイメージできなくても心配いりません。次のページ以降で実際にシャーディング環境を構築してみることで、理解を深めていきましょう。

シャーディングを試してみる(前半)

このページと次のページでは、実際にシャーディング構成を作ってみます。

今回は、1台の物理マシンにポートを分けて5つのサーバを立ち上げます。具体的にはconfigサーバ、mongosサーバ、および3つのシャード(node0、node1、node2)です。3つのmongodがそれぞれ別のシャードになります図2参照⁠⁠。

図2 シャーディング構成図
図2 シャーディング構成図

システムを構成するサーバの準備

ディレクトリ作成

まずデータディレクトリとログディレクトリを作成します。手順はすべてMongoDBを展開したディレクトリで行うことを想定しています。

$ cd (MongoDBの展開ディレクトリ)
$ mkdir -p data/config
$ mkdir data/node0
$ mkdir data/node1
$ mkdir data/node2
$ mkdir log

シャードの起動

$ bin/mongod --shardsvr --port 30000 --dbpath data/node0 --logpath log/node0.log --fork   
$ bin/mongod --shardsvr --port 30001 --dbpath data/node1 --logpath log/node1.log --fork   
$ bin/mongod --shardsvr --port 30002 --dbpath data/node2 --logpath log/node2.log --fork

mongodコマンドに--shardsvrのオプションを指定することにより、このmongodがシャードになります。

configサーバ起動

$ bin/mongod --configsvr --port 20001 --dbpath data/config --logpath log/config.log --fork

mongodコマンドに--configsvrのオプションを指定することにより、このmongodがconfigサーバになります。

mongosサーバ起動

$ bin/mongos --configdb localhost:20001 --port 20000 --logpath log/mongos.log --chunkSize 1 --fork

mongosコマンドによりmongosサーバを起動します(mongodではありません⁠⁠。--configdbにてconfigサーバを指定します。mongosサーバはメモリ上にのみ存在するプロセスであるため、dbpathを指定する必要はありません。chunkSizeはチャンクのサイズをしています。デフォルトでは64Mですが、今回はチャンクが分割される動作を確認したいため、小さい1MBに設定します。

確認

psコマンドで5つのプロセスが見えればOKです。

$ ps -ef | grep mongo
root 1221 ・・ bin/mongod --shardsvr --port 30000 --dbpath data/node0 --logpath log/node0.log
root 1235 ・・ bin/mongod --shardsvr --port 30001 --dbpath data/node1 --logpath log/node1.log
root 1236 ・・ bin/mongod --shardsvr --port 30002 --dbpath data/node2 --logpath log/node2.log
root 1239 ・・ bin/mongod --configsvr --port 20001 --dbpath data/config --logpath log/config.log
root 1241 ・・ bin/mongos --configdb localhost:20001 --port 20000 --logpath log/mongos.log --chunkSize 1

mongosサーバにシャードの追加

mongoシェルで、mongosサーバのadminデータベースに接続します

$ bin/mongo localhost:20000/admin

sh.addShardメソッドでシャードを追加していきます。

mongos> sh.addShard("localhost:30000")    // ←30000ポートのmongodを追加
{ "shardAdded" : "shard0000", "ok" : 1 }  // ←okの値が1であれば正常です

mongos>  sh.addShard("localhost:30001")   // ←30001ポートのmongodを追加
{ "shardAdded" : "shard0001", "ok" : 1 }  // ←okの値が1であれば正常です

mongos> sh.addShard("localhost:30002")    // ←30002ポートのmongodを追加
{ "shardAdded" : "shard0002", "ok" : 1 }  // ←okの値が1であれば正常です

「sh」というオブジェクトにはシャーディングの設定を簡単にするためのメソッドがあります。sh.help()を実行することにより使い方のヘルプを見ることができます。

sh.statusメソッドで追加したシャードが正しく追加されているかどうか確認します。

mongos> sh.status()
--- Sharding Status ---
  sharding version: { "_id" : 1, "version" : 3 }
  shards:
        {  "_id" : "shard0000",  "host" : "localhost:30000" } // ←3つのmongodが追加されている
        {  "_id" : "shard0001",  "host" : "localhost:30001" }
        {  "_id" : "shard0002",  "host" : "localhost:30002" }
  databases:
        {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }

データの投入

続いて、mongosサーバ経由でデータを投入してみます。

mongosサーバに接続している状態で、logdbというデータベースを作ります。

mongos> use logdb
switched to db logdb

続いて、logsというコレクションに10万件データを投入します。mongoシェルではjavascriptの文法が使えるため、forループによりデータを挿入しています。

mongos> for(var i=1; i

最後にuidにインデックスを張ります。理由は、これからこのコレクションをシャード化する場合、シャードキーに対応するインデックスを作成しておく必要があるためです。

mongos> db.logs.ensureIndex( { uid : 1 } );

この時点ではまだシャーディングは有効になっていません。単純に最初のノードに10万件のデータが入っているだけです図3参照⁠⁠。

図3 シャーディング有効前のイメージ図
図3 シャーディング有効前のイメージ図

この状態を確認するには、mongoシェルにてmongosサーバのconfigデータベースの中身を見ればわかります。configデータベースのchunksコレクションにクエリをかけてみましょう。

mongos> use config
switched to db config
mongos> db.chunks.count()    // ←チャンクの数を表示
0                            // ←0であることがわかる

シャーディングを試してみる(後半)

シャーディングの有効化

それではいよいよシャーディングを有効にして、データを分散させてみましょう。シャーディングを有効にするにはshオブジェクトのenableShardingメソッドにデータベース名を指定します。

mongos> use admin
switched to db admin
mongos> sh.enableSharding("logdb")
{ "ok" : 1 }

次にsh.shardCollectionメソッドでシャード化するコレクションを指定します。第一引数は、(データベース名).(コレクション名)の文字列、第二引数はインデックスを作成するときのハッシュです。

mongos> sh.shardCollection("logdb.logs" , { uid : 1 })
{ "collectionsharded" : "logdb.logs", "ok" : 1 }

シャーディングの状態確認

ここでsh.statusメソッドでシャーディングの状態を表示すると、3つのシャードサーバにそれぞれチャンクができている様子がわかります。

mongos> sh.status()
--- Sharding Status ---
  sharding version: { "_id" : 1, "version" : 3 }
  shards:
    {  "_id" : "shard0000",  "host" : "localhost:30000" }
    {  "_id" : "shard0001",  "host" : "localhost:30001" }
    {  "_id" : "shard0002",  "host" : "localhost:30002" }
  databases:
    {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
    {  "_id" : "logdb",  "partitioned" : true,  "primary" : "shard0000" }
      logdb.logs chunks:
          shard0001       1    // ←shard0001のチャンク数
          shard0002       1    // ←shard0002のチャンク数
          shard0000       8    // ←shard0000のチャンク数
        { "uid" : { $minKey : 1 } } -->> { "uid" : 10083 } on : shard0001 Timestamp(2000, 0)
        { "uid" : 10083 } -->> { "uid" : 20166 } on : shard0002 Timestamp(3000, 0)
        ・・・

上記の出力では、shard0000のチャンク数が8つ、shard0001のチャンク数が1つ、そしてshard0002のチャンク数が1つとなっています。

このようにチャンクの数が偏っているのは、まだデータが移動中であるためです。しばらくした後に、もう一度sh.statusを実行してください。

--- Sharding Status ---
  sharding version: { "_id" : 1, "version" : 3 }
  shards:
    {  "_id" : "shard0000",  "host" : "localhost:30000" }
    {  "_id" : "shard0001",  "host" : "localhost:30001" }
    {  "_id" : "shard0002",  "host" : "localhost:30002" }
  databases:
    {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
    {  "_id" : "logdb",  "partitioned" : true,  "primary" : "shard0000" }
      logdb.logs chunks:
        shard0001       3    // ←shard0001のチャンク数
        shard0002       3    // ←shard0002のチャンク数
        shard0000       4    // ←shard0000のチャンク数
          { "uid" : { $minKey : 1 } } -->> { "uid" : 10083 } on : shard0001 Timestamp(2000, 0)
          { "uid" : 10083 } -->> { "uid" : 20166 } on : shard0002 Timestamp(3000, 0)

しばらく時間がたつと、チャンクの数が3,3,4と均等になっていることがわかります図4参照⁠⁠。

図4 チャンクの数が均等になったイメージ図
図4 チャンクの数が均等になったイメージ図
データ数やチャンク数はイメージです。

また、出力の後半に各チャンクに入っているシャードキーの範囲が出力されています。

   { "uid" : 10083 } -->> { "uid" : 20166 } on : shard0002 Timestamp(3000, 0)

上記の例では、shard0002のチャンクには、uidの範囲が10083≦uid<20166であるコレクションが格納されていることがわかります。

[参考]$minKeyはすべての値よりも小さいとみなされ、$maxKeyはすべての値よりも大きいとみなされます。

また、別の確認方法として、mongosサーバのconfigテーブルを見る方法もあります。

mongos> use config
switched to db config
mongos> db.chunks.count()
10
mongos> db.chunks.findOne()

各シャードサーバの状態確認

シャーディングされている状態で、各シャードサーバがどのようになっているか確認しましょう。やり方は簡単で、各シャードサーバにmongoシェルで接続すればOKです。まずnode1にアクセスしてみましょう。

$ bin/mongo localhost:30000/logdb
> db.logs.count()
39503
> exit

このように、各シャードサーバに入っているコレクションの数を参照することができます。また、findメソッドなどで普通にコレクションを参照することもできます。他のシャードサーバでも同じです。

$ bin/mongo localhost:30001/logdb
> db.logs.count()
30248
> exit

$ bin/mongo localhost:30002/logdb
> db.logs.count()
30249
> exit

次回のテーマ

今回はMongoDBのデータ分散機能であるシャーディングについて紹介しました。シャーディング機能を使用することにより、負荷分散とリソース分散によるコスト削減を実現できます。また、自動バランシングというMongoDBの特徴的な機能も紹介しました。

次回は、前回の記事で紹介したレプリケーションと今回紹介したシャーディングを組み合わせた構成を紹介します。

おすすめ記事

記事・ニュース一覧