рм в каталоге с миллионами файлов

104

Предыстория: физический сервер, около двух лет, диски SATA 7200 об / мин, подключенные к RAID-карте 3Ware, noatime с установленной файловой системой ext3 FS и данные = заказано, без сумасшедшей нагрузки, ядро ​​2.6.18-92.1.22.el5, время безотказной работы 545 дней , Каталог не содержит никаких подкаталогов, только миллионы маленьких (~ 100 байт) файлов, с некоторыми большими (несколько КБ).

У нас есть сервер, который за последние несколько месяцев немного кукушка, но мы заметили это только на днях, когда он не смог выполнить запись в каталог из-за слишком большого количества файлов. В частности, он начал выдавать эту ошибку в / var / log / messages:

ext3_dx_add_entry: Directory index full!

На рассматриваемом диске осталось много инодов:

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

Так что я предполагаю, что это означает, что мы достигли предела того, сколько записей может быть в самом файле каталога. Не знаю, сколько будет файлов, но, как видите, не может быть больше, чем три миллиона или около того. Не то чтобы это хорошо, заметьте! Но это первая часть моего вопроса: что это за верхний предел? Это настраивается? Прежде чем орать на меня - я хочу смягчить это ; этот огромный каталог вызвал всевозможные проблемы.

В любом случае, мы отследили проблему в коде, который генерировал все эти файлы, и исправили его. Теперь я застрял с удалением каталога.

Несколько вариантов здесь:

  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 )

    Это на самом деле сокращенная версия; Реальный, который я запускаю, который просто добавляет отчеты о прогрессе и чистую остановку, когда у нас заканчиваются файлы для удаления, это:

    экспорт я = 0;
    время (в то время как [верно]; сделать
      ls -Uf | голова -n 3 | grep -qF '.png' || сломать;
      ls -Uf | голова -n 10000 | xargs rm -f 2> / dev / null;
      export i = $ (($ i + 10000));
      echo "$ i ...";
    сделанный )

    Кажется, это работает довольно хорошо. Когда я пишу это, он удалил 260 000 файлов за последние тридцать минут или около того.

Теперь по вопросам:
  1. Как упомянуто выше, настраивается ли ограничение на количество записей в каталоге?
  2. Почему потребовалось «реальные 7m9.561s / пользователь 0m0.001s / sys 0m0.001s», чтобы удалить один файл, который был первым в списке, возвращаемом пользователем ls -U, и, возможно, потребовалось десять минут, чтобы удалить первые 10000 записей с команда в # 3, но теперь она довольно счастливо тащится? В этом отношении он удалил 260 000 примерно за тридцать минут, но теперь требуется еще пятнадцать минут, чтобы удалить еще 60 000. Почему огромные колебания в скорости?
  3. Есть ли лучший способ сделать это? Не хранить миллионы файлов в каталоге; Я знаю, что это глупо, и это не случилось бы на моих часах. Погугление проблемы и просмотр SF и SO предлагает множество вариантов find, которые не будут значительно быстрее моего подхода по нескольким очевидным причинам. Но есть ли у идеи delete-via-fsck ноги? Или что-то еще целиком? Мне не терпится услышать нестандартное (или не очень известное) мышление.
Спасибо за чтение маленького романа; не стесняйтесь задавать вопросы, и я обязательно отвечу. Я также обновлю вопрос, указав окончательное число файлов и продолжительность запуска сценария удаления после его получения.

Окончательный вывод сценария !:

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

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

Таким образом, три миллиона файлов были удалены за чуть более четырех часов.

