Вопрос: rm в каталоге с миллионами файлов


Справочная информация: физический сервер, около двух лет, диски SATA с частотой 7200 об / мин, подключенные к 3Ware RAID-картам, ext3 FS, установленный noatime и data = упорядоченный, не под сумасшедшей нагрузкой, ядро ​​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)

Я попробовал это первым. Я сдался и убил его после того, как он проработал полтора дня без какого-либо заметного воздействия.

  • unlink (2) в каталоге: определенно стоит рассмотреть, но вопрос в том, будет ли быстрее удалять файлы внутри каталога через fsck, чем удалять через unlink (2). То есть, так или иначе, я должен отметить эти иноды как неиспользуемые. Это предполагает, конечно, что я могу сказать fsck не отбрасывать записи в файлы в / lost + found; в противном случае, я только что перенесла свою проблему. В дополнение ко всем остальным проблемам, после прочтения об этом немного больше, оказывается, мне, вероятно, придется называть некоторые внутренние функции FS, так как ни один из вариантов unlink (2), которые я могу найти, не позволил бы мне просто беспечно удалить каталог с записями в нем. Пух.
  • while [ true ]; do ls -Uf | head -n 10000 | xargs rm -f 2>/dev/null; done )
  • На самом деле это сокращенная версия; реальный, который я запускаю, который просто добавляет некоторые отчеты о проделанной работе и чистую остановку, когда у нас заканчиваются файлы для удаления:

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

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


    96
    2017-09-22 23:57


    Источник


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


    Ответы:


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

    В соответствии с эта страница,

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

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


    29
    2017-09-26 05:49



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


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

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

    Проблема в основном сводится к тому, что readdir () использует фиксированный размер буфера 32 Кбит для извлечения файлов. Поскольку каталог становится все больше и больше (размер увеличивается с добавлением файлов), ext3 получает медленнее и медленнее, чтобы извлекать записи, а размер буфера для дополнительного 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);
    }
    

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

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

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

    Теперь вы можете сделать 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
    

    Был удивлен, что это все так хорошо работает!


    72
    2017-11-06 19:06



    Две незначительные проблемы: одна, [256] вероятно, должно быть [FILENAME_MAX], и два, мой Linux (2.6.18 == CentOS 5.x), похоже, не включает запись d_type в dirent (по крайней мере, согласно getdents (2)). - BMDan
    Не могли бы вы немного рассказать о балансировке btree и почему удаление в порядке помогает предотвратить это? Я попробовал Googling для этого, к сожалению, безрезультатно. - ovgolovin
    Потому что теперь мне кажется, что если мы удаляем порядок, мы вынуждаем перебалансировку, поскольку мы удаляем листья с одной стороны и оставляем друг друга: en.wikipedia.org/wiki/B-tree#Rebalancing_after_deletion - ovgolovin
    Надеюсь, я не беспокою вас об этом. Но все же я начал вопрос об удалении файлов в порядке stackoverflow.com/q/17955459/862380, который, похоже, не получил ответа, который объяснит проблему на примере, что будет понятно для обычных программистов. Если у вас есть время и так хочется, не могли бы вы заглянуть в него? Может быть, вы могли бы написать лучшее объяснение. - ovgolovin
    Это замечательный фрагмент кода. Это был единственный инструмент, который я смог найти, чтобы перечислить и удалить 11 000 000 (одиннадцать миллионов) файлов сеансов, которые были созданы в каталоге, вероятно, в течение нескольких лет. Процесс Plesk, который должен был держать их под контролем с помощью find и других трюков в других ответах здесь, не смог завершить прогон, поэтому файлы просто продолжали наращивать. Это дань двоичному дереву, которое файловая система использует для хранения каталога, что сеансы могли работать вообще - вы могли бы создать файл и получить его без задержки. Просто списки были непригодными для использования. - Jason


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


    31
    2017-09-23 00:27



    На самом деле мне действительно нравится этот ответ. В практическом плане, в данном случае нет, но это не тот, о котором я бы мог подумать. Браво! - BMDan
    Точно, о чем я тоже думал. Это ответ на вопрос 3. Идеально, если вы спросите меня :) - Joshua


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

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

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


    11
    2017-09-23 05:45



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


    Я не сравнивал это, но этот парень:

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

    5
    2018-06-04 11:52





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

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

    Я отправил сообщение об ошибке в этой проблеме с поиском: http://savannah.gnu.org/bugs/?31961


    4
    2017-12-23 19:54



    Это спасло меня! - jestro


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

    tune2fs -O ^has_journal <device>
    

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


    3
    2018-04-23 22:29



    Я собирался предложить его монтировать как ext2 вместо ext3, чтобы избежать ведения журнала операций метаданных. Это должно сделать то же самое. - Peter Cordes


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

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

    который также должен ускорить работу.


    3
    2017-09-27 02:03



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


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

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

    2
    2017-09-23 04:04



    rm -rf побежал полтора дня, и я, наконец, убил его, не зная, действительно ли это что-то совершило. Мне нужен индикатор прогресса. - BMDan
    Что касается rm, который является очень медленным, «время найти. -Delete» на 30k файлах: 0m0.357s / 0m0.019s / 0m0.337s real / user / sys. "time (ls -1U | xargs rm -f)" в тех же файлах: 0m0.366s / 0m0.025s / 0m0.340s. Это, в основном, территория с ошибкой. - BMDan
    Вы могли бы просто запустить strace -r -p <pid of rm> для присоединения к уже запущенному процессу rm. Затем вы можете увидеть, как быстро unlink системные вызовы прокручиваются. (-r ставит время с момента предыдущего системного вызова в начале каждой строки.) - Peter Cordes


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


    2
    2017-09-27 04:18



    Да, это включено, но удивительное предложение! - BMDan


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

    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
    2017-09-26 12:05



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