背景:物理服务器,已有大约两年的历史,已将7200-RPM SATA驱动器连接到3Ware RAID卡,安装了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!


有问题的磁盘上还有大量的索引节点:

Filesystem            Inodes   IUsed   IFree IUse% Mounted on
/dev/sda3            60719104 3465660 57253444    6% /


所以我想这意味着我们达到了目录文件本身中可以包含多少个条目的限制。不知道会有多少个文件,但是正如您所看到的,最多不能超过300万个。不好意思,请注意!但这是我的问题之一:上限到底是多少?可调吗?在大喊大叫之前,我想把它调小一点;这个巨大的目录引起了各种各样的问题。

无论如何,我们在生成所有这些文件的代码中找到了问题,并已进行了纠正。现在我不得不删除目录。

这里有一些选择:



rm -rf (dir)

我尝试过首先。在运行了一天半之后,没有任何明显的影响,我放弃并杀死了它。


目录上的unlink(2):绝对值得考虑,但问题是,通过fsck删除目录中的文件是否比通过unlink(2)删除要快。也就是说,我必须将这些inode标记为未使用。当然,这是假设我可以告诉fsck不要将条目丢弃到/ lost + found中的文件中。否则,我只是解决了我的问题。除了所有其他问题外,在阅读了更多内容之后,事实证明我可能必须调用一些内部FS函数,因为我找不到的unlink(2)变体都不允许我随意删除在其中包含条目的目录。小熊维尼。


while [ true ]; do ls -Uf | head -n 10000 | xargs rm -f 2>/dev/null; done )

这实际上是缩短的版本;我正在运行的真正的文件是:

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...";
done )


,这似乎只是在添加一些进度报告和当我们用尽要删除的文件时的干净停止工作得很好。在我撰写本文时,它在过去30分钟左右的时间内删除了260,000个文件。


现在,对于以下问题:

如上所述,每个目录的进入限制可调吗?
为什么要花“真正的7m9.561s /用户0m0.001s / sys 0m0.001s”来删除单个文件,它是ls -U返回的列表中的第一个文件,所以用#3中的命令删除第一个10,000个条目大概花了十分钟,但是现在它很愉快了吗?为此,它在大约30分钟内删除了260,000,但是现在又花了15分钟才删除了60,000。为什么速度会发生巨大波动?
有没有更好的方法来做这种事情?不在目录中存储数百万个文件;我知道那很傻,而且在我的手表上不会发生。仔细研究问题并查看SF和SO,可以发现find上的许多变化,由于一些不言而喻的原因,这些变化不会比我的方法快得多。但是通过fsck删除的想法有什么作用吗?还是完全其他?我很想听听开箱即用(或不为人所知的盒子)的想法。

感谢您读小小说。随时提出问题,我一定会回答。我还将用文件的最终数量以及删除脚本运行多长时间来更新问题。


最终脚本输出!:

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

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


因此,在四个小时内删除了300万个文件。

评论

rm(GNU coreutils)8.4具有此选项:“ -v,--verbose解释正在执行的操作”。它将显示所有要删除的文件。

实际上,这是完成进度条的一种好方法:由于每个文件的长度为37个字符(36 +'\ n'),因此我可以轻松地为此编写一个解析器,并且因为printf()是cheap和rm命令已经加载了文件名,没有特别的性能损失。似乎不适合做整个工作,因为无论如何我永远都不可能得到“ rm”来做这样的事情。但是它可以作为10,000内的进度条很好地工作。也许是“。”每一百个文件?

rm -rfv | pv -l> / dev / null。 pv应该在EPEL存储库中可用。

pv绝对是太棒了。醒来后,我留下了许多光伏装置的痕迹。

我最近也遇到了同样的问题。谢谢!

#1 楼

应该尝试使用data=writeback挂载选项,以防止记录文件系统。此操作仅应在删除期间执行,但是如果在删除操作过程中关闭服务器或重新引导服务器,则存在风险。

根据此页面,


使用某些应用程序时,速度会显着提高。例如,当应用程序创建和删除大量小文件时,可以看到(...)速度提高。


