MySQLが「No space left on device」で止まったとき — binlogをrmで消してはいけない

MySQLが「No space left on device」で止まったとき — binlogをrmで消してはいけない

先に結論

「先に結論」の内容を図にまとめたもの

ログにこう出ていたら、ディスクが満杯です。

Error writing file '/var/run/mysqld/mysqld.pid' (Errcode: 28 - No space left on device)
Can't start server: can't create PID file: No space left on device

まず知っておくべきことは、「運用中に満杯になった」のと「満杯の状態で起動しようとした」のでは、 MySQLの振る舞いが違うということです。

表1:状況による違い
状況MySQLの振る舞い復帰
運用中に満杯になった落ちない。1分ごとに空きを確認して待つ空きを作れば自動で続行する
満杯の状態で起動しようとした起動に失敗する(PIDファイルが作れない)空きを作ってから起動し直す

手順は上から順にやります。

表2:緊急時の順番
順やること
1まず空きを作る。MySQLを止めない(詳しく)
2何が食っているかを特定する(詳しく)
3バイナリログなら PURGE BINARY LOGS で消す(詳しく)
4保持期間を設定して再発を止める(詳しく)

バイナリログを rm で消さないでください。 MySQL公式が、手動削除はエラーにつながると明記しています(詳しく)。 「とりあえず消して空きを作る」で、あとから復旧作業が増えます。

MySQLは即死しない

「MySQLは即死しない」の内容を図にまとめたもの

1分ごとに確認して待つ

運用中のMySQLがディスク満杯に遭遇したときの挙動は、公式マニュアルに書かれています。 想像よりずっと粘ります。

「ディスク満杯の状態が起きると、MySQLは1分ごとに、現在の行を書き込むのに十分な空き容量があるかを確認します。 十分な空きがあれば、何事もなかったかのように処理を続けます。 10分ごとに、ディスク満杯の状態を警告するエントリをログファイルに書き込みます」
— B.3.3.4 How MySQL Handles a Full Disk(MySQL 8.4 Reference Manual・要約)

つまり、慌てて再起動する必要はありません。 空きを作れば、1分以内に自分で気づいて続行します。

表3:公式が挙げている対処
方法内容
空きを作る全レコードを挿入できるだけの空きを解放する
mysqladmin kill待機しているスレッドを中断する。1分以内(次の確認時)に反映される

公式はもう1つ、実務で効くことを書いています。 ディスク満杯を引き起こしているスレッドをkillすれば、待たされていた他のスレッドは続行できます。 全体が止まって見えても、原因は1つのクエリであることがあります。

ただし起動はできない

ここが分かれ目です。 運用中なら待ちますが、満杯の状態から起動しようとすると失敗します。 起動時にPIDファイルを書く必要があり、それすら書けないからです。

表4:やってはいけない順番
行動結果
悪い止まって見えるので先に再起動する起動できなくなる。停止中は何も処理できない
良い動いたまま空きを作る1分以内に自動で続行する

「反応がないからとりあえず再起動」が、一番状況を悪くします。 動いているうちは待っているだけなので、止めずに空きを作ってください。 もう止まってしまったなら、空きを作ってから起動し直します。

何が食っているか調べる

「何が食っているか調べる」の内容を図にまとめたもの

闇雲に消さないでください。上から順に見れば数分で分かります。

# 1. どのパーティションが満杯か
df -h

