mongodbの輪廻転生(スケールイン)



俺です。
満を持して準備したmongodbも、時には育たないことがあります。
そんなときはスケールインしなければなりません。

今回は育たなかったmongodbを供養して、小さい形で輪廻転生させる話です。

# 条件と移行前移行後

- 移行中はサービス停止を伴う
- mongodbクラスタのシャードを削除するのではなく、新しく準備する
- mongodumpで静止点バックアップを取得してmongorestoreでDB単位のリストアを実行する
- 各mongodbクラスタに作られているデータベース及びコレクションはバッティングしないこと(潰してしまうので)

## as is

- mongodb cluster *2
- cluster内のロール
	- conf * 3
	- shard * 2
		- replica set *2(member *3)

計 18(9*2)台

各アプリケーションサーバでmongosが起動し、mongos経由でmongodbクラスタを利用しているものとします。

## to be

- mongodb cluster *1
- cluster内のロール
	- shard * 1
	- replica set *1
	- conf + replica set member *3

計 3台

各アプリケーションサーバでmongosが起動し、mongos経由でmongodbクラスタを利用しているものとします。

# 移行後モンゴの構築

## mongodbをインストール

Amazon Linux 2015.09なら現時点でmongodb 3.0.9-1がインストールされます。
よーしお兄さんWiredTiger使っちゃうぞー

```
$ sudo yum install mongodb -y
```

## mongodb config(metadata)用定義

- /etc/mongoc.conf

```
# mongodb config

systemLog:
  destination: file
  path: /var/log/mongodb/mongod_config_27019.log
  logAppend: true
processManagement:
  fork: true
  pidFilePath: /var/run/mongodb/mongod_config_27019.pid
net:
  port: 27019
storage:
  dbPath: "/var/lib/mongodb/mongoc"
  engine: wiredTiger
  wiredTiger:
    engineConfig:
      journalCompressor: none # default snappy
    collectionConfig:
      blockCompressor: none # default snappy
sharding:
  clusterRole: "configsvr"
```

- /etc/init.d/mongoc

mongo configの起動スクリプトを準備します。
yumでインストールしたmongodbのinitスクリプトをコピーして修正します。

- mongocの起動

```
$ sudo service mongoc start
```


## mongodb shard(replicaset)用定義

いつかまた大きく育つことを祈り、replSetNameにオリジン00を指定しておきます。

- /etc/mongod.conf

```
# mongodb mongod

systemLog:
  destination: file
  path: /var/log/mongodb/mongod_27017.log
  logAppend: true
processManagement:
  fork: true
  pidFilePath: /var/run/mongodb/mongod_27017.pid
net:
  port: 27017
storage:
  dbPath: "/var/lib/mongodb/mongod"
  engine: wiredTiger
  wiredTiger:
    engineConfig:
      journalCompressor: none # default snappy
    collectionConfig:
      blockCompressor: none # default snappy
replication:
  replSetName: "mongodb_replicaset00"
sharding:
  clusterRole: "shardsvr"
```

- /etc/init.d/mongod

mongodの起動するスクリプトを作成します。
yumでインストールしたmongodbのinitスクリプトをコピーして修正します。

- mongodの起動

```
$ sudo service mongod start
```

以上で新環境のmongo config/mongodが起動しました。

## replica setの作成

3台バラバラになっているモンゴのハートを一つにします。

- Primaryにするmongodbノードで操作

```
$ mongo --port=27017
mongod> config = {   "_id": "mongodb_replicaset00",   "members": [     { "_id": 0, "host": '172.16.11.21:27017'},     { "_id": 1, "host": '172.16.11.22:27017'},     { "_id": 2, "host": '172.16.11.23:27017'}     ] }
mongod> rs.initiate(config);
PRIMARY
```


## backup用replica set memberの設定

replica set memberは3台あるので、1台をhiddenにしておきます。

- backup用replica set member

今回は"_id":"1"のmemberをhidden memberにしておきます。

