ファイルを削除してもディスク容量が空かない理由:unlinkと開いたままのファイル

rmで大きなファイルを消してもdfの空き容量が増えないことがあります。POSIXが定めるリンク数と開いたファイルの規則、duとdfの結果が食い違う理由、Linuxで原因を探して容量を取り戻す手順をまとめました。

開発OSLinux

ディスクがいっぱいになり、大きなログファイルを rm で消したのに、df の空き容量が変わらないことがあります。同じディレクトリを du で測ると、そのファイルの分は合計から消えています。どちらのツールも間違ってはいません。ファイルの名前はなくなっても、ファイル本体はまだ残っています。

df -k /var                    # ファイルシステムの空き容量(KB単位)
rm /var/log/app/huge.log
df -k /var                    # 空き容量が変わらないことがある
du -sk /var/log/app           # ディレクトリの合計は減る

名前を消すこととファイルを消すこと

Unix系のファイルシステムでは、ディレクトリエントリ、つまりファイル名はファイルへのリンクです。1つのファイルにハードリンクで複数の名前を付けることができ、POSIXは stat 構造体の st_nlink をそのファイルのハードリンク数と定めています。

POSIXでは、rm は各ファイルに対して remove() と同等の動作をし、remove() はパスがディレクトリでなければ unlink() と同等です。unlink() は名前を1つ取り除き、リンク数を減らします。

領域がいつ解放されるかは、POSIX.1-2024の unlink() の説明に書かれています。リンク数が0になり、開いているファイル記述子やメモリマッピングを通じてそのファイルを参照するプロセスが1つもないとき、領域は解放されます。最後のリンクを削除する時点で参照しているプロセスがあれば、リンクは unlink() が戻る前に削除されますが、ファイル内容の削除はそうした参照がすべてなくなるまで延期されます。

close() の説明には記述子側の規則があります。リンク数が0のファイルは、そのファイルの記述子がすべて閉じられたときに領域が解放される、という内容です。ただし unlink() の説明はメモリマッピングも参照として数えるため、記述子がすべて閉じられても、ファイルを元にしたマッピングが残っていれば領域はまだ戻りません。プロセスが終了すると、開いていたファイル記述子は閉じられ、そのプロセスが作成したマッピングは解除されます。

つまり rm は名前を消すコマンドです。ほかのハードリンクが残っているか、どこかのプロセスがファイルを開いたりマッピングしたりしていれば、データはディスクに残ります。

duとdfの結果が食い違う理由

POSIXは du を、指定したファイルを起点とするファイル階層に割り当てられた領域を報告するもの、df を、ファイルシステムの利用可能な領域を報告するものと定義しています。

名前が消えたファイルはディレクトリをたどっても到達できないので、du の合計から外れます。一方で領域はまだ解放されていないため、df の空き容量は増えません。この差は「削除したのにまだ参照が残っているファイル」を疑う手がかりになります。ただし、2つの数字がずれる原因はこれだけとは限りません。

Linuxで探す

ここから先はPOSIXではなく、Linuxと個別のツールの機能です。/proc についてはLinux man-pages 6.19に記載されている内容に基づいています。

  • lsof +L1:lsofのマニュアル4.99.0は、+L1 でunlinkされた開いているファイルを選択できると説明しています。リンク数が1未満の開いているファイルだけを表示します。特定のプロセスに絞るには、選択条件をANDで結ぶ -a と -p <PID> を組み合わせます。
  • /proc/<PID>/fd/:プロセスが開いているファイル記述子1つにつき1つ、記述子の番号を名前にしたシンボリックリンクがあります。
  • /proc/<PID>/maps:ファイルを元にしたマッピングで、そのファイルが削除されていると、パス名の後ろに (deleted) が付きます。マニュアルは、この表示があいまいだとも書いています。
  • /proc/<PID>/exe:実行中のプログラムの実行ファイルがunlinkされた場合、このリンクにも (deleted) が付きます。

容量を取り戻すには

そのファイルへの参照をすべてなくす必要があります。開いている記述子はすべて閉じ、ファイルを元にしたマッピングはすべて解除しなければなりません。リンク数はすでに0なので、最後の参照がなくなった時点で領域が解放されます。通常はプロセスが終了すると、この2つが同時に起こります。

次の順番で進めると影響を抑えられます。

  1. ファイルをつかんでいるプロセスをすべて探します。1つのファイルを複数のプロセスが参照していることがあり、unlink() の説明は記述子とマッピングを別々に数えるので、片方だけ見ても足りない場合があります。lsof +L1 の結果と、関係するプロセスの /proc/<PID>/maps をあわせて確認します。
  2. プログラムがログファイルの開き直しやgraceful reloadの方法をドキュメントで提供していれば、まずそれを使います。やり方はプログラムごとに違うので、それぞれのドキュメントに従ってください。
  3. そうした方法がない場合に限り、サービスごとに影響を検討したうえで、最後の手段として再起動を考えます。
  4. 対処のあと、df -k で空き容量が実際に増えたか確認します。増えていなければ、参照しているプロセスやほかのハードリンクが残っていないか見直します。

rm をもう一度実行しても意味はありません。消す名前がもうないからです。

領域が解放された後は、POSIXによりそのファイルのシリアル番号である st_ino が再利用される可能性があります。

この記事ではPOSIXの規則とLinuxでの確認方法だけを扱います。Windows、ネットワークファイルシステム、スナップショットなどファイルシステム固有の機能は挙動が異なる場合があるため、対象外としました。

ブログへ