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全て同じ設定です。
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全て同じ設定です。
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サーバ
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として設定ファイルを作ります。
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と連携したりと便利になってきているので。