该选项在fstab中或在装入操作期间设置,替换data=ordereddata=writeback。包含要删除文件的文件系统必须重新安装。

评论


他还可以从commit选项中增加时间:“此默认值(或任何低值)会影响性能,但这对数据安全性有好处。将其设置为0与将其保留为默认值具有相同的效果(5秒)。将其设置为非常大的值将提高性能”。

–克里斯蒂安·丘皮图
2010-09-26 19:14



除了我正在查看的文档(gentoo.org/doc/en/articles/l-afig-p8.xml#doc_chap4)明确提到,写回看起来很出色,它仍然记录元数据,我认为其中包括我正在使用的所有数据更改(我当然不会更改文件本身中的任何数据)。我对选项的理解不正确吗?

– BMDan
2010-09-27的1:20

最后,仅供参考,在该链接中未提及的事实是data = writeback可能是一个巨大的安全漏洞,因为给定条目指向的数据可能没有应用程序写入的数据,这意味着可能导致崩溃在暴露的旧的,可能是敏感的/私有数据中。这里不是问题,因为我们只是暂时将其打开,但是我想提醒所有人注意这一警告,以防您或遇到该建议的其他人不知道。

– BMDan
2010-09-27的1:23

承诺:这很漂亮!感谢您的指导。

– BMDan
10-9-27在1:26



在将元数据写入主文件系统之前,data = writeback仍然记录元数据。据我了解,它只是不强制诸如在编写范围图和将数据写入这些范围之类的东西之间进行排序。也许还有其他排序约束也会放宽,如果您从中受益匪浅。当然,完全不安装轴颈的安装可能会具有更高的性能。 (这可能会使元数据更改仅发生在RAM中,而在取消链接操作完成之前不需要在磁盘上放置任何内容)。

– Peter Cordes
2015年3月17日在11:30



#2 楼

虽然此问题的主要原因是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


感到惊讶的是,它仍然如此好用!

评论


两个小问题:一个[256]应该是[FILENAME_MAX],第二个我的Linux(2.6.18 == CentOS 5.x)似乎不包含d_type条目(至少根据getdents( 2))。

– BMDan
2011年11月7日,下午5:01

您能否详细说明一下btree重新平衡,为什么删除顺序有助于防止重新平衡?不幸的是,我尝试了Google搜索,但没有成功。

– ovgolovin
13年7月28日在23:28

因为现在在我看来,如果我们要按顺序删除,便会强制进行重新平衡,因为我们将一侧的叶子移开,而另一侧则留下:en.wikipedia.org/wiki/B-tree#Rebalancing_after_deletion

– ovgolovin
13年7月28日在23:37

我希望我不要打扰您。但是我还是开始提出有关按顺序stackoverflow.com/q/17955459/862380删除文件的问题,该问题似乎没有得到答案,该答案将用示例解释问题,这对于普通程序员而言是可以理解的。如果您有时间和感觉,可以调查一下吗?也许您可以写出更好的解释。

– ovgolovin
13年8月8日在18:35

这是一段了不起的代码。这是我发现能够列出和删除大约11,000,000(一千一百万)会话文件的唯一工具,这些文件可能已存在目录中,并且已有数年的历史了。 Plesk进程原本应该使用查找和此处其他答案中的其他技巧来控制它们,但该进程无法完成运行,因此文件不断建立。这是对文件系统用来存储目录的二叉树的致敬,这些会话完全可以工作-您可以创建文件并毫不延迟地检索它。只是列表无法使用。

–詹森
16-2-12在15:10



#3 楼

是否可以将所有其他文件从该文件系统备份到临时存储位置,重新格式化分区,然后还原文件?

评论


实际上,我真的很喜欢这个答案。在实际情况下,在这种情况下,不可以,但是这不是我会想到的。太棒了!

– BMDan
2010-09-23 10:23

正是我在想什么。这是对问题3的答案。

–约书亚
2010-10-10 15:31



#4 楼

ext3中没有每个目录文件的限制,只是文件系统inode的限制(尽管我认为子目录的数量是有限制的)。

删除文件后,您仍然可能会遇到问题。 >
当目录中包含数百万个文件时,目录条目本身将变得非常大。必须对目录项进行每次删除操作的扫描,并且每个文件都需要花费不同的时间,具体取决于其条目所在的位置。不幸的是,即使在删除所有文件之后,目录条目仍保留其大小。因此,即使目录现在为空,需要扫描目录条目的其他操作仍将花费很长时间。解决该问题的唯一方法是重命名目录,使用旧名称创建新目录,然后将所有剩余文件传输到新目录。然后删除重命名的名称。

评论


确实,删除所有内容后,我仅注意到了这种行为。幸运的是,我们已经将目录移出了“火线”,因此我可以将其rmdir出来。

– BMDan
10 Sep 23 '10:24



就是说,如果没有每个目录的文件限制,为什么我会得到“ ext3_dx_add_entry:目录索引已满!”该分区上何时仍有可用的inode?此目录内没有子目录。

– BMDan
2010-09-23 10:26



嗯,我做了更多的研究,似乎目录可以占用的块数量是有限的。文件的确切数量取决于一些因素,例如文件名长度。 gossamer-threads.com/lists/linux/kernel/921942似乎表明,使用4k块,目录中应该可以包含超过800万个文件。它们的文件名特别长吗?

– Alex J. Roberts
2010-09-23 12:04

每个文件名正好是36个字符长。

– BMDan
2010-09-23 15:34

好吧,这就是我的主意:)

