Сегодня мы хотим рассказать о некоторых последних апдейтах системы Sherlock [это высокопроизводительный кластер Стэнфордского университета — прим. пер.], которые значительно ускоряют листинг файлов в каталогах с большим количеством записей.

В отличие от обычных статей, это скорее инсайдерский отчёт о том, как происходит регулярная работа над Sherlock, чтобы поддерживать его в наилучшем виде для наших пользователей. Надеемся в будущем публиковать больше таких статей.

Листинг многих файлов занимает время


Всё началось с вопроса в техподдержку от пользователя. Он сообщил о проблеме, что выполнение ls занимает несколько минут в каталоге c более 15 000 записей в $SCRATCH [каталог для временных файлов — прим. пер.].

Тысячи файлов в одном каталоге обычно создают трудности для файловой системы и такое определённо не рекомендуется. Пользователь знал это и признал, что это нехорошо, но упомянул, что на его ноутбуке листинг выполняется в 1000 раз быстрее, чем в Sherlock. Конечно, это нас задело. Поэтому мы заглянули глубже.

Потому что ls выглядит красиво


Мы рассмотрели, что на самом деле делает ls при листинге каталога, и почему процесс занимает так много времени. В большинстве современных дистрибутивов ls по умолчанию выполняется как ls --color=auto, потому что всем нравится расцветка.

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

Одно из простых решений проблемы — вообще отключить цвет в ls, но представьте возмущение пользователей. Ни в коем случае нельзя забирать цветной вывод, мы не монстры.

Поэтому мы заглянули глубже. ls раскрашивает записи через переменную среды LS_COLORS, которую задаёт dircolors(1) на основе файла конфигурации dir_colors(5). Да, исполняемый файл считывает конфигурационный файл для создания переменной среды, которую потом использует ls (а если вы не знаете о файлах door (do), то dir_colors сработает, несмотря ни на что).

Разберёмся подробнее


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

$ mkdir $SCRATCH/dont
$ touch $SCRATCH/dont/{1..10000} # don't try this at home!
$ time ls --color=always $SCRATCH/dont | wc -l
10000

real    0m12.758s
user    0m0.104s
sys     0m0.699s

12,7 секунд для 10 000 файлов, не очень хорошо.
Кстати, нужен флаг --color=always: хотя он обращается в ls --color=auto, но ls обнаруживает, когда он не подключен к терминалу (например, по каналу или с перенаправлением выдачи) и отключает раскраску, если установлено значение auto. Умный парень.
Так что же занимает столько времени? Мы посмотрели с помощью strace:

$ strace -c ls --color=always $SCRATCH/dont | wc -l
10000
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 44.21    0.186617          19     10000           lstat
 42.60    0.179807          18     10000     10000 getxattr
 12.19    0.051438           5     10000           capget
  0.71    0.003002          38        80           getdents
  0.07    0.000305          10        30           mmap
  0.05    0.000217          12        18           mprotect
  0.03    0.000135          14        10           read
  0.03    0.000123          11        11           open
  0.02    0.000082           6        14           close
[...]

Ничего себе: 10 000 вызовов lstat(), 10 000 вызовов getxattr() (которые все терпят неудачу, потому что в нашей среде нет атрибутов, которые ищет ls), 10 000 вызовов capget().

Наверняка это можно оптимизировать.

Атрибут capabilities? Неа


Следуя советам бага 10-летней давности, мы попытались отключить проверку атрибута capabilities:

$ eval $(dircolors -b | sed s/ca=[^:]*:/ca=:/)
$ time strace -c ls --color=always $SCRATCH/dont | wc -l
10000
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 98.95    0.423443          42     10000           lstat
  0.78    0.003353          42        80           getdents
  0.04    0.000188          10        18           mprotect
  0.04    0.000181           6        30           mmap
  0.02    0.000085           9        10           read
  0.02    0.000084          28         3           mremap
  0.02    0.000077           7        11           open
  0.02    0.000066           5        14           close
[...]
------ ----------- ----------- --------- --------- ----------------
100.00    0.427920                 10221         6 total

real    0m8.160s
user    0m0.115s
sys     0m0.961s

Ух ты, ускорение до 8 секунд! Мы избавились от всех этих дорогих вызовов getxattr(), и вызовы capget() тоже исчезли, отлично.

Но ещё остались эти надоедливые вызовы lstat(), хотя…

Сколько нужно цветов?


Поэтому мы более подробно рассмотрели LS_COLORS.

Сначала просто отключили эту переменную:

$ echo $LS_COLORS
rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.axa=00;36:*.oga=00;36:*.spx=00;36:*.xspf=00;36:
$ unset LS_COLORS
$ echo $LS_COLORS

$  time ls --color=always $SCRATCH/dont | wc -l
10000

real    0m13.037s
user    0m0.077s
sys     0m1.092s

Что!?! По-прежнему 13 секунд?

Оказывается, когда переменная среды LS_COLORS не определена или отсутствует только один из её элементов <type>=color:, она по умолчанию использует встроенную базу данных и всё равно использует цвета. Поэтому, если вы хотите отключить раскраску для определённого типа файла, вам нужно переопределить её с помощью <type>=: или <type> 00 в файле DIR_COLORS.

После множества проб и ошибок мы сузили круг поиска до этого:

EXEC 00
SETUID 00
SETGID 00
CAPABILITY 00

что записывается как

