rm в каталозі з мільйонами файлів


104

Довідка: фізичний сервер, близько двох років, 7200-RPM SATA-накопичувачі, підключені до 3Ware RAID-картки, 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). Тобто, так чи інакше, я маю позначати ці вставки як невикористані. Це, звичайно, передбачає, що я можу сказати fsck не відкидати записи до файлів у / lost + found; в іншому випадку я просто перенесла свою проблему. На додаток до всіх інших проблем, прочитавши про це трохи більше, виявляється, що, мабуть, мені доведеться викликати деякі внутрішні функції FS, оскільки жоден із варіантів відключення (2), які я можу знайти, не дозволив би мені просто видалити каталог із записами в ньому. Пух.
  3. while [ true ]; do ls -Uf | head -n 10000 | xargs rm -f 2>/dev/null; done )

    Це фактично скорочена версія; справжній, який я працюю, який просто додає деякий звіт про прогрес і чисту зупинку, коли у нас закінчуються файли для видалення, це:

    експорт i = 0;
    час (поки [правда]; робити
      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. Чому для видалення одного файлу, який був першим у списку, який повернув ls -U, і знадобилося, можливо, десять хвилин, щоб видалити перші 10 000 записів із записом "real 7m9.561s / user 0m0.001s / sys 0m0.001s". команда в №3, але тепер вона тягнеться цілком щасливо? З цього приводу він видалив 260 000 приблизно за тридцять хвилин, але зараз потрібно ще п’ятнадцять хвилин, щоб видалити ще 60000. Чому величезні гойдалки в швидкості?
  3. Чи є кращий спосіб зробити подібні речі? Не зберігати мільйони файлів у каталозі; Я знаю, що це нерозумно, і це не сталося б на моєму годиннику. Перебіг проблеми та перегляд SF та SO пропонує безліч варіацій find, які не будуть значно швидшими, ніж мій підхід з кількох очевидних причин. Але чи має ідея delete-via-fsck ноги? Або щось інше цілком? Мені дуже хочеться почути нестандартне (або все-таки не добре відоме) мислення.
Дякую за прочитання невеликого роману; не соромтеся задавати питання, і я обов'язково відповім. Я також оновлю питання з кінцевою кількістю файлів і тим, як довго тривав сценарій видалення, коли я це мав.

Кінцевий вихід сценарію !:

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

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

Отже, три мільйони файлів видалено за трохи більше чотирьох годин.


1
rm (GNU coreutils) 8.4 має такий варіант: "-v, --верху поясніть, що робиться" . Він відобразить усі файли, які видаляються.
Крістіан Цюпіту

2
Насправді це був би акуратний спосіб зробити смужку прогресу: оскільки кожен файл буде тридцять сім символів (36 + a '\ n'), я можу легко написати парсер для цього, і оскільки printf () є дешево, а команда rm вже має ім'я завантаженого файлу, особливого штрафу за продуктивність немає. Схоже, що не для початку цілого шебангу, тому що я ніколи не можу змусити зробити щось подібне. Але це може спрацювати як планка прогресу протягом 10 000; можливо "". на кожні сто файлів?
BMDan

8
rm -rfv | pv -l >/dev/null. pv має бути доступним у сховищі EPEL .
Крістіан Цюпіту

5
ПВ надзвичайно приголомшливий. Я залишаю слід від ПВ-інсталяцій.
BMDan

У мене це саме таке питання було нещодавно. Дякую!
Річо

Відповіді:


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

Нарешті, FYI, що не згадується в цьому посиланні, полягає в тому, що дані = записування можуть бути величезною дією в безпеці, оскільки дані, на які вказує даний запис, можуть не мати даних, записаних там додатком, що означає, що може призвести до краху у старих, можливо, чутливих / приватних даних, що піддаються впливу. Тут не викликає занепокоєння, оскільки ми лише тимчасово вмикаємо його, але я хотів попередити всіх про цей застереження на випадок, якщо ви чи інші, хто наткнувся на цю пропозицію, не знали.
BMDan

фіксація: це дуже гладко! Дякуємо за вказівник.
BMDan

2
data=writebackвсе ще журнали метадані перед тим, як записати їх у основну файлову систему. Як я розумію, він просто не вимагає впорядкування між такими речами, як написання карти масштабу та запис даних у ці розширення. Можливо, є й інші обмеження для замовлення, які також розслаблюють, якщо ви бачили, що від цього виграєте перф. Звичайно, монтаж без журналу взагалі може бути ще більш високим. (Це може призвести до того, що зміни метаданих просто відбудуться в оперативній пам'яті, не потребуючи нічого на диску до завершення опції від’єднання).
Пітер Кордес

80

Хоча основною причиною цієї проблеми є продуктивність ext3 з мільйонами файлів, фактична першопричина цієї проблеми інша.

Коли каталог повинен бути вказаний, у каталозі викликається readdir (), який дає список файлів. readdir - це виклик posix, але реальний системний виклик Linux, який використовується тут, називається "getdents". Записи каталогу каталогів Getdents заповнюючи буфер із записами.

Проблема зводиться головним чином до того, що цей readdir () використовує фіксований буфер розміром 32Kb для отримання файлів. Оскільки каталог стає все більшим і більшим (розмір збільшується в міру додавання файлів), ext3 стає повільніше і повільніше для отримання записів, а розмір додаткового 32-фунтового буфера readdir 32Kb достатній лише для включення частини записів у каталог. Це призводить до того, що 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

Був якийсь здивований, це все ще працює так добре!


1
Дві незначні проблеми: одна, [256]мабуть , повинна бути [FILENAME_MAX], і дві, мій Linux (2.6.18 == CentOS 5.x), схоже, не включає запис d_type у drent (принаймні відповідно до getdents (2)).
BMDan

1
Не могли б ви детальніше розглянути питання про перестановку btree і чому видалення, щоб запобігти цьому? Я спробував Googling для цього, на жаль, безрезультатно.
овголовін

1
Тому що зараз мені здається, що якщо ми видаляємо порядок, ми змушуємо перебалансувати, коли ми видаляємо листя з одного боку, а з іншого - en.wikipedia.org/wiki/B-tree#Rebalancing_after_deletion
ovgolovin

1
Я сподіваюся, що я вас не заважаю цим питанням. Але все ж у мене виникло питання про видалення файлів у порядку stackoverflow.com/q/17955459/862380 , який, схоже, не отримує відповіді, яка пояснить проблему на прикладі, що буде зрозумілим для звичайних програмістів. Якщо у вас є час і ви відчуваєте себе таким, чи могли б ви заглянути? Можливо, ви могли б написати краще пояснення.
овголовін

2
Це дивовижний фрагмент коду. Це був єдиний інструмент, за яким я міг знайти список і видалити близько 11 000 000 (одинадцять мільйонів) файлів сеансів, які були накопичені в каталозі, ймовірно, за кілька років. Процес Плеска, який повинен був тримати їх під контролем, використовуючи пошук та інші хитрощі в інших відповідях тут, не зміг завершити виконання, тому файли просто нарощувались. Це данина двійковому дереву, яке файлова система використовує для зберігання каталогу, що сеанси змогли взагалі працювати - ви можете створити файл і отримати його без зволікань. Просто списки були непридатними.
Джейсон

31

Чи можна було б створити резервну копію всіх інших файлів з цієї файлової системи на тимчасове місце зберігання, переформатувати розділ, а потім відновити файли?


3
Насправді мені дуже подобається ця відповідь. Що стосується практичної справи, то в цьому випадку ні, але я б не подумав. Браво!
BMDan

Саме те, про що я теж думав. Це відповідь на питання 3. Ідеально, якщо ви запитаєте мене :)
Джошуа

12

Не існує обмеження для файлу файлів у ext3, лише обмеження в області файлової системи (я думаю, що існує обмеження на кількість підкаталогів).

У вас все ще можуть виникнути проблеми після видалення файлів.

Коли в каталозі є мільйони файлів, сам запис каталогу стає дуже великим. Вхід у каталог повинен бути сканований для кожної операції з видалення, і це потребує різного часу для кожного файлу, залежно від місця його входу. На жаль, навіть після видалення всіх файлів запис каталогу зберігає свій розмір. Тому подальші операції, які потребують сканування запису каталогу, займуть багато часу, навіть якщо каталог зараз порожній. Єдиний спосіб вирішити цю проблему - перейменувати каталог, створити нову зі старим іменем та перенести всі інші файли до нового. Потім видаліть перейменований.


Дійсно, я помітив саме таку поведінку після того, як все видалив. На щастя, ми вже вивели каталог з "лінії вогню", як би там не було, тому я міг просто запустити його.
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); 
?>

