MAATRIX / Блог / Свободно 40%, а запись встала: фрагментация пула ZFS

Свободно 40%, а запись встала: фрагментация пула ZFS

MAATRIX

df -h показывает 40% свободного места на датасете, а новые файлы пишутся так, будто диск забит под завязку. Первая реакция — не поверить собственным глазам: места вроде хватает с запасом, значит проблема должна быть в чём-то другом. На практике для ZFS это одна из самых типичных и при этом самых незаметных проблем — дело не в количестве свободного места, а в том, как оно расположено на пуле. Разберём, почему так происходит и что с этим делать до того, как это случится на вашем сервере.

Симптом: место есть, а запись тормозит

Ситуация выглядит примерно одинаково на разных серверах. Была база данных или файловое хранилище на ZFS, работало годами без нареканий. Потом постепенно, без резкого скачка, скорость записи новых данных начала проседать — сначала на копеечные доли секунды, которые никто не замечал, потом настолько, что это стало видно в логах приложения или в жалобах пользователей на «зависания» при сохранении.

Первое, что делает любой инженер в такой ситуации — смотрит на занятое место. df -h или zfs list показывают что-то вроде 60% занято, 40% свободно. Логика подсказывает: если бы место заканчивалось, было бы понятно, но 40% — это солидный запас, проблема явно не в этом. Именно эта интуиция и уводит расследование в сторону на первом же шаге, потому что для обычных файловых систем вроде ext4 или XFS она обычно верна, а для ZFS — нет.

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

Первые подозреваемые: CPU, диск, сеть

Прежде чем разбираться в тонкостях ZFS, стоит закрыть очевидные версии — иначе легко потратить день на изучение фрагментации, а причина окажется в перегруженном CPU или деградировавшем накопителе.

Стандартный порядок проверки в таких случаях:

top -o %CPU          # не съедает ли CPU что-то постороннее (шифрование, компрессия, скрабы)
iostat -xz 1         # утилизация диска, средняя длина очереди, await
zpool status -v      # нет ли деградации, ресильвера, ошибок чтения/записи/чексум
smartctl -a /dev/nvme0n1   # здоровье самого накопителя, TBW, температура

Если iostat показывает, что диск утилизирован на 100% при относительно скромной нагрузке в IOPS, а zpool status чист — это уже сигнал, что дело не в перегрузке контроллера, а в том, что каждая операция записи стала «дороже», чем раньше. CPU в таких случаях обычно ни при чём: если у вас не включена дедупликация (о том, почему её в большинстве случаев стоит избегать, мы писали в статье про дедупликацию в ZFS), нагрузка на процессор от самого ZFS остаётся стабильной и не объясняет деградацию именно записи.

Сеть проверяется быстро и обычно отпадает первой — если тормозит локальная запись на смонтированный датасет, а не сетевой шаринг, узкое место точно не в канале. После того как CPU, сеть и явная деградация железа исключены, а диск при этом показывает аномально высокую утилизацию на скромной нагрузке, расследование логично сворачивает в сторону самой файловой системы — и здесь начинается разговор про copy-on-write.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Почему ZFS никогда не пишет поверх старых данных

Чтобы понять, откуда берётся фрагментация именно в ZFS, а не в файловых системах вообще, нужно вспомнить один из фундаментальных принципов её устройства — copy-on-write (COW).

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

Из этого принципа вытекает почти всё, за что ZFS ценят:

  • Снапшоты почти бесплатны — снапшот — это просто «заморозка» указателей на определённый момент, а не копирование данных.
  • Устойчивость к повреждениям при сбое питания — раз данные не переписываются на месте, не может возникнуть состояние «наполовину записанный блок поверх старого».
  • Контрольные суммы и самолечение — при чтении блока ZFS всегда может сверить его с суммой, посчитанной на момент записи.

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

Фрагментация пула: почему важно не количество, а расположение

Здесь стоит зафиксировать ключевую мысль всей статьи: для ZFS имеет значение не только сколько свободного места осталось, но и как оно физически расположено на пуле.

Представьте два пула с одинаковыми 40% свободного места. В первом это пространство — несколько крупных непрерывных областей, в которые ZFS может писать длинными последовательными блоками почти на максимальной скорости накопителя. Во втором те же 40% раскиданы тысячами мелких «дырок» по 4-128 КБ между занятыми блоками — и чтобы записать один крупный файл, ZFS приходится дробить запись на множество мелких фрагментов, разбросанных по разным физическим адресам, вместо одной последовательной операции.

