Это совершенно бесполезный, ненужный в практическом применении, но забавный небольшой пост про директории в *nix системах. Пятница же.

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

Для понимания поста немного тезисов:

  • всё есть файл. директория — это тоже файл
  • в айноде хранятся мета-данные от файла, но имя файла там не хранится
  • имя файла хранится в данных директории
  • размер директории, тот самый который показывается в ls и по умолчанию 4Кб, зависит от количества файлов в директории и длины их имен
  • очевидно, что чем больше файлов, тем больше размер директории

А теперь интересное: создаём директорию с миллионом файлов, проверяем размер директории, а потом вытираем все файлы и смотрим на размер директории.

$ mkdir niceDir && cd niceDir
# в зависимости от скорости носителя, следующая команда может занять 2-10 минут
$ for ((i=1;i<133700;i++)); do touch long_long_looong_man_sakeru_$i ; done
$ ls -lhd .
drwxr-xr-x 2 user user 8.1M Aug 2 13:37 .
$ find . -type f -delete
$ ls -l
total 0
$ ls -lhd .
drwxr-xr-x 2 user user 8.1M Aug  2 13:37 .

Как можно заметить, размер директории не поменялся, хотя казалось бы :)

Починить размер директории (без удаления) можно только с помощью fsck (и опции -D) в отмонтированном состоянии.