```
mongodb_replicaset00:PRIMARY> cfg = rs.config()
{
        "_id" : "mongodb_replicaset00",
        "version" : 2,
        "members" : [
                {
                        "_id" : 0,
                        "host" : "172.16.11.21:27017",
                        "arbiterOnly" : false,
                        "buildIndexes" : true,
                        "hidden" : false,
                        "priority" : 1,
                        "tags" : {

                        },
                        "slaveDelay" : 0,
                        "votes" : 1
                },
                {
                        "_id" : 1,
                        "host" : "172.16.11.22:27017",
                        "arbiterOnly" : false,
                        "buildIndexes" : true,
                        "hidden" : false,
                        "priority" : 1,
                        "tags" : {

                        },
                        "slaveDelay" : 0,
                        "votes" : 1
                },
                {
                        "_id" : 2,
                        "host" : "172.16.11.23:27017",
                        "arbiterOnly" : false,
                        "buildIndexes" : true,
                        "hidden" : false,
                        "priority" : 0,
                        "tags" : {

                        },
                        "slaveDelay" : 0,
                        "votes" : 1
                }
        ],
        "settings" : {
                "chainingAllowed" : true,
                "heartbeatTimeoutSecs" : 10,
                "getLastErrorModes" : {

                },
                "getLastErrorDefaults" : {
                        "w" : 1,
                        "wtimeout" : 0
                }
        }
}  
mongodb_replicaset00:PRIMARY> cfg.members[2].priority = 0
0
mongodb_replicaset00:PRIMARY> cfg.members[2].hidden = true
true
mongodb_replicaset00:PRIMARY> rs.reconfig(cfg)
{
        "ok" : 1,
        "$gleStats" : {
                "lastOpTime" : Timestamp(1455458942, 1),
                "electionId" : ObjectId("56c07ea680f80b802b651ba2")
        }
}
mongodb_replicaset00:PRIMARY>rs.config()
mongodb_replicaset00:PRIMARY> rs.config()
{
        "_id" : "mongodb_replicaset00",
        "version" : 2,
        "members" : [
                {
                        "_id" : 0,
                        "host" : "172.16.11.21:27017",
                        "arbiterOnly" : false,
                        "buildIndexes" : true,
                        "hidden" : false,
                        "priority" : 1,
                        "tags" : {

                        },
                        "slaveDelay" : 0,
                        "votes" : 1
                },
                {
                        "_id" : 1,
                        "host" : "172.16.11.22:27017",
                        "arbiterOnly" : false,
                        "buildIndexes" : true,
                        "hidden" : false,
                        "priority" : 1,
                        "tags" : {

                        },
                        "slaveDelay" : 0,
                        "votes" : 1
                },
                {
                        "_id" : 2,
                        "host" : "172.16.11.23:27017",
                        "arbiterOnly" : false,
                        "buildIndexes" : true,
                        "hidden" : true,
                        "priority" : 0,
                        "tags" : {

                        },
                        "slaveDelay" : 0,
                        "votes" : 1
                }
        ],
        "settings" : {
                "chainingAllowed" : true,
                "heartbeatTimeoutSecs" : 10,
                "getLastErrorModes" : {

                },
                "getLastErrorDefaults" : {
                        "w" : 1,
                        "wtimeout" : 0
                }
        }
}          
```


# アプリケーションサーバ(mongos)の設定

## 新クラスタ用mongos定義作成

新しいmongo clusterへの接続定義を作成します。
新しいmongo clusterのmongo configノードのIPはDNS登録しておきます。
(公式ドキュメント上はmongodのノード名もDNSかhostsで名前解決できるようにしておくのが推奨されてたはず..)

- /etc/mongos.conf

```
  # mongos(mongodb router)

systemLog:
  destination: file
  path: /var/log/mongodb/mongos.log
  logAppend: true
processManagement:
  fork: true
  pidFilePath: /var/run/mongodb/mongos.pid
net:
  port: 37017
sharding:
  configDB: mongodb-conf0.mongo.internal:27019,mongodb-conf1.mongo.internal:27019,mongodb-conf2.mongo.internal:27019
```

- /etc/init.d/mongos-new

mongos起動スクリプトをコピーして修正します。

## mongos起動

- command

```
$ sudo service mongos-new start
```

# シャードの設定

新mongosの設定完了済みのサーバで作業する



## シャード追加

