Один большой архив или миллион файлов: что дешевле хранить годами
Есть архив логов, фотографий, отсканированных документов или экспортов — миллионы мелких файлов, которые нужно хранить годами, а трогать редко. Вопрос звучит просто: держать как есть, отдельными файлами, или упаковать всё в один большой архив и не мучить файловую систему? Простого ответа нет — оба варианта имеют цену, только она проявляется в разных местах: в одном случае платите операциями и метаданными каждый день, в другом — временем и неудобством в тот редкий момент, когда понадобится один конкретный файл.
Содержание
- Откуда берётся цена хранения множества мелких файлов
- Объектные хранилища: платите не за терабайты, а за операции
- Что теряете, когда упаковываете в архив
- Промежуточные варианты: контейнерные форматы с индексом
- Как выбрать: решает паттерн доступа, а не объём данных
- Практика: как перейти с одного подхода на другой без простоя
Откуда берётся цена хранения множества мелких файлов
У каждого файла в файловой системе есть накладные расходы, не зависящие от его размера. Нужен inode — структура с правами, владельцем, временем изменения и указателями на блоки данных. Нужна запись в каталоге, которая связывает имя с этим inode. Если директория большая, нужно ещё обновлять её собственный индекс. Файл в 200 байт и файл в 200 мегабайт с точки зрения этих операций стоят почти одинаково — разница тонет на фоне фиксированных издержек метаданных.
На практике это означает три конкретные вещи:
- Место на диске тратится не только на данные, но и на метаданные, и на округление до блока файловой системы (файл в 200 байт на ext4 с блоком 4 КБ всё равно резервирует минимум 4 КБ).
- Число inode у файловой системы фиксировано при форматировании и не зависит от того, сколько байт вы туда положите. Мы разбирали отдельно, как посчитать средний размер файла до того, как кончатся inode — для архива из миллионов мелких файлов это первое, что стоит проверить, прежде чем вообще спорить про экономику.
- Каждая операция — листинг, бэкап, проверка целостности, обход дерева для индексации — линейно растёт по числу файлов, а не по объёму данных. Директория на миллион мелких файлов ведёт себя на диске тяжелее, чем один файл в разы большего объёма, именно из-за этого.
Если у вас уже есть такая директория и она вызывает конкретные симптомы — ls думает секундами, find ползёт, бэкап не укладывается в ночное окно — это отдельная эксплуатационная проблема, разобранная в статье про то, где именно ломается директория с миллионом файлов. Здесь же речь не про то, "тормозит ли", а про то, что дешевле по деньгам на годовой перспективе — и это отдельный вопрос.
Объектные хранилища: платите не за терабайты, а за операции
Если данные лежат не на локальном диске сервера, а в S3-совместимом хранилище (AWS S3, Backblaze B2, self-hosted MinIO или Garage), экономика мелких файлов смещается. Объём хранения там обычно стоит предсказуемо и линейно — рубль или цент за гигабайт в месяц. А вот операции — PUT (загрузка объекта), GET (чтение), LIST (листинг), HEAD (проверка метаданных) — у большинства провайдеров тарифицируются отдельно, обычно по количеству запросов, а не по объёму переданных данных.
Практическое следствие: миллион отдельных объектов — это как минимум миллион PUT-операций при первичной загрузке и потенциально миллионы GET/HEAD при каждом полном обходе (индексация, аудит, повторная синхронизация). Один архив на 40 гигабайт, загруженный тем же миллионом файлов внутри — это одна операция PUT. Разница в количестве биллингуемых операций может быть на порядки, при абсолютно одинаковом объёме данных. Это тот же принцип, что и с inode на локальном диске, только выраженный не в лимите, а в цене за каждый вызов API.
Дополнительная деталь, о которую многие спотыкаются: операция LIST в объектных хранилищах с плоским пространством ключей (а не иерархией директорий, как в классической ФС) на самом деле проходится по префиксу и может быть не мгновенной и не бесплатной при регулярном полном сканировании бакета с миллионами ключей. Если ваш процесс раз в сутки перечисляет весь бакет, чтобы понять, что изменилось, — это тоже строка в счёте, которая растёт вместе с числом объектов, а не с их суммарным весом. Мы отдельно писали про разницу между объектным хранилищем и классической файловой системой — модели индексации там принципиально другие, но накладные расходы на операцию с единицей хранения никуда не деваются.
Точные тарифы у каждого провайдера свои и меняются, поэтому конкретные цифры за 1000 запросов здесь приводить не будем — их стоит смотреть в актуальном прайсе конкретного хранилища. Общий принцип устойчив: чем больше отдельных объектов, тем больше операций, и при прочих равных архивация в более крупные единицы хранения снижает счёт за операции, даже если счёт за объём остаётся тем же.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверЧто теряете, когда упаковываете в архив
Экономия на метаданных и операциях не бесплатна — плата переносится на доступ. У архива есть фундаментальное ограничение: чтобы достать один файл, форматы вроде tar.gz в общем случае требуют прочитать поток последовательно от начала, потому что сжатие потоковое и индекса положения файлов внутри архива нет.
# tar.gz: чтобы достать один файл, придётся распаковать
# поток вплоть до нужной позиции — при большом архиве это долго
tar -xzf archive-2026-08.tar.gz path/to/one-file.jpg
Есть форматы, которые снимают эту проблему частично — у них есть центральный индекс (central directory), и можно прочитать один файл без разбора всего архива:
# zip хранит центральный индекс — можно достать один файл,
# не трогая остальные, даже из архива на десятки гигабайт
unzip -l archive.zip | grep "one-file.jpg"
unzip -p archive.zip path/to/one-file.jpg > one-file.jpg
# то же самое видно и через список содержимого без распаковки
zipinfo archive.zip | head
Но и zip не решает вторую проблему — частичное обновление. Добавить один файл в существующий zip можно (zip -u), но это означает переписать структуру индекса архива, а не просто дописать данные в конец. Удалить один файл из середины архива — то же самое: большинство инструментов физически пересобирают архив заново, что при размере в десятки-сотни гигабайт означает полное перечитывание и перезапись. Если ваши данные регулярно меняются на уровне отдельных файлов — упаковка в один большой архив создаёт новую проблему взамен старой: вместо накладных расходов на миллион мелких файлов вы получаете накладные расходы на пересборку целого архива при каждом изменении.
Отдельный практический риск — целостность. Один большой архив — это единая точка отказа: битый байт в середине потока tar.gz при повреждении может сделать нечитаемым всё, что идёт после него, тогда как повреждение одного файла среди миллиона отдельных задевает только этот файл. Если у вас уже был случай, когда архив переставал открываться из-за ошибки CRC, вы знаете это на практике — мы разбирали, что ещё можно спасти из архива, который не распаковывается с ошибкой контрольной суммы.
Промежуточные варианты: контейнерные форматы с индексом
Между "миллион файлов как есть" и "один монолитный tar.gz" есть форматы, спроектированные именно под частый выборочный доступ к содержимому без полной распаковки.
SQLite как контейнер. Вместо файлов на диске — одна база SQLite, где каждая запись — это путь и BLOB с содержимым. SQLite сам эффективно работает с большим числом записей (B-tree индекс по ключу), поддерживает произвольное чтение и обновление одной записи без пересборки всей базы, и физически это один файл на файловой системе — один inode вместо миллиона.
sqlite3 archive.db <<'SQL'
CREATE TABLE files (
path TEXT PRIMARY KEY,
mtime INTEGER,
data BLOB
);
SQL
# добавление файла — обычная вставка, не требует пересборки всей базы
sqlite3 archive.db "INSERT INTO files VALUES ('logs/2026-08-01.log', 1756684800, readfile('logs/2026-08-01.log'))"
# выборочное чтение одного файла — без разбора остального содержимого
sqlite3 archive.db "SELECT writefile('out.log', data) FROM files WHERE path='logs/2026-08-01.log'"
Это не универсальное решение — SQLite не рассчитан на файлы весом в гигабайты каждый (там разумнее хранить путь до объекта во внешнем хранилище, а не сам BLOB), и параллельная запись из нескольких процессов ограничена блокировками на уровне файла базы. Но для сценария "много мелких файлов, нужен выборочный доступ по ключу, редкие изменения" — рабочий и недооценённый вариант.
SquashFS как read-only слой. Если данные после архивации меняться не будут вообще (закрытый проект, готовый релиз, снапшот для холодного хранения), можно собрать сжатый read-only образ файловой системы и монтировать его как обычную директорию — без распаковки на диск.
mksquashfs /data/finished-project project-2026-08.squashfs -comp zstd
mount -o loop project-2026-08.squashfs /mnt/project-2026-08
ls /mnt/project-2026-08/reports/ # обычный листинг, файлы читаются напрямую из образа
Плюс подхода — один файл на диске (экономия на метаданных и операциях), но при этом произвольный доступ к любому вложенному файлу работает как с обычной ФС, без полной распаковки. Минус — read-only: обновить содержимое можно только пересобрав образ целиком, то есть это вариант для по-настоящему завершённых, неизменяемых данных.
Как выбрать: решает паттерн доступа, а не объём данных
Объём хранимых данных в этом выборе почти не участвует — 40 гигабайт что архивом, что россыпью файлов займут плюс-минус одно и то же место (с поправкой на накладные расходы метаданных, которые для файлов мельче блока файловой системы могут быть заметны). Решает то, как вы читаете и меняете эти данные.
| Паттерн доступа | Лучше подходит | Почему |
|---|---|---|
| Читаете почти всегда всё целиком (бэкап для восстановления, ретроспективный разбор инцидента, готовый релиз) | Один архив / squashfs | Экономия на метаданных и операциях максимальна, редкое чтение целиком не страдает от отсутствия произвольного доступа |
| Часто нужен один конкретный файл по ключу, но данные почти не меняются | zip с индексом или SQLite-контейнер | Произвольный доступ без полной распаковки, при этом одна единица хранения вместо миллиона |
| Данные регулярно обновляются на уровне отдельных файлов | Отдельные файлы или объектное хранилище с шардированием по префиксу | Пересборка архива при каждом изменении дороже, чем накладные расходы на метаданные конкретного файла |
| Приложению нужен прямой путь в файловой системе (веб-сервер отдаёт файлы напрямую, легаси-код без API хранилища) | Отдельные файлы, при масштабе — иерархия поддиректорий | Изменение модели доступа под архив или объектное хранилище — уже архитектурная правка, не всегда оправданная ради экономии на хранении |
Если данных настолько много, что вопрос "архив или файлы" перерастает в вопрос "какое хранилище вообще использовать", стоит сначала прикинуть стоимость хранения терабайта на пятилетнем горизонте для разных вариантов — свой диск на сервере, объектное хранилище, холодный тариф — и уже на эту базу накладывать решение про архивацию. Для данных, которые точно не понадобятся в ближайший год, но выкидывать которые нельзя, отдельный разговор — холодное хранение архивов: там экономика ещё сильнее в пользу упаковки, потому что чтение по определению редкое и разовое.
Практика: как перейти с одного подхода на другой без простоя
Миграция в обе стороны делается поэтапно, без блокировки сервиса.
Из россыпи файлов в архив:
# 1. Собрать список файлов старше порога (условно — не менялись полгода)
find /data/uploads -type f -mtime +180 > to-archive.txt
# 2. Упаковать по списку, сохранив пути
tar -cf archive-cold-2026-08.tar -T to-archive.txt
# 3. Сверить количество файлов до удаления оригиналов — не пропустить ни одного
tar -tf archive-cold-2026-08.tar | wc -l
wc -l to-archive.txt
# 4. Удалить только те файлы, что реально попали в архив
xargs -a to-archive.txt rm --
Из архива обратно в отдельные файлы (если паттерн доступа изменился и понадобился произвольный доступ):
mkdir -p /data/uploads/restored
tar -xf archive-cold-2026-08.tar -C /data/uploads/restored
Держите в голове главную асимметрию: превратить россыпь файлов в архив — дёшево и быстро (один проход tar). Обратная операция тоже несложная технически, но если вы архивировали именно потому, что упирались в лимиты диска или операций — распаковка обратно вернёт ту же проблему, только позже. Прежде чем архивировать, стоит убедиться, что паттерн доступа к этим данным действительно "редко и целиком", а не просто временно неактивен.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Сжимать архив или оставить без сжатия?
Зависит от типа данных. Для уже сжатых форматов (jpg, mp4, gz-логи) сжатие почти не экономит место, но требует CPU на упаковку и распаковку. Для текстовых логов, CSV, JSON — сжатие обычно даёт заметную экономию, но конкретный процент стоит проверить на своих данных, а не считать заранее.
Что если файлов настолько много, что даже сборка одного архива не укладывается в разумное время?
Это отдельная проблема, не связанная напрямую с выбором "архив или файлы" — сама операция чтения миллионов файлов для упаковки упирается в те же накладные расходы метаданных. Разбор того, во что реально упирается архивация большого каталога, поможет понять, где узкое место — часто это не диск и не CPU, а количество системных вызовов.
Можно ли архивировать частями, а не всё сразу одним большим файлом?
Да, и это часто лучше единого монолита — например, по одному архиву на месяц или на проект. Так каждый архив остаётся управляемого размера, а произвольный доступ сужается до "найти нужный месяц, затем открыть архив за него", что почти всегда быстрее полной распаковки одного гигантского файла.
Стоит ли архивировать данные в объектном хранилище, если провайдер не берёт плату за операции?
Если тариф действительно построен только на объёме без платы за запросы — экономического стимула архивировать меньше, но накладные расходы на листинг миллионов ключей и практические лимиты некоторых API (пагинация, тайминги на массовые операции) всё равно остаются аргументом в пользу укрупнения единиц хранения.
Есть ли смысл держать и архив, и отдельные файлы одновременно?
На переходный период — да: например, недавние файлы остаются отдельными для быстрого доступа, а по мере старения переезжают в архив по расписанию. Это стандартная практика для логов и снапшотов: горячие данные — россыпью, холодные — упакованы.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →