Btrfs давно перестал быть экспериментальной забавой и стал штатной файловой системой в openSUSE, Fedora и на сетевых хранилищах Synology. Главный его козырь не сжатие и даже не снимки, а подтомы, они же subvolumes: самостоятельные деревья файлов внутри одного общего пула. Администратор, который понял эту механику, почти перестаёт резать диск на разделы. Раздел не умеет менять размер, а подтом умеет, и все они делят место по мере записи. Ниже собрана рабочая схема разметки, от команды mkfs.btrfs до тонкой настройки сжатия zstd и атрибута NoCOW, который возвращает базам данных привычную скорость записи.
Отдельные подтомы надёжнее привычных разделов
Подтом выглядит как обычный каталог, но у него своя иерархия и собственное пространство inode. Корень любого подтома всегда несёт номер 256, и команда ls -i мгновенно отличает его от простого каталога. Место новый подтом не резервирует: весь пул общий, и каждый подтом растёт ровно настолько, насколько в него пишут. Не нужно заранее угадывать, сколько отдать под домашние каталоги, сколько под корень и сколько под журналы, а потом годами жить с переполненным разделом логов рядом с полупустым домом.
Второе свойство ещё интереснее. Снимок в Btrfs не рекурсивен: если внутри подтома лежит другой подтом, в копию он попадает пустой заглушкой с inode 2. Поначалу это выглядит ограничением, по факту это рабочий инструмент. Системные файлы кладут в один подтом, пользовательские данные, журналы и кэши раздают другим, и каждый снимок корня выходит чистым и компактным, без накопленных за годы терабайт фотографий и машин обучения.
Свежая файловая система сама представляет собой подтом верхнего уровня с идентификатором 5. У него неприятная особенность: удалить его нельзя. Поэтому на этом уровне ничего не хранят, а всю структуру разворачивают в дочерних подтомах с префиксом @, как принято в openSUSE и Arch Linux.
Разметка диска и каркас подтомов с нуля
Классическую схему, обкатанную годами в дистрибутивах, полезно воспроизвести руками хотя бы один раз, чтобы понимать, что установщик делает за кулисами. Порядок подготовки каркаса выглядит так:
- Разметку выполняют утилитой sgdisk, под системный раздел EFI оставляют 512 МиБ, весь остаток отдают Btrfs;
- На разделе создают файловую систему командой mkfs.btrfs и временно монтируют её в /mnt;
- Внутри точки монтирования создают подтомы с префиксом @, по одному на каждую группу данных;
- Подтом верхнего уровня оставляют пустым, чтобы случайные файлы не оседали там навсегда;
- Каркас проверяют командой btrfs subvolume list и отмонтируют диск.
sudo sgdisk -n 0:0:+512M -t 1:ef00 /dev/nvme0n1
sudo sgdisk -n 0:0:0 -t 2:8300 /dev/nvme0n1
sudo mkfs.fat -F32 /dev/nvme0n1p1
sudo mkfs.btrfs -L system /dev/nvme0n1p2
sudo mount /dev/nvme0n1p2 /mnt
cd /mnt
sudo btrfs subvolume create @
sudo btrfs subvolume create @home
sudo btrfs subvolume create @var_log
sudo btrfs subvolume create @var_cache
sudo btrfs subvolume create @var_tmp
sudo btrfs subvolume create @snapshots
sudo btrfs subvolume create @swap
sudo btrfs subvolume list /mnt
sudo umount /mnt
Имена вроде @var_log и @var_cache выглядят громоздко, зато в списке подтомов сразу видно, что где живёт. Дистрибутивные установщики собирают похожий набор сами, а вручную эту схему применяют при установке Arch Linux и при переносе системы на новый диск. Номера созданных подтомов видны в выводе команды btrfs subvolume show, они не меняются до самого удаления подтома.
Монтирование подтомов через fstab и опция subvol
Подтом монтируется как отдельная файловая система с опцией subvol, в которой указан путь внутри пула. Сначала узнают UUID раздела:
sudo blkid /dev/nvme0n1p2
Затем в /etc/fstab добавляют строки с общим UUID и разными значениями subvol:
UUID=7a3e91c2-4d0b-4f8e-9a51-c6d2e0b4f7a3 / btrfs subvol=/@,compress=zstd,noatime 0 0
UUID=7a3e91c2-4d0b-4f8e-9a51-c6d2e0b4f7a3 /home btrfs subvol=/@home,compress=zstd,noatime 0 0
UUID=7a3e91c2-4d0b-4f8e-9a51-c6d2e0b4f7a3 /var/log btrfs subvol=/@var_log,compress=zstd,noatime 0 0
UUID=7a3e91c2-4d0b-4f8e-9a51-c6d2e0b4f7a3 /var/cache btrfs subvol=/@var_cache,compress=zstd,noatime 0 0
UUID=7a3e91c2-4d0b-4f8e-9a51-c6d2e0b4f7a3 /var/tmp btrfs subvol=/@var_tmp,compress=zstd,noatime 0 0
UUID=7a3e91c2-4d0b-4f8e-9a51-c6d2e0b4f7a3 /.snapshots btrfs subvol=/@snapshots,compress=zstd,noatime 0 0
UUID=7a3e91c2-4d0b-4f8e-9a51-c6d2e0b4f7a3 /swap btrfs subvol=/@swap,noatime 0 0
После сохранения файла всё монтируется и проверяется:
sudo mount -a
findmnt -t btrfs
В выводе findmnt видно, какой подтом подключён к какой точке; та же информация лежит в четвёртом столбце файла /proc/self/mountinfo. Опция noatime отменяет запись времени доступа при каждом чтении и заметно бережёт накопитель, опция compress включает сжатие, о котором речь дальше.
Тонкость, о которую спотыкаются многие: опции compress, nodatacow и nodatasum действуют на всю файловую систему сразу, и применяются настройки того подтома, который смонтирован первым. Разнести эти режимы по подтомам через mount не выйдет, особенность известна давно и упирается в устройство слоя VFS ядра Linux. Есть и альтернатива опции subvol: монтирование по номеру через subvolid, номер смотрят в выводе btrfs subvolume show. Но имена читаются лучше номеров, поэтому в fstab почти всегда пишут subvol.
Прозрачное сжатие zstd экономит место без потери скорости
Алгоритм zstd работает в ядре с версии 4.14 и стал основным выбором: он сжимает почти так же плотно, как zlib, но быстрее и на упаковке, и на распаковке. Уровни идут от 1 до 15, поддержка уровней появилась с ядром 5.1, а с ядра 6.15 добавились отрицательные уровни для режима реального времени. Уровень по умолчанию равен 3: приличное сжатие почти без потери скорости. Явный уровень пишут через двоеточие, например compress=zstd:5 для каталогов с архивами, где скорость вторична, а экономия места первична.
Сжимаются только новые записи: включение опции не трогает уже записанные файлы. Данные перед упаковкой режутся на куски по 128 КиБ, поэтому случайная перезапись середины файла не вынуждает распаковывать весь файл целиком. Платой становится чуть больший расход метаданных, потому что экстентов становится больше. Несжимаемые данные ядро отсеивает само, пробуя сжать первый кусок, а файлы, размеченные вызовом fallocate, из сжатия исключаются всегда: предвыделение обязано гарантировать место под будущую запись.
Для перекомпрессии старых файлов служит дефрагментация с флагом алгоритма:
sudo btrfs filesystem defrag -r -czstd /home
Флаг -r обходит каталоги рекурсивно. Постоянное сжатие для отдельного файла или каталога задаётся свойством:
sudo btrfs property set /home/user/backup.tar compression zstd
Свойство на каталоге наследуют новые файлы внутри него. Есть и старый интерфейс chattr со строчной +c, но он негибок и всегда включает zlib, поэтому в живых системах его почти не применяют. Проверить, что дала экономия, проще всего утилитой compsize из одноимённого пакета: она покажет, сколько данных лежит несжатыми, сколько сжатыми и какой вышел итоговый коэффициент.
Атрибут NoCOW возвращает базам данных скорость записи
Copy-on-write прекрасна для снимков, но режет по живому случайные перезаписи. Каждый раз, когда СУБД переписывает блок на месте, Btrfs подыскивает изменённому блоку новое место, обновляет метаданные и множит экстенты. Файл базы данных за неделю превращается в лоскутное одеяло, кэш страниц работает впустую, и время запроса ползёт вверх. Автоматическая дефрагментация autodefrag тут не спасает, для больших нагрузок базы данных она прямо не рекомендуется.
Решение давнее и официальное: атрибут NoCOW, который ставится утилитой chattr с флагом +C. Файлы в таком каталоге пишутся на место, без копирования при записи. Цена суровая: атрибут подразумевает отказ от контрольных сумм данных и отключает сжатие, а при сбое питания возможна частичная запись. Для базы это осознанный обмен: целостность транзакций держит журнал упреждающей записи самой СУБД, а не файловая система.
Атрибут наследуют только новые файлы, поэтому на живой каталог с данными его просто так не навесить. Правильный порядок такой: данные переносят в сторону, каталог очищают, ставят флаг, данные возвращают. Удобнее всего выделить базе отдельный подтом:
sudo btrfs subvolume create /mnt/@pgdata
sudo mount -o subvol=/@pgdata,noatime /dev/nvme0n1p2 /mnt/var/lib/postgresql
sudo chattr +C /mnt/var/lib/postgresql
lsattr -d /mnt/var/lib/postgresql
Буква C в выводе lsattr подтверждает, что флаг стоит. Такой подтом решает сразу три задачи: запись идёт на место, файлы базы не попадают в снимки системы, а при желании объём ограничивается квотами qgroup. Тот же приём нужен MySQL в /var/lib/mysql и образам виртуальных машин, у них workload тот же.
Файлу подкачки отсутствие копирования при записи нужно по другому поводу: ядро требует от swap-файла на Btrfs атрибута NoCOW и не переносит его через снимки. Свежие версии btrfs-progs делают всё одной командой:
sudo btrfs filesystem mkswapfile --size 4g /swap/swapfile
sudo swapon /swap/swapfile
Утилита сама ставит атрибут и создаёт правильный файл, вручную пришлось бы прогонять цепочку из truncate, chattr +C и mkswap.
Снимки и откаты без риска потерять пользовательские файлы
Теперь видна награда за аккуратный каркас. Снимок создаётся мгновенно, потому что данные не копируются: новый подтом ссылается на те же блоки, и место тратится только на изменившиеся страницы.
sudo btrfs subvolume snapshot -r / /.snapshots/root-$(date +%F)
Флаг -r делает снимок только для чтения, что и нужно для контрольных точек. Откат системы сводится к переименованию подтомов: текущий корень убирают в сторону, а снимок клонируют на его место. В openSUSE этот ритуал автоматизирован инструментом snapper, который ставит снимок перед каждым обновлением пакетов, а в Arch Linux за вход в старое состояние отвечает скрипт grub-btrfs, добавляющий снимки в меню загрузчика.
Пользовательские файлы при откате не страдают: они живут в @home и в снимок корня не попадают. Журналы, кэши и базы остаются при своих, потому что отгоророжены собственными подтомами, и хранилище снимков @snapshots тоже никого не затрагивает.
Здесь надо честно сказать: снимок не резервная копия. Снимок и оригинал делят одни и те же блоки, и испорченный сектор бьёт по обоим сразу. Настоящая копия делается отправкой снимка на другой диск:
sudo btrfs send /.snapshots/root-2026-01-15 | sudo btrfs receive /mnt/backup
Следующий снимок уходит уже инкрементально, с опцией -p и указанием предыдущего, и по проводу идут только различия. Целостность остального хозяйства проверяет скраб:
sudo btrfs scrub start -B /
sudo btrfs scrub status /
Скраб читает все данные и сверяет контрольные суммы. Файлы с атрибутом NoCOW в эту проверку не попадают, у них сумм просто нет, и это ещё один повод держать базы на отдельном носителе с собственными копиями.
Проверка конфигурации и ошибки, которые дорого обходятся
Готовая система проверяется тремя командами: btrfs subvolume list / показывает все подтомы, findmnt -t btrfs подтверждает точки монтирования, btrfs subvolume show / рассказывает детали корня, включая идентификатор и путь внутри пула.
Ошибки в этой области повторяются из года в год, и почти все они тихие: система работает, просто не так, как планировалось. Флаг +C, поставленный на каталог с данными, ничего не меняет для уже существующих файлов, и администратор неделями смотрит на медленную базу, будучи уверенным в настройке. Опция nodatacow в fstab действует на весь пул, а не на один подтом, и заодно отключает сжатие всей файловой системы. Включённое сжатие не ужимает старые данные, поэтому после добавления compress место само не появится, нужна перекомпрессия через defrag. Файл подкачки, забытый внутри снимаемого подтома, при откате превращается в сюрприз: снимок содержит его пустую заглушку, и система просыпается без подкачки.
Отдельного разговора достоин выбор уровня сжатия. Уровни с 4 по 8 жмут плотнее, но медленнее, уровни с 9 по 15 тратят ощутимое время процессора ради нескольких процентов выигрыша. На обычной рабочей машине уровень 3 остаётся разумным компромиссом, а почти несжимаемые домашние видео разумнее держать в отдельном подтоме без compress, чтобы ядро не тратило такты впустую.
Схема из подтомов окупается в первый же откат. Один раз потратив полчаса на каркас, администратор получает систему, где обновление откатывается одной командой, домашние каталоги не зависят от судьбы корня, журналы не съедают место из-под баз, а базы пишут с той скоростью, на которую способно железо. Предсказуемость и есть то свойство, ради которого Btrfs ставят и на серверы, и на рабочие станции.