BMDan
источник
1
У rm (GNU coreutils) 8.4 есть эта опция: "-v, --verbose объяснить, что делается" . Он будет отображать все файлы, которые удаляются.
Кристиан Чупиту
2
На самом деле, это был бы отличный способ сделать индикатор выполнения: так как каждый файл был бы длиной тридцать семь символов (36 + a \ n), я мог бы легко написать для этого парсер, и так как printf () дешево и команда rm уже имеет имя загруженного файла, особой потери производительности нет. Похоже на то, что я не начинаю делать весь шебанг, так как я никогда не смогу заставить "гм" сделать что-то подобное. Но он может работать довольно хорошо, как индикатор прогресса в пределах 10 000; возможно "." за каждые сто файлов?
BMDan
8
rm -rfv | pv -l >/dev/null, PV должен быть доступен в репозитории EPEL .
Кристиан Чупиту
5
PV это потрясающе. Я оставляю за собой следы фотоэлектрических установок.
BMDan
У меня была точно такая же проблема недавно. Спасибо!
richo

Ответы:

30

Опция data=writebackмонтирования заслуживает того, чтобы ее попытались предотвратить ведение журнала файловой системы. Это должно быть сделано только во время удаления, однако существует риск, если сервер отключается или перезагружается во время операции удаления.

Согласно этой странице ,

Некоторые приложения показывают очень значительное улучшение скорости при использовании. Например, улучшения скорости можно увидеть (...), когда приложения создают и удаляют большие объемы небольших файлов.

Опция устанавливается во fstabвремя или в процессе монтирования, заменяя data=orderedна data=writeback. Файловая система, содержащая файлы для удаления, должна быть перемонтирована.

Кольцо Ø
источник
1
Он также может увеличить время с помощью commit опции : «Это значение по умолчанию (или любое низкое значение) ухудшит производительность, но это хорошо для безопасности данных. Установка его в 0 будет иметь тот же эффект, что и установка по умолчанию (5 секунд). ). Установка очень больших значений улучшит производительность ".
Кристиан Чупиту
1
Обратная запись выглядит звездно, за исключением документации, на которую я смотрел ( gentoo.org/doc/en/articles/l-afig-p8.xml#doc_chap4 ), явно упоминается, что это все еще метаданные журналов, которые, как я предполагаю, включают в себя все данные, которые я меняется (я, конечно, не изменяю никаких данных в самих файлах). Мое понимание варианта неверно?
BMDan
Наконец, к вашему сведению, в этой ссылке не упоминается тот факт, что data = writeback может быть огромной дырой в безопасности, поскольку данные, на которые указывает указанная запись, могут не иметь данных, которые были записаны приложением, что означает, что может произойти сбой в старых, возможно, конфиденциальных / секретных данных. Не беспокойся, так как мы только временно включаем его, но я хотел предупредить всех об этом предупреждении на случай, если вы или другие, кто сталкивался с этим предложением, не знали.
BMDan
совершить: это довольно гладко! Спасибо за указатель.
BMDan
2
data=writebackвсе еще журналы метаданных перед записью их в основную файловую систему. Насколько я понимаю, он просто не навязывает порядок между такими вещами, как запись карты экстентов и запись данных в эти экстенты. Может быть, есть и другие ограничения в отношении порядка, которые тоже ослабляют, если вы видели от этого выигрыш в производительности. Конечно, установка без журнала вообще могла бы быть даже более высокой производительностью. (Это может позволить изменениям метаданных просто происходить в ОЗУ, без необходимости иметь что-либо на диске до завершения операции unlink).
Питер Кордес
80

Хотя основной причиной этой проблемы является производительность ext3 с миллионами файлов, действительная основная причина этой проблемы другая.

Когда каталог должен быть в списке, вызывается readdir () для каталога, который выдает список файлов. readdir - это вызов posix, но используемый здесь системный вызов Linux называется «getdents». Получатели перечисляют записи каталога, заполняя буфер записями.

Проблема в основном связана с тем, что readdir () использует фиксированный размер буфера 32 Кб для извлечения файлов. По мере того, как каталог становится все больше и больше (размер увеличивается с добавлением файлов), ext3 становится все медленнее и медленнее для извлечения записей, а дополнительный размер буфера readdir 32 КБ достаточен только для включения части записей в каталоге. Это заставляет readdir зацикливаться снова и снова и вызывать дорогой системный вызов снова и снова.

Например, в тестовом каталоге, который я создал с более чем 2,6 миллионами файлов внутри, выполнение «ls -1 | wc-l» показывает большой вывод результатов многих системных вызовов getdent.

$ 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 вручную, поэтому не существует интерфейса для его нормального использования (проверьте страницу man, чтобы увидеть 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);
}

