開発環境ではMySQLをDockerコンテナとして動かすことが多いのですが、あるとき突然コンテナが起動しなくなりました。
ログをみてみるとmysqldがエラーを出していたため何度も再起動しようとして失敗していたみたいです。
この記事では、MySQLがデータベースの破損により起動できなくなったとき、どのように復旧したかをまとめています。
InnoDB: Database page corruption on disk or a failed file read~のエラー
まずはともかく、docker logsでログを見てみます。
ログをみるとMySQLサーバーの起動に失敗しているらしいというのが分かりました。
ログの最後には以下のInnoDBのエラーが出ています。
2020-09-28T08:54:53.753076Z 0 [ERROR] InnoDB: Database page corruption on disk or a failed file read of page [page id: space=106, page number=2399]. You may have to recover from a backup.
ざっくり訳「読み込みに失敗したよ。バックアップから復旧したほうがいいかも。」
どうやらDBが破損しているっぽいですね。
結論としては、復旧するステップは以下の通りです。
- STEP1. 念の為物理バックアップを作成しておく
- STEP2. 強制的に再起動を試す
- STEP3. (起動したら)ダンプを作って復旧作業する
STEP1. 物理バックアップを作成
まずはデータベースの本体ともいえる、物理データベースファイルのバックアップを取ります。
念の為補足ですが、ここでいうバックアップは、mysqldumpなどを使って論理的なスナップショットをとることとは異なります。違いについて詳しく知りたい人はドキュメントを読みましょう。
物理ファイルはデフォルトでは以下のディレクトリにあるので、コピー(cp)すればOK。
/var/lib/mysql
Dockerコンテナの環境でVolumesに置いている場合は、一旦他のコンテナからマウントすることで取り出せます。やり方については以下の記事でまとめています。
-
Dockerのvolumesからデータを救出する~コンテナが起動できなくなったときの話
volumesを使うとデータを永続化できる Dockerコンテナでデータを永続化させるのに、Volumesという仕組みがあります。 永続化とは、コンテナ内で作成したデータをそのコンテナやイメージを削除 ...
続きを見る
ここまでで、物理バックアップを作成しておけば、以下のステップで失敗してもやり直しが効きます。
STEP2. 再起動してみる
物理バックアップを取った上で、設定を少し変更してから再起動してみます。
my.cnf などに以下を追加します。
[mysqld]
innodb_force_recovery = 1
この設定をすることによりmysqldをリカバリモードで起動させます。
innodb_force_recoveryは0~6を指定でき、デフォルトは0。不具合などの異常時に限り、1以上の数を指定することになります。
この数を大きくしていくほど、予期しないエラーへの対処力が上がりますが、4以上を指定するとデータベースが破損しまうのを回避できない場合があることを覚えておく必要があるでしょう。
詳細は公式ドキュメントを参照
設定ファイルを更新したら、デーモンかコンテナごと再起動します。
STEP3. ダンプを取得・復旧作業
docker logsでログを確認し、mysqldの起動と、エラーがないかどうかチェックします。
無事再起動しているのが確認できたら、復旧作業に入ります。
下記を実行してダンプを取得します。
$ mysql -u root -p -h db -Ne"show databases"|grep -v information_schema | grep -v performance_schema > /root/db_list.txt
ここでは、わざわざデータベースごとに切り分けてダンプを取得することにします。
状況によっては、全てのデータベースを一括で取得する –all-databasesオプションを使ってもよいでしょう。
$ mkdir /root/db_backup/
$ cat /root/db_list.txt | while read i; do mysqldump -u root -p -h db "$i" --routines --databases > /root/db_backup/"$i".sql; echo $i; sleep 5; done
※今回はrootを使用していますが、環境に合わせたユーザに置き換えてください。
※上記は別のコンテナから実行しているので- h オプションでdb ホストを指定している。環境に合わせ実行しているホストとポートを指定しましょう。
実行すると /root/db_backup/ にダンプファイルが入っているはず。
後はこのダンプを煮たり焼いたりして復旧作業をします。
不具合のあるデータベースのファイルを一度すべて削除してあげます。
$ rm -rf `ls -d /var/lib/mysql/* | grep -v "/var/lib/mysql/mysql"`
その上で、リストアを実行します。
$ for db in `cat /root/db_list.txt`; do echo -e "Importing $db..."; mysql -u root -p -h db < /root/db_backup/$db.sql; done
ちなみに、物理データを削除して復旧した後はメタ情報を管理する内部テーブルも綺麗になくなっているので、こちらも合わせて復旧する必要があります。
$ mysql_upgrade -u USERNAME -p --force
$ systemctl restart mysql または service mysql restart
最後にmysqldを再起動しましょう。
再起動しないとこういったエラーになるので注意。
'performance_schema'.'session_variables' has the wrong structure"
まとめ
MySQLコンテナが無限に再起動を繰り返すのはInnoDBのエラーでmysqldが起動できなかったためでした。
エラー原因はデータベースファイルの破損だったので、リカバリモードで起動しダンプを取得することでリストアすることができました。
リストアモードで強制的に起動できない場合や、ダンプの取得ができない場合などは、別の手段を探すか既存の論理バックアップからの復旧を検討する必要があります。