混雑したディレクトリからファイルを削除するという点で、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
使用は非常に正常であると確信しています。
結論
-
readdir()
+unlink()
関数の組み合わせを使用して、数百万のファイルを含むディレクトリを削除できます。 - 実際には、次のように
rm -r /my/dir/
を使用することをおrm -r /my/dir/
します より巧妙に-最初に比較的小さなファイルのリストをメモリに作成し、readdir()
数回呼び出してから、このリストからファイルを削除します。 これにより、削除の速度を上げるよりも、読み込みと書き込みの負荷をよりスムーズに切り替えることができます。 - システムの負荷を減らすには、
nice
またはionice
と組み合わせて使用します。 または、スクリプト言語を使用して、ループに小さなスリープ()を挿入します。 または、ls -l
を使用してファイルのリストを生成し、それを低速のパイプに渡します。 - もちろん、インターネット上に書かれているすべてのものを信じないでください! さまざまなブログでこの問題が議論されていることが多く、壊れた解決策を定期的に提案しています。
PS:残念ながら、ディレクトリの反復読み取り用のPython関数が見つかりませんでした。 os.listdir()およびos.walk()は、ディレクトリ全体を読み取ります。 PHPでもreaddirがあります。