それでは、1つのフォルダーから何百万ものファイルをどのように削除しますか?



混雑したディレクトリからファイルを削除するという点で、iに魅力的なドットを付けます。



ハードディスクの異常なオーバーフローや、1つのフォルダーから何百万ものファイルを削除する方法の記事を読んで、とても驚きました。 標準のLinuxツールキットで混雑したディレクトリを操作するための単純なツールは本当にありませんgetdents()



直接呼び出すなどの低レベルのメソッドに頼る必要があります。



問題に気付いていない人のために、簡単な説明:階層なしで単一のディレクトリに大量のファイルを誤って作成した場合-つまり 1つのフラットディレクトリにある500万個のファイルから、それらをすばやく削除することはできません。 さらに、Linuxのすべてのユーティリティが原則的にこれを行うことができるわけではありません-プロセッサ/ HDDに大きな負荷をかけるか、大量のメモリを占有します。



だから私は時間を取って、テストサイトを組織し、さまざまなツールを試しました。どちらもコメントで提案され、さまざまな記事や自分の記事で見つかりました。



準備する



稼働中のコンピューターのHDDに混雑したディレクトリを作成したくないので、削除する必要を感じず、別のファイルに仮想FSを作成し、ループデバイスを介してマウントします。 幸いなことに、Linuxではこれは簡単です。



空の200GBファイルを作成します

 #!python f = open("sparse", "w") f.seek(1024 * 1024 * 1024 * 200) f.write("\0")
      
      





dd if=/dev/zero of=disk-image bs=1M count=1M



など、これにはddユーティリティを使用することをお勧めしますが、これは比較にならないほど遅く動作し、結果は同じです。


ext4でファイルをフォーマットし、ファイルシステムとしてマウントします

 mkfs -t ext4 -q sparse # TODO: less FS size, but change -N option sudo mount sparse /mnt mkdir /mnt/test_dir
      
      





残念ながら、実験後にmkfs.ext4コマンドの-Nオプションについて学びました。 イメージファイルのサイズを増やすことなく、FS上のiノード数の制限を増やすことができます。 しかし、一方で、標準設定は実際の条件により近くなっています。


多くの空のファイルを作成します(数時間動作します)

 #!python for i in xrange(0, 13107300): f = open("/mnt/test_dir/{0}_{0}_{0}_{0}".format(i), "w") f.close() if i % 10000 == 0: print i
      
      





ちなみに、最初に十分な速さでファイルが作成された場合、後続のファイルがよりゆっくりと追加され、ランダムな一時停止が発生し、カーネルのメモリ使用量が増加しました。 そのため、フラットディレクトリに多数のファイルを保存すること自体は悪い考えです。


FS上のすべてのiノードが使い果たされていることを確認します。

  $ df -i
 / dev / loop0 13107200 13107200 38517 100%/ mnt 


ディレクトリファイルサイズ〜360Mb

  $ ls -lh / mnt /
 drwxrwxr-x 2 seriy seriy 358M 11月  1 03:11 test_dir 


次に、このディレクトリとそのすべてのコンテンツをさまざまな方法で削除してみましょう。



テスト



各テストの後、ファイルシステムキャッシュをリセットします

sudo sh -c 'sync && echo 1 > /proc/sys/vm/drop_caches'





すべてのメモリをすぐに消費せず、同じ条件下で取り外し速度を比較するためです。



rm -rによる削除



$ rm -r /mnt/test_dir/





straceでは、連続して数回(!!!)がgetdents()



呼び出し、それからループで多くのunlinkat()



などを呼び出します。 30MBの RAMを使用しましたが、成長していません。

コンテンツを正常に削除します。

  iotop
  7664 be / 4 seriy 72.70 M / s 0.00 B / s 0.00%93.15%rm -r / mnt / test_dir /
  5919 be / 0ルート80.77 M / s 16.48 M / s 0.00%80.68%[loop0] 


つまり rm -r ///



混雑したディレクトリを削除するのは問題ありません。



rm経由の削除./*



$ rm /mnt/test_dir/*





600 MBに成長したシェルの子プロセスを開始し、 ^C



に釘付けにします^C



何も削除しませんでした。

明らかに、アスタリスクによるglob



はシェル自体によって処理され、メモリに蓄積され、ディレクトリ全体が考慮された後にrm



コマンドに渡されます。



find -execによるアンインストール



$ find /mnt/test_dir/ -type f -exec rm -v {} \;





straceでは、 getdents()



のみを呼び出しgetdents()



find



プロセスは600 MBに成長し、 ^C



に釘付けになりました^C



何も削除しませんでした。

find



はシェルの*と同じように動作します-最初にメモリ内に完全なリストを作成します。



find -deleteによる削除



$ find /mnt/test_dir/ -type f -delete





^C



によって釘付けにされた600MBまで成長 何も削除しませんでした。

前のコマンドと同様です。 そしてこれは非常に素晴らしいです! 私は当初このチームに希望を置きました。



ls -fおよびxargsによる削除



$ cd /mnt/test_dir/ ; ls -f . | xargs -n 100 rm





-fオプションは、ファイルのリストをソートする必要がないことを示します。

プロセスの階層を作成します。

  |  -ls 212Kb
  |  -xargs 108Kb
     |  -rm 130Kb#rmのpidは常に変化しています 


正常に削除します。

  iotop#たくさんジャンプします
  5919 be / 0ルート5.87 M / s 6.28 M / s 0.00%89.15%[loop0] 


この状況でのls -f



は、 find



よりも適切に動作し、ファイルのリストを不必要にメモリに蓄積しません。 パラメータのないls



find



)-ファイルのリスト全体をメモリに読み込みます。 並べ替えのために明らかに。 ただし、このメソッドはrm



常に呼び出すため、追加のオーバーヘッドが発生するため、不適切です。

これから別の方法が続きますls -f



の出力をファイルにリダイレクトし、このリストからディレクトリの内容を削除できます。



perl readdirを使用したアンインストール



$ perl -e 'chdir "/mnt/test_dir/" or die; opendir D, "."; while ($n = readdir D) { unlink $n }'



$ perl -e 'chdir "/mnt/test_dir/" or die; opendir D, "."; while ($n = readdir D) { unlink $n }'



ここで選択

strace



、ループ内でgetdents()



1回呼び出し、次に何度もunlink()



を呼び出しgetdents()



380KBのメモリが必要であり 、成長していません。

正常に削除します。

  iotop
  7591 be / 4 seriy 13.74 M / s 0.00 B / s 0.00%98.95%perl -e chdi ...
  5919 be / 0ルート11.18 M / s 1438.88 K / s 0.00%93.85%[loop0] 


readdirを使用することは非常に可能です。



Cプログラムreaddir + unlinkを使用してアンインストールします



 //file: cleandir.c #include <dirent.h> #include <sys/types.h> #include <unistd.h> int main(int argc, char *argv[]) { struct dirent *entry; DIR *dp; chdir("/mnt/test_dir"); dp = opendir("."); while( (entry = readdir(dp)) != NULL ) { if ( strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..") ){ unlink(entry->d_name); // maybe unlinkat ? } } }
      
      





$ gcc -o cleandir cleandir.c





$ ./cleandir





strace



、ループ内でgetdents()



1回呼び出し、次に何度もunlink()



を呼び出しgetdents()



。 成長するのではなく、 128 KBのメモリが必要でした

正常に削除します。

  iotop:
  7565 be / 4 seriy 11.70 M / s 0.00 B / s 0.00%98.88%./cleandir
  5919 be / 0ルート12.97 M / s 1079.23 K / s 0.00%92.42%[loop0] 


繰り返しますが、結果をメモリに蓄積せずにファイルをすぐに削除しない場合、 readdir



使用は非常に正常であると確信しています。



結論





PS:残念ながら、ディレクトリの反復読み取り用のPython関数が見つかりませんでした。 os.listdir()およびos.walk()は、ディレクトリ全体を読み取ります。 PHPでもreaddirがあります。



All Articles