– Alex J. Roberts
2010-09-24 0:13

#5 楼

我还没有进行基准测试,但是这个人做到了:

rsync -a --delete ./emptyDirectoty/ ./hugeDirectory/


#6 楼

TLDR:使用rsync -a --delete emptyfolder/ x

此问题有5万个视图,并且有很多答案,但似乎没有人对所有不同的答复进行基准测试。有一个指向外部基准测试的链接,但是已经有7年以上的历史了,并且没有查看此答案中提供的程序:https://serverfault.com/a/328305/565293

Part此处的困难在于,删除文件所花费的时间在很大程度上取决于所使用的磁盘和文件系统。就我而言,我在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.43s

find x/ -type f -delete:29.79

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

rsync -a --delete empty/ x:25.11s

(以下是此答案中的程序,但已修改为不打印任何内容或在删除文件之前等待。)

./dentls --delete x:29.74

rsync版本被证明是每次我重复测试时都会获得冠军,尽管利润微不足道。 perl命令比我的系统上的任何其他选项都要慢。

令人震惊的是,事实证明,从最高答复这个问题的程序在我的系统上没有比简单的rm -rf更快。首先,答案表明问题在于rm使用的readdir具有固定缓冲区大小为32Kb的getdents。在我的Ubuntu 18.04系统上事实并非如此,该系统使用的缓冲区大四倍。在Arch Linux系统上,它使用的是getdents64

