背景:物理サーバー、約2年前、3Ware RAIDカードに接続された7200-RPM SATAドライブ、ext3 FSマウントされたnoatimeとdata = ordered、クレイジーな負荷ではない、カーネル2.6.18- 92.1.22.el5、稼働時間545日ディレクトリにはサブディレクトリが含まれず、数百万の小さな(〜100バイト)ファイルだけで、いくつかの大きな(数KB)ファイルがあります。
過去数か月の間に少しカッコウになったサーバーがありますが、先日、ファイルが多すぎるためにディレクトリに書き込めなくなったことがわかりました。具体的には、/ var/log/messagesにこのエラーをスローし始めました。
_ext3_dx_add_entry: Directory index full!
_
問題のディスクには、多くのiノードが残っています。
_Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sda3 60719104 3465660 57253444 6% /
_
つまり、ディレクトリファイル自体に含めることができるエントリ数の制限に達したことを意味していると思います。ファイルがいくつあるかはわかりませんが、ご覧のとおり、300万個を超えることはありません。それは良いことではありません。しかし、それは私の質問の一部です:正確にその上限は何ですか?調整可能ですか?怒鳴られる前に、チューニングしたいdown;この巨大なディレクトリは、あらゆる種類の問題を引き起こしました。
とにかく、これらすべてのファイルを生成していたコードで問題を追跡し、修正しました。今、私はディレクトリを削除することに行き詰まっています。
ここにいくつかのオプションがあります:
rm -rf (dir)
私はこれを最初に試しました。目に見える影響なしに1日半実行した後、私はあきらめて殺しました。
while [ true ]; do ls -Uf | head -n 10000 | xargs rm -f 2>/dev/null; done )
_ これは実際には短縮版です。私が実行している実際の1つは、削除するファイルが不足すると、進捗レポートとクリーンストップを追加するだけです。
export i = 0; time(while [true]; do ls -Uf | head -n 3 | grep -qF '.png' || break; ls- Uf | head -n 10000 | xargs rm -f 2>/dev/null; export i = $(($ i + 10000)); echo "$ i ..."; 完了)
これはかなりうまく機能しているようです。私がこれを書いているとき、過去30分かそこらで260,000ファイルを削除しました。
ls -U
_によって返されたリストの最初のファイルである単一のファイルを削除するのに「実際の7m9.561s /ユーザー0m0.001s/sys 0m0.001s」が必要で、削除におそらく10分かかったのはなぜですか#3のコマンドを使用した最初の10,000エントリですが、今ではかなり順調に進んでいますか?さらに言えば、約30分で260,000を削除しましたが、さらに60,000を削除するにはさらに15分かかります。なぜ速度が大きく変動するのですか?find
に多くのバリエーションが提供されますが、いくつかの自明な理由により、私のアプローチよりも大幅に速くなることはありません。しかし、 delete-via-fsckのアイデアには足がありますか?それとも完全に別のものですか?すぐに(またはあまり知られていないボックスの内部で)考えを聞きたいと思っています。最終的なスクリプトの出力!:
_2970000...
2980000...
2990000...
3000000...
3010000...
real 253m59.331s
user 0m6.061s
sys 5m4.019s
_
つまり、300万のファイルが4時間かけて少しで削除されました。
data=writeback
マウントオプションは、ファイルシステムのジャーナリングを防ぐために試してみる価値があります。これは削除時間中にのみ実行する必要がありますが、削除操作中にサーバーがシャットダウンまたは再起動されている場合はリスクがあります。
このページ によると、
一部のアプリケーションは、使用すると速度が大幅に向上します。たとえば、アプリケーションが小さなファイルを大量に作成および削除すると、速度の向上が見られます(...)。
このオプションは、fstab
またはマウント操作中に設定され、data=ordered
とdata=writeback
。削除するファイルを含むファイルシステムを再マウントする必要があります。
この問題の主な原因は何百万ものファイルを使用したext3のパフォーマンスですが、この問題の実際の根本的な原因は異なります。
ディレクトリをリストする必要がある場合、ファイルのリストを生成するディレクトリでreaddir()が呼び出されます。 readdirはposix呼び出しですが、ここで使用されている実際のLinuxシステムコールは「getdents」と呼ばれます。 Getdentsは、バッファをエントリで満たすことにより、ディレクトリエントリをリストします。
問題の主な原因は、readdir()が32Kbの固定バッファサイズを使用してファイルをフェッチすることです。ディレクトリがどんどん大きくなる(ファイルが追加されるとサイズが大きくなる)につれて、ext3はエントリのフェッチに次第に遅くなり、追加のreaddirの32Kbバッファサイズは、ディレクトリにエントリの一部を含めるのに十分なだけです。これにより、readdirが繰り返しループし、高価なシステムコールを繰り返し呼び出します。
たとえば、260万以上のファイルを内部に作成したテストディレクトリで「ls -1 | wc-l」を実行すると、多くのgetdentシステムコールの大きなstrace出力が表示されます。
$ strace ls -1 | wc -l
brk(0x4949000) = 0x4949000
getdents(3, /* 1025 entries */, 32768) = 32752
getdents(3, /* 1024 entries */, 32768) = 32752
getdents(3, /* 1025 entries */, 32768) = 32760
getdents(3, /* 1025 entries */, 32768) = 32768
brk(0) = 0x4949000
brk(0x496a000) = 0x496a000
getdents(3, /* 1024 entries */, 32768) = 32752
getdents(3, /* 1026 entries */, 32768) = 32760
...
さらに、このディレクトリで費やされた時間は重要でした。
$ time ls -1 | wc -l
2616044
real 0m20.609s
user 0m16.241s
sys 0m3.639s
これをより効率的なプロセスにする方法は、はるかに大きなバッファーを使用して手動でgetdentsを呼び出すことです。これにより、パフォーマンスが大幅に向上します。
現在、手動でgetdentsを呼び出すことは想定されていないため、通常それを使用するインターフェイスはありません(getdentsのmanページを確認してください!)ただし、can手動で呼び出し、システムコールの起動方法をより効率的にします。
これにより、これらのファイルのフェッチにかかる時間が大幅に短縮されます。私はこれを行うプログラムを書きました。
/* I can be compiled with the command "gcc -o dentls dentls.c" */
#define _GNU_SOURCE
#include <dirent.h> /* Defines DT_* constants */
#include <err.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
struct linux_dirent {
long d_ino;
off_t d_off;
unsigned short d_reclen;
char d_name[256];
char d_type;
};
static int delete = 0;
char *path = NULL;
static void parse_config(
int argc,
char **argv)
{
int option_idx = 0;
static struct option loptions[] = {
{ "delete", no_argument, &delete, 1 },
{ "help", no_argument, NULL, 'h' },
{ 0, 0, 0, 0 }
};
while (1) {
int c = getopt_long(argc, argv, "h", loptions, &option_idx);
if (c < 0)
break;
switch(c) {
case 0: {
break;
}
case 'h': {
printf("Usage: %s [--delete] DIRECTORY\n"
"List/Delete files in DIRECTORY.\n"
"Example %s --delete /var/spool/postfix/deferred\n",
argv[0], argv[0]);
exit(0);
break;
}
default:
break;
}
}
if (optind >= argc)
errx(EXIT_FAILURE, "Must supply a valid directory\n");
path = argv[optind];
}
int main(
int argc,
char** argv)
{
parse_config(argc, argv);
int totalfiles = 0;
int dirfd = -1;
int offset = 0;
int bufcount = 0;
void *buffer = NULL;
char *d_type;
struct linux_dirent *dent = NULL;
struct stat dstat;
/* Standard sanity checking stuff */
if (access(path, R_OK) < 0)
err(EXIT_FAILURE, "Could not access directory");
if (lstat(path, &dstat) < 0)
err(EXIT_FAILURE, "Unable to lstat path");
if (!S_ISDIR(dstat.st_mode))
errx(EXIT_FAILURE, "The path %s is not a directory.\n", path);
/* Allocate a buffer of equal size to the directory to store dents */
if ((buffer = calloc(dstat.st_size*3, 1)) == NULL)
err(EXIT_FAILURE, "Buffer allocation failure");
/* Open the directory */
if ((dirfd = open(path, O_RDONLY)) < 0)
err(EXIT_FAILURE, "Open error");
/* Switch directories */
fchdir(dirfd);
if (delete) {
printf("Deleting files in ");
for (int i=5; i > 0; i--) {
printf("%u. . . ", i);
fflush(stdout);
sleep(1);
}
printf("\n");
}
while (bufcount = syscall(SYS_getdents, dirfd, buffer, dstat.st_size*3)) {
offset = 0;
dent = buffer;
while (offset < bufcount) {
/* Don't print thisdir and parent dir */
if (!((strcmp(".",dent->d_name) == 0) || (strcmp("..",dent->d_name) == 0))) {
d_type = (char *)dent + dent->d_reclen-1;
/* Only print files */
if (*d_type == DT_REG) {
printf ("%s\n", dent->d_name);
if (delete) {
if (unlink(dent->d_name) < 0)
warn("Cannot delete file \"%s\"", dent->d_name);
}
totalfiles++;
}
}
offset += dent->d_reclen;
dent = buffer + offset;
}
}
fprintf(stderr, "Total files: %d\n", totalfiles);
close(dirfd);
free(buffer);
exit(0);
}
これは根本的な根本的な問題(多くのファイル、パフォーマンスが低いファイルシステム)に対抗するものではありませんが。投稿されている代替案の多くよりもはるかに高速である可能性があります。
先見性として、影響を受けるディレクトリを削除して、後で再作成する必要があります。ディレクトリはサイズが大きくなるだけで、ディレクトリのサイズが原因で内部にいくつかのファイルがあってもパフォーマンスが低下し続ける可能性があります。
編集:かなり整理しました。実行時にコマンドラインで削除できるようにするオプションを追加し、正直なところ振り返ってみるとせいぜい疑わしいだけのツリーウォークの要素を削除しました。また、メモリ破損を引き起こすことが示されました。
これでdentls --delete /my/path
新しい結果。 182万ファイルのディレクトリに基づいています。
## Ideal ls Uncached
$ time ls -u1 data >/dev/null
real 0m44.948s
user 0m1.737s
sys 0m22.000s
## Ideal ls Cached
$ time ls -u1 data >/dev/null
real 0m46.012s
user 0m1.746s
sys 0m21.805s
### dentls uncached
$ time ./dentls data >/dev/null
Total files: 1819292
real 0m1.608s
user 0m0.059s
sys 0m0.791s
## dentls cached
$ time ./dentls data >/dev/null
Total files: 1819292
real 0m0.771s
user 0m0.057s
sys 0m0.711s
これはまだとてもうまくいくので、ちょっと驚きました!
他のすべてのファイルをこのファイルシステムから一時的な保存場所にバックアップし、パーティションを再フォーマットして、ファイルを復元することはできますか?
Ext3にはディレクトリごとのファイル制限はありません。ファイルシステムのiノード制限のみです(ただし、サブディレクトリの数には制限があると思います)。
ファイルを削除しても問題が発生する場合があります。
ディレクトリに数百万のファイルがある場合、ディレクトリエントリ自体が非常に大きくなります。削除操作ごとにディレクトリエントリをスキャンする必要があり、エントリの場所によって、ファイルごとにさまざまな時間がかかります。残念ながら、すべてのファイルが削除された後でも、ディレクトリエントリはそのサイズを保持します。そのため、ディレクトリが空になっていても、ディレクトリエントリのスキャンを必要とする以降の操作には長い時間がかかります。この問題を解決する唯一の方法は、ディレクトリの名前を変更し、古い名前で新しいディレクトリを作成し、残りのファイルを新しいディレクトリに転送することです。次に、名前を変更したものを削除します。
私はそれをベンチマークしていません、しかし この人はしました :
rsync -a --delete ./emptyDirectoty/ ./hugeDirectory/
上記のユーザーから提案されたようにext3 fsのパラメーターを変更した後でも、findが機能しませんでした。消費されるメモリが多すぎます。これは、PHPスクリプトがトリックを実行しました-高速でわずかなCPU使用量、わずかなメモリ使用量:
<?php
$dir = '/directory/in/question';
$dh = opendir($dir)) {
while (($file = readdir($dh)) !== false) {
unlink($dir . '/' . $file);
}
closedir($dh);
?>
この問題に関するバグレポートを検索で投稿しました: http://savannah.gnu.org/bugs/?31961
次のことを確認してください。
mount -o remount,rw,noatime,nodiratime /mountpoint
これも少しスピードアップするはずです。
最近、同様の問題に直面し、ring0を取得できませんでしたdata=writeback
動作するように提案します(ファイルがメインパーティションにあるためと思われます)。回避策の調査中に、私はこれに偶然遭遇しました:
tune2fs -O ^has_journal <device>
これにより、data
オプションにmount
を指定しても、ジャーナリングは完全にオフになります。これをnoatime
と組み合わせると、ボリュームにdir_index
セット、そしてそれはかなりうまくいくように見えました。削除は実際には終了せずに終了し、システムは応答性を維持し、問題なくバックアップと実行(ジャーナリングをオンに戻した状態)になりました。
lsは非常に遅いコマンドです。試してください:
find /dir_to_delete ! -iname "*.png" -type f -delete
dir_index
ファイルシステムに設定しますか? (tune2fs -l | grep dir_index
)そうでない場合は、有効にします。新しいRHELの場合は通常オンです。
数年前、/
ファイルシステムに1600万XMLファイルのあるディレクトリを見つけました。サーバーの重要性のため、次のコマンドを使用して終了までに約時間を使用しました。
Perl -e 'for(<*>){((stat)[9]<(unlink))}'
これは古い7200 rpmhddで、IOボトルネックとCPUスパイクにもかかわらず、古いWebサーバーはサービスを継続しました。
Extファイルシステムでのiノードの削除はO(n ^ 2)であることを覚えているので、削除するファイルが多いほど、残りのファイルが速くなります。
私が同様の問題に直面したことが一度ありました(私の推定では約7時間の削除時間を考慮していました)、結局jftugaが提案したルート 最初のコメント に行きました。
私が好むオプションは、すでに提案されているnewfsアプローチです。基本的な問題は、すでに述べたように、削除を処理する線形スキャンには問題があります。
rm -rf
は、ローカルファイルシステムにほぼ最適である必要があります(NFSは異なる場合があります)。しかし、数百万のファイルでは、ファイル名ごとに36バイト、inodeごとに4バイト(ext3の値をチェックしないと思います)、40 *数百万であり、RAMディレクトリにのみ保持されます。
おそらく、Linuxでファイルシステムメタデータキャッシュメモリをスラッシングしているので、ディレクトリファイルの1ページのブロックは、別の部分を使用しているときに消去され、次のときにキャッシュのそのページに再度ヒットするだけです。ファイルが削除されます。 Linuxパフォーマンスチューニングは私の領域ではありませんが、/ proc/sys/{vm、fs} /にはおそらく関連するものが含まれています。
ダウンタイムに余裕がある場合は、dir_index機能をオンにすることを検討してください。これは、ディレクトリインデックスを線形から、大きなディレクトリ(ハッシュされたbツリー)での削除に最適なものに切り替えます。 tune2fs -O dir_index ...
の後にe2fsck -D
を続けても機能します。ただし、これが役立つと確信していますbefore問題がありますが、既存のv.largeディレクトリを処理するときに変換(-D
を指定したe2fsck)がどのように実行されるかわかりません。バックアップ+簡単に確認できます。
明らかにここではリンゴ同士ではありませんが、少しテストをセットアップして次のことを行いました。
ディレクトリに100,000個の512バイトファイルを作成(dd
および/dev/urandom
in a loop);時間を忘れていましたが、これらのファイルの作成には約15分かかりました。
上記のファイルを削除するには、以下を実行しました。
ls -1 | wc -l && time find . -type f -delete
100000
real 0m4.208s
user 0m0.270s
sys 0m3.930s
これはPentium 4 2.8GHzボックスです(100 GBを結合IDE 7200 RPMと思います; EXT3)。カーネル2.6.27。
このような場合、Perlが不思議に機能することがあります。このような小さなスクリプトがbashや基本的なシェルコマンドよりも優れているかどうか、すでに試しましたか?
#!/usr/bin/Perl
open(ANNOYINGDIR,"/path/to/your/directory");
@files = grep("/*\.png/", readdir(ANNOYINGDIR));
close(ANNOYINGDIR);
for (@files) {
printf "Deleting %s\n",$_;
unlink $_;
}
または、おそらくもっと高速なPerlのアプローチ:
#!/usr/bin/Perl
unlink(glob("/path/to/your/directory/*.png")) or die("Could not delete files, this happened: $!");
編集: Perlスクリプトを試してみました。より冗長なものは、正しいことを行います。私の場合、256 MBの仮想サーバーでこれを試しましたRAMおよび50万ファイル。
time find /test/directory | xargs rm
結果:
real 2m27.631s
user 0m1.088s
sys 0m13.229s
に比べ
time Perl -e 'opendir(FOO,"./"); @files = readdir(FOO); closedir(FOO); for (@files) { unlink $_; }'
real 0m59.042s
user 0m0.888s
sys 0m18.737s
ディレクトリの書き換えに関する問題が発生している可能性があります。最初に最新のファイルを削除してみてください。ディスクへの書き戻しを延期するマウントオプションを確認します。
進行状況バーについては、rm -rv /mystuff 2>&1 | pv -brtl > /dev/null
のようなものを実行してみてください
大規模なOracleデータベースサーバーに時々集まる可能性がある数百万のトレースファイルを削除する方法を次に示します。
for i in /u*/app/*/diag/*/*/*/trace/*.tr? ; do rm $i; echo -n . ; done
これにより、かなり遅い削除が発生し、サーバーのパフォーマンスへの影響が少ないことがわかります。通常は、「典型的な」10,000 IOPSセットアップで、100万ファイルあたり1時間という時間に沿ったものです。
多くの場合、ディレクトリがスキャンされ、最初のファイルリストが生成され、最初のファイルが削除されるまでに数分かかります。そこから、削除されたすべてのファイルに対してエコーされます。
端末へのエコーによって引き起こされる遅延は、削除の進行中に大きな負荷を防ぐのに十分な遅延であることが証明されています。
スレッドの残りの部分では、これはさまざまな方法でカバーされていますが、2セントで投入すると思いました。あなたの場合のパフォーマンスの原因はおそらくreaddirです。リンクを解除すると、ディスク上で必ずしもシーケンシャルであるとは限らないファイルのリストが返されます。ファイルは十分に小さいので、おそらくリンク解除操作でジャンプしてスペースがゼロになりすぎることはありません。 readdirを実行してから、iノードを昇順に並べ替えると、パフォーマンスが向上する可能性があります。したがって、readdirをramに入れます(inodeでソート)->リンク解除->利益。
ここではInodeは大まかな近似であると思いますが、ユースケースに基づいて考えるとかなり正確かもしれません...
私はおそらくCコンパイラを作り出し、あなたのスクリプトと道徳的に同等なことをしたでしょう。つまり、opendir(3)
を使用してディレクトリハンドルを取得し、次にreaddir(3)
を使用してファイルの名前を取得します。次に、リンクを解除するときにファイルを集計し、たまに印刷 "%dファイルが削除されました」(および場合によっては経過時間または現在のタイムスタンプ)。
シェルスクリプトバージョンよりも大幅に高速になるとは思いませんが、シェルからやりたいことを明確に行う方法がないため、またはシェルでは実行可能ですが、その方法では非生産的に遅くなります。
TLDR:rsync -a --delete emptyfolder/ x
を使用します。
この質問には5万回の表示とかなりの数の回答がありますが、さまざまな返信すべてをベンチマークした人はいません。外部ベンチマークへのリンクが1つありますが、それは7年以上前のものであり、この回答で提供されているプログラムを見ていません: https://serverfault.com/a/328305/56529
ここでの問題の一部は、ファイルの削除にかかる時間が使用中のディスクとファイルシステムに大きく依存することです。私の場合、Arch LinuxでBTRFSを実行しているコンシューマーSSD(2020-03の時点で更新)で両方をテストしましたが、別のディストリビューション(Ubuntu 18.04)、ファイルシステム(ZFS)、およびドライブで同じ順序で結果を得ましたタイプ(RAID10構成のHDD)。
テストのセットアップは各実行で同じでした:
# setup
mkdir test && cd test && mkdir empty
# create 800000 files in a folder called x
mkdir x && cd x
seq 800000 | xargs touch
cd ..
試験結果:
rm -rf x
:30.43秒
find x/ -type f -delete
:29.79
Perl -e 'for(<*>){((stat)[9]<(unlink))}'
:37.97s
rsync -a --delete empty/ x
:25.11秒
(以下は この答え のプログラムですが、何も印刷しないか、ファイルを削除する前に待機するように変更されています。)
./dentls --delete x
:29.74
rsync
バージョンは、かなり低いマージンでしたが、テストを繰り返すたびに勝者であることが証明されました。 Perl
コマンドは、私のシステムの他のどのオプションよりも低速でした。
ややショッキングなことに、この質問に対するトップアンサーからのプログラムは、私のシステムでは単純なrm -rf
よりも速くないことが判明しました。それがなぜかを詳しく見てみましょう。
まず、答えは、問題はrm
がreaddir
で32Kbの固定バッファサイズでgetdents
を使用していることであると主張しています。これは、4倍のバッファを使用する私のUbuntu 18.04システムには当てはまらないことがわかりました。 Arch Linuxシステムでは、getdents64
を使用していました。
さらに、答えは誤解を招くように、大きなディレクトリにあるファイルをlistingする速度を提供する統計を提供しますが、それらを削除しません(これが問題でした)。 dentls
をls -u1
と比較しますが、単純なstrace
は、getdents
がではないls -u1
が遅い理由を示しています少なくとも私のシステムにはありません(Ubuntu 18.04、ディレクトリに1000000ファイルあり):
strace -c ls -u1 x >/dev/null
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
94.00 7.177356 7 1000000 lstat
5.96 0.454913 1857 245 getdents
[snip]
このls
コマンドは、lstat
への100万回の呼び出しを行い、プログラムの速度を低下させます。 getdents
呼び出しの合計は、0.455秒までです。 getdents
の呼び出しには、同じフォルダでdentls
がどのくらいかかりますか?
strace -c ./dentls x >/dev/null
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
99.91 0.489895 40825 12 getdents
[snip]
そのとおり! dentls
は245ではなく12の呼び出ししか行いませんが、実際にはこれらの呼び出しを実行するためにシステムが長くかかります。したがって、その答えで与えられた説明は実際には正しくありません-少なくとも私がこれをテストすることができた2つのシステムでは。
同じことがrm
とdentls --delete
にも当てはまります。 rm
はgetdents
の呼び出しに0.42秒かかりますが、dentls
は0.53秒かかります。 どちらの場合の場合も、ほとんどの時間はunlink
!の呼び出しに費やされます。
つまり、システムが作成者のシステムのようで、個々のdentls
に多くのオーバーヘッドがない限り、getdents
が大幅に高速化されることを期待しないでください。たぶんglibcの人々は答えが書かれてから数年でそれを大幅に高速化しており、現在ではさまざまなバッファサイズに対して応答するのに線形の時間がかかります。あるいは、getdents
の応答時間は、システムアーキテクチャによっては明らかではない方法で決まる場合があります。
まあ、これは本当の答えではありませんが...
ファイルシステムをext4に変換し、状況が変化するかどうかを確認することはできますか?