А вот когда я пошёл искать, почему это так, то оказалось, что 10 лет назад такое поведение уже обсуждали в lkml. И по мнению разработчиков исправление просто не стоит вложенных усилий.

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


  1. osipov_dv
    02.08.2019 15:17
    -2

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


    1. sledopit Автор
      02.08.2019 15:20

      «Миллион» я больше употребил в переносном значении «очень много». Смысл от этого не сильно меняется. du показывает те же 8.1М, что и ls -ld.


      1. rboots
        02.08.2019 18:03

        Честный миллион файлов сгенерировался у меня за 11 минут 51 секунду и занял 31 мегабайт.


        1. maxzhurkin
          02.08.2019 21:41

          А удалялся как долго?


          1. homm
            05.08.2019 19:46

            Если создавать не из баша, а хотя бы из питона, то создается за ?30 секунд, удаляется за ?17. ext4


        1. tmin10
          02.08.2019 22:03
          +6

          Можно просто выкачать какой-то пакетик из npm и вуаля — миллионы js файликов с функциями типа left-pad.


  1. HardWrMan
    02.08.2019 15:26

    Дык вроде при удалении сам элемент директории не стирается, а просто помечается удалённым. И это на той же xFAT позволяло делать UNDELETE. С другой стороны действительно, накладные расходы на автоматический SQUEEZE не стоят потраченного объема. Вот с количеством инод бывает беда, если вовремя не почистить некоторые папки.


    1. Sly_tom_cat
      02.08.2019 18:15

      А вы не используйте всякие ext4 с внутренней архитектурой унаследованной от древней как гавно мамонта ext2.
      Пользуйтесь нормальными ФС где indode аллокируются динамически, а не так как у extX приколочены гвоздями в группам распределения и выделяются при первичном форматировании и больше — ни ни.


      1. HardWrMan
        02.08.2019 19:30

        Спасибо, и что из эдакого посоветуете для FreeBSD?


        1. Sly_tom_cat
          02.08.2019 19:41
          +1

          ZFS же


          1. HardWrMan
            02.08.2019 22:10

            Он там работает «искаропки» начиная с 8 версии. Но правда не совсем помню, на каких версиях BSD я отлавливал нехватку инод (прошел путь с 6 по 11.2 в данный момент).


            1. click0
              03.08.2019 05:45

              Нехватка inode была на UFS/UFS2.
              С Zfs, как и со многими FS другая проблема — при заполнении раздела на >95% происходит деградация производительности.


      1. Sly_tom_cat
        02.08.2019 19:48

        Минусатора прошу пояснить: что именно не понравилось в моем сообщении, или может я где-то неправду сказал?


        1. maxzhurkin
          02.08.2019 21:46
          +4

          Минусатор, возможно, не получил уведомления о вашем комментарии, а если бы и получил, то наверняка, не передумал бы, а вот ваше «я Д'Артаньян, а вы деритесь в открытую, а не как трус» мало кто способен оценить в вашу пользу.

          P.S. Не благодарите


      1. lagranzh
        03.08.2019 01:58

        какие фс порекомендуете (в контексте дебиана)?


        1. click0
          03.08.2019 05:53

          EXT4 и ZFS, если осилите установку или миграцию.


    1. engine9
      03.08.2019 00:30

      Посоветуйте правильных же!


  1. Pyhesty
    02.08.2019 15:30

    о! как раз хотел узнать об ограничении на количество файлов в директории в разных FAT? а для fat32 и ntfs, ограничения какие-то накладывают на кол-во файлов в каталоге?


    1. black_semargl
      02.08.2019 16:07

      В fat16 корневая директория фиксированного размера. Остальные — просто файлы со специальным атрибутом, произвольного размера
      В fat32 корень вообще из одного элемента, указывающего на файл корневой директории.
      В ntfs примерно так же, только «базовых файлов» там несколько — и корневая директория, и таблица занятости, и т.д. И мелкие файлы могут быть размещены прямо как поле данных в каталоге, рядом с именем.


      1. HardWrMan
        02.08.2019 16:41

        В FAT12 и FAT16 директория в отдельном пространстве и её максимальный размер фиксирован. Он описан в загрузочном секторе FAT.

        В FAT32 корневая директория это файл без дескриптора (т.е. находится в области данных), первый кластер которого №=2. Т.е., если одного кластера не хватает, система его расширит следующим свободным кластером в таблице FAT (не обязательно соседним). Т.е., теоретически можно создать такой большой корневой каталог, что все кластеры будут принадлежать ему. Записать туда, например, метки диска.


        1. black_semargl
          02.08.2019 19:43

          А вот и нет — первый кластер корневой директории прописан в первом секторе по смещению 0х2С.
          После форматирования это действительно 2, но вот после дефрагментации может быть по-разному


          1. HardWrMan
            02.08.2019 22:03
            +1

            А что, дефрагментаторы любят копаться в бутсекторах?


            1. black_semargl
              03.08.2019 16:37

              Некоторые — да. Как собственно дефрагментировать эту корневую директорию, кроме как переместив её на свободное место?


              1. HardWrMan
                03.08.2019 16:52

                Дедовским способом: выдвинуть соседние кластеры на свободное место а затем передвинуть хвосты директории на освобождённое место. Точнее, именно так и работают большинство дефрагментаторов. Например, Perfect Disk при Boot Time Defrag может двигать своп, файл гибернации и MFT (под виндами на NTFS). При этом, он и своп и гибернацию пытается положить после копии MFT в середине диска, сам же MFT просто собирает в единый кусок никуда не двигая его начало. И это логично. Поэтому я и предполагал, что дефрагментатор работает только с областью данных а данные в бутсекторе для него константа. Ведь при изменении данных бутсектора том придется переподключать, чтобы система синхронизировалась к изменениям.


    1. geher
      02.08.2019 16:44

      В FAT установлен предел 65535 запией. Поичем первые две заняты точкой (ссылка на себя) и двумя точками (ссылка наверх).
      Если используются длинные имена (в формате, отличном от 8.3), то будет еще меньше (каждое длинное имя "крадет" несколько записей).


      .


    1. DistortNeo
      02.08.2019 17:00

      В интернетах пишут, что для NTFS — 2^32-1 файлов, а для FAT32 размер директории ограничен реализацией — 2 мегабайта, что соответствует 64K записям формата 8.3. В случае использования длинных имён инфа от имени размещается в этих записях, по 13 символов на запись. В случае использования длинных имён может использоваться до 20 записей на один файл +1 вхождение для формата 8.3, что в итоге приводит к 3120 файлам с именами 255-байтной длины.


      1. HardWrMan
        02.08.2019 17:28

        Ну я в середине нулевых телепортом на ХР установленной на FAT32 выкачивал сайт один ветвистый. Телепорт положил все файлы в одну папку. Количество получилось около 18к. Имена были как на английском, так и на кирилице, почти все файлы LFN, разве что длина имени не 255 символов. ХР эту папку переваривала нормально, 98ая вешалась гарантировано. Причем без синего экрана, просто впадала в ступор.

        PS Путь и имя почти никогда не достигают 255 символов. Я часто попадал в ограничение примерно на 224 +- символов в полном пути+имя.


        1. VolCh
          03.08.2019 14:41

          В случае какого-нибудь докера или chroot плюс node_modules :) вполне могут привести к переполнению 255 символов полного пути. И изнутри виртуальной ФС далеко не всегда легко понять, что происходит.


    1. iig
      03.08.2019 08:50

      Кроме того, в fat директория это линейный список; время доступа к файлу очень сильно зависит от его размера. 65533 файлов в директории — чистое зло.


  1. berez
    02.08.2019 16:01

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

    И кстати, линукс тут неуиноуат — в винде точно такая же проблема.

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


    1. Sly_tom_cat
      02.08.2019 18:25

      Btrfs:
      $ mkdir niceDir && cd niceDir
      $ ls -lhd.
      drwxrwxr-x 1 stc stc 0 авг 2 18:22.
      $ for ((i=1;i<133700;i++)); do touch long_long_looong_man_sakeru_$i; done
      $ ls -lhd.
      drwxrwxr-x 1 stc stc 8,5M авг 2 18:21.
      $ find. -type f -delete
      $ ls -l
      total 0
      $ ls -lhd.
      drwxrwxr-x 1 stc stc 0 авг 2 18:22.

      В Btrfs пустой каталог 0, после очистки каталога — тоже 0.
      С b-tree при удалении нет смысла оставлять блоки, которые больше не используются — они все уходят в свободные.

      XFS:
      $ mkdir niceDir && cd niceDir
      $ ls -lhd.
      drwxrwxr-x 2 stc stc 6 авг 2 18:42.
      $for ((i=1;i<133700;i++)); do touch long_long_looong_man_sakeru_$i; done
      $ ls -lhd.
      drwxrwxr-x 2 stc stc 6,2M авг 2 18:47.
      $ find. -type f -delete
      $ ls -lhd.
      drwxrwxr-x 2 stc stc 6 авг 2 18:47.

      Примерно так же.


    1. xsevenbeta
      05.08.2019 08:13

      reiserfs, живой сервер:
      adminx@xxxx:/informatica_cache/SessLogs> ls -ldh
      drwxr-xr-x 5 adminx etl 85M Aug 5 07:53.

      adminx@xxxx:/informatica_cache/SessLogs> ls -l | grep -c ""
      970181

      ls -1 >> ~/filelist
      adminx@xxxx:/informatica_cache/SessLogs> ls -lath ~/filelist
      -rw-r--r-- 1 adminx etl 68M Aug 5 07:58 /home/adminx/filelist

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


  1. manefesto
    02.08.2019 16:10
    +7

    Экономически не выгодно :)


  1. stetzen
    02.08.2019 17:54

    Лень проверять — означает ли такое поведение, что если внутри директории постоянно создавать и удалять файлы, её размер будет постепенно пухнуть? Если да (а кажется что должно), то в принципе на каких-то крайне тощих устройствах это может стать проблемой, если эту штуку не учесть.


    1. sledopit Автор
      02.08.2019 18:13
      +1

      Нет. В этом случае оно просто переиспользует освобожденное место и всё будет хорошо.


    1. arheops
      02.08.2019 18:19
      +1

      Нет, потому что такое поведение — типично, например, для /var/log/ или /var/spool/mail. Тоесть оно бы сильно мешало работать.
      Потому переиспользование записей — выполняется, а вот их освобождение — нет. Никому не нужно ибо. Если у вас в директории было 500тыс файлов, значит, вполне вероятно, скоро опять будет столько же. Тоесть нет смысла освобождать.


      1. Sly_tom_cat
        02.08.2019 18:56

        Ну не стоит так категорично…

        Если каталог лежит в b-tree, то смысла как-то искусственно сохранять выделенные блоки, которые уже не используется деревом — нет. И например (см. выше) btrfs и xfs после удаления таки все освобождают.


        1. arheops
          02.08.2019 19:10

          У XFS свои приколы. Я за последние полгода два раза перегружал прод, ибо оно вродекак и освобождает, но не сейчас и места нету.


          1. Sly_tom_cat
            02.08.2019 19:29

            Там типа GC (в Python/Go/Java и т.п.) — очень много всего на потом откладывается. И в кеши, в кеши, в кеши. XFS очень сильно кеши любит… порой излишне…
            Но зато на том же тесте с заполнением каталогов она у меня сделала btrfs на том же диске:

            btrfs:
            $ time for ((i=1;i<133700;i++)); do touch long_long_looong_man_sakeru_$i; done
            real 3m29,141s
            user 2m22,319s
            sys 1m24,666s

            xfs:
            $ time for ((i=1;i<133700;i++)); do touch long_long_looong_man_sakeru_$i; done
            real 3m23,376s
            user 2m20,233s
            sys 1m19,493s

            И это при том что btrfs тоже изрядно все кеширует, но все-же не так агрессивно как xfs.


          1. xsevenbeta
            05.08.2019 08:05

            Может, имеет место быть ситуация когда удаляют кем-то занятый файл? В таком случае в файловой системе файла видно уже не будет, а место на диске будет отсутствовать. Увидеть удалённый файл, который занимает место можно по lsof.


            1. arheops
              05.08.2019 09:38

              Ну там файлы были mysql binary logs и mysql точно был перезапущен.
              Такчто нет.


      1. engine9
        03.08.2019 01:00

        Т.е. это не баг а фича, и нет нужды прогонять fsck?


  1. arheops
    02.08.2019 18:17

    Можно починить, можно.
    Сначала делаете rsync в другую дерикторию, потом меняете названия и удаляете старую.
    И не надо ничего отмонтировать.
    Ну, наверно можно то же самое сделать и внутри FS, но как правильно написали в мейлисте никому это не нужно по сути. А этот код потом поддерживать.
    Можно написать внешнюю утилиту. Которая сначала скопирует содержание всех блоков в другую директорию, потом эту удалит и переименует. Но возникает вопрос с файлами открытыми приложениями в данный момент.


    1. vvm13
      02.08.2019 19:52

      Зачем копировать файлы с их содержимым, когда можно сделать хардлинки?


      1. arheops
        02.08.2019 20:20

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


        1. black_semargl
          02.08.2019 20:59

          Создать правильными методами нельзя. Но "." и ".." — это именно хардлинки.


          1. khim
            02.08.2019 21:48
            +2

            Да, но нет. На самом деле "." и ".." в Linux — чисто виртуальная конструкция, с диска ничего не читается и не пишется.

            Что приводит к забавному эффекту: если вы на флешке (то бишь FAT) создаёте каталог, то Linux занесёт дату создания в элементы "." и ".." в этом каталоге — а изменить это будет невозможно никак, в принципе.

            То есть, в частности, воспользовавшись советом arheops вы внесёте изменение, которое потом будет видно из-под Windows… несмотря на то, что rsync, вроде как старается даты модификаций файлов и все аттрибуты сохранять.


            1. black_semargl
              03.08.2019 16:59

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


  1. Harbour
    03.08.2019 14:46
    -1

    проблема высосана из пальца:

    a) создаем миллион файлов и плачем о потерянных 8Mb
    b) хотим миллион файлов но не заботимся об оптимизации и регулярном fsck