此外,该答案会误导性地提供统计信息,从而加快了在大型目录中列出文件的速度,但并未删除它们(这就是问题所在)。它将dentlsls -u1进行了比较,但简单的strace并不是getdents慢的原因,至少在我的系统上不是这样(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 -u1命令对ls进行了百万次调用,这减慢了程序的运行速度。 lstat呼叫加起来总共只有0.455秒。 getdents呼叫在同一文件夹中占用getdents多长时间?

strace -c ./dentls x >/dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 99.91    0.489895       40825        12           getdents
[snip]


对!即使dentls仅拨打12个电话而不是245个电话,实际上运行这些电话仍需要系统更长的时间。因此,答案中给出的解释实际上是不正确的-至少对于我已经能够对其进行测试的两个系统。

dentlsrm也是一样。而dentls --delete花费0.42s调用rmgetdents花费0.53s。无论哪种情况,绝大多数时间都花在调用dentls上!

因此,简而言之,不要指望看到运行unlink的速度大大提高,除非您的系统像作者的系统那样并且有很多各个dentls的开销。自编写答案以来的几年里,也许glibc的人们已经大大加快了速度,现在响应不同的缓冲区大小需要大约线性的时间。或者,getdents的响应时间在某种程度上不明显取决于系统架构。

评论


我注意到使用了-u,我怀疑应该使用-U(相反,-U表示不对每个文件的属性进行排序,从而可以避免使用lstat)。我刚才问了另一个答案,@ Matthew Ife是否偶然使用了-u。

– nh2
20-05-26在22:37



您能否追踪rsync方法并检查它产生了哪个系统调用,以弄清楚为什么它比这里的dentls更快?

– nh2
20 May 26 '22:39

从来没有见过,但是说实话,确实是很好的工作。我最初的回答是将近10岁。在计算方面,这就是EON。在非常少的ext4上,扩展确实确实加快了大文件的unlink()速度,而且我敢肯定,诸如块预分配之类的功能有助于在更大的目录上更快地运行取消链接。至于getdents(),有一些开销,但是在现代块设备上,它可以忽略不计。实际上,我什至不确定现代文件系统中“目录中的数百万个文件”的问题是否具有与以前相同的影响。

–马修·伊夫(Matthew Ife)
20 May 27 '15:19

正如@MatthewIfe所说,从我最初提出这个问题时起,情况已经发生了很大变化。我也有机会与coreutils维护者一起度过了一段时间,他对自从CentOS 5包含完整的旧石器版本发布以来,他们极大地改善了rm -r性能的方式持非常有说服力的观点。 X。这个故事的寓意是:如果您使用的是现代系统,则可能只需运行rm -rf。否则,可以在Github上运行更新版本的dentls,但是@ nh2应该完全接受我的PR。 :D

– BMDan
20/11/13在22:26

#7 楼

即使按上述用户的建议更改了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

评论


这救了我!

– Jestro
2011-3-20在10:12

#8 楼

我最近遇到了类似的问题,无法获得ring0的data=writeback建议(可能是由于文件位于我的主分区上)。在研究变通办法时,我偶然发现了以下问题:

tune2fs -O ^has_journal <device>


这将完全关闭日记功能,而不管给datamount选项如何。我将其与noatime结合使用,并设置了dir_index音量,它似乎工作得很好。删除实际上完成了,而无需我杀死它,我的系统保持了响应状态,并且现在可以正常运行并恢复运行了(重新启用日志)。

评论


我建议将其挂载为ext2而不是ext3,以避免记录元数据操作。这应该做相同的。

– Peter Cordes
15年3月17日在9:26

#9 楼

请确保您执行以下操作:

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


,这样也可以加快速度。

评论


很好,但正如我在问题标题中提到的那样,它已经挂载了noatime。 nodiratime是多余的;参见lwn.net/Articles/245002。

– BMDan
2010-09-27在21:19

ppl重复此口头禅“ noatime,nodiratime,nodevatime,noreadingdocsatime”

– poige
16年6月12日在6:11

#10 楼

ls命令很慢。尝试:

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


评论


rm -rf运行了一天半,我最终杀死了它,却不知道它是否真的完成了任何工作。我需要一个进度条。

– BMDan
2010-09-23 10:18

至于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。基本上是误差范围。

– BMDan
2010-09-23 10:20



您可能只需运行strace -r -p 即可附加到已经在运行的rm进程中。然后,您可以查看取消链接的系统调用滚动的速度。 (-r将自上次系统调用起的时间放在每一行的开头。)

– Peter Cordes
15年3月17日在9:14

#11 楼

是否为文件系统设置了dir_index
tune2fs -l | grep dir_index)如果没有,请启用它。
对于新的RHEL通常是启用的。

评论


是的,它已启用,但是很棒的建议!

– BMDan
2010-09-27 21:23

#12 楼

几年前,我在/文件系统中找到了一个包含1600万个XML文件的目录。由于服务器的问题,我们使用了以下命令,该命令花费了大约30个小时来完成:

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


这是一个旧的7200 rpm硬盘,尽管存在IO瓶颈和CPU高峰,旧的Web服务器继续提供服务。

#13 楼

我建议的首选方法是已经建议的newfs方法。同样,如前所述,基本问题是处理删除的线性扫描是有问题的。

rm -rf对于本地文件系统应该是最佳的(NFS会有所不同)。但是在数百万个文件中,每个文件名36个字节和每个inode 4个(猜测,不检查ext3的值),即40 *百万,仅保留在RAM中用于目录。

猜想,您正在破坏Linux中的文件系统元数据缓存,以便在您仍在使用另一部分时清除目录文件的一页的块,而仅在下一个文件被删除时才再次访问该页面的缓存。已删除。 Linux性能调优不是我的专长,但是/ proc / sys / {vm,fs} /可能包含一些相关的内容。

如果可以承受宕机的时间,则可以考虑启用dir_index功能。它将目录索引从线性切换到更适合在大型目录(散列b树)中删除的最佳索引。 tune2fs -O dir_index ...,然后e2fsck -D将起作用。但是,尽管我确信这会在出现问题之前有所帮助,但是我不知道在处理现有的v.large目录时转换(使用e43fsck和-D)如何执行。备份+随便看看。

评论


pubbs.net/201008/squid/…建议/ proc / sys / fs / vfs_cache_pressure可能是要使用的值,但我不知道目录本身是计入页缓存(因为它就是它)还是inode缓存(因为尽管不是inode,但它是FS元数据并因此被捆绑到其中)。如我所说,Linux VM调优不是我的专长。玩,看看有什么帮助。

– Phil P
2010-09-26 12:10

#14 楼

显然这里不是苹果,但我进行了一些测试,然后执行了以下操作:

在目录中创建了100,000个512字节文件(dd/dev/urandom循环);忘记计时了,但是创建这些文件花了大约15分钟。

执行以下操作删除所说的文件:

ls -1 | wc -l && time find . -type f -delete

100000

real    0m4.208s
user    0m0.270s
sys     0m3.930s 


这是奔腾4 2.8GHz盒(我认为是几百GB IDE 7200 RPM; EXT3)。内核2.6.27。

评论


有趣的是,可能是长时间创建文件的事实是否有意义?但这没关系;块缓存应该在RAM中具有所有相关的元数据块。也许是因为unlink(2)是事务性的吗?根据您的估计,在rm期间关闭日记功能是否可能是一种解决方案(尽管确实有些危险)?看起来您无需关闭tune2fs / fsck / reboot就可以完全关闭已装载文件系统上的日记功能,这在一定程度上违背了此目的。

– BMDan
2010-09-27在1:11

我无法对此发表评论,但是(在多年来的各种NIX讨论中),我总是听到rm对大量文件的运行速度非常慢,因此使用find -delete选项。在外壳上使用通配符,它​​将扩展匹配的每个文件名,并且我假设对此有一个有限的内存缓冲区,因此您可以看到这样做的效率如何。

–gravyface
2010-09-28在12:58



rm会很慢,因为它正在按名称查找文件,这意味着一个接一个地遍历目录条目,直到找到为止。但是,在这种情况下,由于要处理的每个条目(此时)是列表中的第一个条目(ls -U / ls -f),因此它的速度应该差不多。就是说,本应像冠军一样运行的rm -rf 尽可能慢。也许是时候为coreutils编写补丁以加速大规模删除了吗?也许是为了实现rm -rf以某种递归方式秘密地进行遍历/排序?像这样的不确定性是我问这个问题的原因。 ;)

– BMDan
2010-09-30在3:11

运行创建步骤后,重新启动计算机。您应该得到一个明显较慢的删除。

–马特
2012年4月23日在23:09

#15 楼

在这种情况下,有时Perl可以创造奇迹。您是否已经尝试过这样的小脚本是否能胜过bash和基本的shell命令?

#!/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()。如果是这样,那将需要永远的回报。没有预先读取所有目录条目的第一个建议的修改可能有一些缺点;但是,以当前形式,它也将在一次读取所有目录条目时使用大量CPU。目标的一部分是分而治之。尽管外壳扩展存在问题,但是此代码与'rm -f * .png'并没有本质上的区别。如果有帮助,则目录中没有我不想删除的内容。

