レプリケーションしてるMySQLで、マスタやスレーブが障害停止した場合のリカバリプラン
MySQLで、レプリケーションベースのHAな構成について考えたメモです。
3台(というか2台+1台)がいいかなぁと思っていて、前半はその理由を、後半では{マスタ,スレーブ}が{再起不能になった,ちょっとダウンしてすぐ復帰した}場合のリカバリプランについて書きます。
今のところはこれがベストかなと思っているのですが、「こうしたほうがいいと思う!」「ここがおかしい!」などなどのご意見はコメント、TBなどでいただけるとうれしいです。
ゴール
- マスタが落ちてもぐーすか寝ていられるようにしたい
- リカバリの作業はできるだけ単純に、かつ、短時間で完了するようにしたい
- めんどくさいのはいや
基本構成、方針
- 2台+1台
- サービスで使うのは2台 (db1, db2)
- もう1台は管理用 (db3)
- スレーブを多数並べる構成にはしない
- 台数増えると管理コストが上がる
- マスタダウン時のフェイルオーバとそのフェイルバックの作業が煩雑になる
- DBサーバは、並べるだけで済むWebサーバと違う
- アクセスが捌けなくなりそうなときは:
- MySQLやアプリのチューニングで乗り越える
- どーしても突破できないときは、スケールアップを考える
- それでもどーしても突破できないときは、水平パーティショニングを考える
- それでもそれでもどーしても突破できないときは、垂直パーティショニングを考える
レプリケーションの世界の話
- 双方向レプリケーションしている理由
- 例えば「プライマリがフリーズしたのでリセットして復帰した場合」など、プライマリだったサーバが短い時間の後に復帰した場合の復旧作業を楽にするため。詳しくは後述。
- 経験上、「短時間の停止」は割とよくあることなので、復帰手順を簡単にしたい。
用語の整理: プライマリとセカンダリとバックアップ
- プライマリとは、更新系クエリを受け付ける唯一のサーバのこと
- プライマリの実体というか正体というかは浮動IPアドレス
- セカンダリとは、参照系のクエリを受け付けるサーバのこと
- バックアップとは、実サービスには使わないスレーブのこと
- クライアントは、プライマリのVIPに向けて更新系クエリを発行する
- プライマリのVIPはひとつのサーバにしか付与されないので、更新系クエリを受け付け処理するのは、ただ一つのサーバということになる
- バックアップの用途:
- 日次のスナップショットの採取に (7世代ぐらい保存する)
- for db in ALL_DB; do mysqldump --opt --no-autocommit --hex-blob -master-data=2 --database $db | gzip -c > YYYYMMDD/$db.gz; done
- 集計などに
- 実サービスには投入していないので、重いクエリを発行してもOK。
- 日次のスナップショットの採取に (7世代ぐらい保存する)
障害時の対応手順
プライマリが再起不能
- 作業概要
- プライマリがダウンした時点で、(自動的に)セカンダリがプライマリに昇格する
- 旧プライマリは故障解消後、バックアップからのまるごとコピーを使って復旧する。
- プライマリがダウン
- ★サービス停止★ (更新系のみ停止)
- <セカンダリをプライマリに昇格>
- →更新、参照共に新プライマリに行く
- ★新プライマリ復旧★
- ★サービス仮復旧★
- 旧セカンダリとバックアップで、SHOW SLAVE STATUSのExec_Master_Log_Posを比較する
- ▼(A):旧セカンダリExec_Pos = バックアップExec_Pos:
- 旧セカンダリとバックアップのデータの同期点がとれたので次のステップ(C)へ移る→
- ▼旧セカンダリExec_Pos > バックアップExec_Pos:
- ▼旧セカンダリExec_Pos < バックアップExec_Pos:
- 起こるとちょっとまずいシチュエーション
- 旧セカンダリとバックアップで、SHOW SLAVE STATUSのRead_Master_Log_Posを比較する
- ▼(A):旧セカンダリExec_Pos = バックアップExec_Pos:
- バックアップの復旧
- (C):バックアップのmysqldを停止して、<丸ごとバックアップを採取>する
- master.infoを編集して、新プライマリのスレーブになるようにする
- Master_Host を新プライマリの個有名(db0ではなくdb2など)に変更
- 新プライマリは昇格時にバイナリログをローテートしているので、以下の通りに変更する
- Master_Log_Fileは新しいログファイル名
- Read_Master_Log_Posは4
- バックアップのmysqldを起動する
- ★バックアップ復旧★
- 新セカンダリの復旧
- 新セカンダリのサーバのセットアップができたら
- スレーブのロードバランスグループに入らないようにする
- (C)で採取した丸ごとバックアップを展開する
- master.infoを編集して、新プライマリのスレーブになるようにする
- Master_Host を新プライマリの個有名(db0ではなくdb2など)に変更
- 新プライマリは昇格時にバイナリログをローテートしているので、以下の通りに変更する
- Master_Log_Fileは新しいログファイル名
- Read_Master_Log_Posは4
- 新セカンダリのmysqldを起動する
- 新プライマリと同期するまで待つ
- 新セカンダリでSHOW MASTER STATUSし、新プライマリで、そのPosにCHANGE MASTER TOする
- CHANGE MASTER TO MASTER_LOG_FILE = 'mysql-bin.000XXX', MASTER_LOG_POS = XXX;
- 新プライマリで START SLAVE
- レプリケーションが追いついたのを確認してスレーブのロードバランスグループに入れる
- ★新セカンダリ復旧★
- ★サービス完全復旧★
プライマリが短時間停止
- 比較的短時間の停止
- OS rebootした
- ハングアップしたのでリセットした
- プライマリがダウン
- ★サービス停止★ (更新系のみ停止)
- <セカンダリをプライマリに昇格>
- →更新、参照共に新プライマリに行く
- ★新プライマリ復旧★
- ★サービス仮復旧★
- 旧セカンダリとバックアップで、SHOW SLAVE STATUSのExec_Master_Log_Posを比較する
- (以下↑の手順と同じ)
- 旧プライマリのmysqldは起動しない
- 新プライマリのSHOW SLAVE STATUSのExec_Master_Log_Posと旧プライマリの最新のバイナリログの最後のend_log_posを比較
- mysqlbinlog $(ls -1t mysql-bin.[0-9]*|head -n 1) | grep end_log_pos | tail -n 1
- ▼(A):新プライマリExec_Pos = 旧プライマリend_log_pos:
- ステップ(B)へ移る→
- ▼新プライマリExec_Pos > 旧プライマリend_log_pos:
- sync_binlog = 0 になっていると、起こりうる。
- 不意の電源断とかリセットがかかった場合、ディスクにsyncされていないbinlogは失われてしまうので。
- この場合、旧プライマリのデータは使えないので、「プライマリが再起不能」の手順に従い、データを捨てて復旧する。
- sync_binlog = 0 になっていると、起こりうる。
- ▼新プライマリExec_Pos < 旧プライマリend_log_pos:
- 新プライマリのSHOW SLAVE STATUSのRead_Master_Log_Posと旧プライマリの1つ前のバイナリログの最後のend_log_posを比較
- ▼新プライマリRead_Pos = 旧プライマリend_log_pos:
- Exec_Posが同じになるまで待って、ステップ(A)へ移る→
- ▼新プライマリRead_Pos > 旧プライマリend_log_pos:
- ありえない。
- ▼新プライマリRead_Pos < 旧プライマリend_log_pos:
- 起こるとちょっとまずいシチュエーション。
- 新プライマリリレーログと旧プライマリのバイナリログをmysqlbinlogで見て比較し、差分のクエリを新プライマリに対して手動で発行する。
- 新プライマリは既に更新系クエリを受け付けているので、差分の確認とその適用は、新プライマリになった後で受け付けたクエリも加味して慎重に検討する必要がある。
- 後、Exec_Posが同じになるまで待って、ステップ(A)へ移る→
- ▼新プライマリRead_Pos = 旧プライマリend_log_pos:
- 新プライマリのSHOW SLAVE STATUSのRead_Master_Log_Posと旧プライマリの1つ前のバイナリログの最後のend_log_posを比較
セカンダリが再起不能
セカンダリがダウンすると、参照系のクエリはプライマリに流れるようにしているので、セカンダリがダウンしてもサービスには影響なし。
- 作業概要
- バックアップで<まるごとバックアップを採取する>
- バックアップはサービスに使ってないので止めても影響なし
- それをセカンダリに展開して、mysqldを起動
- バックアップで<まるごとバックアップを採取する>
セカンダリが短時間停止
- 作業概要
- mysqldを立上げるだけでOK