Механизм поиска свободного места в ZFS (allocator) устроен так, что он старается находить достаточно большие непрерывные участки под записываемые данные. Когда таких участков почти не остаётся, а есть только мелкая «нарезка», allocator тратит больше времени на поиск подходящего места и в итоге чаще соглашается на менее оптимальные, более мелкие и разрозненные куски. Каждая такая мелкая запись на вращающемся диске — это лишнее позиционирование головки, а на SSD/NVMe — менее эффективное использование очередей команд и внутреннего wear-leveling контроллера. В обоих случаях итог один: пропускная способность записи падает, даже если суммарно свободного места формально достаточно.

Именно поэтому ситуация «40% свободно, а пишет медленно» логически совместима с ZFS так, как она не совместима, например, с ext4 в большинстве сценариев — там фрагментация свободного пространства тоже существует, но её влияние на производительность записи обычно куда менее выражено, потому что ext4 умеет переписывать блоки на месте и не порождает такой же лавинообразный поток «дырок» от каждого изменения.

Метрика, которую не смотрели: fragmentation в zpool list

Ключевая ошибка в диагностике на этом этапе — смотреть только на процент занятого места и не смотреть на отдельную метрику фрагментации пула, которую ZFS считает и показывает явно.

zpool list
NAME    SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP  HEALTH  ALTROOT
tank   3.62T  2.17T  1.45T        -         -    47%    59%  1.00x  ONLINE  -

Обратите внимание: колонка CAP (занято) и колонка FRAG (фрагментация) — это два принципиально разных числа. CAP в 59% говорит только о том, сколько места занято данными. FRAG в 47% — это оценка ZFS того, насколько раздроблено оставшееся свободное пространство. Именно это второе число объясняет, почему запись медленная, а не первое.

Более подробную разбивку можно получить командой:

zpool list -v tank

она покажет фрагментацию по каждому vdev отдельно — иногда деградация конкретного диска в зеркале или raidz даёт неравномерную картину, и это стоит учитывать, если в пуле не один накопитель, а массив. Отдельно фрагментацию можно опросить и точечно:

zpool get fragmentation tank

Именно момент, когда в этой метрике обнаруживается высокое значение при вполне приемлемом проценте занятого места — и есть тот самый поворотный пункт расследования: становится ясно, что дело не в нагрузке и не в деградации железа, а именно в том, как ZFS распределила свободное пространство по пулу за время его жизни. Базовые термины пула, датасета и виртуальных устройств, на которых строится вся эта картина, разобраны в статье ZFS за 15 минут: пул, датасет, снапшот — если какие-то из них звучат незнакомо, стоит сначала закрыть этот пробел.

Стоит также свериться с логами zpool iostat -v 1 за период деградации — если средняя латентность записи (await в iostat, либо wait time в zpool iostat -w) заметно выросла именно на фоне роста FRAG, а не CAP, это дополнительное подтверждение диагноза, а не просто совпадение.

Порог 80% и почему фрагментация сама не проходит

У ZFS есть эмпирическое правило, о котором знают многие администраторы, но которое часто игнорируют, пока не столкнутся с проблемой на практике: не стоит доводить заполнение пула ZFS выше примерно 80%.

Дело не в какой-то жёсткой технической границе, а в том, что чем ближе пул к полному заполнению, тем меньше остаётся крупных непрерывных свободных областей и тем агрессивнее allocator вынужден соглашаться на мелкие, разрозненные куски — фрагментация в этой зоне растёт не линейно, а заметно ускоряется. Конкретный процент, при котором это становится ощутимо, варьируется в зависимости от паттерна нагрузки, размера записываемых блоков, включённого сжатия и типа vdev (зеркало, raidz), поэтому 80% — это ориентир и разумный запас, а не точная граница, за которой обязательно случится катастрофа.

Второй важный и часто неожиданный для новичков момент: фрагментация пула ZFS практически не уменьшается сама по себе со временем, даже после удаления большого количества данных. Причина в том же самом механизме copy-on-write — удаление файлов освобождает блоки, но не «сшивает» их обратно в крупные непрерывные области автоматически, а любая последующая активная запись (в том числе те же снапшоты, реплики, обычная работа приложений) продолжает создавать новую фрагментацию поверх старой. В отличие от систем, где регулярная фоновая дефрагментация — штатная практика, у ZFS такого встроенного «самолечения» фрагментации пула нет.