– BMDan
2010-09-27 21:30



我一上班就要多尝试。我只是试图在一个目录中创建100000个文件,然后发现+ xargs + rm组合花了7.3秒,Perl + unlink(glob)...组合花了2.7秒。试了几次,结果总是一样的。在工作中,我将尝试使用更多文件。

–珍妮·皮卡琳(Janne Pikkarainen)
2010-09-28 5:57

我在测试时学到了一些新东西。至少对于ext3和ext4,即使从那里删除了所有文件,目录条目本身仍然很大。经过几次测试后,我的/ tmp / test目录占用了15 MB的磁盘空间。除了删除目录并重新创建之外,还有其他清除方法吗?

–珍妮·皮卡琳(Janne Pikkarainen)
2010-09-28 12:53

不,您需要重新创建它。在处理邮件系统和每个收件人的文件夹以及在发生重大问题后进行清理时,我遇到了这一问题:除了创建新目录并重新整理目录,然后废除旧目录外,别无选择。因此,您可以减少没有目录的时间窗口,但不能消除它。

– Phil P
2010-09-28 23:49

请注意,glob()将对结果进行排序,就像shell globbing正常进行的一样,因此,由于您只有100k文件,因此所有内容都可以轻松放入并且排序速度很快。对于更大的目录,您只需要opendir()/ readdir()/ closedir()即可避免排序。 [我通常对shell说,因为zsh具有glob修饰符以使排序顺序不排序,这在处理大量文件时非常有用; *(上) ]

– Phil P
2010-09-28 23:50



#16 楼

我记得在ext文件系统中删除inode的是O(n ^ 2),所以删除的文件越多,剩下的文件就会越快。

有一次我遇到了类似的问题问题(尽管我的估计是在删除时间约7小时内),但最终在第一条评论中使用了jftuga建议的路线。

#17 楼

好吧,这不是一个真正的答案,但是...

是否可以将文件系统转换为ext4并查看情况是否发生变化?

评论


看来,执行此“活动”需要在已挂载的文件系统上执行fsck,这令人震惊。有更好的方法吗?

– BMDan
2010-09-27 21:25

在转换之前,即必需的tunefs命令之前,必须先卸载文件系统。

– marcoc
2010-09-28 7:00

#18 楼

好了,线程的其余部分已经以各种方式解决了这个问题,但是我想我会投入两分钱。您遇到的性能问题可能是readdir。您将获得一个文件列表,这些文件在磁盘上不一定以任何方式顺序排列,这会导致在取消链接时在所有位置进行磁盘访问。文件足够小,以至于取消链接操作可能不会跳得太多,从而使空间清零。如果您读取了dir,然后按升序排列了索引节点,则可能会获得更好的性能。因此,将readdir读入ram(按inode排序)->取消链接->利润。

Inode在这里是一个大概的近似值..但基于您的用例,它可能是相当准确的...

评论


如果我错了,请纠正我,但是unlink(2)不会将inode归零,它只是从目录中删除对其的引用。不过,我喜欢这种方法。想进行一些时间试验,看看它是否成立?

– BMDan
2010-09-30的3:07

#19 楼

我可能会淘汰一个C编译器,并完成与您的脚本相同的工作。也就是说,使用opendir(3)获取目录句柄,然后使用readdir(3)获取文件名,然后在我断开链接时整理文件,并偶尔打印“%d个文件已删除”(可能经过的时间或当前时间戳记) )。

我不希望它比shell脚本版本快得多,只是因为我曾经不得不不时地淘汰编译器,因为没有一种干净的方法做我想从shell中执行的操作,或者因为虽然可以在shell中执行该操作,但这样做的效率很低。

评论


他至少可以从修改coreutils的rm源代码开始。

–克里斯蒂安·丘皮图
2010-09-26 19:51

#20 楼

您可能会遇到目录重写问题。尝试先删除最新文件。查看将推迟写回磁盘的安装选项。

