Сборщик мусора
Добро пожаловать на одиннадцатую пилюлю Nix.
В предыдущей десятой пилюле, мы провели параллель между изолированной средой сборки, предоставляемой nix-build
и изолированной оболочкой разработки, предоставляемой nix-shell
.
С этого момента мы больше не будем концентрироваться на на создании пакетов и вместо этого исследующим критический важный компонент Nix: сборщик мусора.
Используя инструменты Nix, мы часто строим деривации, включая файлы .drv
и выходные пути.
Эти артефакты попадают в хранилище Nix и занимают место на нашем носителе так что мы можем захотеть освободить немного места, удалив деривации, которые нам больше не нужны.
На решении этого вопроса мы сфокусируемся в одиннадцатой пилюле.
По умолчанию, Nix придерживается консервативной стратегии, решая, какие из дериваций являются «нужными».
В этой пилюле мы познакомимся с техникой выполнения более «опасных» операций обновления и удаления.
Как работает сборщик мусора?
Языки программирования со сборщиком мусора (garbage collector) используют концепцию множества корневых элементов (корней сборщика мусора или GC-корней) для отслеживания «живых» объектов. GC-корень — это объект, который всегда считается «живым» (пока не будет явным образом удалён из списка GC-корней). Процесс сборки мусора начинается с GC-корней, и рекурсивно помечает объекты, до которых может добраться, как «живые» Все остальные объекты могут быть собраны и удалены.
Вместо объектов, сборщик мусора Nix оперирует путями в хранилище, причём GC-корни также являются путями в хранилище.
Этот подход гораздо более принципиален, чем у традиционных менеджеров пакетов, таких как dpkg
или rpm
, которые могут оставлять неиспользуемые пакеты или висящие файлы.
Реализация очень проста и прозрачна для пользователя.
Первичные GC-корни хранятся в /nix/var/nix/gcroots
.
Если существует символическая ссылка на путь в хранилище, то связанный путь в хранилище является корнем GC.
Nix позволяет этому каталогу иметь подкаталоги: он просто рекурсивно перебирает подкаталоги в поиске символических ссылок на пути в хранилище. При обнаружении символической ссылки, её цель добавляется в список живых путей.
Подводя итог, можно сказать, что Nix ведёт список корней СМ.
Эти корни могут быть использованы для вычисления списка всех живых путей в хранилище.
Любой другой путь считается мёртвым.
Удаление такие путей — простая операция.
Сначала Nix переносит мёртвые пути в каталог /nix/store/trash
, что является атомарной операцией.
После этого корзина чистится.
Экспериментируем со сборщиком мусора
Прежде чем начать, запустим сборщик мусора Nix, чтобы получить чистую систему для наших экспериментов:
$ nix-collect-garbage
finding garbage collector roots...
[...]
deleting unused links...
note: currently hard linking saves -0.00 MiB
1169 store paths deleted, 228.43 MiB freed
Если мы запустим сборщик мусора снова, он, как и ожидается, не найдёт объектов для удаления. После завершения сборки мусора, хранилище Nix содержит только пути с ссылками из корней GC.
Теперь установим новую программу, bsd-games
, исследуем её путь в хранилище, и убедимся, что он является GC-корнем.
Команда nix-store -q --roots
нужна, чтобы запросить корни GC, которые ссылаются на заданную деривацию.
В нашем случае, окружение текущего пользователя ссылается на bsd-games
:
$ nix-collect-garbage
finding garbage collector roots...
[...]
deleting unused links...
note: currently hard linking saves -0.00 MiB
1169 store paths deleted, 228.43 MiB freed
Теперь удалим его, запустим сборщик мусора и убедимся,что bsd-games
всё ещё находится в хранилище Nix.
$ rm /nix/var/nix/profiles/default-9-link
$ nix-env --list-generations
[...]
8 2014-07-28 10:23:24
10 2014-08-20 12:47:16 (current)
$ nix-collect-garbage
[...]
$ ls /nix/store/b3lxx3d3ggxcggvjw5n0m1ya1gcrmbyn-bsd-games-2.17
ls: cannot access /nix/store/b3lxx3d3ggxcggvjw5n0m1ya1gcrmbyn-bsd-games-2.17: No such file or directory
Старое поколение всё ещё в хранилище Nix, потому что оно является корнем GC. Как вы увидим ниже, все профили и их поколения — это корни GC.
Удаление корня СМ — простая операция.
В нашем случае мы удаляем поколение, которое ссылается на bsd-games
, запускаем сборщик мусора и убеждаемся, что bsd-games
больше недоступен в хранилище Nix:
ℹ️ Замечание:
nix-env --list-generations
не опирается на какие-либо метаданные. Он получает поколения, основываясь исключительно на именах файлов в каталоге профилей.
Обратите внимание, что мы удалили ссылку из каталога /nix/var/nix/profiles
, не из /nix/var/nix/gcroots
.
Помимо прочего, Nix трактует /nix/var/nix/profiles
как корень GC.
Это полезно, поскольку это значит, что любой профиль и его поколения являются корнями GC.
Другие пути также считаются корнями GC, например, /run/booted-system
в NixOS.
Команда nix-store --gc --print-roots
печатает все пути, которые считаются корнями GC при запуске сборщика мусора.
Косвенные корни
Вспомните, что построение пакета GNU hello
с помощью nix-build
создаёт в текущем каталоге символическую ссылку result
.
Несмотря на выполненную выше сборку мусора, программа hello
всё ещё работает.
Следовательно, она не была собрана.
Так как не существует других дериваций, которые зависят от пакета GNU hello
, значит, он и является корнем GC.
Фактически, nix-build
автоматически делает символическую ссылку result
корнем GC.
Обратите внимание, что речь идёт не о собранной деривации, а только о символической ссылке.
Эти корни GC добавляются в каталог /nix/var/nix/gcroots/auto
.
$ ls -l /nix/var/nix/gcroots/auto/
total 8
drwxr-xr-x 2 nix nix 4096 Aug 20 10:24 ./
drwxr-xr-x 3 nix nix 4096 Jul 24 10:38 ../
lrwxrwxrwx 1 nix nix 16 Jul 31 10:51 xlgz5x2ppa0m72z5qfc78b8wlciwvgiz -> /home/nix/result/
Нам пока не важно, какое имя носит символическая ссылка, ставшая корнем GC.
Значение имеет то, что она существует и указывает на /home/nix/result
.
Это называется косвенным корнем GC.
Корень GC считается косвенным, если он определён где-то вне каталога /nix/var/nix/gcroots
.
В нашем случае это значит, что цель символической ссылки result
не будет удаляться сборщиком мусора.
Есть два способа удалить деривацию, считающуюся «живой» из-за косвенного корня GC:
- Удалить косвенный корень GC из
/nix/var/nix/gcroots/auto
. - Удалить символическую ссылку
result
.
В первом случае деривация будет удалена из хранилища Nix в течение сборки мусора и result
станет висячей символической ссылкой.
Во втором случае деривация удаляется вместе с косвенным корнем в /nix/var/nix/gcroots/auto
.
Запуск nix-collect-garbage
после удаления корня GC или косвенного корня GC удалит деривацию из хранилища.
Чистим всё
Главный источник дублирования программ в хранилище Nix возникает из-за корней GC, связанных с nix-build
и поколениями профиля.
Запуск nix-build
приводит к созданию корня GC для сборки, который ссылается на определённые версии определённых библиотек, например, glibc
.
После обновления, мы должны удалить предыдущую сборку, если мы хотим, чтобы сборщик мусора удалил соответствующие деривации, а так же, если мы хотим очистить старые зависимости.
То же касается и профилей.
Манипулирование профилем nix-env
создаёт дополнительные поколения.
Старые поколения ссылаются на старые программы, увеличивая таким образом дублирование после обновления в хранилище Nix.
Другие системы обычно «забывают» всё о своём предыдущем состоянии сразу после обновления. В Nix мы тоже можем выполнить такой тип обновления (заставив Nix удалить старые деривации, включая старые поколения), но это надо делать вручную. Потребуются четыре шага:
- Во-первых, мы скачиваем новую версию канала nixpkgs, который содержит описание всех программ.
Это делается с помощью
nix-channel --update
. - Затем мы обновляем установленные пакеты, запустив
nix-env -u
. Так у нас появится новое поколение с обновлёнными программами. - Затем мы удаляем все косвенные корни, созданные
nix-build
: будьте осторожны, потому что это приводит к появлением висячих системных ссылок. Более умной стратегией будет одновременное удаление целей этих символических ссылок. - Наконец, с помощью опции
-d
при запускеnix-collect-garbage
удаляем старые поколения всех профилей с последующей сборкой мусора. После этого к предыдущим поколениям вернуться нельзя. Прежде, чем запускать эту команду, важно убедиться, что новое поколение корректно работает.
Вот эти четыре команды:
$ nix-channel --update
$ nix-env -u --always
$ rm /nix/var/nix/gcroots/auto/*
$ nix-collect-garbage -d
Заключение
Сборка мусора в Nix — мощный механизм для чистки вашей системы.
Команды nix-store
позволяют нам узнать, почему определённая деривация присутствует в хранилище Nix, независимо от того, подлежит ли она сборке, или нет.
Также мы выяснили, как выполнить более деструктивные операции удаления и обновления.
В следующей пилюле
В следующей пилюле мы запакуем другой проект и объясним паттерн проектирования «Входы». До сих пор мы работали только с одной деривацией; теперь нам предстоит организовать маленький репозиторий для программ. Паттерн «Входы» широко используется в nixpkgs; он позволяет нам разделять деривации из одного репозитория и обеспечивает тонкую настройку.