1. 序章
ファイルの削除は、Linux管理の重要な部分です。 手動でもスクリプトでも、アップグレード、ログローテーション、バックアップ、およびその他の多くのアクティビティの一部としてファイルを削除します。 ディレクトリには大量のファイルが含まれている可能性があるため、それらを最適に処理する方法を知っていると、多くの時間を節約できます。
このチュートリアルでは、Linuxで大きなディレクトリを効率的に削除する方法を探ります。 まず、ファイルの削除全般について説明します。 その後、大きなディレクトリがいつ、どのように、そしてなぜ発生するのかを示します。 次に、多くのファイルを処理する際の機能とパフォーマンスの観点から、いくつかのツールをテストします。
このチュートリアルのコードは、GNU Bash5.1.4を使用したDebian11(Bullseye)でテストしました。 これはPOSIXに準拠しており、そのような環境で機能するはずです。
2. ファイルの削除
Linuxでは、ファイルはiノードです。 iノードは、ファイルの内容がどこにあるかなど、ファイルのメタデータを格納します。 一方、ディレクトリは、iノードを指す名前のリストです。
このため、ファイルを削除するにはさまざまな方法があります。
2.1. リンクの削除
ファイルへのハードリンクまたはハンドルがなくなると、そのiノードが使用可能になります。 その場合、カーネルはiノード番号を空きとしてマークします。
$ touch /file.ext
$ tail --follow /file.ext &
[1] 667
$ lsof /file.ext
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tail 667 root 3r REG 8,16 0 666 file.ext
$ rm /file.ext
まず、ファイルを作成し、tailで開いて視聴します。 その後、 lsof (開いているファイルの一覧表示)コマンドを使用して、ファイルへのハンドルが存在することを確認します。 最後に、実際のファイルを削除します。
その結果、ハンドルが開いているため、iノードが残っているだけです。 バックグラウンドtailプロセスを強制終了すると、それが削除されます。
2.2. パージ
重要なのは、ファイルのメタデータとコンテンツは、上書きされるまで、つまりがパージされるまで、ストレージ上にそのまま残る可能性があるということです。 この動作は、古いファイルシステムと新しいextファイルシステムで異なります。 それは、最後の所有者からのすべてがまだそこにある家を売るようなものです。 これは私たちにとってどういう意味ですか?
わざわざ引っ越し業者に電話する必要はありません。 同様に、ストレージのセグメントをデータで書き換えるのにコストがかかる主な理由は2つあります。それは、速度の低下と摩耗です。
iノードは最大でキロバイトであるため、 ext3 以降のバージョンは実際にそれらをゼロにしますが、内容をパージする必要はありません。 この動作はファイルコンテナとどのように関連していますか?
3. 大規模なディレクトリを作成する
上記から、ディレクトリとコンテンツのすべての参照を削除するだけで、ディレクトリの削除が最も効率的であると推測できます。 実際には、サイズはそれほど問題ではありませんが、オブジェクトの量はです。
数千または数百万のエントリを持つファイルストアは、多くの理由で存在します。
- ログローテーション
- データベースファイル
- 分散ファイルシステム
- 特定のユースケース
重要なのは、カーネルが多くのファイルをどれだけうまく処理できるかは、ファイルシステムのタイプに大きく依存することです。 たとえば、 XFS は複数の小さなファイルでは低速になる可能性がありますが、ReiserFSはそれらを処理するために特別に作成されたものです。
それでは、100万ファイルのディレクトリを作成しましょう。
$ mkdir /dir1m
$ for $f in {1..1000000}; do touch /dir1m/$f.ext; done
/ dir1m を、削除するためのいくつかのツールでテストします。 time を使用すると、操作の実行速度がわかります。
4. rm で大きなディレクトリを削除する(削除)
従来のrmは、実際にはリンク解除ファイルのみを実行し、それらをパージしません。
ただし、ディレクトリに対してこれを行うには、いくつかの方法があります。これについては、後で説明します。
4.1. ワイルドカード
rmとglobbingを組み合わせると、次の問題が発生する可能性があります。
$ rm --force /dir1m/*.ext
/bin/rm: cannot execute [Argument list too long]
ここでの問題は、ワイルドカード拡張は100万のファイル名すべてが引数になることを意味することです。 その結果、コマンドラインが長くなりすぎて、シェルがの実行を拒否します。
ただし、ディレクトリ全体を削除したい場合は、この構文を使用する理由はありません。
4.2. 再帰
–recursive ( -r )フラグは、多くのファイルを処理する場合に最適です。 実際、ディレクトリまたはサブディレクトリを削除するには、再帰を使用する必要があります。
$ time rm --recursive --force /dir1m
real 13.57s
user 1.04s
sys 8.11s
cpu 67%
これが私たちの最初の実際の結果です。100万ファイルを削除するのに約14秒かかりました。 では、標準の rm に代わるものは何でしょうか?
5. findを使用したファイルの検索と削除
もちろん、findコマンドを使用してファイルを削除することもできます。 ただし、完了するまでにはるかに多くのリソースと膨大な時間が使用されます。
1つの改善点は、GNU-deleteスイッチをfindに使用することです。
$ time find /dir1m -delete
real 29.93s
user 1.11s
sys 8.40s
cpu 31%
これを行うと、rmコマンドの呼び出しが回避されます。 さらに、xargsを介してパフォーマンスを向上させることができます。
$ time find /dir1m -print0 | xargs --null --no-run-if-empty rm --recursive --force
real 12.80s
user 1.16s
sys 8.62s
cpu 76%
基本的に、 nullで区切られたファイルパスを出力し、rmを実行するxargsに渡します。 単一のディレクトリの場合、パフォーマンスはfindまたはxargsの有無にかかわらず同じです。
最後のオプションを除いて、これらのオプションはすべて、主に rmと–recursiveの内部反復を使用しないために低速です。 さらに、彼らは不必要に各ファイルを調べます。 これは、削除されるものをフィルタリングする場合にのみ意味があります。
6. rsyncを使用して大規模なディレクトリを削除する
効率的に削除するためのありそうもないオプションは、rsyncコマンドです。
$ mkdir /void
$ time rsync --archive --delete /void/ /dir1m/
real 15.74s
user 1.50s
sys 12.47s
cpu 88%
$ rm --recursive --force /void /dir1m
まず、空のディレクトリ /voidを作成します。 次に、 / dir1mを–archiveおよび–delete フラグを介して、空の / void に同期し、残りを削除します。
rm と同様に、 rsyncはunlink()システムコールを使用します。 rm とは異なり、rsyncは他に多くのことをしません。
同じように機能する別のオプションがあります。
7. perlを使用してディレクトリの内容を削除する
実際、 perl は、テキスト処理だけでなくファイル操作にも役立ちます。 Cで記述されており、低レベルのシステムコールにも適しています。
$ cd /dir1m
$ time perl -e 'for(<*>){((stat)[9]<(unlink))}'
real 17.05s
user 2.57s
sys 13.36s
cpu 93%
ここでは、 -e (実行)実行する <*>を介して現在のディレクトリ内のすべてのファイルに対してunlink()を呼び出すワンライナー。 。
スクリプト言語とそのインタープリターのオーバーヘッドのため、このメソッドはrsyncおよびrmよりも少し遅くなります。 それでも、 perl は、必要に応じて、正確なフィルタリングのためのオプションを提供します。
8. 概要
この記事では、Linuxでディレクトリを効率的に削除する方法について説明しました。
私たちのテストで明らかに勝ったのはrmコマンドです。 ただし、削除するものをある程度制御したい場合は、findとperlが実行可能な代替手段です。
結論として、最も効率的な方法を選択するために何をすべきかを常に定義する必要があります。