对于进度条,请尝试运行类似rm -rv /mystuff 2>&1 | pv -brtl > /dev/null的内容

评论


在首先删除最新文件方面:ls -Ur?我很确定会加载目录条目,然后反转它们;我不相信ls足够聪明,无法从目录条目列表的末尾开始,然后倒回目录的开头。 “ ls -1”也可能不是一个好主意,因为它可能需要50+ MB的内核和几分钟的运行时间。您需要“ ls -U”或“ ls -f”。

– BMDan
10-9-27在1:28



仅当文件名以可预测的方式增加时,才可能可行。但是,我尝试将ls -1传递给反向,并传递给xargs。如果要查看中间结果,请使用文件而不是管道。您尚未提供有关文件命名的任何信息。您将反向生成模式,并使用该模式删除文件。您可能需要处理丢失的文件条目。鉴于对所需内存的评论,您对I / O需要重写目录有了一个想法。

–BillThor
2010-09-28 4:12



#21 楼

这是我删除有时可能会聚集在大型Oracle数据库服务器上的数百万个跟踪文件的方法:

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


我发现这导致删除速度相当慢,对服务器性能的影响很小,通常是在“典型”的10,000 IOPS设置中每百万个文件每小时花费一小时。

扫描目录,生成初始文件列表和删除第一个文件通常需要几分钟。从那里开始,对于每个删除的文件回显。

事实已经证明,回显到终端所引起的延迟足以防止删除进行中的任何重大负担。

评论


您被地球吞噬而活着。怎么样呢?:查找/ u * -maxdepth 3 -mindepth 3 -type d -path'* / app / *'-name diag -print0 | xargs -0I =查找= -mindepth 4 -maxdepth 4 -type d -name'trace'-print0 | xargs -0I =查找= -mindepth 1 -maxdepth 1 -name'* .tr'吗?将-delete添加到最后一个以实际删除内容;按照书面规定,它只列出了要删除的内容。请注意,这是针对附近目录中有很多无关紧要的东西而优化的;如果不是这种情况,则可以大大简化逻辑。

– BMDan
2014-10-10 14:21



find -delete会导致过多的I / O并容易影响生产性能。也许与ionice。

–罗伊
14-10-19在8:15

不过,它只是通过提高效率来引起所有I / O!在您的示例中,globbing都是预先加载的(也就是说,文件的完整列表是在第一个rm发生之前生成的),因此从那里启动时,您的I / O效率相对较高,而后又是痛苦的,乱序的可能不会引起太多I / O,但涉及scandir重复遍历目录的rms(不会引起I / O,因为它已经被加载到块缓存中了;另请参阅vfs_cache_pressure)。如果您想放慢速度,可以选择ionice,但我可能会使用小数秒睡眠。

– BMDan
2014-10-28 17:59



查找/ u * / app / * / diag -path'* / trace / *。tr'-execdir rm {} +将在每个目录中运行一个rm,因此您的CPU开销较少。我想,只要您有大量的CPU时间可用来为每个取消链接的工作分配一个完整的rm进程来限制磁盘IO,但这很丑陋。如果一次在整个目录的rm之间进行睡眠太突发,则每个断开链接都具有睡眠的perl会更好。 (-execdir sh -c ...也许)

– Peter Cordes
2015年3月17日在9:42



#22 楼

您可以使用“ xargs”并行化功能:

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


评论


这无济于事。瓶颈是驱动器上不良的随机I / O。进行并行删除可能会使情况变得更糟,并且只会增加CPU负载。

– Wim Kerkhoff
11年8月20日在21:16

#23 楼

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


评论


哇。我猜想这在“不止一种给猫咪剥皮的方式”阵营中肯定是正确的。认真地,但是,与排序和唯一?无论如何,默认情况下,“ ls”会排序,并且我希望文件名是唯一的。 :/

– BMDan
2010-09-30 14:39

#24 楼

实际上,如果您使用的shell进行命令行扩展,则此方法会更好一些:

ls|cut -c -4|sort|uniq|awk '{ print "echo "  ";rm -rf "  "*"}' |sh