MAATRIX / Блог / Мониторинг показывал 30% диска, а приложение писало «нет места»

Мониторинг показывал 30% диска, а приложение писало «нет места»

MAATRIX

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

Как выглядела картина в момент инцидента

Ситуация типична для сервера, который работает не первый месяц: приложение (это может быть веб-бэкенд, очередь задач, почтовый сервер или что угодно, активно создающее файлы) начинает получать ошибки при попытке создать новый файл — сохранить загруженную картинку, записать временный файл сессии, создать лог, положить письмо в очередь. В логах приложения при этом честно написано системное сообщение об ошибке — «нет места на устройстве». Дежурный инженер первым делом открывает мониторинг диска — и видит там что-то в духе 30% занятого пространства. Треть, а не 95-100%, при которых обычно и начинаются проблемы с местом.

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

Обычная панель мониторинга диска (Zabbix, Grafana с node_exporter, встроенные графики хостинг-панели) в подавляющем большинстве конфигураций по умолчанию отслеживает именно объём занятых байт — то, что показывает df -h. Это разумная метрика для 90% случаев, потому что типичная причина заполнения диска — это большие файлы: логи, бэкапы, дампы баз данных, видео, образы контейнеров. Но у файловой системы есть второй, независимый лимит, который эта же самая панель обычно не показывает вовсе — и именно в него здесь упёрлись.

Два разных ресурса файловой системы: байты и inode

Когда вы форматируете раздел под ext4, XFS или любую другую классическую файловую систему, при создании файловой системы (mkfs) резервируются не только блоки под содержимое файлов, но и отдельная, заранее фиксированная область под inode — структуры метаданных. На каждый файл и каждую директорию в файловой системе выделяется ровно один inode, независимо от того, сколько байт этот файл занимает. Пустой файл размером 0 байт и файл на 10 гигабайт с точки зрения расхода inode абсолютно равноценны — оба тратят один inode.

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

  • Место в байтах — заканчивается, когда сумма размеров всех файлов (плюс служебные накладные расходы блоков) достигает объёма раздела. Это то, что видно в df -h.
  • Число inode — заканчивается, когда общее КОЛИЧЕСТВО файлов и директорий достигает предела, заданного при форматировании. Это видно только в df -i, и подавляющее большинство систем мониторинга по умолчанию эту метрику не собирает и не показывает.

Если на файловой системе накопилось огромное количество мелких файлов — при этом каждый из них занимает совсем немного места, а суммарный байтовый объём остаётся скромным — можно полностью исчерпать доступные inode задолго до того, как закончится байтовое пространство. В этот момент система физически не может создать ни одного нового файла, даже нулевого размера: свободный inode взять неоткуда, хотя гигабайты свободного места на разделе действительно есть. Ядро в этом случае возвращает ровно ту же самую ошибку ENOSPC — «нет места на устройстве», — что и при обычном заполнении диска, потому что с точки зрения POSIX-интерфейса это одна и та же категория отказа. Разница видна только если явно посмотреть на использование inode отдельной командой.

Типичное соотношение по умолчанию при форматировании ext4 — примерно один inode на каждые несколько килобайт раздела (точное число зависит от версии mke2fs и профиля использования, указанного при форматировании, — -T news, -T largefile и так далее). Это рассчитано на «средний» файл в несколько десятков-сотен килобайт. Если реальная нагрузка на сервер создаёт файлы в разы мельче ожидаемого — по несколько байт-килобайт каждый, но в огромном количестве, — стандартное соотношение перестаёт работать, и лимит по числу файлов наступает раньше лимита по объёму.

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

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

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

Хронология расследования: от противоречия к диагнозу

Дальше разберём, как обычно раскручивается такой инцидент, если действовать по шагам, а не гадать.

