MAATRIX / Блог / Сколько inode вам выдали: считаем средний размер файла до того, как место кончится

Сколько inode вам выдали: считаем средний размер файла до того, как место кончится

MAATRIX

Сервер пишет No space left on device, а df -h в это же время показывает 20% занятого диска и кучу свободного места. Первая реакция — паника и попытки понять, что сломалось в ядре. На деле почти всегда дело не в байтах, а в inode: у файловой системы есть отдельный, фиксированный при создании лимит на число файлов, и он кончается независимо от того, сколько места они занимают. Ниже — как это устроено, как посчитать свой критический средний размер файла и что делать, если лимит уже упёрся в потолок.

Что такое inode и почему их число фиксировано

Inode (index node) — это структура метаданных, которая хранит всё о файле, кроме его имени: владельца, права, время изменения, размер, указатели на блоки данных на диске. Имя файла живёт отдельно, в записи каталога, которая просто ссылается на номер inode. Именно поэтому жёсткая ссылка (ln без -s) — это не копия файла, а второе имя для того же inode.

Ключевой момент: на файловых системах вроде ext4 таблица inode создаётся один раз, при форматировании (mkfs), и её размер фиксирован. Утилита mkfs.ext4 смотрит на размер раздела и коэффициент "байт на один inode" (bytes-per-inode, по умолчанию задаётся профилем в /etc/mke2fs.conf, ориентировочно один inode на каждые 16 КБ пространства для профиля по умолчанию — точное значение зависит от версии e2fsprogs и размера раздела) и заранее резервирует под метаданные фиксированное число слотов. Если файлов создано больше, чем есть слотов в этой таблице, новый файл создать нельзя — даже если на диске гигабайты свободного места под данные.

XFS в этом смысле устроена гибче: она выделяет inode динамически, по мере необходимости, из пространства, ограниченного параметром imaxpct (по умолчанию порядка 25% раздела). Это не убирает лимит полностью, но отодвигает его и делает менее вероятным на практике. Если пересаживаете сервер с большим числом мелких файлов на новый диск, разница между ext4 и XFS в этом вопросе — весомый аргумент; подробнее о том, когда выбор файловой системы вообще имеет значение, — в статье про ext4 и XFS.

Как посмотреть свою квоту inode: df -i

Самый быстрый способ проверить, не в inode ли дело, — та же команда df, но с ключом -i:

df -i

Вывод по структуре похож на обычный df -h, только вместо байтов — количество inode:

Filesystem      Inodes  IUsed   IFree IUse% Mounted on
/dev/sda1      1310720 1308910    1810   99% /

Здесь видно: свободных inode осталось всего 1810 при общем лимите 1,3 млн — использование 99%. Сравните это со столбцом IUse% в обычном df -h: если там, скажем, 30%, а тут 99% — вот и объяснение ошибки записи при формально свободном месте. Полезно смотреть оба вывода рядом:

df -h; echo "---"; df -i

Проверить точное число inode на разделе ext4 можно и через tune2fs:

sudo tune2fs -l /dev/sda1 | grep -i inode

Строки Inode count и Free inodes дадут те же цифры, что и df -i, но плюс размер одного inode (Inode size, обычно 256 байт на современных ext4) — это тоже часть бюджета диска, о которой редко вспоминают. Для XFS аналогичная информация — через:

sudo xfs_info /

Там будет строка вида isize=512 agcount=4, agsize=... — размер и число групп размещения, откуда и берётся динамический пул inode.

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

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

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

Считаем критический средний размер файла для своего диска

Раз число inode фиксировано, а объём диска тоже известен, можно посчитать пороговый средний размер файла: если реальный средний файл на разделе меньше этого порога, вы упрётесь в inode раньше, чем в байты. Формула простая:

критический_средний_размер = размер_раздела / общее_число_inode

Возьмём цифры из примера выше: раздел на 20 ГБ, 1 310 720 inode.

20 * 1024 * 1024 * 1024 / 1310720 ≈ 16 КБ

Получаем те самые ~16 КБ — это не совпадение, а прямое следствие коэффициента bytes-per-inode, с которым раздел форматировался. Смысл цифры: если средний файл на этом разделе меньше 16 КБ, диск физически не заполнится до 100% по месту раньше, чем закончатся inode. Больше 16 КБ — вы, скорее всего, упрётесь в свободное место раньше, чем в лимит файлов.

Посчитать реальный средний размер файла на разделе можно так:

FILES=$(find / -xdev -type f | wc -l)
BYTES=$(df -B1 --output=used / | tail -1)
echo "Файлов: $FILES, средний размер: $((BYTES / FILES)) байт"

Флаг -xdev у find важен — без него команда пройдёт и по смонтированным внутрь / разделам (например, /boot или примонтированным томам), что исказит подсчёт. На каталогах с миллионами файлов find может выполняться минуту и дольше — это ожидаемо, ждите.

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

Типичные сценарии, где inode кончаются первыми

