数百万のファイルがあるディレクトリでのrm


104

背景:物理サーバー、約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万個以上になることはありません。それは良いことではありません、気に!しかし、それは私の質問の一部です:その上限は正確に何ですか?調整可能ですか?私が怒鳴る前に、私はそれを抑えたいです。この巨大なディレクトリはあらゆる種類の問題を引き起こしました。

とにかく、これらすべてのファイルを生成していたコードの問題を追跡し、修正しました。今、私はディレクトリを削除することにこだわっています。

ここにいくつかのオプションがあります:

  1. rm -rf (dir)

    最初にこれを試しました。目立った影響なしに1日半走った後、私はそれをあきらめて殺した。

  2. ディレクトリでのunlink(2):間違いなく検討に値しますが、質問はunlink(2)で削除するよりもfsckでディレクトリ内のファイルを削除する方が速いかどうかです。つまり、何らかの方法で、これらのiノードを未使用としてマークする必要があります。もちろん、これはfsckに/ lost + found内のファイルにエントリをドロップしないように指示できることを前提としています。そうでなければ、私は問題を動かしただけです。他のすべての懸念に加えて、これについてもう少し読んだ後、おそらく私が見つけることができるunlink(2)バリアントのどれも私がただ単に削除することを許可しないため、いくつかの内部FS関数を呼び出さなければならないでしょうエントリが含まれるディレクトリ。プー。
  3. while [ true ]; do ls -Uf | head -n 10000 | xargs rm -f 2>/dev/null; done )

    これは実際には短縮バージョンです。私が実行している実際のものは、削除するファイルがなくなったときに進行状況レポートとクリーンストップを追加するだけです:

    export i = 0;
    時間(while [true]; do
      ls -Uf | ヘッド-n 3 | grep -qF '.png' || ブレーク;
      ls -Uf | ヘッド-n 10000 | xargs rm -f 2> / dev / null;
      export i = $(($ i + 10000));
      echo "$ i ...";
    完了)

    これはかなりうまく機能しているようです。これを書いているとき、過去30分ほどで260,000個のファイルが削除されました。

さて、質問のために:
  1. 上記のように、ディレクトリごとのエントリ制限は調整可能ですか?
  2. で返されたリストの最初の1つのファイルを削除するのに「実際の7m9.561s /ユーザー0m0.001s / sys 0m0.001s」ls -Uがかかり、最初の10,000エントリを削除するにはおそらく10分かかりました。 #3のコマンドですが、今ではかなり順調に進んでいますか?さらに言えば、約30分で260,000を削除しましたが、さらに60,000を削除するのにさらに15分かかりました。なぜ速度が大きく変動するのですか?
  3. この種のことを行うより良い方法はありますか?ディレクトリに何百万ものファイルを保存しないでください。私はそれがばかげていることを知っています、そして、それは私の時計で起こりませんでした。問題をグーグルで調べ、SFとSOを調べると、多くのバリエーションが提供されますfindが、いくつかの自明の理由で、私のアプローチよりも大幅に高速化することはできません。しかし、delete-via-fsckのアイデアには足がありますか?それとも完全に何か?すぐに使用できる(またはあまり知られていないボックスの)思考を聞きたいと思っています。
小説を読んでくれてありがとう。気軽に質問してください。必ず返信します。また、最終的なファイル数と、削除スクリプトを実行した後の実行時間で質問を更新します。

最終的なスクリプト出力!:

2970000...
2980000...
2990000...
3000000...
3010000...

real    253m59.331s
user    0m6.061s
sys     5m4.019s

そのため、4時間でわずか300万個のファイルが削除されました。


1
rm(GNU coreutils)8.4にはこのオプションがあります:"-v、--verbose何が行われているかを説明します"。削除されているすべてのファイルが表示されます。
クリスチャンシウピトゥ

