rm包含数百万个文件的目录


104

背景:物理服务器,已有大约两年的历史,已将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万个左右。不好意思,请注意!但这是我的问题之一:上限到底是多少?可调吗?在我得到大叫,我想调整它下来 ; 这个巨大的目录引起了各种各样的问题。

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

这里有一些选择:

  1. rm -rf (dir)

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

  2. 目录上的unlink(2):绝对值得考虑,但问题是,通过fsck删除目录中的文件是否比通过unlink(2)删除要快。也就是说,我必须将那些inode标记为未使用。当然,这是假定我可以告诉fsck不要将条目丢弃到/ lost + found中的文件中。否则,我只是解决了我的问题。除了所有其他问题外,在阅读了更多内容之后,事实证明我可能必须调用一些内部FS函数,因为我找不到的unlink(2)变体都不允许我随意删除在其中包含条目的目录。呸。
  3. while [ true ]; do ls -Uf | head -n 10000 | xargs rm -f 2>/dev/null; done )

    这实际上是缩短的版本;我正在运行的真正的文件是:当我们用完要删除的文件时,它只是添加了一些进度报告和干净的停止位置:

    出口i = 0;
    时间([[true];
      ls -Uf | 头-n 3 | grep -qF'.png'|| 打破;
      ls -Uf | 头-n 10000 | xargs rm -f 2> / dev / null;
      出口i = $((($ i + 10000));
      回显“ $ i ...”;
    完成)

    这似乎工作得很好。在我撰写本文时,它在过去三十分钟左右的时间内删除了260,000个文件。

现在,对于以下问题:
  1. 如上所述,每个目录的条目限制是否可调?
  2. 为什么要用“真实的7m9.561s /用户0m0.001s / sys 0m0.001s”来删除单个文件,该文件是所返回的列表中的第一个文件,而删除该文件ls -U的前10,000个条目可能要花10分钟的时间#3中的命令,但现在它运行得很愉快?为此,它在大约30分钟内删除了260,000,但是现在又花了15分钟才删除了60,000。为什么会出现巨大的速度波动?
  3. 有没有更好的方法来做这种事情?不在目录中存储数百万个文件;我知道那很傻,而且在我的手表上不会发生。探究问题并仔细研究SF和SO会发现很多变化find,但由于一些不言而喻的原因,这些变化不会比我的方法快得多。但是通过fsck删除的想法有什么作用吗?还是完全其他?我渴望听到开箱即用(或不为人所知的盒子)的想法。
感谢您阅读这本小小说。随时提出问题,我一定会回答。我还将用文件的最终数量以及删除脚本运行多长时间来更新问题。

最终脚本输出!:

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

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

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


1
rm(GNU coreutils)8.4具有此选项:“ -v,--verbose解释正在做什么”。它将显示所有正在删除的文件。
Cristian Ciupitu

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

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

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

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

Answers:


30

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

根据此页面

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

的选项被设置在任一fstab或安装操作过程中,替换data=ordereddata=writeback。包含要删除文件的文件系统必须重新安装。


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

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

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

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

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
有两个小问题:一,[256]可能应该是[FILENAME_MAX],二,我的Linux(2.6.18 == CentOS 5.x)似乎不包含d_type条目(至少根据getdents(2)而言)。
BMDan 2011年

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

1
因为现在在我看来,如果我们
要按

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

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

31

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


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

正是我在想什么。这是对问题3的答案。理想的选择是:)
约书亚

12

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

删除文件后,您仍然可能有问题。

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


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

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

3
嗯,我做了更多的研究,似乎目录可以占用的块数是有限的。文件的确切数量取决于一些因素,例如文件名长度。这gossamer-threads.com/lists/linux/kernel/921942似乎表明,与4K块,你应该能够在目录超过800万个文件。它们的文件名特别长吗?
亚历克斯·J·罗伯茨

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

好吧,这就是我的主意:)
Alex J. Roberts


4

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

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

tune2fs -O ^has_journal <device>

无论data给的选项如何,此操作都会完全关闭日记功能mount。我将其与结合在一起,noatime并且音量已dir_index确定,并且看起来效果很好。删除实际上完成了,而无需我杀死它,我的系统保持了响应能力,并且现在可以备份并运行(重新启用日记),没有任何问题。


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

3

确保您这样做:

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

这也应该加快速度。


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

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

2

ls非常慢的命令。尝试:

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

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

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。基本上是误差范围。
BMDan 2010年

1
您可能只需要运行strace -r -p <pid of rm>即可附加到已在运行的rm进程。然后,您可以查看unlink系统调用滚动的速度。(-r将自上次系统调用以来的时间放在每一行的开始。)
Peter Cordes

2

dir_index设置为文件系统?(tune2fs -l | grep dir_index)如果没有,请启用它。通常在新的RHEL上启用。


1
是的,它已启用,但是很棒的建议!
BMDan 2010年

2

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

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

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


1

我建议的首选方法是已经建议的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将工作。但是,尽管我确信这会出现问题之前有所帮助但是我不知道-D在处理现有的v.large目录时转换(带有的e2fsck )如何执行。备份+随便看看。


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

1

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

在一个目录(dd/dev/urandom循环)中创建了100,000个512字节文件;忘记为它计时,但是创建这些文件大约花费了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年

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

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

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

1

在这种情况下,有时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。目标的一部分是分而治之。尽管shell扩展存在问题,但是该代码与'rm -f * .png'并没有本质上的区别。如果有帮助,则目录中没有我不想删除的内容。
BMDan 2010年

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

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

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

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


0

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

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


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

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

0

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

我认为Inode是一个大概的近似值..但是根据您的用例,它可能是相当准确的...


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

0

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

我不希望它比shell脚本版本快得多,只是因为我曾经不得不不时地淘汰编译器,因为没有干净的方法可以从shell中完成所需的工作,或者因为尽管可以在shell中使用,但这种方式的生产效率却很低。


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

0

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

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


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

仅当文件名以可预测的方式增加时,才可能可行。但是,我尝试将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设置中,每百万个文件大约需要一个小时。

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

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


您正在被地球吞噬活着。怎么样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 2014年

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

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

find /u*/app/*/diag -path '*/trace/*.tr' -execdir rm {} +会在rm每个目录中运行一个,因此您的CPU开销会更少。我想,只要您有大量的CPU时间来节省,通过rm为每个unlink工作分叉整个过程来限制磁盘IO ,但这很丑陋。如果一次在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负载。
Wim Kerkhoff

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

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

-2

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

ls|cut -c -4|sort|uniq|awk '{ print "echo " $1 ";rm -rf " $1 "*"}' |sh
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.