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

Если принимать файлы через веб-приложение и сразу сохранять их в постоянное хранилище, один обрыв соединения или всплеск крупных загрузок быстро превращается в проблему для всего сервиса. Устойчивая схема отделяет передачу байтов от бизнес-логики: клиент загружает файл во временную область, сервер проверяет его и только после этого делает доступным. Ниже — архитектурный разбор для приложения с объектным хранилищем и PostgreSQL 16; конкретные лимиты и задержки нужно измерять на вашем провайдере и оборудовании.
Почему один endpoint становится узким местом
В простом варианте браузер отправляет файл на сервер приложения, тот держит запрос открытым, принимает тело и записывает объект. Пока это работает, схема кажется естественной: одна точка входа, одна запись в базе, один ответ. Но сервер одновременно становится прокси для всего объёма данных и диспетчером бизнес-операций.
При медленном соединении запрос дольше занимает сетевые и серверные ресурсы. При нескольких больших файлах приложение конкурирует с API за пропускную способность, соединения и место на диске. Если процесс перезапустится после записи файла, но до фиксации метаданных, хранилище и база разойдутся. Если сначала создать запись в базе, а загрузка оборвётся, появится объект «в ожидании», который никто не завершит.
Это не означает, что загрузка через приложение всегда неправильна. Она оправдана, когда файлы небольшие, поток умеренный, а серверу нужно обработать данные до сохранения. Но для крупных файлов и переменного трафика передачу обычно выгоднее вынести из приложения.
Разделите путь файла на состояния
Вместо одного действия задайте жизненный цикл: создана попытка загрузки, файл передан, проверка завершена, объект опубликован или отклонён. В PostgreSQL храните идентификатор, владельца, статус, размер, тип, ключ объекта и временные метки. Само содержимое — в объектном хранилище. Не записывайте в базу состояние «готово», пока файл нельзя безопасно получить по правилам приложения.
Надёжность даёт не обещание, что загрузка никогда не оборвётся, а возможность безопасно повторить её и определить, на каком шаге она остановилась.
Типовой маршрут выглядит так:
- Создать попытку. Сервер проверяет право пользователя на загрузку и создаёт запись со статусом «ожидает загрузки». Повторный запрос не должен создавать бесконечные дубликаты.
- Передать содержимое. Клиент загружает файл прямо в объектное хранилище по ограниченному разрешению либо передаёт его приложению, если это осознанный выбор для небольших файлов.
- Подтвердить завершение. Сервер сверяет результат с ожидаемыми параметрами и переводит запись в состояние «на проверке». Клиентское сообщение об успехе само по себе не является доказательством.
- Проверить и опубликовать. Фоновый обработчик проверяет ограничения, затем связывает объект с бизнес-сущностью и меняет статус на «готов».
- Убрать незавершённое. Периодическая задача удаляет просроченные попытки и временные объекты, не затрагивая опубликованные файлы.
У каждого перехода должен быть идемпотентный результат: повторное подтверждение или обработка сообщения не создают вторую публикацию. Идемпотентность1 особенно важна, когда клиент повторяет запрос после тайм-аута, не зная, успел ли сервер выполнить операцию.
Три схемы передачи и их цена
| Схема | Когда подходит | Чем платите |
|---|---|---|
| Через приложение | Небольшие файлы, умеренный поток, предварительная обработка на сервере | Приложение пропускает через себя весь трафик и дольше держит запросы |
| Прямая загрузка в объектное хранилище | Крупные файлы и рост объёма трафика | Нужно согласовать временное разрешение, проверку результата, очистку и публикацию |
| Многочастная загрузка | Очень большие файлы или нестабильное соединение | Сложнее управлять частями, повторной отправкой и удалением незавершённых загрузок |
В прямой схеме приложение выдаёт разрешение только на конкретный объект и ограниченное время, а клиент отправляет данные напрямую. Такой подход снимает передачу файла с серверов приложения, но не отменяет контроль: сервер должен сам решить, разрешать ли доступ к готовому объекту. Для S3-совместимого хранилища детали подписанных запросов и многочастной загрузки зависят от конкретного провайдера.
Многочастный режим позволяет повторить передачу отдельной части после обрыва, а не отправлять заново весь файл. Взамен появляются незавершённые сессии и расходы на оставленные части. Настройте их автоматическую очистку средствами хранилища или периодической задачей и проверьте, как именно провайдер считает такой объём и срок хранения.
Где схема ломается
Файл доступен до проверки
Нельзя считать объект безопасным только потому, что его загрузил авторизованный пользователь. Проверяйте размер и допустимый формат на серверной стороне; для сценариев с недоверенными файлами добавьте антивирусную или иную профильную проверку. До завершения проверок держите объект закрытым для обычных пользователей. Расширение имени и тип, сообщённый браузером, не дают надёжной гарантии содержимого.
Метаданные и объект расходятся
PostgreSQL и объектное хранилище — две независимые системы: общей атомарной транзакции между ними обычно нет. Поэтому проектируйте восстановление, а не рассчитывайте на идеальную последовательность. Например, если загрузка завершилась, а подтверждение не дошло, повторная сверка должна найти объект и безопасно довести попытку до следующего состояния. Если запись есть, а объекта нет, попытка должна истечь или перейти в ошибку.
Повторы создают дубликаты
Тайм-аут не говорит клиенту, завершилась ли операция. Используйте идентификатор попытки загрузки, проверяйте владельца и текущее состояние, а подтверждение обрабатывайте повторно без повторной публикации. Не делайте имя исходного файла ключом объекта: разные пользователи могут загрузить файлы с одинаковыми именами.
Очистка удаляет нужное
Временный объект и опубликованный файл должны различаться состоянием и, по возможности, правилами хранения. Очистка ориентируется на возраст незавершённой попытки, но перед удалением сверяет актуальный статус. Отдельно проверьте гонку: задача очистки стартовала одновременно с подтверждением загрузки.
Что измерять до запуска
Универсальных безопасных размеров файлов или тайм-аутов нет: они зависят от сети, хранилища, формата и нагрузки. Соберите базовую линию на типичных и предельных файлах, затем повторите тест при параллельной загрузке. Смотрите не только на среднее время: важны хвостовые задержки, доля обрывов и скорость восстановления после них.
- Время передачи и обработки отдельно; размер файла и число одновременных загрузок.
- Ошибки по этапам: создание попытки, передача, подтверждение, проверка, публикация.
- Количество и возраст незавершённых попыток, а также объём временных объектов и частей.
- Расхождения между записью в базе и объектом; число повторных обработок.
- Стоимость хранения и исходящего трафика: крупный файл могут скачать много раз, и это бывает дороже самой загрузки.
Сравнивайте схему через приложение с прямой передачей в одинаковых условиях: тот же размер файлов, параллельность и сеть. Если замера нет, не подставляйте выдуманное ускорение — сначала измерьте пропускную способность, загрузку процессора и время ответа API.
Что делать дальше
Начните с диаграммы состояний одной загрузки и определите, кто может переводить её между состояниями. Затем прогоните сценарии «клиент оборвался», «сервер перезапустился», «подтверждение пришло дважды» и «объект загрузился, но запись не обновилась». После этого настройте очистку незавершённых объектов, права доступа и мониторинг расхождений. Инвалидация кеша и доставка файлов через CDN — отдельные темы; здесь важнее сначала обеспечить корректный жизненный цикл исходного объекта.
1. Идемпотентность — свойство операции, при котором повтор запроса не меняет результат сверх первого успешного выполнения. Например, повторное подтверждение одной и той же загрузки не создаёт вторую запись о публикации.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: Amazon S3 User Guide: Uploading and copying objects using multipart upload, OWASP File Upload Cheat Sheet
Частые вопросы
Нужно ли всегда загружать файл напрямую в объектное хранилище?
Что делать, если соединение оборвалось во время загрузки?
Как не открыть файл до проверки?
Что будет, если объект записался, а строка в базе — нет?
Нужно ли хранить файлы в PostgreSQL 16?
Как понять, что пора переходить на многочастную загрузку?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.