Первой идеей было хранить их прямо в Postgres. PostgreSQL — вообще топовая база, она умеет работать с разными типами данных, не только стандартными для OLTP баз.
Файлы можно хранить как набор байт в поле типа
bytea. PostgreSQL унесет большие значения таких полей в специальное внутреннее хранилище TOAST.Но при проработке выяснились требования:
🟢 файлы большие: могут весить больше 5 МБ и даже больше 10 МБ
🟢 файлы не меняются, но загружаются новые версии, нужно хранить и старые версии тоже
🟢 файлы читаются намного чаще, чем пишутся
🟢 файлы не участвуют в транзакциях совместно с другими данными
Дополнительные факторы:
🟢 у нас настроена репликация БД (а значит будут тащиться и файлы, съедят пропускную способность)
🟢 соседняя команда уже использует S3 хранилище для изображений, т.е. нужная инфраструктура есть
По совокупности факторов приняли решение, что будем использовать S3 хранилище. В PostgreSQL — хранение метаданных (имя, тип, путь, права и др.), сами файлы — в S3. Спойлер: оказалось удобно, о решении не пожалели.
В продолжение истории давайте немного расскажу про S3 хранилище, это может пригодиться на сисдиз собесе.
➡️ Что такое S3
S3 расшифровывается как Simple Storage Service. Собственно, три S 😃
Это облачное хранилище файлов. Там хранят и картинки, и видео, и текстовые и любые другие файлы. S3 запустил Amazon, но сейчас такое хранилище есть у всех крупных облачных провайдеров, например, у Yandex Cloud, MTS Cloud.
➡️ Как устроено S3
В облачном хранилище есть бакеты. Бакет — это как отдельный namespace.
В бакетах лежат объекты (собственно наши разнородные файлы). Каждый файл можно получить по ключу. Ключ - это строка, которая выглядит как путь + имя файла:
reports/2025/december.pdf
На самом деле внутри S3 нет папок, это не файловая система, а плоское хранилище. Просто ключ так выглядит.
По уровню доступа бакеты бывают публичные и приватные.
Файлы из публичного бакета могут просматривать все: этот тип используют например для изображений товаров в интернет-магазине, общедоступных видео и др.
Файлы из приватного бакета могут читать только те, у кого есть нужные права.
➡️ Как загружать файлы в S3
Загрузка в S3 работает через HTTP/HTTPS.
Если файлы генерит сам бэкенд-сервис (например, формирует PDF или XLSX по каким-то данным), то тут понятный сценарий: в микросервисе подключаем библиотеку для работы с S3, с ее помощью вызываем нужные методы API S3 и загружаем файлы в хранилище.
Если файлы загружает пользователь, то возможны варианты:
✔️ Загрузка через бэкенд
➡️ Фронт отправляет файл на бэк
➡️ Бэк сохраняет метаданные в свою PostgreSQL, а сам файл кладет в S3 и сохраняет путь.
Это просто и безопасно, но дополнительная нагрузка на бэк.
Подходит для маленьких файлов, где важен контроль.
✔️ Загрузка в S3 напрямую с фронта через preSigned URL
➡️ Фронт просит бэк: "дай ссылку для загрузки файла" и передает бэку метаданные файла (имя, размер, тип и др.).
➡️ Бэк проверяет права, с помощью API S3 генерирует специальный URL для загрузки этого файла (preSigned URL) с коротким временем жизни (5-15 минут) и отдает его фронту.
➡️ Фронт по этому URL делает PUT и отправляет файл напрямую в S3.
Плюсы: не забиваем полосу передачей файлов на бэк
Минусы: требует более аккуратной настройки
Что такое preSigned URL
Это временная ссылка, которая дает доступ на загрузку (или скачивание) конкретного файла.
➡️ Как фронт получает файл
Если это публичный бакет (доступ всем), то просто загружает по ссылке.
Если это приватный бакет, то как и загрузка: либо сквозь бэк либо напрямую из S3 по ссылке, полученной от бэка.
S3 поддерживает multipart загрузку для больших файлов. Можно разбить файл на части и загрузить по частям.
➡️ MinIO
MinIO — это open-source объектное хранилище, поддерживающее S3 API, и его можно поднять локально. Используется как для локальной разработки и тестов, так и для замены облачного хранилища.
Правда, с декабря 2025 в open-source ветке MinIO закрыта активная разработка 😭
Используется ли у вас S3?
