Миллионы мелких файлов на storage VPS: иноды и перенос
Иноды кончаются раньше места: df -h показывает терабайты, а запись падает. Разбираем лимиты ext4 и XFS, что делать с исчерпанными инодами и как перенести миллионы файлов за часы.
Место на storage VPS есть, а файлы не создаются
Storage VPS с миллионами мелких файлов ограничен числом инодов. Иноды размечаются один раз, в момент создания файловой системы, и после этого их количество не меняется. Когда они кончаются, любая программа получает ошибку No space left on device, хотя df -h в ту же секунду показывает свободные терабайты. Отличить одно состояние от другого можно двумя командами.
df -h /srv
df -i /srvПервая показывает блоки, вторая показывает иноды. Если Use% в первой далеко от ста, а IUse% во второй равен ста, у вас кончились не гигабайты, а записи о файлах. Ниже разобрано, откуда берётся этот потолок, что делать, когда он уже достигнут, и почему перенос такого архива на другой сервер идёт сутками.
$ df -h /
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 39G 14G 24G 38% /
$ df -i /
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/vda1 2621440 2621440 0 100% /Число инодов в этом примере не выдумано. Системный диск на 40 ГиБ, размеченный mkfs.ext4 со значениями по умолчанию, получает ровно 2 621 440 инодов: 40 ГиБ делённые на 16384 байта на инод. Почта в формате Maildir, где под каждое письмо заводится отдельный файл, или выгрузка 1С, где на каждый документ пишется свой XML, выбирают такой запас за несколько месяцев.
Что такое инод и почему их количество конечно
Инод (inode, индексный дескриптор) хранит метаданные одного объекта файловой системы: права, владельца, временные метки, размер и адреса блоков с данными. Имени файла в иноде нет. Имя лежит в каталоге и указывает на номер инода, поэтому две жёсткие ссылки на одни данные занимают один инод. Файл, каталог, символическая ссылка и сокет занимают по одному иноду каждый, и размер здесь ни при чём. Пустой файл стоит целого инода и почти не стоит блоков. Отсюда весь эффект: архив сканов расходует иноды в тысячи раз быстрее, чем место.
В ext4 таблицы инодов создаются командой mkfs.ext4 и сами по себе потом не растут. mke2fs считает их число как размер тома, делённый на параметр bytes-per-inode. По умолчанию это 16384 байта на инод, а сам инод занимает 256 байт, так что под таблицы уходит около полутора процентов тома.
The data behind this chart
[
{
"label": "-i 4096",
"mln_inodes": 268.4
},
{
"label": "-i 8192",
"mln_inodes": 134.2
},
{
"label": "16384, \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e",
"mln_inodes": 67.1
},
{
"label": "32768, \u0442\u0438\u043f big",
"mln_inodes": 33.6
},
{
"label": "65536, \u0442\u0438\u043f huge",
"mln_inodes": 16.8
}
]Это не замер, а деление: терабайт в байтах на значение bytes-per-inode. При настройках по умолчанию терабайт даёт 67.1 млн инодов, и фотоархив такого потолка обычно не достигает.
Значение mke2fs выбирает сам, по размеру тома. До 4 ТБ берётся тип default с ratio 16384, от 4 ТБ до 16 ТБ тип big с ratio 32768, от 16 ТБ тип huge с ratio 65536. Вывод отсюда неочевидный. Том на 4 ТиБ получает по 33.6 млн инодов на терабайт, то есть около 134 млн всего, а том на 3,9 ТиБ получает по 67.1 млн, то есть около 262 млн. Диск больше, а запас файлов вдвое меньше. Если вы заказываете том именно под мелкие файлы, размечайте его сами и не полагайтесь на автоматику.
Как посмотреть свои цифры
df -i
sudo tune2fs -l /dev/sdb1 | grep -i inodetune2fs печатает Inode count, Free inodes, Inodes per group и Inode size. Число из Inode count для ext4 менять нечем: операции «добавить инодов» в файловой системе не существует.
XFS устроен иначе. Иноды в нём выделяются по мере появления файлов, а не размечаются заранее, поэтому жёсткого потолка нет. Ограничение задаёт параметр imaxpct. Man-страница mkfs.xfs описывает его так: максимальный процент места файловой системы, который может быть отдан под иноды, по умолчанию 25% для файловых систем меньше 1 ТБ, 5% для меньше 50 ТБ и 1% для больше 50 ТБ.
xfs_info /srv
sudo xfs_growfs -m 25 /srvПервая команда показывает текущий imaxpct в строке data, вторая задаёт новое значение. Инод в XFS с контрольными суммами занимает 512 байт, поэтому 5% от тома на 4 ТБ это примерно 200 ГБ под иноды, порядка 400 млн файлов. Для архива из мелких файлов XFS почти всегда спокойнее ext4, потому что ошибиться числом заранее нельзя.
На ZFS и Btrfs метаданные тоже выделяются динамически, и df -i там показывает оценку, которая меняется вместе со свободным местом. Если провайдер отдаёт готовый том на ZFS, отдельного лимита инодов у вас нет, но место под метаданные всё равно расходуется.
Куда уходят иноды на storage VPS
sudo du --inodes -x -d 1 /srv | sort -ndu --inodes считает файлы вместо байтов, -x не даёт уйти на другую файловую систему, -d 1 печатает только первый уровень. Запустите команду на корне тома, спуститесь в самый большой каталог, повторите. Обычно хватает трёх или четырёх шагов, чтобы найти виновника.
Второй способ сразу показывает каталоги с наибольшим числом файлов:
sudo find /srv -xdev -type f -printf '%h\n' | sort | uniq -c | sort -rn | head -20Что обычно съедает иноды на архивном сервере:
- сканы и фотографии, разложенные по файлу на страницу, где договор из десяти листов это десять объектов;
- выгрузки 1С, если конфигурация пишет по файлу на документ вместо одного архива на сеанс;
- почта в Maildir, где отдельный файл заводится под каждое письмо и каждое вложение;
- каталоги
.gitкрупных репозиториев с распакованными объектами; - временные файлы клиентов синхронизации, которые остались после обрыва связи;
- сессии PHP в
/var/lib/php/sessions, если сборщик мусора не отрабатывает.
Есть и вторая цифра, которая удивляет. Мелкий файл занимает целый блок, и при блоке 4 КБ пять миллионов файлов по 2 КБ займут не 10 ГБ, а 20 ГБ. Проверяется парой команд:
du -sh /srv/scans
du -sh --apparent-size /srv/scansПервая показывает занятые блоки, вторая сумму настоящих размеров. Разница между ними и есть плата за то, что файлы мелкие.
Иноды кончились. Что делать сейчас
Совет «удалите что-нибудь» здесь бесполезен, потому что удаление файла на 500 ГБ освобождает ровно один инод. Иноды освобождает только удаление многих мелких файлов, поэтому порядок действий такой.
Сначала уберите заведомо ненужное: временные файлы клиентов синхронизации, старые сессии, кэш миниатюр. Удаление тоже идёт медленно, потому что это миллион операций с метаданными. Это нормально, не перезапускайте команду, дайте ей закончить.
sudo find /var/lib/php/sessions -type f -mtime +2 -delete
sudo find /srv/tmp -type f -mtime +7 -delete
df -i /srvДальше упакуйте холодные годы в архивы. Место у вас есть, кончились именно иноды, поэтому архив спокойно ляжет на тот же том. Миллион сканов за 2019 год после упаковки занимает один инод вместо миллиона.
sudo apt install -y zip zstd pv
tar -C /srv/scans -cf /srv/archive/scans-2019.tar 2019
tar -tf /srv/archive/scans-2019.tar | wc -lВторой командой сверьте число файлов в архиве с тем, что было в каталоге, и только после совпадения удаляйте оригинал. Сжатие для сканов и фотографий почти ничего не даёт, JPEG и PDF уже сжаты внутри, поэтому -cf без z экономит часы. Для выгрузок 1С в XML картина обратная: там --zstd уменьшает объём в разы.
Если из архива иногда нужен один документ, tar неудобен, потому что до нужного файла придётся прочитать архив. ZIP хранит оглавление и достаёт файл сразу.
cd /srv/scans
zip -0 -qr /srv/archive/scans-2019.zip 2019
unzip -l /srv/archive/scans-2019.zip | tail -3
unzip -p /srv/archive/scans-2019.zip 2019/03/dogovor-0412.pdf > /tmp/dogovor.pdfФлаг -0 отключает сжатие, потому что содержимое уже сжато.
Третий вариант, если провайдер это умеет: расширить том. В ext4 файловая система разбита на блок-группы, у каждой своя таблица инодов, и при росте добавляются новые группы с тем же числом инодов на группу. Иноды растут пропорционально новому размеру, и это единственный способ получить их больше без перезаливки.
sudo resize2fs /dev/sdb1
sudo tune2fs -l /dev/sdb1 | grep -i 'inode count'Четвёртый вариант радикальный: пересоздать файловую систему. Данные придётся унести с тома и вернуть обратно, поэтому делайте это один раз и с запасом.
sudo mkfs.ext4 -i 4096 -L data /dev/sdb1
sudo mkfs.ext4 -N 300000000 -L data /dev/sdb1-i задаёт байты на инод, -N задаёт число инодов напрямую. Плата за -i 4096 считается сразу: инод занимает 256 байт, значит под таблицы уйдёт 6,25% тома, на 4 ТБ это 250 ГБ, которых вы никогда не увидите как свободное место. Взамен терабайт даёт 268.4 млн файлов.
Как посчитать нужное число инодов заранее
Посчитайте текущее число объектов и прирост за месяц. find /srv/scans | wc -l даёт первое, разница между двумя такими запусками с интервалом в месяц даёт второе. Умножьте прирост на 36 месяцев, прибавьте текущее число, умножьте на два и подставьте результат в -N. Запас нужен потому, что пересмотр решения стоит полной перезаливки тома. Если посчитанное число больше того, что даёт -i 4096 на вашем томе, значит объектов слишком много для одного тома, и архив пора раскладывать по нескольким.
Почему перенос миллиона файлов идёт сутками
Скорость канала здесь почти ни при чём. Платить приходится за каждый файл, а не за каждый байт. На один файл rsync делает stat с обеих сторон, открывает его, пишет во временный файл, выставляет права, владельца и времена, потом переименовывает. На приёмной стороне это несколько операций с метаданными, и каждая проходит через журнал файловой системы. Даже при нулевой сетевой задержке получаются сотни или тысячи файлов в секунду, а не десятки тысяч.
Задержка добавляет второй множитель. Протоколы, которые делают отдельный обмен на каждый файл, например клиенты SFTP, WebDAV или каталог, смонтированный через sshfs, упираются в RTT (round trip time, время прохода пакета туда и обратно). При RTT 33 мс один обмен на файл даёт 30 файлов в секунду, и гигабитный канал этого не меняет. Для читателя из России, который заливает архив в европейский дата-центр, RTT обычно составляет от 30 до 60 мс, поэтому страну для архива стоит выбирать с оглядкой на задержку и на закон.
The data behind this chart
[
{
"label": "30 \u0444\u0430\u0439\u043b\u043e\u0432/\u0441, \u043e\u0431\u043c\u0435\u043d \u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u0444\u0430\u0439\u043b",
"duration_h": 46.3
},
{
"label": "100 \u0444\u0430\u0439\u043b\u043e\u0432/\u0441",
"duration_h": 13.9
},
{
"label": "500 \u0444\u0430\u0439\u043b\u043e\u0432/\u0441",
"duration_h": 2.8
},
{
"label": "2000 \u0444\u0430\u0439\u043b\u043e\u0432/\u0441",
"duration_h": 0.7
},
{
"label": "10000 \u0444\u0430\u0439\u043b\u043e\u0432/\u0441, \u043e\u0434\u0438\u043d \u043f\u043e\u0442\u043e\u043a",
"duration_h": 0.14
}
]Это арифметика, а не замер: пять миллионов делить на скорость. Верхняя строка, 46.3 часа, это почти двое суток, и ровно так выглядит заливка архива протоколом с обменом на каждый файл. Нижняя строка, 0.14 часа, это тот же архив одним потоком.
Свою скорость измеряйте на подкаталоге, а не угадывайте. Возьмите один месяц, засеките время, разделите число файлов на секунды.
find /srv/scans/2024-01 -type f | wc -l
time rsync -a /srv/scans/2024-01/ storage:/mnt/data/scans/2024-01/Полученные файлы в секунду умножьте на весь архив. Если выходит больше суток, меняйте способ, а не запасайтесь терпением. Отдельно полезно понимать, какую скорость диска реально ждать от storage VPS, потому что на мелких файлах приёмник упирается в метаданные задолго до заявленных цифр последовательного чтения.
Есть ещё расход памяти. По умолчанию rsync 3.x использует инкрементальный обход и не строит весь список файлов заранее. Опции --delete-before, --delete-after, --prune-empty-dirs и --delay-updates этот режим отключают, и тогда rsync сначала прочитает всю иерархию целиком. На десяти миллионах файлов это гигабайты оперативной памяти, и процесс может быть убит OOM-killer ещё до начала передачи.
Как перенести миллионы мелких файлов на другой сервер
Первый способ, самый быстрый: один поток tar через ssh. Файлы едут внутри потока вместе с метаданными, отдельного обмена на каждый файл нет, диск читается последовательно.
tar -C /srv/scans -cf - 2019 | pv -bat | ssh -T user@storage 'tar -C /mnt/data/scans -xf -'pv -bat показывает переданный объём, среднюю скорость и прошедшее время. Для выгрузок 1С и любых текстовых данных добавьте сжатие, поток уменьшится в разы:
tar -C /srv/1c --zstd -cf - . | ssh -T user@storage 'tar -C /mnt/data/1c --zstd -xf -'У потока один минус, и он серьёзный: докачки нет. Обрыв на девяноста процентах означает повтор с начала. Поэтому режьте архив на куски, которые не жалко потерять, по году или по месяцу, и гоните их по очереди. Подробный разбор способа, вместе с проверкой целостности на приёмнике, есть в тексте про передачу каталога одним потоком через ssh и tar.
Второй способ, когда нужны докачка и версии: restic. Он режет данные на куски и складывает их в pack-файлы, по умолчанию около 16 МиБ каждый, поэтому на приёмнике оказываются тысячи объектов вместо миллионов. Проблема инодов на стороне хранилища исчезает сама собой.
sudo apt install -y restic
sudo sh -c 'umask 077; openssl rand -base64 32 > /root/.restic-pass'
export RESTIC_REPOSITORY=sftp:user@storage:/mnt/data/restic
export RESTIC_PASSWORD_FILE=/root/.restic-pass
restic init
restic backup /srv/scans
restic snapshotsФайл с паролем храните вне этого сервера: без него репозиторий не открывается никак. Первый прогон всё равно прочитает каждый файл и быстрее диска не будет. Зато второй читает только изменившееся, а на приёмнике не появляется новых миллионов файлов. Расписание, проверку и восстановление разбирает руководство по резервным копиям на VPS с помощью restic. И помните, что вторая копия рядом с оригиналом копией не считается: почему storage VPS сам по себе ещё не бэкап стоит прочитать до того, как удалите исходный каталог.
Третий способ, когда файлы обязаны остаться файлами: несколько rsync параллельно. Один процесс упирается в задержку и метаданные, четыре процесса по разным каталогам используют канал полнее.
ls -1 /srv/scans | xargs -P 4 -I{} rsync -a --mkpath --info=progress2 /srv/scans/{}/ storage:/mnt/data/scans/{}/--mkpath создаёт недостающие каталоги на приёмнике. Больше четырёх параллельных процессов на обычном томе обычно не помогает, потому что упор переходит в метаданные приёмной файловой системы. Флаг -z на сканах и фотографиях только тратит процессор.
После переноса сверьте результат. Совпадение числа файлов это минимум, сухой прогон rsync строже.
find /srv/scans -type f | wc -l
ssh storage 'find /mnt/data/scans -type f | wc -l'
rsync -ani /srv/scans/ storage:/mnt/data/scans/ | head -20Пустой вывод последней команды означает, что расхождений по именам, размерам и временам нет. Флаг -c заставит сравнивать контрольные суммы, но на миллионах файлов это отдельная многочасовая работа.
Что спрашивать у провайдера
На страницах тарифов лимиты инодов почти никогда не печатают. У обычного VPS их и нет: том ваш, файловую систему создаёте вы, и число инодов задаёт ваша же команда mkfs. Отсюда список вопросов до оплаты.
- Отдаётся ли диск сырым блочным устройством, которое можно разметить самому.
- Если образ уже готов, какая на нём файловая система и с каким bytes-per-inode. Проверяется на месте командами
df -iиtune2fs -l. - Можно ли расширить том без пересоздания, чтобы
resize2fsдобавил иноды. - Есть ли лимит на число файлов, если вы берёте не VPS, а управляемое хранилище.
Последний пункт стоит отдельного внимания. В объектных хранилищах инодов нет вообще, там платят за операции, и миллион мелких объектов это миллион запросов PUT со своей ценой. Разницу между моделями разбирает сравнение storage VPS и управляемого storage box, а базовое отличие тарифа с большим диском от обычного описано в тексте про storage VPS против обычного VPS. Объём для архива, который растёт по числу файлов, а не по гигабайтам, считается по своим правилам, и оценка нужного размера storage VPS полезна до заливки, а не после.
Если в архиве лежат сканы паспортов и договоров, это персональные данные, и хранить их стоит зашифрованными независимо от страны и провайдера. Варианты, которые не ломают перенос потоком, разобраны в материале про шифрование данных на storage VPS.
FAQ
Почему df показывает свободное место, а система пишет No space left on device?
Потому что кончились иноды, а не блоки. Запустите df -i: в такой ситуации IUse% равен 100 при Use% заметно меньше ста. Инод это запись о файле, и на ext4 их число задаётся при создании файловой системы и дальше не меняется, поэтому миллионы мелких файлов исчерпывают запас на почти пустом диске. Если IUse% тоже далёк от ста, ищите другую причину: место могут держать удалённые, но всё ещё открытые процессом файлы, это видно в sudo lsof +L1.
Иноды кончились прямо сейчас. Что делать?
Удаление одного большого файла не поможет, оно освобождает ровно один инод. Найдите каталоги с наибольшим числом файлов командой sudo du --inodes -x -d 1 /srv | sort -n, удалите заведомо мусорное (временные файлы клиентов синхронизации, старые сессии, кэш миниатюр), затем упакуйте холодные годы архива в один tar или zip на год. Миллион файлов после упаковки занимает один инод. Проверяйте df -i после каждого шага, и не прерывайте долгое удаление: миллион файлов удаляется миллионом операций с метаданными.
Можно ли добавить иноды на ext4 без пересоздания файловой системы?
Отдельной операции «добавить инодов» не существует. Рабочий обходной путь один: увеличить сам том у провайдера и выполнить resize2fs. ext4 добавит новые блок-группы, каждую со своей таблицей инодов, и число инодов вырастет пропорционально новому размеру. Если увеличить том нельзя, остаётся перезаливка через mkfs.ext4 -i 4096 или mkfs.ext4 -N с нужным числом, либо переход на XFS, где иноды выделяются динамически и ограничены только параметром imaxpct.
Что быстрее перенести миллионы мелких файлов: rsync, tar или restic?
Быстрее всех один поток tar через ssh, потому что нет обмена на каждый файл и диск читается последовательно, но у потока нет докачки, поэтому его гоняют кусками по году. restic медленнее на первом прогоне, зато складывает данные в pack-файлы примерно по 16 МиБ, продолжает работу после обрыва и не создаёт миллионы файлов на приёмнике. rsync берите тогда, когда файлы должны остаться файлами, и запускайте его в несколько параллельных процессов по разным подкаталогам.