MongoDBのReplica Setを障害から復旧させる方法を二通り書く。

MongoDB 3.4

構成について

以下の3台構成の場合の復旧方法について書く。

  • primary
  • arbiter
  • secondary(hidden) ※バックアップ用

primaryのpriorityは1, arbiterとsecondary(hidden)のpriorityは0にしている。
primary, arbiter, secondary(hidden)の順でReplica Setに登録している。

ホスト名について、primaryサーバをdb1001, arbiterをdb1002, secondary(hidden)をdb1003とする。
バックアップを保管しているサーバをbk0000とする。

Replica SetのPrimaryのサーバ自体に障害が発生した場合

OSを起動することができない状態や、サーバのデータを消失してしまった状態など、サーバ自体をすぐに使えなくなった場合を想定。

対応

secondary(hidden)をPrimary昇格させる

手順

secondary(hidden)サーバのMongoDBからPrimary昇格のためrs.reconfigを実行する。Secondaryからrs.reconfigは通常実行できないので、{force: true}をつける必要がある。
以下手順は公式ドキュメントのReconfigure a Replica Set with Unavailable Membersを参考に作成。

$ ssh db1003
$ mongo localhost:27017/admin -uroot -p ${PASSWORD}

rs:SECONDARY> cfg = rs.conf()
# secondary(hidden)のpriorityとhiddenの設定を変える。
# 念のためpriorityを最も高く設定し、元primaryをReplica Setに再接続してもPrimary/Secondaryが入れ替わらないようにする。
rs:SECONDARY> cfg.members[2].priority = 10
rs:SECONDARY> cfg.members[2].hidden = false
# primaryを省いてconfを再修正
rs:SECONDARY> cfg.members = [cfg.members[1], cfg.members[2]]
# cfg確認
rs:SECONDARY> cfg
# 反映
rs:SECONDARY> rs.reconfig(cfg, {force: true})
rs:SECONDARY> rs.config()
rs:PRIMARY> #Primary昇格成功

事後対応

secondary(hidden)をPrimaryに昇格させてサービスを継続させた後、旧primaryの障害を取り除き、Replica Setに再参加させる。

旧primaryの障害を取り除いた後、旧primaryのMongoDBのデータを空にしてから起動する。

$ ssh db1001
# mongodの停止確認
$ sudo service mongod status
$ cd /var/lib/mongo/
# データを空にする(ディスクに余剰があれば念のため退避)
$ sudo mv mongod mongod.bk
$ sudo -u mongod mkdir mongod
$ sudo service mongod start

現primaryでrs.addを実行し、旧primaryをReplica Setに参加させる。

$ ssh db1003
$ mongo localhost:27017/admin -uroot -p ${PASSWORD}
rs:PRIMARY> rs.add("db1001:27017")
# 確認
rs:PRIMARY> rs.config()
rs:PRIMARY> rs.status()

旧primaryでデータの同期が始まる。データが同期されたかをログインと実データを見てみることで確認する。

$ ssh db1001
$ mongo localhost:27017/admin -uroot -p ${PASSWORD}
rs:SECONDARY> rs.slaveOk()
# 確認
rs:SECONDARY> show dbs

現secondaryはhiddenにしたいので、priorityとhiddenを変更する。

$ ssh db1003
$ mongo localhost:27017/admin -uroot -p ${PASSWORD}
rs:PRIMARY> cfg = rs.config()
rs:PRIMARY> cfg.members[2].priority = 0
rs:PRIMARY> cfg.members[2].hidden = true
# 確認
rs:PRIMARY> cfg
# 反映
rs:PRIMARY> rs.reconfig(cfg)
rs:PRIMARY> rs.config()

現primaryのpriorityを1に戻す。

rs:PRIMARY> cfg = rs.config()
rs:PRIMARY> cfg.members[1].priority = 1
# 確認
rs:PRIMARY> cfg
# 反映
rs:PRIMARY> rs.reconfig(cfg)
rs:PRIMARY> rs.config()

Replica Set全体が論理的に壊れた場合

人的ミスやプログラムのバグなどでデータを大量に誤って更新した場合など、正常データの復旧が難しい場合を想定。

論理バックアップ

前提として、論理バックアップを実行済みの必要がある。

今回は、secondary(hidden)サーバにてmongodumpをcronで日次で実行し、bk0000で保管している。またmongodump実施時間帯を除いて毎時oplogをdumpしている。これらを使用して1時間前までPoint-in-time Recoveryする。

bk0000から実行されるmongodumpのスクリプト抜粋は以下の通り。gzip処理が重いので、bk0000で実行せず各MongoDBサーバで実行されるようにしている。

# dump daily at 3:00
ssh ${TARGET_HOST} "mongodump --host localhost:27017 -u root -p ${PASSWORD} --oplog --gzip --archive" > ${BACKUPDIR}/${DAY}_${TARGET_HOST}_mongodump.dmp.gz

oplogdumpのスクリプト抜粋は以下の通り。先ほどと同様の理由でsshを通して実行している。

# for Point-in-Time Recovery, dump oplog periodically
oplog_from=$(date -d "$(date "+%Y-%m-%d 03:00:00")" +%s)
mkdir ${BACKUPDIR}/${DAY}_${TARGET_HOST}_mongodump.oplogD
cat <<EOF | sudo ssh ${TARGET_HOST} > ${BACKUPDIR}/${DAY}_${TARGET_HOST}_mongodump.oplogD/oplog.gz
mongodump --host localhost:27017 -u root -p ${PASSWORD} \
--authenticationDatabase=admin \
-d local -c oplog.rs \
--query '{"ts": {\$gt: Timestamp($oplog_from, 1)}}' \
--out - |
gzip
EOF

対応

primaryへmongorestoreを実行する。その後oplogを適用して1時間前までPoint-in-time Recoveryする。

ただし、現在MongoDBにあるoplogが1時間前から論理的に壊れる直前まで残っている場合は、mongorestoreする前に論理的に壊れる直前までのoplogをdumpして、さらにPoint-in-time Recoveryすることで、障害発生の直前まで元に戻せる。

$ mongodump --host localhost:27017 -u root -p ${PASSWARD} \
--authenticationDatabase=admin \
-d local -c oplog.rs \
--query '{"ts": {$gt: Timestamp({障害発生時の1時間前}, 1)}, "ts": {$lt: Timestamp({障害発生時}, 1)}}' \
--gzip \
--out {BACKUPDIR}/mongodump.oplogD

--queryオプションについて、次を参考にした。 https://qiita.com/kanagi/items/515b4ec72a4868c967c8

手順その1

mongorestore時に--dropオプションをつけることで、既存のDatabaseを削除してからrestoreしてくれるため、特にReplica Setを初期化する必要がなければ、Primaryに対して--drop付きでmongorestoreすればいい。

$ ssh bk0000
$ scp -p mongo.dmp.gz db1001:/tmp/mongo.dmp.gz
$ ssh db1001
$ sudo mongorestore localhost:27017/admin -uroot -p${PASSWORD} --drop --oplogReplay --writeConcern 1 --numInsertionWorkersPerCollection 8 --gzip --archive=/tmp/mongo.dmp.gz

このあとoplogもrestoreし、1時間前までPoint-in-time Recoveryする。

$ ssh bk0000
$ ssh db1001 mkdir /tmp/oplogR
$ zcat mongodump.oplogD/oplog.gz | ssh db1001 'cat - > /tmp/oplogR/oplog.bson'

$ ssh db1001
$ sudo mongorestore --host localhost:27017 -uroot -p${PASSWORD} --oplogReplay /tmp/oplogR

手順その2

Replica Set全体を初期化し、Replica Setの設定を再度行ったのち、Primaryサーバに対してmongorestoreを実施する。

まずはReplica Setを組んでいるMongoDBサーバ全台を初期化する。

$ for i in db1001 db1002 db1003; do ssh $i 'sudo service mongod stop' & done;wait
$ for i in db1001 db1002 db1003; do ssh $i 'sudo mv /var/lib/mongo/mongod{,.bk}' & done;wait
$ for i in db1001 db1002 db1003; do ssh $i 'sudo -u mongod mkdir /var/lib/mongo/mongod'; done
$ for i in db1001 db1002 db1003; do ssh $i 'sudo service mongod start' & done;wait

Replica Setの設定を再投入する。

$ ssh db1001
$ mongo localhost:27017/admin

mongo> config = {
   _id: '${REPLICA_SET_NAME}', members: [
       {_id: 0, host: 'db1001:27017', priority: 1},
       {_id: 1, host: 'db1002:27017', priority: 0, arbiterOnly: true},
       {_id: 3, host: 'db1003:27017', priority: 0, hidden: true}
   ]
}

mongo> rs.initiate(config);

$ echo 'db.createUser({ user:"root",pwd:"${PASSWORD}", "roles" : [{"role" : "root", db:"admin"}] })' | mongo localhost:27017/admin

restoreをする。方法は--dropをつけない点以外「手順その1」と同じ。