2
実際には、それはプログレスバーを行うためのきちんとした方法です:各ファイルは37文字の長さ(36 + '\ n')になるので、そのためのパーサーを簡単に書くことができ、printf()は安価で、rmコマンドには既にロードされたファイルの名前があり、特別なパフォーマンスの低下はありません。とにかくそのようなことをするために「rm」を得ることができなかったので、シバン全体を行うための非スターターのようです。ただし、10,000以内のプログレスバーとしては十分に機能します。おそらく「。」100ファイルごとに?
BMD

8
rm -rfv | pv -l >/dev/null。pvはEPELリポジトリで利用できるはずです。
クリスチャンシウピトゥ

5
pvは圧倒的に素晴らしいです。私は、その後にPVのインストールの痕跡を残します。
BMD10年

最近、まったく同じ問題が発生しました。ありがとうございました!
リコ

回答:


30

data=writebackマウントオプションは、ファイルシステムのジャーナリングを防ぐために、試されるに値します。これは削除時にのみ行う必要がありますが、削除操作中にサーバーがシャットダウンまたは再起動される場合はリスクがあります。

このページによると、

一部のアプリケーションでは、使用すると速度が大幅に向上します。たとえば、アプリケーションが大量の小さなファイルを作成および削除すると、速度の向上が見られます(...)。

このオプションはfstab、マウント操作中またはマウント操作中に設定され、に置き換えdata=orderedられdata=writebackます。削除するファイルを含むファイルシステムを再マウントする必要があります。


1
また、commit オプションから時間を増やすこともできます。「このデフォルト値(または任意の低い値)はパフォーマンスを低下させますが、データの安全性には適しています。0に設定すると、デフォルトのまま(5秒) )。非常に大きな値に設定すると、パフォーマンスが向上します。」
クリスチャンシウピトゥ

1
私が見ていたドキュメント(gentoo.org/doc/en/articles/l-afig-p8.xml#doc_chap4)を除いて、ライトバックは恒星のように見えます。変更(ファイル自体のデータは変更していません)オプションの私の理解は間違っていますか?
BMD10年

最後に、そのリンクで言及されていないFYIは、data = writebackが巨大なセキュリティホールになる可能性があるという事実です。特定のエントリが指すデータには、アプリによってそこに書き込まれたデータがない場合があるため、クラッシュが発生する可能性があることを意味します機密である可能性のある古いプライベートデータが公開されています。ここでは一時的にオンにしているだけなので、心配する必要はありませんが、あなたやその提案に出くわした人が気付いていない場合に備えて、すべての人に注意を喚起したかったのです。
BMD10年

コミット:それはかなり滑らかです!ポインターをありがとう。
BMD10年

2
data=writebackメインのファイルシステムに書き込む前にメタデータをジャーナルします。私が理解しているように、エクステントマップの書き込みとそれらのエクステントへのデータの書き込みなどの順序付けは強制されません。これによりパフォーマンスの向上が見られた場合、他の順序制約も緩和される可能性があります。もちろん、ジャーナルをまったく使用せずにマウントすると、パフォーマンスがさらに向上する可能性があります。(リンク解除操作が完了する前にディスク上に何かを持たなくても、RAMでメタデータの変更が行われるだけの場合があります)。
ピーターコーデス

80

この問題の主な原因は数百万のファイルを使用した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のマニュアルページを確認してください!)。ただし、手動で呼び出して、システム呼び出し呼び出しをより効率的にすることができます。

これにより、これらのファイルを取得するのにかかる時間が大幅に短縮されます。これを行うプログラムを作成しました。

/* 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

これがまだうまく機能していることにちょっと驚きました!


1
2つの小さな懸念:1つ[256]はおそらくで[FILENAME_MAX]あり、2つは私のLinux(2.6.18 == CentOS 5.x)がdirentにd_typeエントリを含んでいないようです(少なくともgetdents(2)によると)。
BMDan

1
btreeのリバランスについて少し詳しく説明してもらえますか?残念ながら役に立たなかったので、グーグルで試しました。
オボゴロビン

1
:私たちは、順番に削除されている場合は、今では私には思えるので、我々は片側の葉を削除して、他の上に残すように、我々は、リバランスを強制en.wikipedia.org/wiki/B-tree#Rebalancing_after_deletion
ovgolovin

1
この問題に悩まされないことを願っています。しかし、それでも私はstackoverflow.com/q/17955459/862380の順番でファイルを削除することについて質問を始めました。これは例の問題を説明する答えを受け取らないようです。時間がありそうだと感じたら、それを調べてもらえますか?たぶん、あなたはより良い説明を書くことができます。
オヴゴロビン

2
これは驚くべきコードです。おそらく数年かけて、ディレクトリに蓄積された11,000,000(1,100万)のセッションファイルを一覧表示して削除できる唯一のツールでした。ここで他の回答の検索やその他のトリックを使用して制御を維持することになっていたPleskプロセスは、実行を完了できなかったため、ファイルが蓄積され続けました。これは、ファイルシステムがディレクトリを保存するために使用するバイナリツリーへのオマージュであり、セッションはまったく機能していました。ファイルを作成し、遅延なく取得できます。リストだけが使用できませんでした。
ジェイソン

31

このファイルシステムから他のすべてのファイルを一時的な保存場所にバックアップし、パーティションを再フォーマットしてからファイルを復元することは可能でしょうか?


3
実際、私はこの答えが本当に好きです。実際問題として、この場合、いいえ、しかしそれは私が考えていたものではありません。ブラボー!
BMD10年

まさに私が考えていたこと。これは質問3の答えです。あなたが私に尋ねたら理想的です:)
ジョシュア

12

ext3には、ファイルシステムのiノードの制限だけでディレクトリごとのファイル制限はありません(ただし、サブディレクトリの数には制限があると思います)。

ファイルを削除した後でも問題が発生する場合があります。

ディレクトリに数百万のファイルがある場合、ディレクトリエントリ自体が非常に大きくなります。削除操作ごとにディレクトリエントリをスキャンする必要があり、エントリの場所に応じて、ファイルごとにさまざまな時間がかかります。残念ながら、すべてのファイルを削除した後でも、ディレクトリエントリのサイズは保持されます。そのため、ディレクトリが空の場合でも、ディレクトリエントリのスキャンを必要とする以降の操作には時間がかかります。この問題を解決する唯一の方法は、ディレクトリの名前を変更し、古い名前で新しいディレクトリを作成し、残りのファイルを新しいディレクトリに転送することです。次に、名前を変更したものを削除します。


実際、すべてを削除した後、この動作に気づきました。幸いなことに、ディレクトリを「射線」から既にmv'dしていたので、rmdirするだけで済みました。
BMD10年

2
つまり、ディレクトリごとのファイル制限がない場合、「ext3_dx_add_entry:ディレクトリインデックスがいっぱいです!」そのパーティションでまだ利用可能なinodeがあったとき?このディレクトリ内にサブディレクトリはありませんでした。
BMD10年

3
うーん、私はもう少し研究をしました、そして、それはディレクトリが占めることができるブロックの数の制限があるようです。ファイルの正確な数は、ファイル名の長さなどのいくつかの事項に依存します。このgossamer-threads.com/lists/linux/kernel/921942は、4kブロックを使用すると、ディレクトリに800万を超えるファイルを保存できることを示しているようです。特に長いファイル名でしたか?
アレックスJ.ロバーツ

各ファイル名の長さは正確に36文字でした。
BMD

まあ、それはアイデアのうち私です:)
アレックスJ.ロバーツ


4

上記のユーザーから提案されたようにext3 fsのパラメーターを変更した後でも、findは単純に機能しませんでした。あまりにも多くのメモリを消費しました。このPHPスクリプトはトリックを行いました-高速で、取るに足らないCPU使用率、取るに足らないメモリ使用量:

<?php 
$dir = '/directory/in/question';
$dh = opendir($dir)) { 
while (($file = readdir($dh)) !== false) { 
    unlink($dir . '/' . $file); 
} 
closedir($dh); 
?>

findでこのトラブルに関するバグレポートを投稿しました:http : //savannah.gnu.org/bugs/?31961


これは私を救った!!
ジェストロ

3

私は最近、同様の問題に直面し、ring0のdata=writeback提案を機能させることができませんでした(おそらくファイルが私のメインパーティションにあるという事実のため)。回避策を調査中に、私はこれにつまずいた:

tune2fs -O ^has_journal <device>

これは、にdata与えるオプションに関係なく、ジャーナリングを完全にオフにしますmount。私はこれを組み合わせてnoatime、ボリュームがdir_index設定され、かなりうまくいくように見えました。削除は実際に終了することなく完了し、システムは応答性を維持し、問題なくバックアップと実行(ジャーナリングをオンに戻す)になりました。


メタデータopsのジャーナリングを避けるため、ext3ではなくext2としてマウントすることをお勧めします。これは同じことをする必要があります。
ピーターコーデス

3

確認してください:

mount -o remount,rw,noatime,nodiratime /mountpoint

物事も少し速くなるはずです。


4
良い電話ですが、質問のヘッダーで述べたように、すでにnoatimeにマウントされています。nodiratimeは冗長です。lwn.net/Articles/245002を参照してください。
BMD10年

1
pplこのマントラ「noatime、nodiratime、nodevatime、noreadingdocsatime」を繰り返す
poige

2

ls非常に遅いコマンド。試してください:

find /dir_to_delete ! -iname "*.png" -type f -delete

rm -rfは1日半実行されましたが、実際に何かを達成したかどうかを知ることなく、最終的にそれを殺しました。プログレスバーが必要でした。
BMD

4
rmが非常に遅いので、30kファイルの "time find。-delete":0m0.357s / 0m0.019s / 0m0.337s real / user / sys。それらの同じファイルの「時間(ls -1U | xargs rm -f)」:0m0.366s / 0m0.025s / 0m0.340s。これは基本的に許容誤差の領域です。
BMD10年

1
strace -r -p <pid of rm>すでに実行中のrmプロセスにアタッチするために実行することもできます。その後、unlinkシステムコールがどれだけ速くスクロールしているかを確認できます。(-r前のシステムコールからの時間をすべての行の先頭に置きます。)
ピーターコーデス

2

されるdir_indexファイルシステム用に設定されていますか?(tune2fs -l | grep dir_index)そうでない場合は、有効にします。通常、新しいRHELで有効です。


1
はい、有効になっていますが、素晴らしい提案です!
BMD

2

数年前、ファイルシステムに1600万のXMLファイル があるディレクトリを見つけました/。サーバーの批判のため、次のコマンドを使用して完了までに約30時間かかりました。

perl -e 'for(<*>){((stat)[9]<(unlink))}'

それは古い7200 rpmの hddであり、IOボトルネックとCPUスパイクにもかかわらず、古いWebサーバーはサービスを継続しました。


1

推奨されるオプションは、すでに提案されているnewfsアプローチです。基本的な問題は、すでに述べたように、削除を処理するための線形スキャンに問題があることです。

rm -rfローカルファイルシステムに最適に近いはずです(NFSは異なります)。しかし、数百万のファイルで、ファイル名ごとに36バイト、inodeごとに4バイト(ext3の値をチェックしない推測)、それはディレクトリのためだけにRAMに保持される4000万です。

推測では、Linuxのファイルシステムメタデータキャッシュメモリをスラッシングしているので、別の部分を使用している間にディレクトリファイルの1ページのブロックが消去され、次のときにキャッシュのそのページに再びヒットしますファイルが削除されます。Linuxのパフォーマンスチューニングは私の領域ではありませんが、/ proc / sys / {vm、fs} /にはおそらく関連するものが含まれています。

ダウンタイムに余裕がある場合は、dir_index機能を有効にすることを検討してください。ディレクトリインデックスを線形から、大規模なディレクトリ(ハッシュされたBツリー)での削除に最適なものに切り替えます。 tune2fs -O dir_index ...続いてe2fsck -D動作します。ただし、問題が発生するにこれが役立つと確信しています-Dが、既存のv.largeディレクトリを処理するときの変換(でのe2fsck )の実行方法がわかりません。バックアップ+サック・イット・アンド・シー。


1
pubbs.net/201008/squid / .../proc/sys/fs/vfs_cache_pressure使用する値かもしれませんが、ディレクトリ自体がページキャッシュにカウントされるか(それが何であるか)またはiノードキャッシュにカウントされるかどうかはわかりません。 iノード、それはFSメタデータであり、そのためそこにバンドルされています)。私が言うように、Linux VMのチューニングは私の領域ではありません。プレイして、何が役立つかを見てください。
フィルP

1

明らかに、リンゴとリンゴではありませんが、少しテストを設定して、次のことを行いました。

ディレクトリ10万512バイトのファイルを作成した(ddおよび/dev/urandomループ内で)。時間を忘れていましたが、それらのファイルを作成するのにおよそ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。


興味深いので、おそらくファイルが長期間にわたって作成されていたという事実が関連していますか?しかし、それは問題ではありません。ブロックキャッシュには、RAM内のすべての関連メタデータブロックが必要です。たぶんunlink(2)がトランザクショナルだからでしょうか?あなたの推定では、rmの期間中ジャーナリングをオフにすることは潜在的な(確かにいくらか危険ですが)解決策ですか?tune2fs / fsck / rebootを使用せずに、マウントされたファイルシステムでジャーナリングを完全にオフにできるとは思えないため、目的が多少損なわれます。
BMD

私はそれについてコメントすることはできませんが、逸話的に(長年にわたるさまざまなNIXの議論で)、私は常にそれrmが多数のファイルで恐ろしく遅いので、find -deleteオプションだと聞いていました。シェルにワイルドカードを使用すると、一致する各ファイル名が展開され、そのためのメモリバッファーが限られていると想定しているため、どのように非効率になるかを確認できます。
-gravyface

1
rmは名前でファイルを検索するため、速度が遅くなります。つまり、ディレクトリエントリが見つかるまで1つずつ繰り返します。ただし、この場合、渡される各エントリは(その時点で)リストの最初のエントリ(ls -U / ls -f)であるため、ほぼ同じ速度である必要があります。ただし、チャンピオンのように実行されるはずのrm -rf <dir>は、可能な限り低速でした。おそらく、大量の削除を高速化するためにcoreutilsにパッチを作成する時が来たのでしょうか?たぶん、それはrm -rfを実装するために、いくつかの再帰的な方法でひそかにグロビング/ソートしているのでしょうか?このような不確実性が、私が質問をした理由です。;)
BMD10年

1
作成手順を実行した後、マシンを再起動します。削除が著しく遅くなるはずです。
マット

1

このような場合、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

そのglob()呼び出しが何をするのか想像するのをためらいます。scandir()を行うと仮定します。もしそうなら、それは復帰するために永遠にかかるだろう。すべてのdirエントリを事前に読み取らない最初の提案の修正には、いくつかのレッグがあります。ただし、現在の形式では、すべてのディレクトリエントリを一度に読み取るだけで、不当な量のCPUを使用します。ここでの目標の一部は、分割統治することです。このコードは、シェルの展開に関する問題にもかかわらず、「rm -f * .png」と基本的に違いはありません。うまくいけば、ディレクトリに削除したくないものは何もありません。
BMDan

仕事を始めたらすぐにもっとや​​ってみよう。1つのディレクトリに100 000個のファイルを作成しようとしましたが、find + xargs + rmの組み合わせは7.3秒かかり、Perl + unlink(glob)...の組み合わせは2.7秒で終了しました。数回試してみましたが、結果は常に同じでした。職場では、より多くのファイルでこれを試します。
ジャンヌピッカライネン

これをテストしながら、新しいことを学びました。少なくともext3とext4では、そこからすべてのファイルを削除した後でも、ディレクトリエントリ自体は巨大なままです。いくつかのテストの後、/ tmp / testディレクトリは15 MBのディスク容量を使用していました。ディレクトリを削除して再作成する以外に、それをクリーンアップする他の方法はありますか?
ジャンヌピッカライネン

2
いいえ、再作成する必要があります。重要な問題の後、メールシステムと受信者ごとのフォルダーとクリーンアップを処理するときにこれをヒットします。そのため、ディレクトリがない場合の時間枠を短縮できますが、削除することはできません。
フィルP

glob()は、シェルグロビングが通常行うように結果をソートすることに注意してください。したがって、ファイルが100kしかないため、すべてが簡単に適合し、ソートが高速になります。はるかに大きなディレクトリでは、ソートを避けるためだけにopendir()/ readdir()/ closedir()が必要です。[ zshにはソート順序をソートしないようにするglob修飾子があるため、シェルの場合は通常言います。これは、多数のファイルを処理するときに役立ちます。*(oN)]
フィルP

1

私が覚えているのは、extファイルシステムのiノードの削除がO(n ^ 2)であるため、削除するファイルが多いほど、残りは速くなります。

私が同様の問題に直面したことが一度ありましたが(私の推定では約7時間の削除時間を見ました)、最後にjftuga は最初のコメントでルート提案しました


0

まあ、これは本当の答えではありませんが...

ファイルシステムをext4に変換し、物事が変化するかどうかを確認することは可能でしょうか?


この「ライブ」を行うには、マウントされたファイルシステム上でfsckが必要であるように見えますが、これは...驚くべきことです。もっと良い方法がありますか?
BMD

ファイルシステムは、変換前、つまり必要なtunefsコマンドの前にアンマウントする必要があります。
marcoc

0

さて、これはスレッドの残りの部分でさまざまな方法でカバーされていますが、私は2セントで投げると思いました。あなたの場合のパフォーマンスの原因はおそらくreaddirです。ディスク上で必ずしもシーケンシャルであるとは限らないファイルのリストを取得しているため、リンクを解除したときにディスクアクセスが発生します。ファイルは十分に小さいので、おそらくリンク解除操作でスペースがゼロになりすぎることはありません。readdirを実行してから、昇順のiノードでソートすると、パフォーマンスが向上する可能性があります。したがって、readdirをram(inodeでソート)->リンク解除->利益にします。

ここでは、iノードはおおよその概算です。


1
私が間違っていても修正してください。ただし、unlink(2)はiノードをゼロにせず、ディレクトリへの参照を削除するだけです。しかし、私はこのアプローチのチャッツパーが好きです。いくつかのタイムトライアルを実行して、それが当てはまるかどうかを確認してください。
BMD10年

0

私はおそらくCコンパイラを打ち出し、あなたのスクリプトと同等の道徳的なことをしたでしょう。つまり、を使用opendir(3)してディレクトリハンドルreaddir(3)を取得し、次にファイルの名前を取得してから、ファイルのリンクを解除してファイルを集計し、「%d files deleted」(および場合によっては経過時間または現在のタイムスタンプ)を印刷します。

私はそれがシェルスクリプトバージョンよりも顕著に高速になるとは思わない、それは私がシェルから望むことをするきれいな方法がないか、またはシェルで実行できますが、その方法は非生産的に遅くなります。


彼は少なくともrmのソースコードをcoreutilsから変更することから始めることができました。
クリスチャンシウピトゥ

0

ディレクトリで書き換えの問題が発生している可能性があります。最初に最新のファイルを削除してみてください。ディスクへの書き戻しを延期するマウントオプションを確認します。

プログレスバーについては、次のようなものを実行してみてください rm -rv /mystuff 2>&1 | pv -brtl > /dev/null


最初に最新のファイルを削除するという点では、ls -Ur?dirエントリをロードしてから、それらを逆にすると確信しています。lsがdirエントリリストの最後から開始して最初に戻るように十分に賢いとは思わない。「ls -1」も、おそらく50+ MBのコアと実行に数分かかるため、素晴らしいアイデアではありません。「ls -U」または「ls -f」が必要です。
BMDan

ファイル名が予測可能なパターンで増加する場合にのみ実用的です。ただし、ls -1をリバースにパイプし、xargsにパイプします。中間結果を表示する場合は、パイプの代わりにファイルを使用します。ファイル名に関する情報を提供していません。パターンを逆に生成し、パターンを使用してファイルを削除します。不足しているファイルエントリを処理する必要がある場合があります。必要なメモリに関するコメントを考えると、ディレクトリを書き換えるのに必要なI / Oのアイデアがあります。
BillThor

0

これは、大規模なOracleデータベースサーバーに時々収集される何百万ものトレースファイルを削除する方法です。

for i in /u*/app/*/diag/*/*/*/trace/*.tr? ; do rm $i; echo -n . ;  done

これにより、サーバーのパフォーマンスへの影響が少ない削除がかなり遅くなり、通常、「典型的な」10,000 IOPS設定でファイル100万個あたり1時間程度になることがわかりました。

多くの場合、ディレクトリがスキャンされ、最初のファイルリストが生成され、最初のファイルが削除されるまでに数分かかります。そこから、削除されるファイルごとにエコーされます。

端末へのエコーによって引き起こされる遅延は、削除の進行中に大きな負荷を防ぐのに十分な遅延であることが証明されています。


あなたはグロビングによって生きて食べられています。次のようなものはどうfind /u* -maxdepth 3 -mindepth 3 -type d -path '*/app/*' -name diag -print0 | xargs -0I = find = -mindepth 4 -maxdepth 4 -type d -name 'trace' -print0 | xargs -0I = find = -mindepth 1 -maxdepth 1 -name '*.tr'ですか?-delete最後に追加して、実際に削除します。書かれているように、それは削除するものをリストするだけです。これは、近くのディレクトリに興味のないものがたくさんある状況に最適化されていることに注意してください。そうでない場合は、ロジックを大幅に簡素化できます。
BMDan

find -deleteはI / Oを大量に発生させる傾向があり、本番パフォーマンスに影響を与えやすくなります。恐らくイオニスと。
ロイ14年

しかし、単に効率が上がるだけで、すべてのI / Oが発生します!グロブは、すべてのフロントロードあなたの例のために(つまり、最初の前に、ファイルの完全なリストが生成されているrmあなたはそれから、起動時に比較的効率的なI / Oを持っているので、痛みを伴う、アウトオブオーダーに続いて、たまたま)rmSそれはおそらくあまりI / Oを引き起こしませんがscandir、ディレクトリを繰り返し歩くことを含みます(I / OはすでにブロックキャッシュにロードされているためI / Oを引き起こしません;も参照vfs_cache_pressure)。あなたが物事を遅くしたい場合ioniceは、オプションですが、私はおそらく小数秒を使用しますsleep
BMDan 14年

find /u*/app/*/diag -path '*/trace/*.tr' -execdir rm {} +rmディレクトリごとに1つ実行されるため、CPUのオーバーヘッドが少なくなります。膨大なCPU時間がある限り、すべてのrm作業ごとにプロセス全体をフォークすることでディスクIOを抑制しますunlinkが、見苦しいと思います。rm一度にディレクトリ全体をスリープするのがバースト的すぎる場合は、リンク解除ごとにスリープするperlの方が適しています。(-execdir sh -c ...多分)
ピーター・コーデス

-1

「xargs」並列化機能を使用できます。

ls -1|xargs -P nb_concurrent_jobs -n nb_files_by_job rm -rf

1
これは役に立ちません。ボトルネックは、ドライブの貧弱なランダムI / Oです。並列削除を行うとさらに悪化し、CPU負荷が増加する可能性があります。
ウィムケルホフ

-2
ls|cut -c -4|sort|uniq|awk '{ print "rm -rf " $1 }' | sh -x

1
ワオ。それは「猫の皮を剥ぐ複数の方法」キャンプにかなりしっかりと収まっていると思います。まじめに、しかし、ソートとuniqで?とにかく「ls」はデフォルトでソートされ、ファイル名が一意であることを期待しています。:/
BMDan

-2

実際、使用するシェルがコマンドライン展開を行う場合、これは少し優れています。

ls|cut -c -4|sort|uniq|awk '{ print "echo " $1 ";rm -rf " $1 "*"}' |sh
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.