```
$ mongo --port=37017
mongos> sh.status()
--- Sharding Status ---
  sharding version: {
        "_id" : 1,
        "minCompatibleVersion" : 5,
        "currentVersion" : 6,
        "clusterId" : ObjectId("56c081bccabb5eff10c1c995")
}
  shards:
  balancer:
        Currently enabled:  yes
        Currently running:  no
        Failed balancer rounds in last 5 attempts:  0
        Migration Results for the last 24 hours:
                No recent migrations
  databases:
        {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
mongos> sh.addShard("mongodb_replicaset00/172.16.11.21:27017");
{ "shardAdded" : "mongodb_replicaset00", "ok" : 1 }
mongos> sh.status()
--- Sharding Status ---
  sharding version: {
        "_id" : 1,
        "minCompatibleVersion" : 5,
        "currentVersion" : 6,
        "clusterId" : ObjectId("56c081bccabb5eff10c1c995")
}
  shards:
        {  "_id" : "mongodb_replicaset00",  "host" : "mongodb_replicaset00/172.16.11.21:27017,172.16.11.22:27017,172.16.11.23:27017" }
  balancer:
        Currently enabled:  yes
        Currently running:  no
        Failed balancer rounds in last 5 attempts:  0
        Migration Results for the last 24 hours:
                No recent migrations
  databases:
        {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
```


## テスト用データベースとコレクションの作成

newmongodbデータベースを作り、コレクションnewshard_testを作成する。

- mongosでの作業

```
mongos> use newmongodb
mongos> db.createCollection("newshard_test");
{
        "ok" : 1,
        "$gleStats" : {
                "lastOpTime" : Timestamp(1455458157, 1),
                "electionId" : ObjectId("56c07ea680f80b802b651ba2")
        }
}
```

- replica set member(primary)での確認

mongos経由で作成したデータベースとコレクションが、PRIMARYからも確認できる。

```
$ mongo --port=37017
mongodb_replicaset00:PRIMARY> show dbs
config  0.000GB
local   0.000GB
newmongodb    0.000GB
mongodb_replicaset00:PRIMARY> use newmongodb
switched to db newmongodb
mongodb_replicaset00:PRIMARY> show collections;
newshard_test
```
## テストドキュメントの挿入

- newmongodb.newshard_testにnewshard_testというドキュメントを挿入する

```
mongos> db.createCollection("newshard_test");
{
        "ok" : 1,
        "$gleStats" : {
                "lastOpTime" : Timestamp(1455458157, 1),
                "electionId" : ObjectId("56c07ea680f80b802b651ba2")
        }
}
```


これで新mongodb clusterの準備が整いました。

# mongodbのデータ移行

- サービス停止

輪廻転生するモンゴを使うサービスにDB writeが走らないよう、メンテ入れするなりします。

## 移行元の環境からmongodump

- Balancerの停止とmongodump

今回は前提条件の通りにmongos経由でmongodumpします。
dump前にBalancerを止めておきます。

- - Balancerの停止

移行対象のmongodb cluster全てに対してBalancer停止コマンドを実行します。

```
$ mongo --port=27017
mongos> sh.status();
mongos> sh.stopBalancer();
mongos> sh.disableBalancing(true);
mongos> sh.status();
※Balancerが停止していること
$ mongo --port=27117
mongos> sh.status();
mongos> sh.stopBalancer();
mongos> sh.disableBalancing(true);
mongos> sh.status();
※Balancerが停止していること

```

- mongodump

mongodb clusterの数だけmongos経由でmongodumpを実行します。

```
$ mongodump --port=27017 --out=mongodbcluster_shard_dump
$ mongodump --port=27117 --out=mongocluster2_shard_dump
```

## 移行先の環境へmongorestore

db数分mongorestoreを実行します。

- full restoreはエラーになる

config databaseはリストアできませんよとエラーが出ます。

```
$ mongorestore --port=37017 --dir=mongodbcluster_shard_dump/
2016-02-14T23:50:39.692+0900    building a list of dbs and collections to restore from mongodbcluster_shard_dump/ dir
2016-02-14T23:50:39.693+0900    Failed: cannot do a full restore on a sharded system - remove the 'config' directory from the dump directory first
```


- db単位でmongorestoreする

```
$ mongorestore --port=37017 --db=mongodb --dir=mongodbcluster_shard_dump/mongodb/
2016-02-14T23:51:25.823+0900    building a list of collections to restore from mongodbcluster_shard_dump/mongodb/ dir
2016-02-14T23:51:25.860+0900    reading metadata file from mongodbcluster_shard_dump/mongodb/mongocollection.metadata.json
2016-02-14T23:51:25.862+0900    restoring mongodb.mongocollection from file mongodbcluster_shard_dump/mongodb/mongocollection.bson
2016-02-14T23:51:27.035+0900    restoring indexes for collection mongodb.mongocollection from metadata
2016-02-14T23:51:27.054+0900    finished restoring mongodb.mongocollection (33 documents)
2016-02-14T23:51:27.054+0900    done
```

## 移行後確認


移行したコレクション内のドキュメント件数をチェックします。
移行前/移行後同じ件数なので無事移行できました。

- 移行前のコレクション内ドキュメント件数

```
$ mongo --port=27017
MongoDB shell version: 3.0.9
connecting to: 127.0.0.1:27017/test
mongos> use mongodb;
switched to db mongodb
mongos> db.mongocollection.find().count();
33
```

- 移行後のコレクション内ドキュメントの件数

```
$ mongo --port=37017
MongoDB shell version: 3.0.9
connecting to: 127.0.0.1:37017/test
mongos> use mongodb;
switched to db mongodb
mongos> db.mongocollection.find().count();
33
```

- shardの状態を確認

移行前に設定していたシャードキーが剥がれちゃってます。
が、今回は1シャードに縮小したので戻す必要はないです。


```
mongos> sh.status()
--- Sharding Status ---
  sharding version: {
        "_id" : 1,
        "minCompatibleVersion" : 5,
        "currentVersion" : 6,
        "clusterId" : ObjectId("56c081bccabb5eff10c1c995")
}
  shards:
        {  "_id" : "mongodb_replicaset00",  "host" : "mongodb_replicaset00/172.16.11.21:27017,172.16.11.22:27017" }
  balancer:
        Currently enabled:  yes
        Currently running:  no
        Failed balancer rounds in last 5 attempts:  0
        Migration Results for the last 24 hours:
                No recent migrations
  databases:
        {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
        {  "_id" : "newmongodb",  "partitioned" : false,  "primary" : "mongodb_replicaset00" }
        {  "_id" : "mongodb",  "partitioned" : false,  "primary" : "mongodb_replicaset00" }

mongos>
```

# アプリケーション(mongos)新環境への切替

- アプリケーションのmongosエンドポイントを新環境にアクセスするように変更します。
- 移行前環境のmongodbクラスタにアクセスするmongosを停止します。

以上で移行と切替は完了です。
 

# 移行前環境の破棄

切替後、特に問題なければ移行前のmongodbクラスタを破棄します。

`ささやき　えいしょう　いのり　terminate`

おわり。

# おまけ

## シャードのスケールイン

今回のように作り直しするのではなく、シャードだけスケールインしたい時は、オンラインで可能です。

removeShardを使います。
removeShardは2回実行する必要があります。

draining -> dropの順で状態が変化します。


- シャードのdraining

```
mongos> sh.status();
移行前のシャード状態を確認する
mongos> use admin
mongos> db.runCommand( { removeShard: "shard01" } );
{
        "msg" : "draining ongoing",
        "state" : "ongoing",
        "remaining" : {
                "chunks" : NumberLong(0),
                "dbs" : NumberLong(2)
        },
        "note" : "you need to drop or movePrimary these databases",
        "dbsToMove" : [
                "mongocollection",
                "test"
        ],
        "ok" : 1
}
```

- シャードの確認とPrimaryシャードの変更

コレクションの所属するPrimaryシャードが削除対象のシャードだった場合、
2回目のremoveShard実行時に以下のようなエラーメッセージが発生します。

- 削除対象のシャードにPrimaryコレクションが存在するとエラー

```
mongos> db.runCommand( { removeShard: "shard01" } );
{
        "msg" : "draining ongoing",
        "state" : "ongoing",
        "remaining" : {
                "chunks" : NumberLong(0),
                "dbs" : NumberLong(2)
        },
        "note" : "you need to drop or movePrimary these databases",
        "dbsToMove" : [
                "mongocollection",
                "test"
        ],
        "ok" : 1
}
```

- コレクションのPrimaryシャード変更

```
mongos> sh.status();
チャンクが全て残すシャードに寄ったことを確認します。
mongos> db.runCommand( { movePrimary: "mongocollection", to :"shard00" } );
{
        "primary " : "shard00:shard00/172.16.10.21:27017,172.16.10.22:27017,172.16.10.23:27017",
        "ok" : 1
}
mongos> sh.status()
コレクションのPrimaryシャードが削除対象ではないシャードになっていることを確認する
```

- シャードの削除

チャンクが全て残存シャードに移動していること。
コレクションのPrimaryシャードが残存シャードとなっていること。

以上がOKだったらシャードを削除します。さよなら~

```
mongos> use admin
mongos> db.runCommand( { removeShard: "shard01" } );
{
        "msg" : "removeshard completed successfully",
        "state" : "completed",
        "shard" : "shard01",
        "ok" : 1
}
```

おわりのおわり。