Перейти к содержанию
zoryn/ maintainer-assistant

Hasher на tmpfs

Перенос hasher-chroot на tmpfs — файловую систему Linux в памяти — делает сборку примерно в 7 раз быстрее, чем на том же оборудовании с SSD, и, что не менее важно, бережёт диск: иначе каждая сборка в hasher писала бы гигабайты короткоживущих данных (распакованные исходники, объектные файлы, деревья %install, RPM, кэши apt), которые через несколько минут удаляются. Держать эту круговерть в RAM — это прямое продление ресурса SSD/HDD для всех, кто собирает пакеты регулярно. Цена — оперативная память: chroot, распакованные исходники, промежуточный %install-корень и все RPM, произведённые в ходе сборки, живут в RAM до тех пор, пока не отработает cleanup hasher'а.

Главное удобство со стороны zoryn — то, что делает этот сценарий практичным на каждый день: если hasher_dir — это симлинк на несуществующий каталог, zoryn создаёт целевой каталог перед каждой сборкой. Поэтому симлинк на путь внутри /tmp «самовосстанавливается» после каждой перезагрузки — не нужна запись в fstab, не нужен mkdir в .profile, вообще ничего не нужно помнить.

Почему tmpfs

Две причины, и обе важные:

Скорость. Сборка в hasher — это много мелких записей: тысячи файлов в %install, сотни проб configure, постоянная установка/удаление BuildRequires через apt. На SSD всё это упирается в IOPS и fsync; на tmpfs это просто memcpy. Итоговое ускорение на типичных пакетах ALT (shell, python, небольшие C-библиотеки) — около . Тяжеловесы, упирающиеся в компиляцию (chromium, ядро, LLVM), ускоряются заметно меньше — но даже там только %install и cleanup вместе экономят минуты на каждой сборке.

Износ диска. Каждая сборка в hasher пишет несколько гигабайт и тут же удаляет почти всё: chroot распаковывается, используется и сносится; деревья %install собираются и запаковываются в RPM; кэши apt переписываются при каждом изменении конфигурации. На SSD это десятки ГБ TBW в день у активного мейнтейнера, на жёстком диске — часы движения головок. Перенос всего этого в RAM означает, что на постоянный носитель ложатся только финальные SRPM/RPM, которые вы действительно хотите сохранить — ресурс диска перестаёт зависеть от того, как часто вы собираете.

Компромиссы

  • Бюджет RAM. База sisyphus x86_64 hasher — ~1,2 ГБ после установки; плюс исходники, объектные файлы, дерево %install и готовые RPM — рабочий набор среднего пакета 3–5 ГБ. Столько свободной RAM должно быть сверх того, что потребляет сам процесс сборки. Память сама между сборками не освобождается — см. Освобождение RAM ниже.
  • Большие пакеты не поместятся. Chromium, LLVM, webkit, libreoffice — всё, что производит десятки ГБ промежуточного вывода, — выест память и получит OOM на tmpfs. Правильный ответ — держать второй билдер на диске специально для тяжеловесов и собирать такие пакеты на нём: tmpfs-билдер остаётся дефолтом для 95% пакетов, а редкие великаны уезжают на дисковый слот явным флагом. См. Двухуровневая схема ниже.
  • Состояние не переживает перезагрузку. /tmp очищается при загрузке. Для самого chroot это безвредно (hasher всё равно пересобирает его с нуля), но кэш apt и ранее собранные RPM в локальном репозитории hasher теряются. С apt-конфигурацией разберётся штатная синхронизация zoryn.

Настройка

1. Убедитесь, что /tmp — это tmpfs

На современной установке ALT это по умолчанию — /tmp монтируется через tmp.mount из systemd (или через /etc/fstab) как tmpfs с лимитом размера примерно в половину RAM. Проверка:

mount | grep ' /tmp '
# tmpfs on /tmp type tmpfs (rw,nosuid,nodev,…,size=…)

Если нет — включите:

sudo systemctl enable --now tmp.mount

Размер по умолчанию обычно достаточен. Если вы знаете свой рабочий набор — поднимите его в /etc/systemd/system/tmp.mount.d/override.conf, например Options=size=16G.

2. Сделайте ~/hasher симлинком на путь в tmpfs

В ALT для приватного пользовательского tmp принят каталог /tmp/.private/$USER/ — с правами 0700, создаётся автоматически при входе в систему и очищается при перезагрузке. Это и есть правильный родительский каталог для hasher:

ln -s /tmp/.private/$USER/hasher ~/hasher
$ ls -l ~/hasher
lrwxrwxrwx 1 user user 26  /home/user/hasher -> /tmp/.private/user/hasher

Создавать /tmp/.private/$USER/hasher руками не нужно — ради этого и следующий шаг.

3. Доверьте восстановление после перезагрузки zoryn

После перезагрузки /tmp (а с ним и /tmp/.private/$USER/hasher) исчезают, и ~/hasher становится «висящим» симлинком.

Делать ничего не нужно. Перед каждой сборкой zoryn build / zoryn builder status / любая команда, которой нужен каталог hasher, выполняет шаг «ensure», который:

  1. Замечает, что ~/hasher — симлинк.
  2. Разрешает цель (/tmp/.private/$USER/hasher).
  3. Делает mkdir -p по этому пути.

