0,0
рейтинг
25 сентября 2012 в 16:43

Разработка → Необычное переполнение жесткого диска или как удалить миллионы файлов из одной папки

Предисловие


Скорей всего, матерым системным администраторам статья будет не очень интересна. В первую очередь она ориентирована на новичков, а также на людей, которые столкнулись с подобной проблемой — необходимостью удалить огромное количество файлов из одной папки в ОС Linux (Debian в моем случае), а также с закончившимся местом на диске, когда df -h выдает что почти 30% свободно.

Начало


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

В первый же день отпуска мне звонят с жалобой — сайт недоступен. MySQL падает с ошибкой Error 28 «No space left on device».

Казалось бы, проблема банальна — кончилось место на диске. Правда, df -h показывает, что на диске имеется вполне достаточное количество свободного места, ну да я же в отпуске, разбираться лень — посоветовал им поискать на диске ненужные файлы (старые бекапы и т.д.) и их удалить. Удалили, вроде все заработало.

Прошла пара часов и проблема вернулась. Странно — свободное место на жестком диске за это время практически не уменьшилось. После беглого гугления обнаружился топик на serverfault, в котором говорится, что проблема может возникнуть также из-за того, что кончилось не место на диске, а айноды!

Ввожу в консоль df -i — и оказывается действительно, айноды у меня закончились.

Проблема


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

И нашел — оказалось, проблема была в папке с сессиями php.

Видимо, по какой-то причине сломался механизм автоочистки этой папки, что привело к тому, что в ней скопилось огромное количество файлов. Насколько огромное — сказать сложно, потому что никакие стандартные команды линукс, такие, как ls, find, rm и т.д. — с этой папкой не работают. Просто виснут, заодно подвешивая весь сервер. Могу только сказать, что сам файл директории стал весит около гигабайта, а также что файлов там точно более полумиллиона, потому что столько я оттуда уже удалил.

Решение


Решение очевидное — надо удалить все эти файлы сессий. При этом желательно, чтобы сервер продолжал работать в штатном режиме. Для начала я переименовал папку сессий, в которой лежит куча файлов, а вместо нее создал пустую — чтобы спокойно из старой (переименованной) удалять все файлы, и чтобы это не мешало созданию новых файлов сессий.

Также в крон добавил автоматическое удаление файлов сессий старше одного часа, чтобы проблема больше не повторилась.

И перешел к основной проблеме — очистке жесткого диска.

Попробовал решение «в лоб»:
rm -rf ./*

Сервер повис, ничего не удалилось

Попробовал известный способ для удаления большого числа файлов
find . -type f -exec rm -v {} \;


Ничего, сервер виснет, файлы не удаляются.

А теперь что самое интересное — файловый менеджер mc достаточно успешно справлялся с задачей удаления этих файлов! То есть, когда запускаешь удаление папки — файлы удаляются, mc не виснет. Удаление идет со скоростью примерно 5 000 файлов в минуту, правда при этом создается огромная нагрузка на жесткий диск, что приводит к неработоспособности сервера.

А хотелось бы, чтобы эти файлы постепенно удалялись в фоновом режиме, и не мешали нормальной работе сайта.

Собственно, решение опять нашлось в гугле — Olark делится способом, как он отобразил список из 8 миллионов файлов в 1 папке, используя системный вызов getdents

Здесь находится документация по функции getdents, а также пример кода, который ее использует.

Правда, этот пример мне не совсем подошел — даже если ставить большой размер буфера, как советует Olark в своем блоге, все равно сервер виснет при попытке прочитать всю папку разом.

Опытным путем подобрал размер буфера в 30 килобайт, который позволяет считать около 550 названий файлов из директории, при этом не подвешивая сервер и не создавая излишней нагрузки на диск. А также немного переписал код примера, чтобы вместо отображения имени файла он его удалял.

В итоге у меня получился такой код:
#define _GNU_SOURCE
#include <dirent.h>     /* Defines DT_* constants */
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/syscall.h>

#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while (0)

struct linux_dirent {
        long           d_ino;
        off_t          d_off;
        unsigned short d_reclen;
        char           d_name[];
        };

#define BUF_SIZE 1024*30

int
main(int argc, char *argv[])
{
    int fd, nread;
    char buf[BUF_SIZE];
    struct linux_dirent *d;
    int bpos;
    int deleted;
    char d_type;
    char temp[100];

    fd = open(argc > 1 ? argv[1] : ".", O_RDONLY | O_DIRECTORY);
    if (fd == -1)
            handle_error("open");
            
    deleted = 0;

    nread = syscall(SYS_getdents, fd, buf, BUF_SIZE);
    
    if (nread == -1)
            handle_error("getdents");

    if (nread != 0) {
        for (bpos = 0; bpos < nread;) {
            d = (struct linux_dirent *) (buf + bpos);

            d_type = *(buf + bpos + d->d_reclen - 1);

            if(d->d_ino && d->d_ino != 22332748 && d->d_ino != 22332761) { // тут я прописал inode самой директории и директории верхнего уровня, чтобы он не пытался удалять файлы "." и ".." - принимаю подсказки, как это сделать лучше
                sprintf(temp,"%s/%s", argv[1], (char *) d->d_name);
                remove(temp);
                deleted += 1;
            }

            bpos += d->d_reclen;
        }
    }
    
    printf("deleted %d\n", deleted);

    exit(EXIT_SUCCESS);
}


Код компиллируется обычным gcc
gcc listdir.c -o listdir