Хотя это не решает основную фундаментальную проблему (много файлов в файловой системе, которая плохо работает с ней). Вероятно, это будет намного, намного быстрее, чем многие из опубликованных альтернатив.

В качестве предварительного решения следует удалить поврежденный каталог и переделать его после. Каталоги только увеличиваются в размере и могут работать плохо даже с несколькими файлами внутри из-за размера каталога.

Редактировать: я убрал это совсем немного. Добавлена ​​опция, позволяющая вам удалять из командной строки во время выполнения, и удалялась куча вещей из Treewalk, которые, честно говоря, в лучшем случае сомнительны. Также было показано, чтобы произвести повреждение памяти.

Теперь вы можете сделать dentls --delete /my/path

Новые результаты. Исходя из каталога с 1,82 миллиона файлов.

## 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 в dirent (по крайней мере, согласно getdents (2)).
BMDan
1
Не могли бы вы рассказать подробнее о ребалансировке дерева и почему удаление по порядку помогает предотвратить это? Я попробовал Googling для этого, к сожалению, безрезультатно.
Овголовин
1
Потому что теперь мне кажется, что если мы удаляем по порядку, мы принудительно перебалансируем, так как удаляем листья с одной стороны и
уходим
1
Я надеюсь, что я не беспокою вас этим вопросом. Но все же я начал задавать вопрос об удалении файлов по порядку stackoverflow.com/q/17955459/862380 , который, похоже, не получил ответа, который объяснит проблему с примером, который будет понятен обычным программистам. Если у вас есть время и вы чувствуете, что так, не могли бы вы посмотреть на это? Может быть, вы могли бы написать лучшее объяснение.
Овголовин
2
Это удивительный кусок кода. Это был единственный инструмент, который я смог найти в списке и удалить около 11 000 000 (одиннадцати миллионов) файлов сессий, которые создавались в каталоге, возможно, за несколько лет. Процесс Plesk, который должен был держать их под контролем, используя find и другие приемы в других ответах, не смог завершить выполнение, поэтому файлы просто продолжали накапливаться. Это дань бинарному дереву, которое файловая система использует для хранения каталога, что сессии могли работать вообще - вы можете создать файл и получить его без задержки. Просто списки были непригодны.
Джейсон
31

Можно ли было бы сделать резервную копию всех других файлов из этой файловой системы во временное хранилище, переформатировать раздел, а затем восстановить файлы?

jftuga
источник
3
Мне действительно нравится этот ответ, на самом деле. С практической точки зрения в данном случае нет, но я бы не подумал. Браво!
BMDan
Именно то, о чем я думал. Это ответ на вопрос 3. Идеально, если вы спросите меня :)
Джошуа
12

В ext3 не существует предельного значения для каждого каталога, только ограничение inode для файловой системы (я думаю, что существует ограничение на количество подкаталогов).

У вас могут остаться проблемы после удаления файлов.

Когда в каталоге содержатся миллионы файлов, сама запись в каталоге становится очень большой. Запись каталога должна проверяться для каждой операции удаления, и для каждого файла требуется различное количество времени, в зависимости от того, где находится его запись. К сожалению, даже после того, как все файлы были удалены, запись каталога сохраняет свой размер. Таким образом, дальнейшие операции, требующие сканирования записи каталога, все равно будут занимать много времени, даже если каталог теперь пуст. Единственный способ решить эту проблему - переименовать каталог, создать новый со старым именем и перенести все оставшиеся файлы в новый. Затем удалите переименованный.

