TT Lab
はじめる
学ぶ 学習パス コース

ストレージとマウント

dfとduはなぜ違う答えを出すのか

TT Labで続きを見る

一言でいうと

dfはファイルシステムに問い合わせ、duはディレクトリを歩き回って数えます。そのため、名前のないファイルは、dfには捕捉され、duには捕捉されません。

なぜ必要なのか

ログがディスクを食いつくしたので、大きなファイルをrmで消したのに、dfの空き容量が1バイトも増えません。誰もが一度は経験し、一度は慌てます。

ここで「rmが効かなかった」と結論づけると、その夜は長くなります。実際に起きたことは次のとおりです。rmはファイルを消すコマンドではなく、ディレクトリから名前を1つ外す(unlink)コマンドです。データブロックが返却されるには、2つの条件が同時に成り立つ必要があります。

  1. その実体を指す名前が1つも残っておらず
  2. その実体を開いているプロセスも1つもないとき

ログを書いていたアプリケーションがまだそのファイルを開いていれば、2つ目の条件が崩れます。名前は消えましたがデータは生きており、duはパスを歩いて数えるので、名前のないそのファイルを永遠に見つけられません。dfとduがずれる瞬間が、まさにこの状況のサインです。

どう動くのか

容量の問題を絞り込む順序は次のとおりです。

df -h                                    # 블록 사용량
df -i                                    # inode 사용량 (이게 100% 일 수도 있다)
du -x -h --max-depth=1 / | sort -h       # 어느 디렉터리가 큰가 (-x: 다른 fs 로 안 넘어감)
lsof +L1                                 # 삭제됐지만 열려 있는 파일
ls -l /proc/<PID>/fd | grep deleted
find / -xdev -size +500M -printf '%s %p\n' | sort -rn | head

それぞれのツールが答える問いが違います。

ツール 問い 見逃すもの
df ファイルシステムにどれだけ残っているか どこが食ったか
df -i inodeはどれだけ残っているか ブロックの使用量
du このパスの下がどれだけ大きいか 名前のないファイル、別のファイルシステム
lsof +L1 削除されたのに開かれているファイルがあるか すでに閉じられたもの

dfはスーパーブロックに問い合わせて100ギガバイトが使われていると答え、duはディレクトリを歩き回って、名前のある40ギガバイトだけを数えます。差である60ギガバイトは、削除されて名前はないが、プロセスのfd 7が握っているファイルで、2つの数字の差がそのままそのサイズです

duには、落とし穴がさらに2つあります。

削除された開いているファイルの復旧

プロセスがまだ開いているなら、/proc/PID/fdの下に、そのファイルへのシンボリックリンクが生きています。

ls -l /proc/1234/fd | grep deleted
cp /proc/1234/fd/7 /backup/recovered.log

ここで順序がいちばん重要です。プロセスを再起動した瞬間に、最後の参照が消えてブロックが返却され、復旧の機会が完全になくなります。容量が急ぎの状況でも、最初の行動は再起動ではなくコピーであるべきです。

容量だけを急いで回収する必要があるなら、> /proc/1234/fd/7でファイルを空にできます。ログファイルなら、この方法が、プロセスを生かしたまま容量を返してくれます。

何がどれだけ占めているかを素早く絞り込む

容量の問題は時間との勝負です。全体をなめる代わりに、上から半分ずつ切り分けて降りていくと、たいてい3、4回で犯人が見つかります。

du -xh --max-depth=1 / 2>/dev/null | sort -rh | head

-xは、別のファイルシステムに移らないようにします。これを外すと、/proc、/sys、ネットワークマウントまで数えるので、時間がかかり、数字も間違います。--max-depth=1で1層ずつ降りながら、最も大きなディレクトリにcdすることを繰り返します。

大きなファイルが数個なのか、小さなファイルが数百万個なのかを、先に見分けます。処方が違います。

find /var -xdev -type f -size +100M -printf '%s\t%p\n' | sort -rn | head
find /var -xdev -type f | wc -l

前者が答えをくれれば、そのファイルを消すか移せば終わりです。後者の数字が数百万なら、inodeとディレクトリの探索が問題なので、消すこと自体に時間がかかります。

古いものから消します。rmに引数を渡しすぎるとArgument list too longが出るので、-deleteやxargsを使います。

find /var/log/app -xdev -type f -name '*.log' -mtime +14 -delete
find /var/cache/thumbs -xdev -type f -mtime +30 -print0 | xargs -0 -r rm -f

消す前に、何が使っているかを見ます。あるプロセスが書き続けているファイルを消すと、容量は戻らず、そのプロセスだけがおかしくなります(前のモジュールの「消したのに開かれているファイル」)。

lsof -nP +D /var/log/app 2>/dev/null | head

同じことが繰り返されないようにします。一度片づけて終わりにすると、必ずまたいっぱいになります。ログはlogrotateに任せ(copytruncateは、開いているfdを扱う安全装置です)、コンテナホストなら、docker system dfでイメージ・ボリューム・ビルドキャッシュを別々に見て、--filter until=で期間を決めて整理します。アラートは90%ではなく、増加の速度でかけるほうがよいです。1日に5%ずついっぱいになっているなら、2日後がいつなのかが分かり、その場合は明け方に起こされずにすみます。

現場での姿

inodeの枯渇です。セッションファイルやメールキューが小さなファイルを数百万個作ると、ブロックが残っていてもinodeが先に尽きます。df -iを見ないと、原因を見つけられません。そしてext4はフォーマット後にinodeを増やせないので、根本的な解決は、再フォーマットか、そのワークロードを別のファイルシステム(XFSは動的に割り当てます)に移すことです。

duが長くかかって、障害対応が遅れます。ファイルが数百万個あると、duは数分かかります。急ぐときは、du --max-depth=1で上から絞り込んでいくほうが速いです。

次のラボですること

小さなファイルを大量に作ってinodeの消費を観察し、スパースファイルの2種類のサイズを比べ、削除されたのに開かれているファイルを自分で作って、dfとduの不一致を再現します。