Это не значит, что фрагментация вообще не может снижаться — при удалении больших непрерывных объектов (например, старого снапшота, под которым освобождается сразу крупный непрерывный диапазон) метрика может локально улучшиться. Но рассчитывать на то, что пул «расфрагментируется сам» в общем случае не стоит — это скорее исключение, чем закономерность.

Что делать: мониторинг, запас и план на пересоздание пула

Из всего разобранного выше следуют практические выводы, которые стоит превратить в рутину, а не вспоминать о них постфактум.

Мониторить фрагментацию как отдельную метрику. Она должна быть на том же дашборде, что и процент занятого места, но не подменять его собой — это разные сигналы. Простая проверка для крон-задачи или системы алертинга:

zpool list -H -o name,cap,frag | while read name cap frag; do
  echo "$name: cap=${cap%\%} frag=${frag%\%}"
done

Порог алерта на фрагментацию стоит выбирать по опыту конкретной нагрузки, но само по себе отслеживание этого числа отдельно от занятого места — уже половина дела: подробнее о том, как вообще выстраивать мониторинг диска и на что реагировать в первую очередь, мы разбирали в статье про медленный диск на VPS и как его проверить.

Держать разумный запас по заполнению. Не подходить вплотную к порогу, за которым фрагментация резко ухудшает производительность записи — держать пул заполненным, скажем, до 70-75% в спокойном режиме, оставляя явный буфер, а не действовать по принципу «долью, пока не кончится место». О том, сколько дискового пространства вообще стоит закладывать с запасом на сервере — не только применительно к ZFS, но и как общий принцип планирования ёмкости — есть отдельный разбор: сколько дискового пространства закладывать с запасом.

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

Знать, что делать, если фрагментация уже критична. В части тяжёлых случаев, когда пул годами эксплуатировался у самого порога заполнения, постепенная «дефрагментация на месте» у ZFS попросту недоступна как штатная операция — единственный практический способ восстановить производительность записи это создать новый, менее фрагментированный пул (или на новых дисках, или временно освободив место) и перенести данные заново — например, через zfs send/zfs receive, что заодно перезаписывает данные крупными непрерывными блоками на новом пуле. Механику самой репликации мы разбирали в статье про zfs send и receive. Это не быстрая и не безболезненная операция, особенно на больших объёмах, но именно поэтому дешевле не доводить пул до такого состояния, чем потом его лечить.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Можно ли снизить фрагментацию пула ZFS без пересоздания, только удалив часть данных?

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

Какой процент фрагментации считается уже опасным?

Единого универсального порога нет — влияние зависит от нагрузки, размера блоков, типа vdev и включённого сжатия. Ориентируйтесь не на одно абсолютное число, а на динамику: если FRAG в zpool list растёт вместе с ухудшением реальной скорости записи, это уже повод разбираться, даже если формальный процент кажется не критичным.

Помогает ли сжатие (compression) снизить фрагментацию?

Косвенно и не всегда предсказуемо — сжатие уменьшает объём физически записываемых данных, что откладывает момент приближения к порогу заполнения, но само по себе не меняет механику copy-on-write и не устраняет фрагментацию как явление. О том, сколько реально экономит сжатие в ZFS, мы отдельно писали в статье про сжатие в ZFS.

Стоит ли использовать zfs send/receive регулярно как профилактику, а не только при критичной фрагментации?

Для нагрузок, где скорость записи действительно критична, периодический перенос на свежий пул по расписанию (например, раз в год или при достижении порогового значения FRAG) — вполне рабочая практика планирования ёмкости, а не крайняя мера. Для менее чувствительных к записи нагрузок это, как правило, избыточно — достаточно держать запас по заполнению и следить за метрикой.

Влияет ли количество дисков в пуле (raidz против зеркала) на скорость роста фрагментации?

Да, но опосредованно — через паттерн распределения записи и размер страйпа. Прямой рецепт здесь дать сложно без замера на конкретной нагрузке, а вот следить за FRAG по каждому vdev через zpool list -v полезно в любой конфигурации, чтобы увидеть, не деградирует ли один конкретный vdev быстрее остальных.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →