中年engineerの独り言 - crumbjp

LinuxとApacheの憂鬱

10TBクラスのmongodb ReplicaSet 運用ナレッジ

オススメの構成

[primary]
  - priority = 2
[secondary]
  - priority = 1
   :
 (必要なだけ) 
   :
[backup]
  - priority = 0
  - hidden = true

POINT

1. Primaryが好き勝手変動すると運用上の取り回しが悪いのでなるべく同じノードがPrimaryになるようにする

2. バックアップ中はOplog反映遅延が起こるのでクライアントクエリーをさせないhidden属性にしておく

3. 増強やバックアップや復旧を円滑に行うため、全てのノードでデータディレクトリは別のEBSボリュームにしておく

 

 例えば10TBクラスのEBSでヘビーユースのReplicaSetだと毎日差分スナップショットを取っても4時間ほど掛かりバックアップ後にOpTimeが追いつくまで5~6時間かかる。

スナップショット(バックアップ)のとり方

データ整合性を確保するために、スナップショット中の書き込みを抑止する必要がある。

rs:PRIMARY> db.fsyncLock();

** ここでスナップショットを取る **

rs:PRIMARY> db.fsyncUnlock();

スクリプトにしてcron.dailyにでも放り込んでおくと良い

#!/bin/bash
cd /path/to
bash snapshot.sh --tmp
mongo --host backup-prd1 -u root -p `cat safe_file` <<<'db.fsyncLock()';
bash snapshot.sh
mongo --host backup-prd1 -u root -p `cat safe_file` <<<'db.fsyncUnlock()';

EBSの場合、差分スナップショットなので直前に一回捨てスナップショットを取っておくと差分が小さくなり、lock期間を短くする事が出来る。

snapshot.shは、awsコマンドなりSDKなりで適当に実装すればいい

Oplog設定・設計

config
replication:
  oplogSizeMB: 614400 #600GB
起動中に変更する場合
db.adminCommand({replSetResizeOplog: 1, size: 614400})
サイズの決め方

以下のコマンドで確認しながら最低3日分位は確保する。理由は当記事を読んでいけば解る。出来れば7日分程度欲しいが更新が激しい場合は非効率になってしまう場合もある。

rs:PRIMARY> rs.printReplicationInfo()
configured oplog size:   614400MB
log length start to end: 183206secs (50.89hrs)
oplog first event time:  Mon Jul 05 2021 21:52:36 GMT+0900 (JST)
oplog last event time:   Thu Jul 08 2021 00:46:02 GMT+0900 (JST)
now:                     Thu Jul 08 2021 00:46:02 GMT+0900 (JST)

うわ。。足りてないね・・・

oplog first event time がこのノードが保持している一番古いOplogなのでこれ以上遅れてしまったSecondaryはもう同期出来なくなる。

遅延状態の確認方法
rs:PRIMARY> rs.status().members.map((member)=> [member.name, member.optimeDate.toLocaleString()])
[
        [
                "primary-prd1:27017",
                "Thu Jul  8 00:16:10 2021"
        ],
        [
                "secondary-prd1-prd4:27017",
                "Thu Jul  8 00:16:09 2021"
        ],
        [
                "secondary-prd2:27017",
                "Thu Jul  8 00:16:09 2021"
        ],
        [
                "secondary--prd3:27017",
                "Thu Jul  8 00:16:09 2021"
        ],
        [
                "backup-prd1:27017",
                "Wed Jul  7 16:18:33 2021"
        ]
]

ノードが壊れた場合

パターン

1. データファイル破損

エラーになってmongodが起動しない

 → ノード再構築

2. [rsBackgroundSync] replSet error RS102 too stale to catchup...

反映しなければならないOplog古すぎ、ReplicaSetメンバーの誰も持っていない

状態は1. と同じ。

3. [initandlisten] Taking 999 samples and assuming that each section of oplog contains approximately...

mongodを再起動した際に、エラーにはならないがこのログで止まる

 → ひたすら待つ。1日待っても良い。Secondary復帰後もOplog反映が遅くどんどん遅延して行くが1日位で正常なパフォーマンスに戻り追いつき始める

ノード再構築

1. 単純にデータディレクトリ以下を全て消す

この場合のmongodの挙動は以下

1. mongod起動(STARTUP2)

2. 他のreplicaSetメンバーから接続され同期が始まる(STARTUP2)

3. 同期元のノードからCollection毎にデータ取得 -> Index構築 を繰り返す(STARTUP2)

4. 全てのDB/Collectionの構築が終わったら2.時点のOplogを起点に差分を反映する(STARTUP2)

5. Oplogが追いついたら完了(SECONDARY)

 大きなCollectionでIndexが大量あれば、3.は時間がかかる。

 私の運用しているReplicaSetでは余裕で2週間はかかるのでOplogを巨大にしない限り4.の段階で Stale となる。

 それをやったとしてもOplogが追い付くまでに掛かる時間も膨大で完了まで1ヶ月以上は覚悟しなければならない。

 TBクラス以上の大きなReplicaSetでは現実的ではない。

2. なるべく新しいスナップショットからデータディレクトリを復旧(本命)

この場合のmongodの挙動は以下

1. mongod起動(STARTUP2)

2. データディレクトリ上の最後のcheckpointの状態でデータ復旧。1日近く掛かる事もある

3. 2.終了時に直ちにSECONDARYとなるがSnapshotを取った時点のデータなのでOpTimeが激しく古い状態(SECONDARY)

4. 通常のReplicaSetメンバーと同様の扱いでOplog反映を始める(SECONDARY)

 3. の時点でクライアントクエリーを処理するとセマンティック上の問題が起きるので、プロセス起動前に rs.reconfig() を行いhiddenノードにしておく必要がある

 4.が始まっても原因不明のパフォーマンス劣化状態が続く。1日程度で通常のパフォーマンスになるので3日分程度のOplogは必ず確保する必要がある。

Oplog遅延見積もり
snapshotに掛かる時間(5時間)+(3.)に掛かる時間(24時間)+(4.)開始後24時間で2時間程度しか反映が進まない
= 5 + 24 + 24 - 2
= 最大51時間遅延する

手順

SECONDARYが壊れたら?

1. rs.reconfig() で壊れたノードをhiddenに変更する

2. dailyで取っているバックアップノードのスナップショットからノード再構築

3. rs.reconfig() で再構築したノードのhiddenを解除する

バックアップノードが壊れたら?

1. rs.reconfig() で1つのSECONDARYをhiddenに変更する

2. hiddenセカンダリーの捨てスナップショットを2〜3回取る(1回目はフルスナップショット、2回目は長時間のフルスナップショット分の差分、3回目は2回目のスナップショットも本来最短で取れる差分よりは大きい分の差)

3. fsyncLock() を掛けて本スナップショットを取り完了後にfsyncUnlock()

4.3.のスナップショットからバックアップノード再構築

5.3.で起きた遅延が解消したら、rs.reconfig() で再構築したノードのhiddenを解除する