Я опублікував звіт про помилку щодо цієї проблеми з пошуку: http://savannah.gnu.org/bugs/?31961


Це мене врятувало !!
jestro

3

Нещодавно я стикався з подібною проблемою і не зміг отримати data=writebackпропозицію Ring0 працювати (можливо, через те, що файли знаходяться на моєму головному розділі). Під час дослідження обхідних шляхів я натрапив на це:

tune2fs -O ^has_journal <device>

Це повністю вимкне журнал, незалежно від dataопції, що надається mount. Я поєднав це з noatimeобсягом і dir_indexвстановив об'єм , і, здавалося, він працює досить добре. Видалення фактично завершено, не вбиваючи його, моя система залишалася реагуючою, і тепер це резервне копіювання та запуск (з журналом знову) без проблем.


Я збирався запропонувати встановити його як ext2 замість ext3, щоб уникнути реєстрації метаданих. Це слід зробити так само.
Пітер Кордес

3

Переконайтесь, що ви робите:

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

що також повинно трохи пришвидшити.


4
Хороший дзвінок, але це вже встановлено у часі, як я вже згадував у заголовку до питання. І нодиратім час зайвий; див. lwn.net/Articles/245002 .
BMDan

1
ppl повторити цю мантру "noatime, nodiratime, nodevatime, noreadingdocsatime"
poige