Алекс Дж. Робертс
источник
Действительно, я заметил только это поведение после удаления всего. К счастью, у нас уже была директория из «линии огня», так что я мог просто ее найти.
BMDan
2
Тем не менее, если нет ограничений на количество файлов в каталоге, почему я получаю "ext3_dx_add_entry: индекс каталога заполнен!" когда на этом разделе еще были доступны иноды? В этом каталоге не было подкаталогов.
BMDan
3
хм, я провел немного больше исследований, и кажется, что есть предел количества блоков, которые может занять каталог. Точное количество файлов зависит от нескольких вещей, например длины файла. Этот gossamer-threads.com/lists/linux/kernel/921942, кажется, указывает на то, что с блоками 4k вы можете иметь более 8 миллионов файлов в каталоге. Это были особенно длинные имена файлов?
Алекс Дж. Робертс
Каждое имя файла было ровно 36 символов.
BMDan
ну это я из идей :)
Алекс Дж. Робертс
4

find просто не работает для меня, даже после изменения параметров ext3 fs, как предложено пользователями выше. Потребляется слишком много памяти. Этот скрипт PHP добился цели - быстрое, незначительное использование процессора, незначительное использование памяти:

<?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

Недавно я столкнулся с подобной проблемой и не смог заставить работать data=writebackпредложение ring0 (возможно, из-за того, что файлы находятся на моем основном разделе). Исследуя обходные пути, я наткнулся на это:

tune2fs -O ^has_journal <device>

Это полностью отключит ведение журнала, независимо от того, какой dataпараметр дать mount. Я совместил это с тем, noatimeчто громкость dir_indexустановилась, и это, казалось, работало довольно хорошо. Удаление фактически завершилось, и мне не пришлось его убивать, моя система оставалась отзывчивой, и теперь она снова запущена (с включенным ведением журнала) без проблем.

Мэтью Рид
источник
Я собирался предложить смонтировать его как ext2 вместо ext3, чтобы избежать регистрации операций метаданных. Это должно сделать то же самое.
Питер Кордес
3

Убедитесь, что вы делаете:

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

что должно немного ускорить процесс.

karmawhore
источник
4
Хороший звонок, но это уже смонтированный noatime, как я уже упоминал в шапке вопроса. И нодирайм избыточен; см. lwn.net/Articles/245002 .
BMDan
1
чел повторить эту мантру "noatime, nodiratime, nodevatime, noreadingdocsatime"
poige
2

Очень медленная команда. Пытаться:

find /dir_to_delete ! -iname "*.png" -type f -delete
bindbn
источник
rm -rf побежал в течение полутора дней, и я наконец убил его, даже не зная, достиг ли он чего-либо. Мне нужен индикатор прогресса.
BMDan
4
Что касается очень медленного rm, то "time find. -Delete" для файлов 30k: 0m0.357s / 0m0.019s / 0m0.337s real / user / sys. "time (ls -1U | xargs rm -f)" для тех же файлов: 0m0.366s / 0m0.025s / 0m0.340s. Который является в основном территорией с ошибкой.
BMDan
1
Вы могли бы просто запустить, strace -r -p <pid of rm>чтобы присоединиться к уже запущенному процессу rm. Затем вы можете увидеть, как быстро unlinkпроходят системные вызовы. ( -rпомещает время после предыдущего системного вызова в начале каждой строки.)
Питер Кордес
2

Является ли dir_indexустановить для файловой системы? ( tune2fs -l | grep dir_index) Если нет, включите его. Обычно для нового RHEL.

Сэм
источник
1
Да, это включено, но внушительное предложение!
BMDan
2

Пару лет назад я нашел каталог с 16 миллионами файлов XML в /файловой системе. Из-за критики сервера мы использовали следующую команду, которая заняла около 30 часов :

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

Это был старый жесткий диск со скоростью 7200 об / мин , и, несмотря на узкое место ввода-вывода и пики ЦП, старый веб-сервер продолжал свою работу.

a_foaley
источник
1

Мой предпочтительный вариант - уже предложенный подход newfs. Основная проблема заключается в том, что, как уже отмечалось, линейное сканирование для удаления проблематично.

rm -rfдолжно быть почти оптимальным для локальной файловой системы (NFS будет другой). Но при миллионах файлов, 36 байтов на имя файла и 4 на индекс (предположение, не проверяя значение для ext3), это 40 * миллионов, которые должны храниться в оперативной памяти только для каталога.

По-видимому, вы перебиваете кэш-память метаданных файловой системы в Linux, так что блоки для одной страницы файла каталога удаляются, пока вы все еще используете другую часть, только для того, чтобы снова попасть на эту страницу кэша, когда следующая файл удален Настройка производительности Linux не является моей областью, но / proc / sys / {vm, fs} /, вероятно, содержит что-то важное.

Если вы можете позволить себе простои, вы можете включить функцию dir_index. Он переключает индекс каталога с линейного на нечто гораздо более оптимальное для удаления в больших каталогах (хэшированные b-деревья). tune2fs -O dir_index ...затем e2fsck -Dбудет работать. Однако, хотя я уверен, что это поможет до возникновения проблем, я не знаю, как выполняется преобразование (e2fsck с -D) при работе с существующим каталогом v.large. Резервное копирование + сосать-и-посмотреть.

Фил П
источник
1
pubbs.net/201008/squid/… предполагает, что это /proc/sys/fs/vfs_cache_pressureможет быть полезным значением, но я не знаю, относится ли сам каталог к ​​кешу страниц (потому что это так) или кешу узлов (потому что, несмотря на то, что он не является inode, это метаданные FS, и по этой причине они включены в него). Как я уже сказал, настройка виртуальной машины Linux не моя область. Играй и смотри, что помогает.
Фил Р
1

Очевидно, что от яблок к яблокам здесь нет, но я настроил небольшой тест и сделал следующее:

Создано 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 

Это Pentium 4 с частотой 2,8 ГГц (пара сотен гигабайт IDE 7200 об / мин, я думаю; EXT3). Ядро 2.6.27.

gravyface
источник
Интересно, может быть, тот факт, что файлы создавались в течение длительного периода времени, имеет значение? Но это не должно иметь значения; кэш блоков должен иметь все соответствующие блоки метаданных в оперативной памяти. Может быть, это потому, что unlink (2) является транзакционным? По вашей оценке, было бы возможным отключить ведение журнала на время действия rm (хотя и несколько опасно)? Не похоже, что вы можете просто полностью отключить ведение журналов в смонтированной файловой системе без tune2fs / fsck / reboot, что несколько противоречит цели.
BMDan
Я не могу комментировать это, но, как это ни странно (в различных обсуждениях NIX на протяжении многих лет), я всегда слышал, что rmэто ужасно медленно на большом количестве файлов, отсюда и find -deleteвариант. С подстановочным знаком на оболочке он будет расширять каждое совпадающее имя файла, и я предполагаю, что для этого существует ограниченный буфер памяти, чтобы вы могли видеть, как это может стать неэффективным.
gravyface
1
rm будет медленным, потому что он ищет файл по имени, что означает, что он перебирает записи каталога по одной, пока не найдет его. В этом случае, однако, поскольку каждая передаваемая запись является (в этой точке) первой в списке (ls -U / ls -f), она должна быть почти такой же быстрой. Тем не менее, rm -rf <dir>, который должен был бежать как чемпион, был медленным, как могло бы быть. Возможно, пришло время написать патч для coreutils, чтобы ускорить массовое удаление? Может быть, это тайно сглаживать / сортировать каким-то рекурсивным способом для реализации rm -rf? Неопределенность, как это, почему я задал вопрос. ;)
BMDan
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 МБ ОЗУ и полмиллиона файлов.

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, может иметь некоторые ноги; однако в его нынешнем виде он также использовал бы ужасное количество ресурсов ЦП при простом чтении всех записей каталога одновременно. Часть цели здесь состоит в том, чтобы разделить и победить; этот код принципиально не отличается от 'rm -f * .png', несмотря на проблемы с расширением оболочки. Если это поможет, в каталоге нет ничего, что я бы не хотел удалять.
BMDan
Я должен попробовать больше, как только доберусь до работы. Я только что попытался создать 100 000 файлов в одном каталоге и найти, что комбинация + xargs + rm заняла 7,3 секунды, комбинация Perl + unlink (glob) ... завершилась за 2,7 секунды. Пробовал пару раз, результат всегда был один и тот же. На работе я попробую это с большим количеством файлов.
Янне Пиккарайнен
Я узнал что-то новое во время тестирования этого. По крайней мере, с ext3 и ext4 сама запись каталога остается огромной даже после удаления всех файлов оттуда. После пары тестов мой каталог / tmp / test занимал 15 МБ дискового пространства. Есть ли другой способ исправить это, кроме удаления каталога и его воссоздания?
Янне Пиккарайнен,
2
Нет, вам нужно воссоздать его. Я сталкиваюсь с этим, когда имею дело с почтовой системой и папкой на получателя, а также очищаюсь после значительных проблем: нет другого способа, кроме как создать новый каталог и перетасовать каталоги, а затем сбросить ноль со старого. Таким образом, вы можете сократить временное окно, когда нет каталога, но не устранить его.
Фил П
Обратите внимание, что glob () будет сортировать результаты, так же, как это обычно происходит с глобализацией оболочки, поэтому, поскольку у вас есть только файлы размером 100 Кб, все легко помещается и сортировка выполняется быстро. С гораздо большим каталогом вы бы хотели использовать opendir () / readdir () / closedir (), чтобы избежать сортировки. [ Обычно я говорю об оболочке, поскольку в zsh есть модификатор glob для сортировки порядка сортировки, что полезно при работе с большим количеством файлов; *(oN)]
Фил П
1

