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


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

## 前提

- AWS EC2 AmazonLinux
- mongoDB 2.6


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

- 参考 [mongoDBのレプリカセットを構築してRails+mongoidで接続する](http://qiita.com/zaru/items/18c53a05a11f7436d46f)

## 構成図

![mongodb_sharding.png](https://qiita-image-store.s3.amazonaws.com/0/2152/60893cf2-fff3-5aea-cd02-16e15a187308.png "mongodb_sharding.png")

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
```

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

```mongodb
> 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
```

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

```mongodb
> 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
```

### シャーディング設定

```mongos
> 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})
```

確認します。

```mongos
> sh.status()
```

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

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

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

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

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

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

```mongos
> use sample

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

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

```mongosサーバ上
> db.hoges.count()
10000
```

```repl_1シャード上
> db.hoges.count()
4800
```

```repl_2シャード上
> db.hoges.count()
5200
```

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

## シャードキーの選定

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

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

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

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

## まとめ

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

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