Linux とWindows では「ファイルを消す」際の挙動が異なるんだ。気をつけろ

これは、ファイルシステムの実装の違いによるものですが、LinuxWindows では、「ファイルを消す」際の挙動が以下のように異なります。

Linux
  • ファイルが使用中であっても、ファイルを削除(rm)することができる
Windows
  • ファイルが使用中の場合、ファイルを削除(rm)を削除することができない


尚、削除 に関わらず、ファイルの移動(mv)、名前の変更(rename)に関しても同じ挙動の違いがあります。
「何?それだけ?そんなん知ってるよ」と思われるかもしれませんが、この違いをきちんと理解しないと、例えば次のような問題の原因と対処法が分かりません。

Linuxサーバーでdfコマンドで使用率を確認したところ、使用可容量=0、使用率=100%となっていました。
そこで、不要なログファイルを約1.6GBほど削除しました。削除後にdfコマンドで再確認したところ、使用可容量=0、使用率=100%のままでした。使用容量は1.6GB分減っていました。
計算すると、使用率は約98%ぐらいになっていないとおかしいのに、使用率が変わらないなんて事があるのでしょうか?


原因が同じ問題として、「df とdu で表示される容量が異なる」というのもあります。

Linux はファイルを削除しても、そのファイルを使用しているプロセスがあれば、ファイルの実体は削除されない

先ほどの問題の原因は、UNIXファイルシステムの特徴が原因です。本当かどうかは、例えば以下のコードを実行すると分かります。

Linuxファイルシステム上にあるように見えるファイルは、実際はinodeへのリンクに過ぎない。inodeには、ファイルのあらゆるプロパティ(アクセス権や所有権など)のほか、ファイルの中味が実際に存在するディスク上のデータブロックのアドレスも記録される。

rmコマンドでファイルを削除すると、ファイルのinodeを指すリンクは削除されるが、inodeそのものは削除されない。削除した時点で、他のプロセス(オーディオ・プレーヤーなど)でファイルがまだ開かれている場合もある。このようなプロセスがすべて終了し、すべてのリンクが削除されるまで、 inodeとそれに関連付けられたデータブロックが書き込みの対象となることはない。

このように実際のファイルが削除されるまでタイムラグがある


Linux ではファイルを消したつもりでも、そのファイルを使用している(ファイルをopen した)プロセスが存在する限り、ファイルの実体は消えません。実際に以下のコードで確認できます。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
  FILE *f;
  char *path = "hoge_hoge.txt";
  char buf[1024];
  
  if(!(f = fopen(path, "w+"))) exit(1);
  
  fputs("hoge", f);
  
  if(unlink(path) < 0) {
    perror(path);
    exit(1);
  }

  rewind(f);
  
  fgets(buf, sizeof buf, f);
  puts(buf);
  
  fclose(f);
  exit(0);
}


Windows の場合は、そもそも他のプロセスがファイルを使用している場合は、そのファイルを消すことができないので、この問題は起こりません。


df とdu で表示される容量が異なるのは、du は「ファイルのディスク使用量を表示する」ので、ls -a で表示されない(inode へのリンクが切れている)ファイルは考慮されないのですが、df は「ファイル・システムのディスク使用状況を表示する」ため、リンクが切れていてもファイルの実態があれば、そのファイルは考慮されるためです。

「df とdu で表示される容量が異なる」問題の解決方法

原因は分かったので、解決方法です。これは、ファイルを使用しているプロセスをkill すれば解決できます。

どのプロセスがファイルを使用しているか確認する

これは、lsof(list open files)コマンドで確認できます。lsof は、現在開いているファイルを一覧するコマンドです。なので、hoge_hoge.txt を開いているプロセスを調べたければ、以下のようにすればおk。

$ sudo lsof | grep hoge_hoge.txt


実際に、/proc ディレクトリを見てみると、プロセスが参照しているファイル(へのリンク)が消されていることを確認できます。

$ sudo ls -al /proc/*/fd/*
/home/lukesilvia/tmp/hoge_hoge.txt (deleted)

あとは、このプロセスをkill して完了です。

Column::今回の問題が原因で起こった障害事例

これが原因で起こった問題として、知っている事例に以下のようなものがあります。

  • MySQL の特定のテーブルが巨大化しているため、そのテーブルのみ、別のDB サーバへデータを移すことにした
  • データ削除は直接データファイル(MYI, MYD)を消すことで対応(mysql インスタンスを起動して行うと時間がかかるため)
  • ファイルは消えたが、mysql を再起動しなかった
  • ファイルは消えていたが、mysql のプロセスがファイルを使っていたため、ディクスの使用量は減らなかった
  • df での使用率が100% に達成し、DB へ書き込みできなくなる
  • 大量のロックが発生


・・・恐ろしすぎます。Windowsファイルシステムも悪くないかもと思った瞬間でした。