# 2. データディレクトリの中で何が大きいか
du -sh /var/lib/mysql/* | sort -rh | head -20

# 3. バイナリログの合計サイズ
du -ch /var/lib/mysql/binlog.* 2>/dev/null | tail -1

# 4. MySQLから見たバイナリログの一覧
mysql -e "SHOW BINARY LOGS;"
表5:見つかりやすいもの
場所・ファイル正体消してよいか
binlog.###### / mysql-bin.######バイナリログPURGE で消す。rm は不可(詳しく)
ibdata1InnoDBの共有表領域消せない。縮まない(詳しく)
ib_logfile* / #ib_redo*REDOログ消さない。サイズは設定で決まる
スロークエリログ・一般クエリログテキストのログ退避してよい。ただしローテートの作法が要る
/tmp の大きなファイルソートや一時テーブルMySQLが使用中なら触らない
*.ibd が巨大特定テーブルのデータ消さない。設計の問題

df と du の数字が合わないときは、削除済みだがプロセスが掴んでいるファイルがあります。 lsof +L1 で見つかります。その場合、プロセスを再起動しないと空きが戻りません。

たいていバイナリログ

「たいていバイナリログ」の内容を図にまとめたもの

「何もしていないのにディスクが埋まった」の正体は、ほぼこれです。 バイナリログは更新のたびに書かれ続け、設定しない限り自動では消えません。

正しい消し方

MySQLの文から消します。公式の構文はこうです。

PURGE BINARY LOGS { TO 'log_name' | BEFORE datetime_expr }
-- 一覧を見てから
SHOW BINARY LOGS;

-- 指定したファイルより前を消す
PURGE BINARY LOGS TO 'binlog.000123';

-- 3日より前を消す
PURGE BINARY LOGS BEFORE DATE_SUB(NOW(), INTERVAL 3 DAY);

この文が大事なのは、インデックスファイルも一緒に更新してくれるからです。 公式はこう書いています。 「削除されたログファイルはインデックスファイルのリストからも取り除かれ、 指定したログファイルがリストの先頭になります」。

表6:PURGE BINARY LOGS の前提(公式)
条件内容
権限BINLOG_ADMIN が必要
前提--log-bin で起動していなければ効果がない
副作用インデックスファイルも更新される

rmで消してはいけない

検索すると「rm で古いバイナリログを消す」という手順が出てきます。 MySQL公式は、これをはっきり警告しています。

「バイナリログファイルを手動で削除すること(Linuxでの rm など)は、エラーにつながる可能性があります。 インデックスファイルに記載されたファイルが外部で削除されていると、PURGE BINARY LOGS はエラーで失敗します。 その場合、インデックスファイルを編集して、利用可能なバイナリログファイルだけを記載した状態にしてから、 PURGE を実行し直す必要があります」
— 15.4.1.1 PURGE BINARY LOGS Statement(MySQL 8.4 Reference Manual・要約)

つまり「消して終わり」になりません。 インデックスファイルと実体がずれた状態になり、次に PURGE しようとすると失敗します。 復旧には、インデックスファイルを手で編集する作業が増えます。

表7:rm と PURGE の違い
rmPURGE BINARY LOGS
ファイル消える消える
インデックスファイルずれたまま残る一緒に更新される
次回の PURGEエラーで失敗する通る
レプリカへの配慮無い使用中のログは消さない

レプリカがある場合

レプリケーションを組んでいるなら、バイナリログはレプリカが読むためのものです。 消す前に、まだ読まれていないかを確認する必要があります。

公式の記述は2段構えです。

表8:レプリカと PURGE(公式)
状況どうなるか
レプリカが接続中で、そのログを読んでいるそのログ以降は削除されない。警告が出る
レプリカが切断中で、必要なログをパージした再接続後にレプリケーションできなくなる

2行目が事故です。 レプリカを止めているあいだに PURGE すると、警告も出ないまま、復旧不能になります。 レプリカがある構成では、消す前に各レプリカがどこまで読んだかを確認してください。

バイナリログ以外

「バイナリログ以外」の内容を図にまとめたもの

binlogを消しても足りないとき、次に疑うものです。

表9:binlog以外の容量の食い方
対象性質対処
ibdata1一度膨らむと縮まない縮めるにはダンプして入れ直すしかない。緊急時にはできない
テキストのログスロークエリログ・一般クエリログ退避したら FLUSH LOGS。消しただけでは書き込みが戻らない
一時ファイル大きなソートやALTER中に作られる触らない。処理が終われば消える
巨大な *.ibd特定テーブルが育っている不要行を消してもファイルは縮まない(OPTIMIZE TABLE が要る)
MySQL以外アプリのログ、古いバックアップ同じディスクに置いていないか確認する

2行目は見落とされます。 ログファイルを rm しても、MySQLは開いたままのファイルに書き続けます。 df の空きが戻りません。退避したら FLUSH LOGS で開き直させてください。

1行目も重要です。ibdata1 は一度膨らむと縮みません。 緊急時の選択肢にはなりません。落ち着いてから、別の作業として計画してください。

二度と起こさない

「二度と起こさない」の内容を図にまとめたもの

空きを作って終わりにすると、また同じことが起きます。 バイナリログは放っておけば増え続けるものだからです。

公式によれば、保持期間の既定は30日です。これを短くします。

-- いまの設定を見る
SHOW VARIABLES LIKE 'binlog_expire_logs_seconds';

-- 3日にする(再起動まで有効)
SET GLOBAL binlog_expire_logs_seconds = 259200;
# 設定ファイルにも書いて、再起動後も効くようにする
[mysqld]
binlog_expire_logs_seconds = 259200

公式は、この値を決めるときの条件も書いています。 「レプリカの最大の遅延時間を考慮して設定すべき」。 短くしすぎると、遅れているレプリカが必要なログを失います。 レプリカがあるなら、遅延の実績を見てから決めてください。

表10:再発を止める手当て
やること効果
binlog_expire_logs_seconds を設定する自動で古いログが消える。既定は30日
テキストログをローテートする無限に育つのを止める
ディスク使用率を監視する80%で気づければ、緊急対応にならない
バイナリログを別ディスクに置くデータ領域を巻き込まない
バイナリログが本当に要るか見直すレプリカもポイントインタイムリカバリも使わないなら、無効化も選択肢

3行目が本命です。 この問題は予告なく起きるのではなく、何週間もかけてじわじわ埋まっていきます。 使用率のグラフを見ていれば、必ず事前に気づけます。

最後の行は慎重に判断してください。 バイナリログを無効にすると、障害時に「最後のバックアップ以降」を復元できなくなります。 容量のために消してよいものではありません。保持期間を短くするのが先です。

AIに任せるときの注意

「AIに任せるときの注意」の内容を図にまとめたもの
表11:AIへの頼み方
頼むこと向くか理由
du の出力を貼って「何が食っている?」向くファイル名から正体が分かる
エラーログを貼って「原因は?」向くErrcode: 28 なら即断できる
「保持期間を3日にする設定を書いて」向く定型。秒数の計算も任せられる
「ディスクがいっぱいです、どうすれば?」向かないrm で消す手順が返る。公式が警告している
削除コマンドをそのまま実行させる向かない取り返しがつかない。レプリカの状態はAIには見えない
ibdata1 を縮める手順向かないダンプと入れ直しが必要。緊急時にやる作業ではない

判定の目安です。答えに rm でバイナリログを消す手順が出てきたら、そのまま実行しないでください。 PURGE BINARY LOGS になっていれば正しい手順です。

よくある質問

MySQLが止まったので再起動していいですか

まだ動いているなら、止めないでください。 公式によれば、運用中のMySQLは1分ごとに空きを確認して待ちます(詳しく)。 満杯のまま止めると、今度は起動できなくなります(表4)。

バイナリログを rm で消してはいけませんか

いけません。公式が「手動削除はエラーにつながる可能性がある」と明記しています(詳しく)。 インデックスファイルとずれて、次の PURGE が失敗します。 PURGE BINARY LOGS を使ってください。

すでに rm してしまいました

インデックスファイルを編集して、実際に存在するファイルだけを記載した状態にしてから、 PURGE を実行し直す必要があります(公式の記述)。 レプリカがあるなら、必要なログを消していないかも確認してください。

ログを消したのに df の空きが戻りません

MySQLがファイルを開いたまま書き続けています(表9)。 lsof +L1 で削除済みの使用中ファイルが見つかります。 テキストログなら FLUSH LOGS で開き直させてください。

ibdata1 が巨大です

一度膨らむと縮みません(表9)。 縮めるにはダンプして入れ直すしかなく、緊急時にやる作業ではありません。 まずバイナリログで空きを作ってください。

保持期間はどれくらいにすべきですか

公式は「レプリカの最大の遅延時間を考慮して設定すべき」としています(詳しく)。 既定は30日です。レプリカが無いなら短くできますが、 障害復旧にバイナリログを使う運用なら、バックアップの間隔より短くしないでください。

バイナリログを無効にしてもいいですか

レプリカもポイントインタイムリカバリも使わないなら選択肢ですが、 「最後のバックアップ以降」を復元できなくなります(表10)。 容量対策としては、保持期間を短くするのが先です。

一部のクエリだけ止まっているように見えます

公式によれば、ディスク満杯を引き起こしているスレッドをkillすれば、 待たされていた他のスレッドは続行できます(表3)。 全体が止まったように見えても、原因は1つのクエリのことがあります。

まとめ

同じMySQLの話では MySQLの「Incorrect string value」の直し方、 サーバー設定を触る話は wwwあり・なしを統一する と .htaccessでスマホとPCを振り分ける、 サーバー選びは レンタルサーバーおすすめ8選 に書きました。

この記事はMySQL 8.4のリファレンスマニュアルの記載にもとづいています。 ディスク満杯の状態を再現しての検証は行っていません。 バージョンによって既定値や挙動は変わります。 本番でバイナリログを削除する前に、必ずバックアップとレプリカの状態を確認してください。 消したバイナリログは戻せません。