Почему DE сталкивается с дисками постоянно
Twoй Airflow worker внезапно падает с OSError: [Errno 28] No space left on device. Spark пишет: FileSystemException: Disk quota exceeded. Kafka log directory растёт, и брокер начинает отвергать продьюсеров. Postgres стопится потому что WAL-директория переполнена. Все эти сценарии — про диск, и первая команда, которую ты вводишь в такой ситуации, — df -h.
Дисковое пространство — это не просто «сколько байт свободно». У файловой системы есть два независимых ресурса: блоки данных (физическое место под содержимое файлов) и inodes (метаданные: имя, размер, владелец, указатели на блоки). Можно исчерпать один, не тронув второй, — и это будет ровно та же ошибка «No space left on device», но с совершенно другой причиной.
В этом уроке разберём df и du — две команды, которые отвечают на разные вопросы: «сколько на ФС-уровне» и «сколько внутри конкретной папки».
df: свободное место по mountpoint-ам
df (disk free) показывает использование всех смонтированных файловых систем. Не директорий — именно ФС. Если у тебя /, /home, /data — это три отдельные ФС, и df покажет их три строки.
$ df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/nvme0n1p2 488245288 312456712 151012576 68% /
tmpfs 8164532 0 8164532 0% /dev/shm
/dev/nvme0n1p1 523248 137224 386024 27% /boot
/dev/sdb1 1953514496 187654400 1668236096 11% /data
Без флагов df показывает блоки по 1 KiB. Это нечитаемо. Всегда используй -h (human-readable):
$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p2 466G 298G 144G 68% /
tmpfs 7.8G 0 7.8G 0% /dev/shm
/dev/nvme0n1p1 511M 134M 377M 27% /boot
/dev/sdb1 1.9T 179G 1.6T 11% /data
Флаг -h использует степени 1024 (KiB, MiB, GiB). Если хочешь степени 1000 (как у дисковых вендоров) — флаг -H. Разница на 1 TB диске — около 7%, и она объясняет, почему «купил 2 ТБ, а в системе 1.81».
df -hT: добавить тип ФС
Иногда важно знать, какая именно ФС под mountpoint-ом — ext4, xfs, tmpfs, или сетевая. Флаг -T:
$ df -hT
Filesystem Type Size Used Avail Use% Mounted on
/dev/nvme0n1p2 ext4 466G 298G 144G 68% /
tmpfs tmpfs 7.8G 0 7.8G 0% /dev/shm
/dev/nvme0n1p1 vfat 511M 134M 377M 27% /boot
/dev/sdb1 xfs 1.9T 179G 1.6T 11% /data
nas:/parquet nfs4 10T 4.2T 5.8T 42% /mnt/lake
tmpfs — это RAM, не диск. Если процесс пишет в /dev/shm или /tmp (на современных дистрибутивах часто tmpfs) — твой объём ограничен RAM, не SSD. nfs4 — сетевая, latency на ней в десятки/сотни раз выше.
df -i: inode-resource (отдельный счётчик)
Файловая система хранит для каждого файла inode — структуру в фиксированной таблице, созданной при mkfs. Сколько inodes доступно — задаётся при форматировании и не растёт автоматически. На ext4 типичное соотношение — один inode на каждые 16 KB места.
Это значит: если на разделе 1 TB и bytes-per-inode=16384, то inodes будет около 61 миллиона. Кажется много? Если у тебя миллиарды мелких лог-файлов (по 1 KB каждый), ты исчерпаешь inodes задолго до байтов.
$ df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2 30924800 892341 30032459 3% /
/dev/sdb1 121634816 60123884 61510932 50% /data
Внимательно посмотри: /data — 50% inode-использования при 11% использования места. Если на этом разделе бесконтрольно создаются мелкие файлы (например, Kafka segment files или Spark _temporary), ты можешь упереться в IUse=100% при свободных 1.5 ТБ места.
Файловая система может исчерпать один ресурс, не тронув второй. Оба дают одну и ту же ошибку 'No space left on device'.
Реальный кейс: «диск пустой, но писать нельзя»
$ df -h /data
Filesystem Size Used Avail Use% Mounted on
/dev/sdb1 1.9T 640G 1.3T 34% /data
$ touch /data/test
touch: cannot touch '/data/test': No space left on device
$ df -i /data
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sdb1 121634816 121634816 0 100% /data
100% inodes занято — touch не может создать новый файл, потому что для него нет inode-слота. Решение: найти, где живут миллионы мелких файлов. Это обычно: /data/spark_temp, /data/kafka-logs с маленьким segment.bytes, или дикое количество .parquet маленького размера.
$ for d in /data/*/; do echo -n "$d: "; find "$d" 2>/dev/null | wc -l; done | sort -t: -k2 -n
/data/checkpoints/: 4321
/data/parquet/: 89432
/data/spark_temp/: 12489321
Виновник найден: spark_temp забит мусором, который Spark не успел очистить. Удалить — освободить миллионы inodes.
du: размер директорий снизу вверх
du (disk usage) считает размер содержимого, рекурсивно проходя дерево директорий. В отличие от df, du оперирует на уровне путей, не файловых систем.
$ du /var/log
4 /var/log/sysstat
12 /var/log/apt/history.log
156 /var/log/apt
8 /var/log/journal/4e3a.../[email protected]
...
204800 /var/log
Без флагов du печатает размер в KB на каждую под-директорию рекурсивно — много шума. Полезные флаги:
Разные комбинации флагов отвечают на разные вопросы.
du -sh: размер всей папки одной строкой
$ du -sh /var/log
2.3G /var/log
$ du -sh /data/parquet
640G /data/parquet
Это самая частая форма. «Сколько весит вот эта папка?» — du -sh.
du -sh DIR/*: размер каждого подкаталога
$ du -sh /var/log/*
4.0K /var/log/alternatives.log
156K /var/log/apt
892M /var/log/airflow
1.2G /var/log/journal
12M /var/log/nginx
240K /var/log/syslog
Раскладывает по подкаталогам верхнего уровня. Глаз сразу видит виновника — /var/log/airflow и /var/log/journal занимают вместе ~2 ГБ.
du -hd 1: то же, но включая родитель
$ du -hd 1 /var/log
156K /var/log/apt
892M /var/log/airflow
1.2G /var/log/journal
12M /var/log/nginx
2.3G /var/log
Разница с du -sh /var/log/*: здесь видна суммарная строка «общий итог по /var/log = 2.3G». Удобно для отчётов.
du + sort -h: топ самых тяжёлых
DE-классика — найти, кто именно жрёт место:
$ du -sh /var/log/* 2>/dev/null | sort -h
4.0K /var/log/alternatives.log
12M /var/log/nginx
156K /var/log/apt
240K /var/log/syslog
892M /var/log/airflow
1.2G /var/log/journal
Флаг -h у sort понимает 4.0K, 892M, 1.2G и сортирует по реальной величине, не лексикографически. Без него 1.2G оказался бы где-то между 12M и 156K.
Хочешь топ-10 самых больших? Добавь | tail -10 или (если нужно от большего к меньшему) | sort -hr | head -10.
$ du -sh /var/log/* 2>/dev/null | sort -hr | head -5
1.2G /var/log/journal
892M /var/log/airflow
12M /var/log/nginx
240K /var/log/syslog
156K /var/log/apt
Swap, overcommit и OOM killer: когда памяти нет
df vs du: почему числа не совпадают
Каноничный вопрос на собеседовании Junior DE/SRE: «Запустил df -h / — показывает 80% занято. Сделал du -sh / — выходит, что файлов на ~50% диска. Куда делось 30%?»
Причины расхождения:
Несколько типичных причин, в порядке частоты на практике.
Случай №1 — самый частый и коварный. Сценарий: Airflow worker пишет в /var/log/airflow/scheduler.log через logging.FileHandler. Logrotate в полночь делает mv scheduler.log scheduler.log.1 + создаёт новый файл. Но Airflow всё ещё держит файл-дескриптор на старом inode. Logrotate потом удаляет .log.1 — старый inode переходит в state «unlinked». Блоки не освобождаются, пока процесс не закроет дескриптор или не перезапустится.
$ sudo lsof | grep deleted | head
airflow 12345 airflow 3w REG 254,2 892M 98321 /var/log/airflow/scheduler.log (deleted)
892M место занято — не виден через du, но df его считает. Решение: перезапустить процесс или попросить его reopen логи (kill -HUP для тех, кто понимает).
DE-паттерн: «найди жирные логи по всем сервисам»
Реальная задача: на VM с Airflow + Spark кончается место, нужно быстро понять, чьи логи самые тяжёлые.
$ sudo du -hd 1 /var/log 2>/dev/null | sort -hr | head -10
2.3G /var/log
1.2G /var/log/journal
892M /var/log/airflow
156M /var/log/spark
24M /var/log/postgres
12M /var/log/nginx
8.4M /var/log/auth.log
240K /var/log/syslog
156K /var/log/apt
84K /var/log/dpkg.log
Лидеры — journal и airflow. Дальше: внутри airflow что именно?
$ sudo du -hd 1 /var/log/airflow | sort -hr | head
892M /var/log/airflow
412M /var/log/airflow/scheduler
298M /var/log/airflow/dag_processor
156M /var/log/airflow/webserver
20M /var/log/airflow/triggerer
6.1M /var/log/airflow/celery
Самый тяжёлый — scheduler. Внутри его — поиск самого жирного файла:
$ sudo find /var/log/airflow/scheduler -type f -printf '%s %p\n' | sort -nr | head -5
345678901 /var/log/airflow/scheduler/scheduler.log
67890123 /var/log/airflow/scheduler/scheduler.log.1
12345678 /var/log/airflow/scheduler/scheduler.log.2
...
Найден огромный файл — теперь решение: настроить ротацию, ограничить уровень логов через AIRFLOW__LOGGING__LOGGING_LEVEL=WARNING, или почистить старые.
Подробнее про cleanup-стратегии — в уроке 03-find-by-size-and-cleanup. Управление systemd-журналом — в модуле 14-systemd-services.
Попробуй сам
- Сколько свободно на твоём корневом разделе:
df -h / - Посмотри тип твоих ФС:
df -hT - Сколько inodes ты использовал:
df -i / - Размер твоей home-директории:
du -sh ~ - Топ-5 самых тяжёлых поддиректорий в
/var:sudo du -hd 1 /var 2>/dev/null | sort -hr | head -5 - Найди deleted-but-open файлы (часто это «потерянное» место):
sudo lsof 2>/dev/null | grep '(deleted)' | awk '{print $7, $9, $10}'
macOS-различия
dfна macOS использует флаг-H(а не-h) для степеней 1000. Флаг-hтоже есть, но он по-другому форматирует. Лучше всегда-hдля совместимости.- На macOS файловая система APFS поддерживает clones (copy-on-write копии). Из-за этого
duможет показывать суммарный размер больший, чем реально на диске — потому что физические блоки шарятся между файлами. du -d Nна macOS работает, но GNU-вариант--max-depth=N— нет. Используй короткую форму.
Главное
df -h— свободно место по ФС-mountpoint-ам. Первая команда приNo space left on device.df -hTдобавляет тип ФС — отличает tmpfs (RAM) от ext4 (SSD) от nfs (network).df -i— inodes. Отдельный ресурс. Может закончиться, оставив сотни ГБ свободного места.du -sh DIR— размер одной папки.du -hd 1 DIR— раскладка по подкаталогам.du DIR/* | sort -h— топ по размеру. Базовый паттерн «найти виновника».dfиduмогут расходиться: deleted-but-open files (lsof | grep deleted), reserved blocks, permission errors, sparse files.- DE-паттерн:
du -sh /var/log/* | sort -hнаходит самый жирный лог за две секунды.