Из того, что я помню, удаление inode в файловых системах ext - O (n ^ 2), поэтому чем больше файлов вы удаляете, тем быстрее будут работать остальные.

Был один раз, когда я столкнулся с подобной проблемой (хотя мои оценки оценивали время удаления ~ 7 часов), в конце концов, в первом комментарии был предложен маршрут, предложенный jftuga .

Хьюберт Карио
источник
0

Ну, это не настоящий ответ, но ...

Можно ли будет конвертировать файловую систему в ext4 и посмотреть, что изменится?

marcoc
источник
Похоже, что для этого «вживую» требуется fsck на смонтированной файловой системе, что ... вызывает тревогу. Есть лучший способ?
BMDan
Файловая система должна быть размонтирована перед преобразованием, то есть перед необходимыми командами tunefs.
Marcoc
0

Хорошо, это было рассмотрено различными способами в остальной части потока, но я думал, что я добавлю свои два цента. Виновником производительности в вашем случае, вероятно, является readdir. Вы получаете список файлов, которые не обязательно каким-либо образом последовательны на диске, что приводит к доступу к диску повсюду, когда вы отключаете связь. Файлы настолько малы, что операция unlink, вероятно, не слишком быстро обнуляет пространство. Если вы прочитаете dir, а затем отсортируете по возрастанию inode, вы, вероятно, получите лучшую производительность. Так что читайте в RAM (сортировка по inode) -> unlink -> profit.

Я думаю, что Inode - это грубое приближение ... но, исходя из вашего варианта использования, он может быть довольно точным ...

MattyB
источник
1
Поправьте меня, если я ошибаюсь, но unlink (2) не обнуляет индекс, а просто удаляет ссылку на него из каталога. Мне нравится наглость этого подхода, хотя. Хотите провести несколько временных испытаний и посмотреть, верно ли это?
BMDan
0

Я бы, вероятно, вытащил компилятор C и сделал бы моральный эквивалент вашего скрипта. То есть используйте opendir(3)для получения дескриптора каталога, затем используйте readdir(3)для получения имени файлов, затем подсчитывайте файлы по мере их отсоединения и время от времени печатайте «% d удаленных файлов» (и, возможно, истекшее время или текущую метку времени).

Я не ожидаю, что он будет заметно быстрее, чем версия сценария оболочки, просто я привык время от времени вырывать компилятор, потому что нет чистого способа сделать то, что я хочу из оболочки, или потому что в то время как выполнимо в оболочке, это непродуктивно медленно.

Vatine
источник
Он мог бы по крайней мере начать с изменения исходного кода rm из coreutils .
Кристиан Чупиту
0