На практике inode вычерпывают не абстрактные "много файлов", а несколько повторяющихся паттернов:

  • Файловые кеши с мелкими объектами. Кеш opcache в файловом режиме, кеш миниатюр CMS, статические кеш-файлы шаблонизатора — каждый элемент кеша может весить пару сотен байт — пара килобайт, а счёт идёт на сотни тысяч и миллионы записей. О самом механизме такого кеша — в материале про файловый кеш и грязные страницы.
  • Сессии PHP по умолчанию. Каждая сессия — отдельный файл в /var/lib/php/sessions (или аналог), обычно несколько сотен байт. При высокой посещаемости и долгом TTL сессий директория легко разрастается до сотен тысяч файлов.
  • Maildir на почтовом сервере. В формате Maildir (в отличие от mbox) каждое письмо — отдельный файл в каталоге cur/new/tmp. Ящик с десятками тысяч коротких писем без вложений съедает inode гораздо быстрее, чем место на диске — письма маленькие, а файлов много.
  • node_modules и артефакты сборки. У JS-проектов счёт файлов в node_modules легко идёт на сотни тысяч при почти нулевом среднем размере — множество мелких .js, .json, .d.ts файлов на пакет.
  • Логи без ротации, разбитые на мелкие файлы. Не гигантский растущий лог-файл (это скорее проблема места), а практика писать отдельный файл на каждый запрос/событие/задачу — типично для самописных систем очередей и трассировки.
  • Git-репозитории с большой историей. Объекты Git в .git/objects — тоже отдельные файлы, и на репозитории с многолетней историей и частыми коммитами их число может быть неожиданно большим (git gc частично решает, упаковывая объекты в pack-файлы).

Общий признак всех сценариев — генерация файлов автоматикой без верхнего предела и без периодической очистки. Ручное создание файлов человеком до таких масштабов почти никогда не доходит.

Что делать, если inode уже кончились

Первый шаг — найти виновника, а не гадать. du тут не поможет (он считает байты), нужен подсчёт количества файлов по каталогам:

for d in /var /home /opt /tmp; do
  echo "$d: $(find "$d" -xdev -type f 2>/dev/null | wc -l)"
done

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

Когда каталог-виновник найден, дальше зависит от природы файлов:

  • Кеш — можно просто очистить: сервис сам перегенерирует нужное по новой. Проверьте перед этим, что за кешем не стоит база, которую резко просевший хит-рейт может ощутимо нагрузить.
  • Сессии — устаревшие сессии обычно чистит сама платформа по крону (session.gc_probability в PHP), но если сборщик мусора не запускался долго — почистите вручную файлы старше TTL сессии:
find /var/lib/php/sessions -type f -mmin +1440 -delete
  • Почта в Maildir — удалять чужие письма руками нельзя, но можно найти ящики-аномалии (кто-то держит папку "Спам" на 200 тысяч писем без очистки) и предложить владельцу почистить или заархивировать в mbox.
  • Артефакты сборки/node_modules — если это не боевой процесс, а накопленные старые сборки CI, безопасно удалить всё, кроме последней:
find /var/ci-cache -maxdepth 1 -type d -mtime +14 -exec rm -rf {} \;

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

Как не упереться в лимит заранее

Реагировать по факту исчерпания — то же самое, что тушить пожар, а не ставить сигнализацию. Что стоит сделать заранее:

  1. Мониторить df -i наравне с df -h. Большинство систем мониторинга (Zabbix, Netdata, Prometheus node_exporter) снимают метрику по использованию inode из коробки — часто её просто забывают включить в алерты, настроенные только на процент занятого места. О базовой настройке мониторинга диска — в статье про мониторинг диска на VPS.
  2. Заранее считать плотность файлов для нового раздела. Если вы точно знаете, что сервис будет плодить мелкие файлы (Maildir, файловый кеш, очередь на файлах), задайте при форматировании явный коэффициент через -i (байт на inode) или явное число через -N:
sudo mkfs.ext4 -N 4000000 /dev/sdb1

Это резервирует 4 млн inode независимо от расчётного коэффициента по умолчанию — годится, если вы заранее знаете порядок величины будущей нагрузки файлами.

  1. Держать под мелкие файлы отдельный раздел или volume. Если сессии, кеш или очередь живут на отдельном разделе (а не в /), исчерпание inode там не парализует всю систему — корень и логи продолжат писаться.
  2. Использовать tmpfs для эфемерных файлов. Сессии, временные блокировки, локальные очереди можно вынести на tmpfs — она хранит данные в памяти, а число inode для неё задаётся при монтировании опцией nr_inodes и практически никогда не становится узким местом на серверах с достаточным объёмом RAM.
  3. Заменить файл-на-объект на СУБД или Redis там, где это уместно. Кеш и сессии из миллионов мелких файлов часто выигрывают при переносе в Redis или SQLite — там одна структура на диске, а не миллион inode. Сравнение подходов — в материале Redis или Memcached для сервера.
  4. Настроить ротацию и очистку по возрасту, а не полагаться на то, что сервис сам не забудет почистить за собой — cron-задача find ... -mtime +N -delete для предсказуемых каталогов кеша и временных файлов снимает проблему системно, а не разово.

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

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

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

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

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

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

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

Почему df показывает свободное место, а запись всё равно падает с No space left on device?

Потому что df -h считает байты, а лимит на количество файлов (inode) — отдельный ресурс. Проверьте df -i: если IUse% там близко к 100%, а по месту — далеко нет, дело именно в inode.

У XFS такая же проблема?

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

Можно ли увеличить число inode на уже работающем ext4-разделе?

Нет, без переформатирования — таблица inode фиксируется при mkfs. При расширении раздела resize2fs добавляет inode только под новое пространство, старую область не пересчитывает.

Как быстро понять, какая директория съедает inode?

Обычный du тут бесполезен — он мерит байты. Считайте количество файлов через find <каталог> -xdev -type f | wc -l по кандидатам верхнего уровня и сужайте поиск внутрь самого "тяжёлого" по числу файлов каталога.

Влияет ли этот лимит на tmpfs (память вместо диска)?

Да, но там предел задаётся опцией монтирования nr_inodes и по умолчанию считается от объёма RAM — на практике при достаточном объёме памяти это редко становится узким местом раньше, чем сама память.

Что будет с уже созданными файлами, если inode закончились?

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

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

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

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