Posted at

mongoDBでシャーディング+レプリカセットしてみる

More than 3 years have passed since last update.


mongoDBのシャーディング+レプリカセット


前提


  • AWS EC2 AmazonLinux

  • mongoDB 2.6

以前、mongoDBのレプリカセットを試したので今度はシャーディングを試してみました。これでデータ量やクエリが増えたとしてもスケールアウトすることで負荷分散が可能になります。


構成図

mongoDBのシャーディングには、mongosのRouterと呼ばれるサーバと、config serverという設定サーバが存在しています。


mongos Router

シャードとクライアントを連携させるルーティングプロセスです。このサーバ自体はデータや状態は持たないただのインターフェイスになっています。Railsなどのアプリケーションは、このmongosに接続をしていくことになります。mongodではなく、mongosという点に注意してください。


configサーバ

シャーディングのメタ情報を保持している設定サーバです。1台もしくは3台で稼働させられますが、本番環境化においては3台のconfigサーバが望ましいです。負荷はさほどないので、スペックが低くても大丈夫です。もしくは、他のサーバと同居させるような形でもいいです。ただし、configサーバ同士が同居するのはNGです。


シャード

データを分散して持つDB群です。今回は、Primary + Secondary + Arbiterの3台構成を1つのシャードとして作ることにしています。


シャーディングの設定


シャード1(repl_1)

Primary + Secondary + Arbiter全て同じ設定です。


/etc/mongod.conf

logpath=/var/log/mongodb/mongod.log

logappend=true
fork=true
dbpath=/var/lib/mongo
pidfilepath=/var/run/mongodb/mongod.pid
#bind_ip=127.0.0.1 ←コメントアウト
port=27017

shardsvr=true
replSet=repl_1


レプリカセットの設定を行います。

> config = {_id: 'repl_1',

members: [
{_id: 0, host: '172.31.100.10:27017'},
{_id: 1, host: '172.31.100.11:27017'},
{_id: 2, host: '172.31.100.12:27017', arbiterOnly: true},
]
}

> rs.initiate(config)


シャード2(repl_2)

Primary + Secondary + Arbiter全て同じ設定です。


/etc/mongod.conf

logpath=/var/log/mongodb/mongod.log

logappend=true
fork=true
dbpath=/var/lib/mongo
pidfilepath=/var/run/mongodb/mongod.pid
#bind_ip=127.0.0.1 ←コメントアウト
port=27017

shardsvr=true
replSet=repl_2


レプリカセットの設定を行います。

> config = {_id: 'repl_2',

members: [
{_id: 0, host: '172.31.100.20:27017'},
{_id: 1, host: '172.31.100.21:27017'},
{_id: 2, host: '172.31.100.22:27017', arbiterOnly: true},
]
}

> rs.initiate(config)


configサーバ


/etc/mongod.conf

logpath=/var/log/mongodb/mongod.log

logappend=true
fork=true
dbpath=/var/lib/mongo
pidfilepath=/var/run/mongodb/mongod.pid
#bind_ip=127.0.0.1 ←コメントアウト
port=27017

configsvr=true



mongos

configサーバのホストとポートを指定します。この時、IPアドレスよりもホスト名などのほうがサーバ障害が起きた時など、楽なのでそうしましょう。

また、mongod.confではなくmongos.confとして設定ファイルを作ります。


/etc/mongos.conf

logpath=/var/log/mongodb/mongod.log

logappend=true
fork=true
pidfilepath=/var/run/mongodb/mongod.pid
#bind_ip=127.0.0.1 ←コメントアウト
port=27017
configdb=mongo-config.example.com:27017

# configサーバが複数台の場合はカンマ区切り
# configdb=mongo-config1.example.com:27017,mongo-config2.example.com:27017,mongo-config3.example.com:27017


mongosを起動します。

$ sudo mongos -f /etc/mongos.conf


シャーディング設定

> db.adminCommand({addshard: "repl_1/172.31.10.155:27017,172.31.100.11:27017", name: "repl_1", allowLocal: true})

> db.adminCommand({addshard: "repl_2/172.31.20.155:27017,172.31.100.21:27017", name: "repl_2", allowLocal: true})

確認します。

> sh.status()

これでシャーディングの設定は完了です。


シャーディング対象を決める

sampleデータベースのhogesコレクションをシャーディング対象にしてみます。

> use admin

> sh.enableSharding("sample")
> sh.shardCollection("sample.hoges" , { uid : 1 })

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

実際にデータを投入してみます。

> use sample

> for(var i=1; i<=10000; i++) db.hoges.insert({"uid":i, "value":Math.floor(Math.random()*100000+1)})

実際に分散しているかを確認します。

> db.hoges.count()

10000

> db.hoges.count()

4800

> db.hoges.count()

5200

ちゃんと各シャードに分散してデータが登録されていることが確認できました。


シャードキーの選定

データを分割させるルール、シャードキーをどのように設定するかが重要であり難しい所ですね。

サンプルでは単純なuidをシャードキーとして指定していましたが、これはあまり効率的ではない指定方法のようです。なぜなら、単純に増加していくだけの値をシャードキーとして指定すると、1つのシャードに負荷が集中してしまうから。

単純に増加するだけの値がダメなら、単純にランダムな値をシャードキーとして指定すればいいのかというと、必ずしもそうでもないみたいです。書き込み(挿入)が分散されるという点では効率的なのですが、参照時に全てのシャードにアクセスをしなくてはならなくなる事があります。例えば、ユーザが投稿した最新の10件を取得するときなどです。参照時には、なるべく同じシャードにデータが固まっている方が効率的です。

じゃあ、どうすればいいの?って感じですね。データやアプリケーションの仕様にもよるんですが、基本的には複合キーで対応するとよくなるかもしれないです。例えばユーザIDと投稿IDという複合とか。


まとめ

mongoDBのシャーディング構築自体は超楽ちんですね。でも、実際に大量データとトラフィックを安定的にさばくには、それなりの運用ノウハウが必要だと感じました。ここらへんは地道に貯めていくしかないか…。

mongoDBインフラの面倒を見るコストが厳しい場合、モノは結構違うけど、DynamoDBとかを検討するのもいいかなと思います。グローバルセカンダリインデックスが変更できるようになったり、AWS Lambdaと連携したりと便利になってきているので。