2

Це дуже повільна команда. Спробуйте:

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

rm -rf бігав півтора дня, і я, нарешті, вбив його, не знаючи ніколи, чи насправді щось досяг. Мені потрібна планка прогресу.
BMDan

4
Що стосується того, що rm дуже повільний, "time find. -Delete" на 30k файлах: 0m0.357s / 0m0.019s / 0m0.337s real / user / sys. "час (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))}'

Це був старий hdd 7200 об / хв , і, незважаючи на вузьке вузол IO і шипи процесора, старий веб-сервер продовжував своє обслуговування.


1

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

rm -rfмає бути близьким до оптимального для локальної файлової системи (NFS було б іншим). Але при мільйонах файлів, 36 байтів на ім’я файлу та 4 на inode (здогадка, не перевіряючи значення для ext3), це 40 * мільйонів, які слід зберігати в оперативній пам’яті лише для каталогу.

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

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


1
pubbs.net/201008/squid/… припускає, що це /proc/sys/fs/vfs_cache_pressureможе бути значенням для використання, але я не знаю, чи враховується сам каталог до кешу сторінки (тому що це таке) або кеш-пам'ять inode (тому що, незважаючи на те, що він не є inode, це метадані FS і вбудовані туди з цієї причини). Як я вже кажу, настройка VM Linux - це не моя область. Пограйте і подивіться, що допомагає.
Phil P

1

Очевидно, що тут немає яблук до яблук, але я встановив невеликий тест і зробив наступне:

Створено 100000 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 RPM; я думаю, EXT3). Ядро 2.6.27.


Цікаво, то, можливо, той факт, що файли створювалися протягом тривалого періоду часу, є актуальним? Але це не має значення; блок кеш-пам'яті повинен мати всі відповідні блоки метаданих в оперативній пам'яті. Може, це тому, що unlink (2) є транзакційним? Як ви вважаєте, чи може вимкнення журналу на тривалість rm є потенційним (хоча і дещо небезпечним) рішенням? Це не виглядає так, що ви можете просто вимкнути журнал повністю на змонтованій файловій системі без tune2fs / fsck / перезавантаження, що дещо перемагає мету.
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: $!");

EDIT: Я просто спробував свої сценарії 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 (); Я припускаю, що це скандир (). Якщо це так, це займе НАЗАД, щоб повернутися. Модифікація першої пропозиції, яка не заздалегідь прочитала всі записи dir, може мати деякі ноги; однак, у своєму нинішньому вигляді він також використає нечесну кількість процесора для того, щоб просто прочитати всі записи каталогів одночасно. Частина мети тут - розділити і перемогти; цей код принципово не відрізняється від 'rm -f * .png', незважаючи на проблеми з розширенням оболонки. Якщо це допомагає, у каталозі немає нічого, що я не хотів би видалити.
BMDan

Мені треба більше спробувати, як тільки я прийду на роботу. Я просто спробував створити 100 000 файлів в одному каталозі і знайти + xargs + rm комбінація зайняла 7,3 секунди, Perl + unlink (glob) ... комбінація закінчилася за 2,7 секунди. Пробував це пару разів, результат завжди був однаковий. На роботі я спробую це з більшою кількістю файлів.
Janne Pikkarainen

Я дізнався щось нове під час тестування цього. Принаймні, з ext3 та ext4, сам запис каталогу залишається величезним навіть після видалення всіх файлів звідти. Після декількох тестів мій / tmp / test каталог займав 15 Мб дискового простору. Чи є інший спосіб очистити це, крім видалення каталогу та відтворення його?
Janne Pikkarainen

2
Ні, вам потрібно відтворити його. Я стикаюсь із цим, коли маю справу з поштовою системою та папкою на одержувача та очищаєш після значних проблем: немає іншого способу, крім створення нового каталогу та перетасування каталогів, а потім виведення нуля старого. Таким чином, ви можете зменшити часове вікно, коли немає каталогу, але не усунути його.
Phil P