И просто запускается из командной строки:
./listdir mod-tmp2


Получившийся файл я поставил в крон и теперь у меня удаляется по 547 файлов в минуту, при этом нагрузка на сервер в пределах нормы — и я надеюсь, в течение недели-другой все файлы все-таки удалятся.

Выводы


  1. Если df -h показывает, что на жестком диске еще есть место — его может и не быть. Надо смотреть также df -i
  2. Не стоит надеяться на механизмы авто-очистки таких вещей, как файлы сессий — в какой-то момент они могут не сработать, и вы окажетесь у целой горы файлов, удалить которые — задача нетривиальная
  3. Стандартные команды линукс, такие как ls, rm, find и т.д. могут пасовать перед нестандартными ситуациями вроде миллионов файлов в одной папке. В таком случае надо использовать низкоуровневые системные вызовы
Искандер Гиниятуллин @rednaxi
карма
125,0
рейтинг 0,0
WEB-разработчик
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (142)

  • +3
    Хм.
    Я спокойно убивал 1M+ сессионных файлов из директории простеньким perl-скриптом на десяток с строчек.
    • +3
      10M+ файликов уже грохнуть становится проблематично.
    • +14
      В данном случае у меня получился не сложный код на С, в котором само удаление занимает тоже не больше десятка строчек.

      Думаю каждый решает задачу тем способом, который ему более близок и знаком, не вижу в данной ситуации особых преимуществ перла перед С
      • +1
        Имхо, на скриптовом языке эту задачу проще и быстрее решить, чем на С
        • +1
          Ключевое слово здесь «имхо»
      • +1
        Все решилось бы проще, если бы вы использовали find по другому:

        find /tmp/ -name 'sess_*' | while read a; do rm -f $a; done
        • 0
          Как бы не так. Когда больше сотни тысяч файлов, будет фэйл:

          bash: /usr/bin/find: Argument list too long

          Это и есть основная проблема.

          Я использую: for i in sess_*; do rm -r $i | echo $i; done
    • –3
      perl — не хардкорно!
      • –1
        Это С
        • 0
          Как я мог так ошибиться, это же C++ ;(
  • +2
    еще неплохо показывает себя прореживание файлов масками, если ls -l возвращает миллион файлов и тормозит то например ls -l abc* возвращает несколько тысяч файлов и не тормозит. тоже самое с rm.
    Правда тут надо знать как эти файлы именуются, что если не работает ls -l и ничего не известно о файлах внутри уже вопрос.
    • 0
      то например ls -l abc*

      Пробовал, тоже тормозит. find, locate, и т.д. — все виснет.

      • +1
        В вашем примере
        find. -type f -exec rm -v {} \;

        rm -rf будет запускаться для каждого файла, есть вариант удаления напрямую через find
        find /path/to/directory -type f -delete
        • +1
          Я знаю, я этот вариант использовал для настройки авто-удаления старых файлов сессий (find /search/path -mmin +60 -delete)

          Но в случае с сабжевой папкой (из которой я уже удалил почти миллион файлов и конца-края пока не видно) find вообще не работал, тупо сразу зависал и если быстро не вырубить — через некоторое время вешал сервер.
          • –3
            echo * | rm не пробовали?
            • 0
              А вы сами то пробовали?
               $ echo 1 | rm
              rm: missing operand
              Try `rm --help' for more information.
              
              • 0
                Это не говоря о том, что между rm * и echo * разницы то никакой нет. Списки всё равно раскрывать несчастному шеллу.
                • 0
                  А нет, вру, есть. echo ж builtin.
              • 0
                echo * | xargs rm
                Вот так работает. Только что 240003 файлов удалил так.
                система FreeBSD 8.2
                • 0
                  есть мнение что 240 003 и 24 000 000 две большие разницы…
                  • 0
                    И еще от файловой системы зависит — у меня например ext3, и пользователям, например, reiserfs мои проблемы вообще не понятны — у них ни айнодов нет, и папки такого размера гораздо лучше пережевываются.
                  • 0
                    Ну до 20 млнов не доходило. Было 7 млн. на 4.9 и я так удалял (lock файлы одной кривой проги)
                    Правда с определенных размеров тормозить начинает (долго ждать). Не знаю точно со скольки.
          • 0
            А как вы определили, что «find вообще не работал»? — сверяли количество файлов до и после?
            Я сталкивался с аналогичной ситуацией. find, конечно, основательно загружал систему, некоторые запущенные фоновые службы не отвечали по сети. Но find свою работу делал — файлы удалялись.
            • 0
              Да, просто в другом окне терминала мониторил df -i.

              Количество свободных inode не увеличивалось => find не работал
    • +6
      Звёздочка обрабатывается шеллом, который эту директорию читает. Последствия понятны?
      • 0
        Расскажите, пожалуйста, для особо одарённых, чем плоха обработка звёздочки шеллом?
        Там где ls * навернётся, for i in * нормально отработает. И там, и там звёздочку обработает шелл.
        • +1
          in * ещё до выполнения развернётся в список в 100500 миллионов имён файлов. С известными последствиями.
      • 0
        «масками» не значит только звездочкой, это просто например.
        Можно и? использовать и [0-9a-Z]. именно этими масками я и удалял миллионы файлов по десятку-сотне, а если в цикл засунуть рекурсивный то вообще автоматом отработает.
  • 0
    А если удалить саму папку и потом на ее месте создать такую же? Когда-то удалял таким образом папку с десятком тысяч файлов, правда это, конечно, меньше чем миллионы.
    • +1
      Я ее переименовал и на ее месте создал такую же пустую.

      Но переименованную надо удалить. А в линуксе нельзя просто «удалить папку», он ругается что она не пустая и надо сначала удалить содержимое, а потом папку.
      • –4
        А в линуксе нельзя просто «удалить папку», он ругается что она не пустая и надо сначала удалить содержимое, а потом папку.
        Это стандартное поведение rmdir (и очень правильное поведение); чтобы удалить папку рекурсивно со всем содержимом, нужно говорить rm -r.

        Кстати, забавный факт: в Linux (во всяком случае, на ext?fs) каталоги удаляются намного дольше по сравнению с FreeBSD (ufs).
        • +6
          Статью не читай, сразу отвечай…
          • 0
            В статье пробовали rm -rf ./* а в комментарии говорят rm -rf.

            Это разные вещи, т.к. при ./* программа сначала находит файлы, подходящие под маску (читает список файлов, матчит их по паттерну), и потом удаляет. В случае «rm -rf folder» же она рекурсивно итерирует по файлам, удаляя их, не читая списка и не производя матчинга.

            Дает ли эта разница в алгоритме реальную пользу — не знаю. Однако это точно не одно и то же.
            • 0
              rm-rf folder я тоже пробовал, тоже виснет
  • +13
    Недавно грохал примерно 2,5М файлов в директории. Помогло так:
    ls | xargs rm
    Весьма быстро.
    • +4
      Подтверждаю! Так же удадял 4 млн. Удаляло часов 10 на нагруженной машине, но никаких нагрузок при этом не выдавало.
      • +8
        +1 за xargs, удалил несколько миллионов, пока искал вариант как не завалить сервер родился — такой монстр — nice -n 19 ionice -c3 find /h
        ome/tmp/ -maxdepth 1 -type f -name sql* | xargs -n1 nice -n 19 ionice -c3 rm
        • +2
          Делал аналогично — сервер все равно вис. Правда, там был raid 5. В итоге скриптом делали мелкие слипы — удалялось несколько тысяч файлов в секунду.

          Первоначально попросили rackspace удалить эту директорию, они запустили rm в 8 процессов и сервер словил kernel panic :)
          • 0
            этот способ — «сын ошибок трудных», возможно я его нашел уже когда было ближе к вменяемому числу файлов, возможно около миллиона, до него пробовал удалять по-разному неизменно держа руку на ctrl+c и следя за la в top
  • +3
    Удаляете по-тихоньку файлы, а пользователи все думает, чего же их постоянно разлогинивает на вашем сайте :)
    Положите сессии в memcached, это можно прямо через php.ini и удаляйте файлы сколько влезет наздоровье.
    На диск потом возвращать их не надо, пусть лежат в memcached, старые сами удалятся.
    • 0
      Я потихоньку удаляю из старой папки (переименованной) — новая папка с сессиями работает в штатном режиме и из нее удаляются только старые сессии (которые больше часа не изменялись)
    • +4
      Чорт, увидел что переименовали папку :)
      В любом случае memcached клевый, посмотрите в его сторону, хранить сессии на диске — не клево
      • 0
        да, за подсказку про memcached спасибо — посмотрю
    • +1
      лучше уже тогда в redis держать сессии.
      • 0
        В плане хранения сессий redis ничем не лучше memcached. Зачем постоянно целиком снепшотить все сессии на диск, совсем не за чем.
        • 0
          а кто постоянно целиком снепшотит все сессии?
          • 0
            а это уже настройка редиса — как зададите, так и будет (в конфиге это настройка «save»).
            в любом случае это — лишнее для сессий.
    • –2
      На Highloade не советую этого делать. У меня например мемкеш не справлялся даже с простым кешированием запросов. Нужно было расширять количество портов для него.
      • 0
        Боюсь спросить что у вас за хайлоуд :)
        • –2
          ~250 запросов/сек
          /proc/sys/net/ipv4/ip_local_port_range
          

          По умолчанию:
          32768   61000
          

          Помогло только:
          16384 65534
          

          Иначе мемкеш через раз не мог подключиться к 127.0.0.1
          • 0
            Про 250 я что то ошибся. Сейчас посмотрел статистику MySQL. Она выросла в 2 раза. Значит и количество запросов к серверу выросло…

          • +1
            /proc/sys/net/ipv4/ip_local_port_range
            32768 61000
            Memcached — 920 Gets и 400 Sets в секунду, а полет нормальный, что мы делаем не так?
            • 0
              Можете показать ваш:
              netstat -atun | awk '{print $5}' | cut -d: -f1 | sed -e '/^$/d' |sort | uniq -c | sort -n
              


              У меня:
              6344 10.0.0.2
              42981 127.0.0.1
              


              Видимо проблема в том что у меня мемкеш коннект не закрывается. Хоть в скриптах везде его закрываю…
              • 0
                вот в том то и дело коллега, ищите кто виноват.

                $ netstat -atun | awk '{print $5}' | cut -d: -f1 | sed -e '/^$/d' |sort | uniq -c | sort -n | wc -l
                102 клиента, а максимальное кол-во открытых соединений на одного 22 штуки
                • 0
                  1810 клиентов… Максимум соединений 51 на одного (не считая мемкеш)
                  • 0
                    если у вас 500 req/s, то получается они у вас просто весят и ничего не делают. pconnect может?
              • +5
                видимо у вас PHP и не постоянное подключение. Либо используйте pconnect либо ускоряйте утилизацию эфемерных портов

                sudo sh -c "echo 5 > /proc/sys/net/ipv4/tcp_fin_timeout"  # освобождать через 5 секунд
                sudo sh -c "echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle"  # включить утилизацию
                
      • +4
        в случае если memcached расположен на той же машине где и клиент (судя по вашему 127.0.0.1 так и есть)
        то для подключения к memcached
        НУЖНО использовать unix сокеты, а никак не тсп.

  • 0
    А сессии-то починили, или крон пусть удаляет?
    • 0
      Вспомнилась недавняя статья про black box и white box сисадминов)
      Приведённое решение проблемы (крон) очень похоже на black box решение
    • +2
      пока крон удаляет, из отпуска вернусь починю
  • 0
    Вы не поробовали не создвать сессию по умолчанию?
    • +1
      Думаю, это можно поставить самым главным выводом из ситуации :)

      Просто сайт далеко не хайлоад (около 5 тысяч посещений/ 20 000 хитов в сутки), и достался «в наследство», поэтому пока что переделать на сайте механизм сессий не видится возможным.

      Пока что сессии нужны для нормальной работы сайта все время
  • +1
    php умеет хранить файлы сессий в БД или в иерархических каталогах /session/a/b/…

    Лень в документации искать, но должна быть такая опция
    • +2
      для тех, кто хочет знать, как — вам сюда
      • 0
        Правда, если использовать эту функцию, то вроде как встроенный в php garbage collector не будет сам удалять протухшие сессии.

        по крайней мере в php.ini так написано
      • 0
        И кстати у меня почему-то эта настройка вообще не работает.

        То есть если указать, например «3;/var/www/.../mod-tmp» то сессии просто перестают вообще сохраняться.
        Если просто "/var/www .." то все ОК
        • +1
          не знаю, не проверял. У меня сессии в redis, он сам все автоматически удаляет.
        • 0
          Там надо структуру каталогов вручную создавать или с помощью скрипта. php.net/manual/en/session.configuration.php#ini.session.save-path" тут все написано
  • 0
    А перенести папку можно? Скажем перенести ее на tmpfs и потом просто отмонтировать.
    • 0
      Переносить без копирования можно только в пределах одного тома. Но идея хороша.
    • 0
      Перенос на другой том это 2 действия — копирование по составным частям + удаление источника.
  • +3
    На серверах с ненастроенными почтарями и 100500 заданиями в кроне такая же проблема бывает.
  • НЛО прилетело и опубликовало эту надпись здесь
    • НЛО прилетело и опубликовало эту надпись здесь
      • 0
        собственно, так и есть — debian + isp manager
      • 0
        начало вашего поста
        Имеется в виду вес не содержимого низлежащих файлов, а вес самой папочки, т.е. листинг имен файлов весом 1Gb… около 13млн инод внутри…


        точно про меня :-)

        только у меня сейчас stat показывает 24 миллиона айнод (и полмиллиона я уже снес)
        • 0
          Так, похоже я неправильно понял вывод команды stat — там inode это просто номер идентификатор ноды.

          Как узнать сколько айнодов в папке?
          • НЛО прилетело и опубликовало эту надпись здесь
      • 0
        И правильно ли я понимаю, что для решения проблемы нужно изменить session.save_path, прописанный isp менеджером, в apache.conf — на дефолтный, и включить session.gc_probability=0?
        • 0
          Неа, в дебиане, насколько я помню, вырезали сие на этапе сборки.
          Есть свой скрипт для проверки сессий, но я пока использую сие чудо.
          for file in /var/www/*
          do
              [ -x /usr/lib/php5/maxlifetime ] && [ -d $file/data/mod-tmp ] && find $file/data/mod-tmp/ -type f -cmin +$(/usr/lib/php5/maxlifetime) -print0 | xargs -n
          200 -r -0 rm
          done
          

          Удобно тем, что я надо думать сколько юзеров ) сделано. Ну и собственно он для дефолтной установки путей +).
  • +1
    find . -type f -exec rm -v {} \;
    

    Для большого кол-ва файлов это крайне неудачный вариант, т.к. rm вызывается на каждый файл.

    Лучше использовать xargs или делать так:
    find . -type f -exec rm -v {} \+
    
    • НЛО прилетело и опубликовало эту надпись здесь
      • НЛО прилетело и опубликовало эту надпись здесь
  • 0
    Rsync достаточно быстро удаляет.
    • +1
      тоьлко сначала сканирует все файлы
  • +24
    Вообще-то скорость и склонность к зависанию определяются файловой системой, на которой папка лежит. Ну и немного ещё sysctl-ками.

    ls тупит т.к. пытается перед выводом прочесть весь список и отсортировать его, чтобы отключить такой режим надо использовать флаг -f. Ну а дальше для неспешного удаления достаточно затротлить вывод `ls -f` через какую-то тулзу (например через перловый скрипт) и через xargs организовать удаление (100 файлов в 10 секунд):
    ls -f . | perl -pe 'select(undef,undef,undef,0.1)' | xargs -n 100 rm
    • +1
      Есть утилита cpipe, она может задавать скорость передачи данных по pipe'у.
      • +4
        pv -L лучше
        cpipe какая-то совсем нестандартная утилита
        • 0
          Спасибо, буду знать.

          Алсо: обе в системе (бубунта/дебиан) не установлены, и обе есть в репозиториях, так что обе одинаково «нестандартные»
  • 0
    Сталкивался с подобной проблемойи повторял примерно те же шаги, что и автор, разьве что не пошел дальше mc
    • 0
      Я вот не пойму как вы и автор вообще открыли папку через mc? Или вы ее удаляли не открывая?
      • 0
        удаляли не открывая, если попробовать открыть сервер виснет
        • 0
          Поэтому и спрашиваю. MC ведь читает содержимое папки.
          • 0
            у меня висло при попытке удалить несколько лямов, но mc открывал папку и даже частями можно было выделять и удалять, только так очень долго. У вас похоже там файлов было даже больше чем 8 лямов, если все иноды закончились.
        • 0
          да, только так…
    • 0
      mc рулит?! :)
  • +12
    rm -rfd путь в дебиане и убунте удаляет миллионы файлов без построения списка файлов…
    • +1
      Что за ключик -d? man rm про него не знает в ubuntu 12.04
      • +2
        -d, --directory
        

        Удалять каталоги с помощью системного вызова unlink(2) вместо rmdir(2), и не требовать, чтобы каталог был пуст перед его разлинковкой. Работает только если у вас есть соответствующие привилегии. Поскольку разлинковка (разрыв связи) каталога приводит к тому, что все файлы в удаленном каталоге теряют ссылочную целостность, то будет благоразумно после этой операции проверить файловую систему командой fsck(8).
  • +2
    Справедливости ради стоит отметить, что проблема нехватки инодов характерна для «олдскульных» файловых систем, где inode table не динамическая, а создается при создании файлухи (фиксированного размера). Во динамических файлухах (например, ZFS) об инодах можно не волноваться. :-)
    • 0
      ext4?
  • +3
    > чтобы он не пытался удалять файлы "." и ".."
    Вспомнил про такой себе клон nc PIE Commander, написанный на асме, у которого был баг:
    Нажатие клавиши F8 (удаление) в случае, если курсор стоял на [..] (переход в родительский каталог), приводило к удалению всего родительского каталога, включая каталог, где находился пользователь. В случае, если родительским оказывался корневой каталог, то происходило удаление вообще всех файлов на диске.
    .
  • +2
    >Попробовал решение «в лоб»: rm -rf ./*
    плохая мысль. Оболочка будет пытаться развернуть маску и полезет читать содержимое директории, целиком за раз. Даже если прочитает, то команда все равно будет слишком длинной и не выполнится.

    >правда при этом создается огромная нагрузка на жесткий диск, что приводит к неработоспособности сервера
    ionice ваш друг, и можно было не городить велосипед
  • 0
    Если у вас файловая система XFS, то рекомендую временно смонтировать её с опцией nobarrier — скорость удаления файлов возрастёт на порядок. После удаления файлов, не забудьте перемонтировать без опции nobarrier.
  • +2
    rm -rf ./*

    А вы задумывались, что происходит при выполнении команды? Виснуть должна не rm, а шелл ещё на шаге подстановки. rm и прочие утилиты даже не должны знать о существовании glob. Напечатав звёздочку, вы заставили шелл генерировать огромную строку из имён файлов, разделённых пробелами. А потом, если даже генерация удалась бы, эту огромную строку rm бы парсил (напишите echo * для доказательства).

    К чему я: написали бы cd .. && rm -r dirname, и всё бы прошло нормально.
    • 0
      + ionice, видимо без него все равно было бы тяжко на этапе чтения
  • –4
    А что мешало создать раздел tmpfs, смонтировать его на каталог, где хранятся сессии, и жить с ним?

    Очистка сессий — процесс автоматический, раздуваться папка не должна, но даже если и случилось такое, просто перегружаем машину (или еще как-то инициализируем этот tmpfs-раздел) и все.

    Скорость работы хорошая, ничего лишнего менять в системе не надо, такая ФС с точки зрения любого ПО.

    Хранить сессии в чем-то более подходящем, вроде memcache, конечно, тоже неплохой выбор, разве что разобраться нужно чуть побольше.
  • 0
    Интересно было бы узнать у автора, что за файловая система была? Как-то игрался с файловой системой ext3 и по совету gentoo-wiki сделал для хранения дерева portage ФС с маленьким размером кластеров. Производительность выросла в разы из-за оптимального хранения большого числа мелких файлов.

    В Вашем случае, если движок сессий нестандартный, лучше использовать «hashed directory structure»

    пример и чтиво
    • 0
      Файловая система ext3, досталась 'по наследству' с остальным хозяйством. В следующем месяце планируем переезд на новый сервер, заодно фс поставим посовременнее и вообще постараемся решить еще значительную часть проблем ( в т.ч, сессии по человечески сделаем)

      А пока мучаемся с тем, что есть.
      • 0
        А, с ext3 была проблема с удалением, висла нещадно. Это да. Можно было заметить на больших файлах или когда файлов много.
        • 0
          С ext4 таких проблем нет.
          • 0
            у меня были проблемы на зеркалом ext4 c небыстрым рейд контроллером
  • –1
    Спасибо, интересно… Неужто кроме этого низкоуровнего сисколла нет возможности считывать директорию итеративно а не целиком?
  • 0
    Я пропустил или в тексте нет упоминания файловой системы? Скажите, пожалуйста, что за система.
  • 0
    уже задавали такой вопрос habrahabr.ru/qa/16200/ автору помог find . -delete
  • +4
    Одному хостеру об этом способе узнать бы пораньше www.peeep.us/ffbe12c2
    • +1
      Очень захватывающее чтиво, без шуток!
      • 0
        Нас другой хостер просто автоматически предупредил что так как файлов более миллиона то бэкап отключается.
        • 0
          В наше время вообще не понимаю нафига всякие OpenVZ и прочие, если можно арендовать настоящую виртуаку. А бэкапиться эта виртуалка должна одним файлом виртуального диска, без заморочек что там внутри.
          • 0
            Не сильно знаток я в этом, но думаю все зависит от железа тоже (какие там инструкции виртуализации поддерживаются и т.д.)
            Мне например достаточно выгодно сидеть на BurstNET в качестве девелоперского VPS с тем что они мне дают по ресурсам, а работает он через OpenVZ
            Правда миллион файлов не пытался сделать в 1 директории и желания нет ))
          • 0
            OpenVZ — это по сути контейнер. Помимо массы технических плюсов есть экономический и для хостера и для клиента. Ситуация с триллионом файлов в контейнере — идиотизм. У openvz есть лимиты и барьеры для этого параметра.
  • 0
    Совет на будущее: размещайте любые директории, где возможно возникновение огромного числа мелких файлов (такие как сессии php) на reiserfs — я так делаю на двух серверах и проблемы с закончившимися нодами не возникает, а удаление выполняется вполне быстро.
  • +2
    ls -U работает по несортированному содержимому директории и прекрасно листит каталоги с миллионами файлов.

    find -delete делает то же самое и удаляет без вызова rm на каждый чих, без переполнения командной строки того же rm.
  • 0
    Проблема с большим числом файлов в каталоге хорошо известна, но дело в том, что возникает она не так часто. Большим считается не только миллион файлов, но даже 10.000 или может 5000. Не работают или очень медленно работают команды rm, cp, mv, du, find. Проблема мне, кажется не во взаимодействии с шеллом, а в алгоритме этих программ, не рассчитанных на большое число файлов. Как вероятно работает rm? Читает файл оглавления папки, удаляет файл, изменяет оглавление папки, записывает его как файл (если это не сделать и вдруг пропадет питание, то это будет плохо для файловой системы). Если файлов N, то эта операция занимает O(N^2) и может выполняться при больших N довольно долго, столько сколько требуется времени чтобы 100.000 раз прочитать и записать на диск файл в 50 Мб? Команда ls работает быстро потому что она только один раз читает файл оглавления папки, не важно сортирует она его или нет, это не займет пару часов. Также полезно хранить файлы не в одной папке, а в нескольких. Если мы разбили одну папку на m, то затраты времени составят m * O((N/m)^2) = O(N^2)/m, т.е. уменьшаться в m раз.
    Еще важен психологический момент — когда скрипт запущен и ничего не выводит, кажется что он завис, хотя как-то работа идет, даже если удаление занимает несколько часов или дней.
    Вывод тут простой — файл оглавления надо считать так или иначе, а копирование, удаление, подсчет размеров надо делать перловыми модулями File. Возможно есть утилита super-purge, но она не решит проблемы с cp, mv, find, du.
    Ответы на все эти вопросы легко получить экспериментальным образом — сделать тестовую конфигурацию и определить что работает, а что нет. Такая статься была бы полезна, триллион файлов не нужен, по 10^3, 10^4, 10^5, 10^6, 10^7 все будет понятно.
  • 0
    if(d->d_ino && d->d_ino != 22332748 && d->d_ino != 22332761) { // тут я прописал inode самой директории и директории верхнего уровня, чтобы он не пытался удалять файлы "." и ".." - принимаю подсказки, как это сделать лучше

    а зачем именно иноды, если можно по имени (как это везде, собственно, и делают)
    
    if(strcmp(d->d_name, ".") && strcmp(d->d_name, ".."))
    

    Кстати, странно, что директория верхнего уровня ".." у вас магическое число, отличное от 2…
  • 0
    Интересно понять что это за ограничения: ОС или недостатки алгоритмов. Надо будет попробовать на досуге чудный erlang, со своими легковесными процессами. Не исключено, что тормоза начинаются на стадии чтения каталога. Если это так, то возникает не менее увлекательная задача параллельного чтения каталога ОС.
    • 0
      да, тормоза начинаются на этапе чтения директории.

      По поводу эрланга к сожалению не скажу — не сталкивался, но в топике приведен код на С который как раз позволяет используя низкоуровневый сискол прочитать директорию такого размера
      • 0
        надо вообще посмотреть через strace и valgrind на что уходит больше всего время. Либ искать функцию, читающую каталог сегментами/порциями либо допускающие «пагинацию» на чтение каталога. Как вариант спросить у ОС (эта информация всегда есть) сколько в этом каталоге элементов, если превышает некоторое магическое число — использовать другой алгоритм. Думаю в АПИ есть функция позволяющая читать заданное число элементов не более указанного. Если есть возможность читать порциями каталог, то эти части скармливать эрлангу по 500-1000 параллельных потоков удаления, за один такт. По умолчанию 32000 процессов можно запустить, но с учетом разных других процессов лучше исходить из достаточного минимума. Итого понадобится 1000 тактов по 1000 процессов в каждом. Если все хорошо это можно увеличить в 32 раза.
      • 0
        Кстати, я вот чего подумал — в Linux API ведь для обхода директории используется конструкция вида

        dp = opendir("/my/dir");
        while( (dirent = readdir(dp)) != NULL ) {
            // do smth with directory entry
        }
        


        Т.е. читается как раз один элемент директории за один оборот цикла. Вы readdir не пробовали использовать?
        Думаю, проблемы с ls или find были из за того, что они сами пытались собрать все элементы директории во внутренний буфер (чтобы отсортировать например)
        • 0
          Если верить автору поста, от которого я узнал про getdents, то он пишет, что readdir за раз читает 32 тысячи элементов каталога.
          However readdir() only reads 32K of directory entries at a time

          Хотя может я что-то не так понял.

          В моем случае приемлемая нагрузка на диск достигается если я читаю за раз около 900 элементов. Если больше — появляются заметные тормоза.

          Сейчас у меня стоит удаление в кроне, которое сносит каждые 2 минуты по 900 элементов — то есть около 1,6 миллионов в сутки. В принципе, этого хватает, чтобы не спеша в фоновом режиме чистить диск без заметной нагрузки на сервер.
    • 0
      Erlang для этого использовать? Шутите?
      В Erlang для получения списка файлов в директории есть только функция www.erlang.org/doc/man/file.html#list_dir-1, которая возвращает сразу полный список ВСЕХ файлов в директории.
      • 0
        Erlang для этого использовать? Шутите?
        в какой-то степени да)) Но у эрланга прекрасная подсистема для включения модулей nif — если что-то не хватает можно дописать всегда. Так что не обязательно через эту функцию — хотя надо посмотреть ее исходники.
        • +1
          Вот её исходники github.com/erlang/otp/blob/OTP_R15B02/erts/emulator/drivers/unix/unix_efile.c#L586 вызывается вот из этого драйвера github.com/erlang/otp/blob/OTP_R15B02/erts/emulator/drivers/common/efile_drv.c#L2643

          Вот Erlang обертка для драйвера github.com/erlang/otp/blob/OTP_R15B02/erts/preloaded/src/prim_file.erl#L893, вот раскодировщик ответа от драйвера github.com/erlang/otp/blob/OTP_R15B02/erts/preloaded/src/prim_file.erl#L1322
          Ну и в этом файле непосредственно API-функция github.com/erlang/otp/blob/OTP_R15B02/lib/kernel/src/file.erl, которая вызывает 2 предыдущие через file_server github.com/erlang/otp/blob/OTP_R15B02/lib/kernel/src/file_server.erl#L137

          Вы думаете, это будет работать быстрее, чем такой примерно код на C?
          dp = opendir("/my/dir");
          while( (dirent = readdir(dp)) != NULL ) {
              // do smth with directory entry TODO: skip subdirectories
              unlink(dirent.d_name);
          }
          
          • 0
            спасибо за ссылки — посмотрю на досуге.

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

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

            Но вообще изначальная идея была такова пробы на эрланге (хотя данную схему можно реализовать на любом языке):

            Так как предположительно, что все виснет еще при чтении каталога, то потребуется функциональность чтения каталога порциями.

            исходные данные: есть две функции — первая возвращает число элементов в папке вторая читает порциями по интерфейсу: ls(offset, count), еще понадобится функция возвращающее максимальное число процессов в ОС на данный момент.
            Определяем число файлов.
            Читаем порцию имен через функцию: ls(offset, count)
            По каждому имени запускаем процесс эрланга на удаление.
            Если максимальное число ОС-процессов не исчерпано, то идем на начало.

            Другой вариант это схемы:
            Определяем число файлов.
            Читаем порцию имен через функцию: ls(offset, count)
            Формируем список-буфер для запуска процессов удаления, но не удаляем
            Когда весь список сформирован, запускаем порциями (с проверкой пределов числа процессов в ОС и эрланге) паралельные процессы удаления
            • 0
              У меня сложилось впечатление, что вы не понимаете то, о чем пишете.

              Скажу сразу, что драйвера ОС в эрланге будут работать медленно.
              Что вы имеете в виду под драйверами ОС в эрланге? И почему они должны быть медленными?

              максимальное число процессов в ОС на данный момент.
              При чем здесь OS процессы? beam запускает потоки а не процессы. И, обычно, запускает их по числу ядер. Или вы собираетесь запускать OS процессы через open_port/2?

              вторая читает порциями по интерфейсу: ls(offset, count)
              В Erlang нет такой функции. Если очень хочется — нужно писать порт/драйвер/nif. Но зачем тогда Erlang?

              В конце концов, все операции с ФС в Erlang идут через один единственный процесс file_server_2, так что сколько бы вы Erlang процессов не запустили, все они будут работать с ФС последовательно (кроме операций над io_device).

              В конце концов, я не понимаю зачем вам понадобилась параллельность для итерации по каталогу. Жесткий диск то один, так что, при правильном использовании, процесс удаления должен упереться именно в производительность диска, т.к. никаких особенных вычислений там не происходит (см в статье абзац про Midnight Commander).
              Почему изначально возникли проблемы — очевидно потому, что rm пытается сперва загрузить полный список файлов в память и уже потом начать удаление. Мой псевдокод на C полный список не загружает, а считывает одно имя файла за раз и сразу его удаляет.
  • 0
    У меня сложилось впечатление, что вы не понимаете то, о чем пишете.
    Ок. Давайте для начала договоримся в терминологии. Если Вы концептуально понимаете разницу между потоками и легковесными эрланговскими процессами, то у Вас не должно возникать таких ощущений.

    Эрланг это далеко не джава и пропитан насквозь легковесными процессами, которые с очень большими оговорками можно называть нитями или тредами. Большая часть эрланга написана на нем самом. Во избежании путаницы, термины: поток, нити, треды не употребляется — все говорят процессы. Разумеется это те легковесные внутренние процессы эрланга. Или вы имеете в виду «внешние потоки» ерланга, взаимодействующие с CPU? Не совсем понятно, что понимается под потоками.

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

    собираетесь запускать OS процессы через open_port/2?
    С чего вы так решили? И про какой порт идет речь (там есть несколько разновидностей и вариантов конфигураций)?
    Если вы про функцию list_dir, которая вызывает port — надо смотреть подробно, что там реально происходит. Или вы подумали, что open_port тупо в итоге вызывает команду типа os:cmd(«ls»)?

    Что вы имеете в виду под драйверами ОС в эрланге? И почему они должны быть медленными?
    Драйвера, которые частично написаны и на Ц и на эрланге — являющиеся в свою очередь слоем между эрлангом и ос. Почему медленными? Думаете процесс ОС дешевле чем процесс эрланга? Если бы там не было взаимодействия с ОС — это было действительно очень быстро.

    Если очень хочется — нужно писать порт/драйвер/nif. Но зачем тогда Erlang?
    Действительно. Зачем? -Когда есть низкоуровневый и понятный C — делай что и как хочешь. Параллельность? Запросто… Безопасность состояний? Это мигом.

    сколько бы вы Erlang процессов не запустили, все они будут работать с ФС последовательно
    Этого вполне достаточно для ФС. А надо ли быстрее тут?

    • 0
      Ок, по порядку. Насчет процессов/потоков.зеленых процессов…
      Вы написали: " можно упереться в предел процессов ОС".
      Я ответил: «beam запускает потоки а не процессы. И, обычно, запускает их по числу ядер.»
      Что я этим хотел сказать: «я не понимаю как вы собрались упереться в предел процессов ОС, если виртуальная машина Erlang для своей работы запускает потоки ОС по числу ядер примерно, и в этих потоках ОС уже исполняет Erlang код, запускает зеленые Erlang потоки, работают планировщики и т.п.
      Чтобы упереться в количество процессов ОС, нужно эти процессы запускать самому. Для запуска процесса OS обычно используют open_port/2, но ИМХО, к задаче удаления файлов это не имеет отношения»

      Дальше.
      Весь миллион заданий будет назначен на исполнение, без опаски, что из-за этого все зависнет.

      Минимальный оверхед на создание потока в Erlang составляет 309 машинных слов. Одно слово это 4 байта на x32 и 8байт на x64. Если вы запустите миллион тасков, то сам факт создания процессов у вас займет 309 * 4 * 10^6 = 1.15 Gb чистого оверхеда (2.3Gb на x64). Да, процессы будут ждать своей очереди на исполнение, но в чем смысл? Ведь упираемся всё равно в диск.

      Действительно. Зачем? -Когда есть низкоуровневый и понятный C
      Вот и я не понимаю, зачем? Зачем для удаления файлов из каталога писать nif-ы (на C, кстати), запускать миллионы Erlang процессов, отжирать гигабайты RAM. если можно написать простейшую линейную программу на C в 30 строчек?

      P.S.: заходите на erlang@conference.jabber.ru лучше.
  • 0
    Чтобы упереться в количество процессов ОС, нужно эти процессы запускать самому.
    Не отношу себя к большим экспертам по процессам ОС, но знаю, что системные лимиты не безграничны. Мною высказано предположение, что могут возникнуть ограничения. Обычно на практике так и происходит. Надо детально смотреть как расходуются системные ресурсы при удалении и т.п. Чтобы не гадать — предположил, что реальные условия не идеальны. Хотя ради эксперимента это возможно излишняя паранойя — но запас прочности даже при излишних предостережениях не помешает. Гораздо приятнее осознавать что система работает с достаточным запасом.

    Сейчас нет под рукой виртуальной машины — захотелось реально покрутить ерланг на миллионе файлов (ну или поменьше — сколько хватит ресурсов). Данная задача меня интересует несколько с другой стороны — не столько удаление, сколько произвольная обработка огромного числа файлов с помощью эрланга. При этом как можно больше задействовав системных ресурсов включая параллельность вычислений средствами языка. Отсюда собственно и растут мои опасения по поводу пределов ОС. Задача удаления очень подходит для тестового полигона.

    я не понимаю как вы собрались упереться в предел процессов ОС, если виртуальная машина Erlang для своей работы запускает потоки ОС
    Задача не стоит достигнуть пределов, но хотелось бы иметь определенное представление как ведет себя ерланг при больших нагрузках связанных с ОС-операциями такими как ФС. В итоге будет видно что при пиковых нагрузках, взаимодействуя с ОС либо ерланг подвесит систему либо будет работать как ни в чем не бывало.

    Для запуска процесса OS обычно используют open_port/2
    Самое эффективное конечно «нифы». Безусловно, если «безопасно нагружать» эрланг — то нужно минимизировать потребление ОС-ресурсов. Хотя порты тоже имеют место — очень удобно, но дорого. В случае использования портов, ресурсы будут быстрее потребляться — можно попытаться балансировать нагрузкой по загрузке портов.

    P.S.: заходите на erlang@conference.jabber.ru лучше.
    Благодарю за приглашение — зайду.
  • 0
    Всё сложно…
    Поиск:
    find /var/www/ -xdev -type f | cut -d '/' -f 2 | sort | uniq -c | sort -n
    

    Удаление:
    perl -e '$dir="/var/www/tmp/";opendir(D,$dir)||die("Err\n");while($f=readdir(D)){unlink($dir.$f)}' 
    

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