LS_COLORS='ex=00:su=00:sg=00:ca=00:'

Это означает: не раскрашивай файлы ни по атрубуту capabilities, ни по битам setuid/setgid, ни по флагу исполняемости.

Ускоряем ls


И если не делать ни одной из этих проверок, то вызовы lstat() исчезают, и теперь совсем другое дело:

$ export LS_COLORS='ex=00:su=00:sg=00:ca=00:'
$ time strace -c ls --color=always $SCRATCH/dont | wc -l
10000
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 63.02    0.002865          36        80           getdents
  8.10    0.000368          12        30           mmap
  5.72    0.000260          14        18           mprotect
  3.72    0.000169          15        11           open
  2.79    0.000127          13        10           read
[...]
------ ----------- ----------- --------- --------- ----------------
100.00    0.004546                   221         6 total

real    0m0.337s
user    0m0.032s
sys     0m0.029s

0,3 секунды на списке 10 000 файлов, рекорд.

Настраиваем Sherlock


От 13 секунд с настройками по умолчанию до 0,3 секунды с небольшой настройкой LS_COLORS означает 40-кратное ускорение за счёт отсутствия setuid / setgid и раскрашенных исполняемых файлов. Не такая большая потеря.

Конечно, теперь это настроено в Sherlock для каждого пользователя.

Но если вы хотите вернуть раскраску, то можете просто вернуться к настройкам по умолчанию:

$ unset LS_COLORS

Но тогда на каталогах с большим количеством файлов обязательно заваривайте кофе, пока работает ls.

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


  1. Kopilov
    07.05.2019 11:21

    Нюанс критичный, статья годная. Интересно только, что именно за процесс ускоряется в 40 раз.
    Если это просмотр пользователем каталога из 10 000 файлов ценой удаления метаданных — сочувствую этому пользователю.
    Если это какой-то автоматизированный процесс, использующий вывод ls в своих нуждах — я обязательно оставлю это здесь:


    1. urtow
      07.05.2019 11:54

      Не «удаления метаданных», а «не раскрашивания вывода ls на основании части метаданных»


    1. FRiMN
      07.05.2019 13:38

      На самом деле хороший вопрос. Зачем кому-то глазами смотреть 10000 файлов? По поводу скриптов: в статье сказано, что ls не использует раскраску при auto, если не выводится непосредственно в консоль.


      1. Psychopompe
        07.05.2019 21:12

        Ну у меня такое бывает иногда (10-20к), нужно проверить, как скрипт отработал.


  1. legolegs
    07.05.2019 15:39

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

    1. Современная ФС, такая как ext4 пережёвывает директории с любым количеством файлов без повреждений и заметной потери производительности. Проверял на десятках миллионов.
    2. Устаревшие ФС, такие как FAT32 могут при обращении к одному файлу тормозить линейно, а иногда и квадратично от к-ва файлов в папке
    3. Простейшим из быстрейших способов получить список файлов является вызов find без параметров, у ls надо читать ман и приказывать ему не сортировать список (и не раскрашивать, как понятно из статьи)
    4. Быстрейшим способом очистить такую директорию является rsync с пустой директорией. А rm * подвесит вам шелл. Если рсинка под рукой нет я бы попробовал find -delete или даже "rm -r .".
    5. Если вы используете ls --color или ls -l то вы не избежите вызова stat() на каждый файл, а это совсем не то же самое, что просто получить список имён.
    6. Если файлы созданы чем-то вроде touch {1..10000000} то их inode расположены подряд и stat() работает сравнительно быстро
    7. Если вы годами копили свои файлы (например, не чистили сессии php) то inode раскиданы по таблице и stat() будет очень медленным, особенно на НЖМД
    8. Вам не нужно читать глазами листинг директории с миллионом файлов. Даже парсить его не нужно, нет и не должно быть таких задач.


    1. ppl2scripts
      07.05.2019 17:35

      4. Если подождать несколько часов, то rm умрёт, сказав что не хватает памяти.  find – примерно та-же история. Единственным надёжным и быстрым способом является rsync с пустой.
      Проверялось несколько лет назад на (очень) больших нечищеных кэшах squid.


    1. AcckiyGerman
      08.05.2019 12:39
      +1

      Я еще заметил, что после удаления или перемещения файлов из конкретной папки, остаются inode записи (ну я думаю, что это они). Их видно в листинге 'ls -l' как большое число в первой строчке, которая начинается с точки и обозначает текущую директорию.
      Вызов 'ls' в такой, вроде бы очищенной (а раньше в ней была пара миллионов файлов), папке занимает длительное время, даже если файлов внутри "уже нет".


      Лечится удалением и пересоздание папки.


      P.s. Ubuntu Server 14.04, ext4


      1. legolegs
        08.05.2019 14:58

        Лечится удалением и пересоздание папки.
        Да, как это часто бывает в IT, некоторые структуры данных могут расти но не обучены уменьшатся. 'e2fsck -D' по идее сожмёт такую директорию.
        PS Это не inode, они сидят в своей таблице глобальной для всей ФС. Это имена файлов или места, где эти имена ранее были, ведь директория — это такой файл со списком имён файлов и номеров ячеек в таблице inode.


  1. Vlad_fox
    07.05.2019 15:53
    +1

    13 секунд пользователю съекономили… теперь он сможет намного больше файлов обработать за трудодень…
    чего бы еще такого полезного замутить?