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の振る舞いが違うということです。
| 状況 | MySQLの振る舞い | 復帰 |
|---|---|---|
| 運用中に満杯になった | 落ちない。1分ごとに空きを確認して待つ | 空きを作れば自動で続行する |
| 満杯の状態で起動しようとした | 起動に失敗する(PIDファイルが作れない) | 空きを作ってから起動し直す |
手順は上から順にやります。
| 順 | やること |
|---|---|
| 1 | まず空きを作る。MySQLを止めない(詳しく) |
| 2 | 何が食っているかを特定する(詳しく) |
| 3 | バイナリログなら PURGE BINARY LOGS で消す(詳しく) |
| 4 | 保持期間を設定して再発を止める(詳しく) |
バイナリログを rm で消さないでください。
MySQL公式が、手動削除はエラーにつながると明記しています(詳しく)。
「とりあえず消して空きを作る」で、あとから復旧作業が増えます。
MySQLは即死しない
1分ごとに確認して待つ
運用中のMySQLがディスク満杯に遭遇したときの挙動は、公式マニュアルに書かれています。 想像よりずっと粘ります。
「ディスク満杯の状態が起きると、MySQLは1分ごとに、現在の行を書き込むのに十分な空き容量があるかを確認します。
十分な空きがあれば、何事もなかったかのように処理を続けます。
10分ごとに、ディスク満杯の状態を警告するエントリをログファイルに書き込みます」
— B.3.3.4 How MySQL Handles a Full Disk(MySQL 8.4 Reference Manual・要約)
つまり、慌てて再起動する必要はありません。 空きを作れば、1分以内に自分で気づいて続行します。
| 方法 | 内容 |
|---|---|
| 空きを作る | 全レコードを挿入できるだけの空きを解放する |
mysqladmin kill | 待機しているスレッドを中断する。1分以内(次の確認時)に反映される |
公式はもう1つ、実務で効くことを書いています。 ディスク満杯を引き起こしているスレッドをkillすれば、待たされていた他のスレッドは続行できます。 全体が止まって見えても、原因は1つのクエリであることがあります。
ただし起動はできない
ここが分かれ目です。 運用中なら待ちますが、満杯の状態から起動しようとすると失敗します。 起動時にPIDファイルを書く必要があり、それすら書けないからです。
| 行動 | 結果 | |
|---|---|---|
| 悪い | 止まって見えるので先に再起動する | 起動できなくなる。停止中は何も処理できない |
| 良い | 動いたまま空きを作る | 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;"
| 場所・ファイル | 正体 | 消してよいか |
|---|---|---|
binlog.###### / mysql-bin.###### | バイナリログ | PURGE で消す。rm は不可(詳しく) |
ibdata1 | InnoDBの共有表領域 | 消せない。縮まない(詳しく) |
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);
この文が大事なのは、インデックスファイルも一緒に更新してくれるからです。 公式はこう書いています。 「削除されたログファイルはインデックスファイルのリストからも取り除かれ、 指定したログファイルがリストの先頭になります」。
| 条件 | 内容 |
|---|---|
| 権限 | 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 しようとすると失敗します。
復旧には、インデックスファイルを手で編集する作業が増えます。
rm | PURGE BINARY LOGS | |
|---|---|---|
| ファイル | 消える | 消える |
| インデックスファイル | ずれたまま残る | 一緒に更新される |
次回の PURGE | エラーで失敗する | 通る |
| レプリカへの配慮 | 無い | 使用中のログは消さない |
レプリカがある場合
レプリケーションを組んでいるなら、バイナリログはレプリカが読むためのものです。 消す前に、まだ読まれていないかを確認する必要があります。
公式の記述は2段構えです。
| 状況 | どうなるか |
|---|---|
| レプリカが接続中で、そのログを読んでいる | そのログ以降は削除されない。警告が出る |
| レプリカが切断中で、必要なログをパージした | 再接続後にレプリケーションできなくなる |
2行目が事故です。
レプリカを止めているあいだに PURGE すると、警告も出ないまま、復旧不能になります。
レプリカがある構成では、消す前に各レプリカがどこまで読んだかを確認してください。
バイナリログ以外
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
公式は、この値を決めるときの条件も書いています。 「レプリカの最大の遅延時間を考慮して設定すべき」。 短くしすぎると、遅れているレプリカが必要なログを失います。 レプリカがあるなら、遅延の実績を見てから決めてください。
| やること | 効果 |
|---|---|
binlog_expire_logs_seconds を設定する | 自動で古いログが消える。既定は30日 |
| テキストログをローテートする | 無限に育つのを止める |
| ディスク使用率を監視する | 80%で気づければ、緊急対応にならない |
| バイナリログを別ディスクに置く | データ領域を巻き込まない |
| バイナリログが本当に要るか見直す | レプリカもポイントインタイムリカバリも使わないなら、無効化も選択肢 |
3行目が本命です。 この問題は予告なく起きるのではなく、何週間もかけてじわじわ埋まっていきます。 使用率のグラフを見ていれば、必ず事前に気づけます。
最後の行は慎重に判断してください。 バイナリログを無効にすると、障害時に「最後のバックアップ以降」を復元できなくなります。 容量のために消してよいものではありません。保持期間を短くするのが先です。
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は即死しない。公式によれば1分ごとに空きを確認して待ち、10分ごとに警告を書く。
- だから、まず止めない。動いたまま空きを作れば、1分以内に自動で続行する。
- 満杯のまま再起動すると起動できなくなる。PIDファイルすら書けない。一番やってはいけない順番。
- 調べる順番は
df→du→SHOW BINARY LOGS。闇雲に消さない。 - たいていバイナリログ。設定しない限り自動では消えない。既定の保持期間は30日。
rmで消してはいけない。公式が警告している。インデックスファイルとずれて次のPURGEが失敗する。- 消すのは
PURGE BINARY LOGS。インデックスファイルも一緒に更新される。 - レプリカが切断中にパージすると、再接続後に復旧できない。警告も出ない。
ibdata1は縮まない。緊急時の選択肢ではない。- 再発を止めるのは
binlog_expire_logs_secondsと監視。じわじわ埋まるので、必ず事前に気づける。
同じMySQLの話では MySQLの「Incorrect string value」の直し方、 サーバー設定を触る話は wwwあり・なしを統一する と .htaccessでスマホとPCを振り分ける、 サーバー選びは レンタルサーバーおすすめ8選 に書きました。
この記事はMySQL 8.4のリファレンスマニュアルの記載にもとづいています。 ディスク満杯の状態を再現しての検証は行っていません。 バージョンによって既定値や挙動は変わります。 本番でバイナリログを削除する前に、必ずバックアップとレプリカの状態を確認してください。 消したバイナリログは戻せません。