Зауважте, що glob () буде сортувати результати так само, як це робиться в глобальній оболонці, тому що у вас є лише 100k файлів, все легко підходить, і сортування відбувається швидко. Маючи значно більший каталог, ви хочете відкрити () / readdir () / closedir (), щоб уникнути сортування. [Я кажу звичайно для оболонки, оскільки zsh має глобальний модифікатор, щоб зробити порядок сортування несортованим, що корисно при роботі з великою кількістю файлів; *(oN)]
Phil P

1

З того, що я пам'ятаю, видалення inode у файлових системах ext - це O (n ^ 2), тому чим більше файлів ви видалите, тим швидше піде решта.

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


0

Ну, це не справжня відповідь, але ...

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


Здається, що для цього "в прямому ефірі" потрібен fsck на змонтованій файловій системі, що є ... тривожним. Є кращий спосіб?
BMDan

Файлова система повинна бути відключена перед перетворенням, тобто перед необхідними командами настройки.
marcoc

0

Добре, це було висвітлено різними способами в іншій частині теми, але я думав, що кину два мої центи. Винуватець виступу у вашому випадку, ймовірно, readdir. Ви повертаєтесь до списку файлів, які не обов'язково є жодним чином послідовним на диску, що спричиняє доступ до диска повсюди, коли ви від’єднуєтесь. Файлів досить мало, що операція від’єднання, ймовірно, не стрибає занадто багато, обнуляючи простір. Якщо ви читаєте, а потім сортуєте за зростанням inode, ви, мабуть, отримаєте кращі показники. Отже, перечитайте в оперативної пам’яті (сортувати за inode) -> від’єднати -> прибуток.

Тут я думаю, що Inode є приблизним наближенням, але, виходячи із випадку, який ви використовуєте, це може бути досить точним ...


1
Виправте мене, якщо я помиляюся, але від’єднання (2) не нульове значення inode, воно просто видаляє посилання на нього з каталогу. Мені подобається чуцпах такого підходу. Потрібно провести кілька випробувань у часі та побачити, чи це правда?
BMDan

0

Я, мабуть, вирвав компілятор С і зробив моральний еквівалент вашого сценарію. Тобто, скористайтеся, opendir(3)щоб отримати обробку каталогу, потім скористайтеся readdir(3)для отримання імені файлів, потім підберіть файли, коли я від’єдную їх, і раз у раз надрукуйте "% d файли видалені" (і, можливо, минув час або поточний штамп часу).

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


Він міг хоча б почати, змінивши вихідний код rm з coreutils .
Cristian Ciupitu

0

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

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


Що стосується видалення перших файлів: ls -Ur? Я впевнений, що ви завантажили б записи, а потім змінити їх; Я не вірю, що ls є достатньо розумним, щоб почати наприкінці списку записів, а потім повернути назад до початку. "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

Я вважаю, що це призводить до досить повільного видалення, яке має низький вплив на продуктивність сервера, як правило, приблизно за годину на мільйон файлів при "типовій" установці 10 000 IOPS.

Часто пройде кілька хвилин, перш ніж сканувати каталоги, сформувати початковий список файлів та видалити перший файл. Звідти і далі, a. відлунюється для кожного видаленого файлу.

Затримка, викликана відлунням до терміналу, виявилася достатньою затримкою для запобігання будь-якого значного навантаження під час видалення.


Тебе їдять живим, куляючи. Як щодо чогось подібного 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буває), тому у вас є щодо ефективного введення / виведення при запуску від того, з подальшим болючим, поза порядком rms які, ймовірно, не викликають багато вводу-виводу, але включають scandirповторне ходіння по каталогу (не спричиняючи введення-виведення, тому що це вже завантажено в кеш-блок; див. також vfs_cache_pressure). Якщо ви хочете уповільнити роботу, ioniceце варіант, але я, мабуть, використовую дробові секунди sleep.
BMDan

find /u*/app/*/diag -path '*/trace/*.tr' -execdir rm {} +запускав би по одній rmдиректорії, тож у вас менше витрат на процесор. Поки у вас є запаси часу для процесора, вимкнення IO диска шляхом форсування всього rmпроцесу для кожної unlinkроботи, я думаю, але це некрасиво. perl зі сном за відключення було б приємніше, якщо спати між 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

1
Ого. Я здогадуюсь, що це досить міцно потрапляє в табір "більше ніж один спосіб зняти шкіру". Серйозно, проте, з родом і uniq? У будь-якому разі "ls" сортує за замовчуванням, і я сподіваюся, що назви файлів будуть унікальними. : /
BMDan

-2

насправді це трохи краще, якщо оболонка, яку ви використовуєте, робить розширення командного рядка:

ls|cut -c -4|sort|uniq|awk '{ print "echo " $1 ";rm -rf " $1 "*"}' |sh
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.