Та же самая логика работает и по SSH для удалённых билдеров — скрипт улетает одним round-trip'ом вместе с проверкой занятости hasher'а. Таким образом любой билдер, у которого hasher_dir — симлинк в tmpfs, самовосстанавливается после перезагрузки и на локальной, и на удалённой стороне.

4. Настройте билдер (необязательно)

У встроенного билдера local уже прописан ~/hasher, так что если вы собираете только локально — настраивать ничего не нужно, вся настройка заканчивается шагом 2.

Для дополнительных локальных слотов или удалённых билдеров оставляйте ~/hasher (или его нумерованных соседей) в качестве каталога hasher. Пример на четыре параллельных локальных слота на tmpfs:

ln -s /tmp/.private/$USER/hasher1 ~/hasher_1
ln -s /tmp/.private/$USER/hasher2 ~/hasher_2
ln -s /tmp/.private/$USER/hasher3 ~/hasher_3
ln -s /tmp/.private/$USER/hasher4 ~/hasher_4

zoryn builder add --name local --type local --multi-add 4 \
  --hasher-dir='~/hasher_{hasher_number}' \
  --repo /srv/repo/sisyphus -y

Шаблон {hasher_number} раскрывается отдельно для каждого слота; все симлинки живут под одним tmpfs-родителем, поэтому четыре слота делят общий лимит /tmp, а не борются за фиксированный бюджет каждый по своему каталогу.

Если хочется обойтись без индиректа через симлинк — указывайте --hasher-dir прямо на путь в tmpfs: функционально это то же самое, просто без одного readlink:

zoryn builder add --name tmpfs --type local \
  --hasher-dir=/tmp/.private/$USER/hasher -y

Двухуровневая схема: tmpfs по умолчанию, диск для тяжеловесов

На практике хорошо работает схема из двух локальных билдеров: основной на tmpfs (быстрый, на каждый день) и второй на SSD/HDD — для тех немногих пакетов, которые не помещаются в RAM. Выбор, на чём собирать, в такой схеме сводится к явному флагу -b в этих редких случаях — в остальных 95% сборок об этом не нужно думать.

Настраивается один раз:

# Основной tmpfs-билдер (штатный `local` — замените его hasher_dir на
# симлинк в tmpfs по шагу 2 выше, больше ничего настраивать не нужно).

# Второй локальный билдер на настоящем диске:
mkdir -p ~/hasher_disk
zoryn builder add --name disk --type local --number 2 \
  --hasher-dir=~/hasher_disk \
  --repo /srv/repo/sisyphus -y

Два разных hasher_number позволяют обоим слотам сосуществовать; ~/hasher_disk — обычный каталог в домашней файловой системе, поэтому он не упирается в лимит /tmp и не ест RAM.

Далее на каждый день:

zoryn build              # tmpfs — быстрый путь для почти любого пакета
zoryn build -b disk      # очередной chromium / LLVM / webkit

Совет: если у вас есть список известных тяжеловесов — достаточно маленького shell-алиаса, прописывать его в конфиге zoryn смысла нет. Настройка билдеров — единственное постоянное изменение.

Освобождение RAM

На tmpfs «дисковое место, занятое chroot» — это и есть оперативная память, удерживаемая вашим процессом. С hsh --lazy-cleanup (значение по умолчанию в zoryn) chroot сохраняется между сборками, чтобы прогреть кэши apt и ускорить повторные запуски; прерванные и упавшие сборки тоже оставляют после себя рабочее дерево. После серии сборок легко обнаружить, что /tmp занят на несколько ГБ.

Освободить — так:

zoryn builder clean --all             # все билдеры, локальные и удалённые — правильный дефолт
zoryn builder clean --all --dry-run   # предпросмотр перед удалением
zoryn builder clean local             # конкретный — если нужно очистить только этот слот

--all захватывает все настроенные билдеры, локальные и удалённые; на tmpfs RAM освобождают только локальные слоты, но попутно вычистить удалённые chroot'ы — безобидно и полезно: так и на удалённой стороне /tmp (или SSD) остаётся в порядке. Оснований избегать --all нет.

Каждый clean удаляет hasher-chroot, вспомогательные каталоги и оставшиеся тарболлы, после чего заново создаёт (теперь пустой) каталог hasher — включая авто-создание цели у «висящего» симлинка из шага 3, так что ~/hasher сразу готов к следующей сборке.

Возьмите за привычку: zoryn builder clean --all в конце сборочной сессии или тогда, когда free -h показывает, что RAM опустилась ниже рабочего набора следующей сборки. Автоматического освобождения нет — ядро само не выгружает файлы под смонтированным tmpfs.

Диагностика

  • Failed to create hasher_dir. Родитель целевого пути симлинка не существует, и у zoryn нет прав его создать. Проверьте ls -ld /tmp/.private/$USER — каталог должен существовать и принадлежать вам. Если его нет — перелогиньтесь (PAM создаст его заново) или sudo install -d -m 0700 -o "$USER" /tmp/.private/$USER.
  • Chroot переживает перезагрузку. Симлинк указан на каталог не в tmpfs (например, /home/...). Проверьте mount | grep tmpfs и readlink -f ~/hasher.
  • OOM в середине большой сборки. Рабочий набор пакета превышает размер tmpfs. Не боритесь — пересоберите на дисковом билдере (zoryn build -b disk из двухуровневой схемы выше). Повышение size= у /tmp — последнее средство: он делится со всем остальным в /tmp, и этот трюк перестаёт работать, как только следующий пакет окажется ещё больше.

См. также