Шаг 1. Зафиксировать противоречие. Мониторинг говорит — места достаточно (условно 30% занято). Приложение говорит — места нет. Оба утверждения не могут быть правдой об одном и том же ресурсе одновременно. Значит, речь о двух разных ресурсах — и это первая рабочая гипотеза, с которой стоит идти дальше, а не тратить время на пересборку приложения или проверку прав доступа.

Шаг 2. Проверить байтовое место напрямую на сервере. Даже если графику в мониторинге вы доверяете, стоит перепроверить руками:

df -h

Вывод подтверждает то, что уже видно на дашборде — раздел, куда приложение пишет файлы, занят на условные 30%, свободного места, если верить только этой команде, много.

Шаг 3. Проверить именно использование inode — отдельной командой. Это ключевой диагностический шаг, который отличает этот сценарий от всех остальных причин ошибки «нет места»:

df -i

Пример типичного вывода в момент такого инцидента:

Filesystem      Inodes  IUsed   IFree IUse% Mounted on
/dev/sda1      6553600 6553600      0  100% /var

Колонка IUse% — это процент занятых inode, а не байт. Если она показывает 100% (или близко к тому) при том, что df -h для того же раздела показывает свободное место — диагноз практически поставлен. Именно эта команда должна была быть первым шагом, но её редко запускают по привычке, потому что обычный мониторинг диска про неё «молчит» — не собирает эту метрику вовсе.

Шаг 4. Найти, где именно копятся мелкие файлы. Дальше нужно понять, какая директория поглотила все inode. Полезная связка команд — посчитать количество файлов по поддиректориям и найти самую «раздутую» ветку:

for dir in /var/*/; do
  echo "$(find "$dir" -xdev | wc -l) $dir"
done | sort -rn | head -10

Команда обходит директории верхнего уровня и считает количество объектов файловой системы (файлов и папок вместе) в каждой — не байты, а именно штуки. Обычно результат сразу выдаёт виновника: директория кэша, куда десятки тысяч мелких файлов писались без ротации; директория сессий веб-приложения; папка с логами, которые пишутся отдельным файлом на каждое событие, а не одним файлом с ротацией; либо очередь сообщений/писем, которая копится на диске без обработки и без TTL.

Корневая причина: накопление мелких файлов без очистки

За такой картиной почти всегда стоит один и тот же механизм — количество файлов росло медленно и незаметно, потому что процесс, который их создаёт, не имеет парной процедуры очистки, либо эта процедура сломана или отключена. Частые конкретные источники:

  • Кэш приложения на файловой системе (например, кэш-бэкенд PHP/Python-фреймворка, если настроен file-драйвер вместо Redis/Memcached) — каждый закэшированный объект превращается в отдельный мелкий файл, и без TTL-очистки или cron-джобы на чистку их число растёт неограниченно.
  • Логи, пишущиеся отдельным файлом на событие, а не одним файлом с ротацией — например, приложение, которое на каждый обработанный запрос создаёт request-<id>.log, вместо того чтобы писать в один общий журнал.
  • Файлы сессий веб-приложения (классический пример — файловые сессии PHP в /var/lib/php/sessions или /tmp) без включённой сборки мусора по истечении времени жизни сессии.
  • Временные файлы промежуточной обработки (конвертация, обработка загрузок, очереди задач), которые должны удаляться после обработки, но остаются из-за упавшего или неверно написанного шага очистки.
  • Почтовая очередь или очередь сообщений, где каждое сообщение — это отдельный файл на диске, и обработчик очереди отстаёт от притока новых сообщений или вовсе не запущен.

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

Практические действия: что делать прямо сейчас

Если вы читаете это в разгар инцидента, порядок действий такой:

  1. Подтвердите диагноз командой df -i (шаг выше) — убедитесь, что IUse% для проблемного раздела действительно близок к 100%.
  2. Найдите директорию-источник командой поиска по количеству файлов (см. шаг 4 выше) — не удаляйте ничего вслепую, сначала локализуйте проблему.
  3. Удалите накопленные мелкие файлы точечно — по возрасту, где это применимо:
find /var/cache/app -type f -mtime +7 -delete

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

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

Как не наступить на эти грабли снова

Главный вывод из инцидента — не «нашли и удалили файлы», а «мониторинг не показывал метрику, без которой этот класс отказов не виден заранее». Три конкретных изменения по итогам разбора:

Обязательно добавьте использование inode как отдельную метрику мониторинга. Не полагайтесь только на процент занятого места в байтах — это буквально другая шкала. Для Zabbix это отдельный item на vfs.fs.inode (или парсинг df -i через UserParameter, если готового шаблона нет), для Prometheus/node_exporter метрика node_filesystem_files и node_filesystem_files_free уже собирается экспортёром по умолчанию — остаётся только добавить на неё алерт и панель в Grafana, потому что сама метрика часто уже есть, просто на неё никто не смотрит и не настроил порог. Порог для алерта стоит ставить с запасом — скажем, при 80-85% занятых inode, точно так же, как для байтового места.

Подробно про настройку такого мониторинга с нуля — в статье «Как установить и настроить мониторинг диска на VPS»; там же разбираются типичные ошибки конфигурации самого мониторинга диска, из-за которых подобные метрики остаются невидимыми.

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

При создании файловой системы под нагрузку, которая заведомо создаёт много мелких файлов, — заранее закладывайте больше inode при форматировании, а не полагайтесь на значение по умолчанию, рассчитанное под типичное соотношение число-файлов/объём. Для ext4 это делается опцией -N (явное число inode) или -i (байт на inode, меньшее значение — больше inode) при вызове mkfs.ext4:

mkfs.ext4 -i 4096 /dev/sdb1

Значение -i 4096 означает «один inode на каждые 4 килобайта раздела» вместо более крупного значения по умолчанию — то есть под этот раздел будет выделено кратно больше inode, чем стандартно, ценой немного большего служебного расхода места на саму таблицу inode. Это разумно для директорий кэша, сессий, поэтапной обработки мелких файлов — везде, где ожидаемый средний размер файла заметно меньше «типичного». Учтите, что число inode фиксируется в момент форматирования и обычным resize2fs не увеличивается — если вы уже отформатировали раздел с недостаточным количеством inode, единственный надёжный способ поднять лимит — пересоздать файловую систему с нужными параметрами и перенести данные, поэтому закладывать запас стоит заранее, а не по факту столкновения с проблемой.

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

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

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

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

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

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

Можно ли увеличить число inode на уже отформатированном разделе без пересоздания файловой системы?

Для ext4 и XFS — нет, число inode фиксируется при форматировании (mkfs) и не меняется командой resize2fs или аналогами даже при расширении раздела. Единственный надёжный вариант — создать файловую систему заново с нужными параметрами (-N или -i у mkfs.ext4) и перенести данные, либо перенести нагрузку на новый раздел с более щедрым запасом inode.

Почему df -h вообще не предупреждает о нехватке inode заранее?

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

Может ли не хватить inode на диске с очень большим объёмом свободного места, в десятки гигабайт?

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

Какой командой быстро проверить, не подходит ли сервер к этой проблеме, не дожидаясь инцидента?

Регулярный df -i по всем смонтированным разделам (можно обернуть в простой cron-скрипт с алертом по превышению порога) плюс периодическая проверка количества файлов в директориях с высоким риском накопления — кэш, сессии, временные файлы, очереди — командой find <dir> | wc -l.

Отличается ли поведение при исчерпании inode на XFS от ext4?

Механизм тот же — оба варианта заранее резервируют фиксированное число inode (в XFS оно называется схоже и тоже видно через df -i), и оба возвращают ENOSPC при исчерпании. XFS в современных версиях умеет динамически выделять дополнительные inode по мере роста файловой системы при определённых настройках, что делает жёсткое исчерпание немного менее вероятным «из коробки», но не отменяет необходимость мониторить эту метрику отдельно.

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

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

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