Скорее всего, у вас возникли проблемы с перезаписью каталога. Попробуйте сначала удалить самые новые файлы. Посмотрите на параметры монтирования, которые будут откладывать обратную запись на диск.

Для индикатора прогресса попробуйте запустить что-то вроде rm -rv /mystuff 2>&1 | pv -brtl > /dev/null

BillThor
источник
С точки зрения удаления самых новых файлов сначала: ls -Ur? Я почти уверен, что загрузит записи dir, а затем обратит их; Я не верю, что ls достаточно умен, чтобы начать с конца списка записей dir и вернуться назад к началу. «ls -1» также, вероятно, не очень хорошая идея, поскольку, вероятно, потребуется более 50 МБ ядра и несколько минут для запуска; вы бы хотели "ls -U" или "ls -f".
BMDan
Вероятно, это только практично, если имена файлов увеличиваются в предсказуемом порядке. Тем не менее, вы можете попробовать ls -1, чтобы повернуть вспять, и направить в xargs. Используйте файлы вместо каналов, если вы хотите увидеть свои промежуточные результаты. Вы не предоставили никакой информации об именах файлов. Вы бы сгенерировали шаблон в обратном порядке и удалили файлы, используя шаблон. Возможно, вам придется обработать отсутствующие записи в файле. Учитывая ваш комментарий о требуемой памяти, у вас есть представление о необходимости ввода-вывода переписать каталог.
BillThor
0

Вот как я удаляю миллионы файлов трассировки, которые иногда могут собираться на большом сервере базы данных Oracle:

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

Я считаю, что это приводит к довольно медленному удалению, которое слабо влияет на производительность сервера, обычно это примерно час на миллион файлов при «типичной» настройке 10000 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
команда find -delete имеет тенденцию вызывать слишком много операций ввода-вывода и легко влияет на производительность. Возможно, с Ионис.
Рой
Это вызывает все эти операции ввода / вывода, просто будучи более эффективным! Глобинг полностью загружен для вашего примера (т. Е. Полный список файлов генерируется до того, как rmпроизойдет первый ), поэтому при запуске вы получаете относительно эффективный ввод-вывод, за которым следуют болезненные неупорядоченные rmоперации. это, вероятно, не вызывает большого количества операций ввода-вывода, но требует scandirмногократного обхода каталога (не вызывает ввода-вывода, поскольку оно уже загружено в кэш блоков; см. также vfs_cache_pressure). Если вы хотите замедлить процесс, ioniceэто вариант, но я бы, вероятно, использовал дробные секунды sleep.
BMDan
find /u*/app/*/diag -path '*/trace/*.tr' -execdir rm {} +будет запускаться по одному rmна каталог, так что вы будете иметь меньше нагрузки на процессор. Думаю, до тех пор, пока у вас есть тонны процессорного времени, дросселирование дискового ввода-вывода путем создания целого rmпроцесса для каждой unlinkработы, но это уродливо. Perl со сном на unlink будет лучше, если сон между rmцелыми каталогами за раз слишком бурный. ( -execdir sh -c ...возможно)
Питер Кордес
-1

Вы можете использовать функции распараллеливания 'xargs':

ls -1|xargs -P nb_concurrent_jobs -n nb_files_by_job rm -rf
Джереми
источник
1
Это не поможет Узким местом является плохой случайный ввод / вывод на диске. Параллельное удаление может ухудшить ситуацию и увеличить нагрузку на процессор.
Вим Керхофф
-2
ls|cut -c -4|sort|uniq|awk '{ print "rm -rf " $1 }' | sh -x
karmawhore
источник
1
Ух ты. Я предполагаю, что это довольно твердо относится к лагерю "больше, чем один способ убить кошку". Серьезно, однако, с сортировкой и уникальным? В любом случае "ls" сортирует по умолчанию, и я уверен, что имена файлов уникальны. : /
BMDan
-2

на самом деле, это немного лучше, если используемая оболочка выполняет расширение командной строки:

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

источник