とても情けない理由でデータベースを壊してしまい、リカバリをすることに。破損個所はその直前の作業から言って何千万件のレコードを持つInnoDB。考えただけで己の「ポチ」を恨みたくなりますが、唯一ラッキーだったのはそのレコードの99%以上はノイズ(必要の無いログなど)で、最終的にはサイズを1000分の1以下にする予定だったということ。じゃなかったら多分復旧には日単位で時間が掛かっていたと思われる…
以下、解ったこと。
INSERT、UPDATE、DELETE中のkill プロセス、ダメ、絶対。
いや知ってました。もちろん知ってましたけど、クエリが暴走して仕方なかったんですよ…- クエリの動作結果に対する見積もりの甘さ
- その停止方法についての認識の甘さ(もう少し待ったら終了できてたかも)
INSERT、UPDATE、DELETE中のkill プロセス、ダメ、絶対。
まず、ログを見よう。ログを追おう。
- 予期せぬシャットダウン
- リカバリ
- startコマンドは失敗する
予期せぬシャットダウンを行うことで、次回起動時にはリカバリが走ります。そのことはmysqlのログを眺めていれば解るんですが、眺めていないとただ起動に失敗したようにしか見えません。最初勘違いしてなんどもstartしてしまっていましたが、リカバリを邪魔するだけで無駄でした。
まず、ログを見よう。ログを追おう。
レコード数が多いとリカバリにも時間が掛かる
もしかするとレコード数では無く破損の範囲かも知れませんし、ログファイルのサイズかも知れませんし、なんどもstartしてしまったせいかもしれませんが、ともかく時間が掛かりました。InnoDBのロールバックを行うのに1件20分、終わったらプロセスを再起動して頭からログ見てリカバリチェック、次のチェックポイントに来たらまた20分…みたいな流れ。我慢して眺めていると徐々に進んでいるのがわかりましたが、流石に待ちきれないので途中で切り上げ。
リカバリが進まない場合にはinnodb_force_recovery
innodb_force_recoveryについてはこの辺を参考に。MySQL :: MySQL 5.0 Reference Manual :: 13.2.6.2 Forcing InnoDB Recovery
今回この方法を採れたのは、リカバリを遅くしている原因であるテーブルが特定できていたことと、そのテーブルの全データを消去しても問題が無かったこと。特に後者のおかげでこの方法が採れました。
- my.cnfにinnodb_force_recovery=3を追加
- mysqldを起動
- innodbリカバリを開始
- リカバリが終了するとmysqldが起動するのでアクセス
- 該当テーブルを削除
- my.cnfからinnodb_force_recovery=3を削除
- mysqldを停止
- innodbリカバリを開始
- リカバリが終了するとmysqldが起動する
- 各クエリが正常に動くことを確認
テーブルのデータは不要でも枠組みは必要だったので「該当テーブルを削除」部分でテーブルを一旦コピー。
CREATE TABLE hoge_tmp LIKE hoge
その上で該当テーブルを削除。
DELETE TABLE hoge
そしてリネーム。
ALTER TABLE hoge_tmp RENAME TO hoge
また「mysqldを停止」といっても多分正常に停止しないので、
killall -9 mysqld
になりそうです。停止するならそれで良いんだけど。InnoDBは復活するさ!何度でもな!(※状況によって差があります)
ググって出る事例を見るとどれも一瞬でリカバリが終わってましたが、本来そんなものなのでしょうか。案件によっては、「アレ?普通に起動した?」と錯覚するほどの速度でリカバリが終わってたり。今回は極端なテーブルだったので時間が掛かりましたが、killくらいでは再起不能にはならないみたいです。InnoDBすごい。ちなみにヤバイのは瞬断とかだって。そのあたりになってくると、
- 全データをdump
- MySQLをインストールし直す
- 全データを戻す
という手段をとらないといけないっぽい。
そもそもカジュアルに複数レコードのDELETEとかしない
考えてみれば…もっと慎重に、100件ずつでもプログラムで回せば良かったです。クエリは負担無く実施されるものにしておき、実行の頻度や量はプログラムのプロセスでコントロールする方が、データベースに対するリスクとしては小さいのかなと。解ってたつもりだったんですが…ダメですね。そんな感じで。
復旧して本当に良かった……
まぁ「まぐれで動いてないか」あとできちんと検証する必要がまだありますけどね……
猛省です。全く誕生日に何をやっているんだ