Ты добавил в своё приложение загрузку аватарок: пользователь выбирает картинку, жмёт кнопку — и она появляется в профиле. Всё работает, пока файлов десять и живут они локально. А потом ты перезаливаешь приложение на сервер — и все аватарки исчезают. Или база данных вдруг весит три гигабайта, резервная копия делается полчаса, а счёт за хостинг растёт. Знакомая ситуация у каждого, кто впервые делает приложение с пользовательскими файлами. Решение у неё одно, и у него есть имя — S3-хранилище. Ниже — простыми словами о том, что это такое, почему файлам не место ни в базе данных, ни на диске сервера, как устроены ключевые понятия вроде bucket и presigned URL и какой сервис выбрать новичку из России.
Содержание
- Что такое S3-хранилище простыми словами
- Почему файлы нельзя хранить в базе данных
- Почему файлы нельзя хранить на диске своего сервера
- Ключевые понятия: bucket, object key и presigned URL
- Какое объектное хранилище выбрать: сравнение сервисов
- Как подключить S3-хранилище к своему приложению
- Безопасность: куда девать ключи доступа
- Когда S3-хранилище действительно нужно
- Частые вопросы
- Заключение
Что такое S3-хранилище простыми словами
S3-хранилище — это специальный сервис, который хранит файлы: картинки, видео, документы, архивы — отдельно от базы данных и отдельно от сервера, где крутится твоё приложение. Твоё приложение обращается к нему по сети: «положи вот этот файл», «дай вот этот файл», «удали вот этот». Всё, больше хранилище ничего не умеет — и в этом его сила.
Правильное техническое название — объектное хранилище. Файл там называется «объектом», потому что хранится целиком, как неделимая вещь, вместе со своим именем и служебной информацией: тип файла, размер, дата загрузки. Это принципиально отличается от базы данных, где информация разложена по таблицам, строкам и колонкам, и от обычной файловой системы, где файлы разбросаны по дереву папок на конкретном диске конкретного компьютера. Объектному хранилищу всё равно, на скольких физических машинах лежит твой файл: снаружи это единый сервис с единым адресом.
Откуда взялась буква и цифра в названии? S3 — это сокращение от Simple Storage Service, первого массового объектного хранилища, которое Amazon запустила ещё в 2006 году. Сервис стал настолько популярным, что его способ общения с приложениями — S3 API — превратился в фактический стандарт. Сегодня десятки провайдеров предлагают хранилища, которые «понимают» тот же самый S3-совместимый API. Для тебя как для разработчика это отличная новость: код, который работает с одним S3-хранилищем, почти без изменений заработает с любым другим. Поменял адрес сервера и ключи доступа — и переехал к другому провайдеру, не переписывая приложение.
Полезная аналогия. База данных — это картотека в офисе: карточки с записями, связями, ссылками друг на друга. А S3-хранилище — это склад на окраине с пронумерованными ячейками. В картотеке ты не хранишь сами вещи — ты хранишь карточку «ящик №42, третий ряд». Так и в приложении: в базе лежит запись «у пользователя Ивана аватарка лежит по такому-то адресу в хранилище», а сам файл — на «складе». Картотека лёгкая, поиск по ней быстрый, а склад справляется с любыми коробками любого размера.
Ещё одно важное свойство, о котором редко думают в начале: надёжность. Объектные хранилища по умолчанию дублируют каждый файл на несколько машин внутри дата-центра, а иногда и между дата-центрами. Провайдеры заявляют очень высокую долговечность данных — то есть вероятность потерять файл из-за сбоя оборудования ничтожно мала. Добиться такого же уровня надёжности на одном арендованном сервере своими руками — отдельный инженерный проект.
И последний штрих к картине: у объектного хранилища нет привычных операций «открыть файл и дописать кусок в середину». Объект либо загружается целиком, либо заменяется целиком, либо скачивается. Для аватарок, видео и документов это не ограничение, а норма — такие файлы и не редактируют по кусочкам. Зато именно благодаря этой простоте хранилище и умеет то, чего не умеют базы и диски: хранить миллионы файлов, раздавать их тысячам пользователей одновременно и стоить при этом недорого.
Почему файлы нельзя хранить в базе данных
Первое, что приходит в голову новичку: «у меня уже есть база данных, положу файлы туда». Технически это возможно — у большинства баз есть тип данных для бинарных объектов. Но на практике это одна из самых дорогих ошибок. В статье про базы данных для новичка мы разбирали, что база отлично справляется с текстом, числами, датами и связями между записями — это её стихия. А вот с большими бинарными файлами она справляется плохо, и вот почему.
Резервные копии раздуваются. Базу данных принято регулярно копировать — это твоя страховка на случай сбоя. Если в базе лежит только текст, копия весит мегабайты и делается за секунды. Если туда же напихали гигабайты картинок и видео, каждая копия превращается в многогигабайтный архив, который долго создаётся, долго скачивается и занимает дорогое место. При этом сами файлы не меняются — копировать их заново каждый день бессмысленно, но база будет делать это снова и снова, потому что не умеет отличить «вечную» картинку от свежей текстовой записи.
Запросы замедляются. База данных устроена так, чтобы быстро находить маленькие записи по условиям: «дай всех пользователей, зарегистрированных вчера». Когда таблица забита тяжёлыми бинарными полями, внутренние механизмы кэширования и индексации начинают работать хуже. База тратит память и процессор на то, для чего не предназначена, и это сказывается даже на тех запросах, которые к файлам отношения не имеют.
Растёт счёт за хостинг. Место, где живёт база данных, — самое дорогое хранилище из всех: быстрые диски, резервирование, реплики. Объектное хранилище за тот же гигабайт берёт заметно меньше, потому что оно спроектировано именно под файлы и использует более дешёвую инфраструктуру. Платить за гигабайт в базе вместо гигабайта в S3 — всё равно что хранить коробки с вещами в арендованном офисе вместо склада.
Отдавать файлы неудобно. Когда браузер пользователя просит картинку, приложению приходится вытащить её из базы, а потом передать по сети. Каждый просмотр картинки — это нагрузка и на базу, и на приложение: сотня человек, открывших ленту с фотографиями, заставит твою базу работать файловым сервером. С S3 всё проще: приложение отдаёт браузеру ссылку, и браузер скачивает файл напрямую из хранилища, минуя твой сервер вообще. Хранилище построено именно для раздачи и переварит такой трафик играючи.
Почему файлы нельзя хранить на диске своего сервера
Вторая идея новичка: «сохраню файлы в папку uploads рядом с кодом приложения». Так делают все учебные примеры, и локально это действительно работает. Проблемы начинаются при публикации — и их сразу три.
Диск сервера часто недолговечен. Современные платформы развёртывания при каждом обновлении приложения собирают его заново, в чистом окружении. Всё, что приложение записало на диск в прошлый раз, при переразвёртывании просто стирается. Мы подробно разбирали этот момент в статье про деплой на Amvera: загрузил новую версию кода — и папка uploads со всеми пользовательскими файлами исчезла. Представь лицо пользователя, который зашёл в приложение, а его аватарка и все документы пропали после твоего «безобидного» обновления.
Один сервер не масштабируется. Пока у тебя сто пользователей и сотня картинок, раздача файлов с того же сервера не проблема. Но если приложение выстрелит и картинки начнут смотреть тысячи людей одновременно, весь трафик упрётся в один сервер: канал, диск, процессор. Объектные хранилища изначально построены для раздачи файлов в любых объёмах — это их основная работа, и растущая популярность приложения для них не стресс, а норма.
Файл «привязан» к одной машине. Как только приложение разрастётся до нескольких серверов за балансировщиком нагрузки, локальный диск сломается концептуально: пользователь загрузил файл на сервер А, а следующий его запрос попал на сервер Б — и там файла нет. Чтобы это починить, всё равно придётся выносить файлы в общее внешнее хранилище. Проще сделать это сразу, чем переписывать под нагрузкой.
Нет резервирования. Диск один — значит, единственная копия файлов одна. Сбой диска, ошибка в скрипте, случайное удаление — и файлы потеряны навсегда. Хранилища, как мы уже говорили, по умолчанию дублируют каждый объект на несколько машин.
Общий принцип звучит так: сервер приложения должен быть «безгосударственным» — его можно в любой момент удалить и поднять заново из кода, ничего не потеряв. Всё, что накапливается со временем, живёт вовне: данные — в базе, файлы — в объектном хранилище.
Ключевые понятия: bucket, object key и presigned URL
Чтобы не плавать в документации, достаточно выучить четыре термина. Они одинаковые у всех S3-совместимых сервисов — и у AWS, и у Яндекса, и у MinIO.
Bucket (бакет) — контейнер для файлов, аналог папки верхнего уровня. Обычно под приложение создают один бакет, например my-snake-app, а внутри раскладывают файлы по «подпапкам»: аватарки отдельно, скриншоты отдельно. Имя бакета уникально в рамках всего сервиса, как доменное имя, поэтому короткие красивые имена часто заняты. У бакета есть настройки: регион, где физически лежат данные, права доступа, правила автоматического удаления старых файлов.
Object key (ключ объекта) — полное имя-путь файла внутри бакета, например avatars/ivan/photo.png. Формально в объектном хранилище нет настоящих папок: есть плоский список объектов, а «папки» — просто префиксы в именах. Но веб-интерфейсы и библиотеки показывают это как привычное дерево, так что разницы в использовании ты не заметишь. Пара «бакет + ключ» однозначно указывает на файл — это его адрес внутри хранилища.
Публичный и приватный доступ. Бакет или отдельный объект можно сделать публичным — тогда файл доступен любому по прямой ссылке. Так делают для картинок сайта, логотипов, открытых материалов. Приватный объект по прямой ссылке не откроется: хранилище потребует подтверждения прав. Так хранят личные документы пользователей, скриншоты, служебные выгрузки. По умолчанию всё приватное — и это правильно: утечки данных через случайно открытые бакеты регулярно попадают в новости, причём страдают даже крупные компании.
Presigned URL (предподписанная ссылка) — самый элегантный механизм во всей этой системе. Проблема: как дать браузеру пользователя загрузить или скачать приватный файл, не сообщая ему секретные ключи от хранилища? Ответ: приложение на своём сервере, где ключи в безопасности, генерирует специальную временную ссылку — с криптографической подписью, которая действует ограниченное время (ты сам задаёшь срок, например 15 минут) и разрешает одну конкретную операцию с одним конкретным файлом. Браузер получает эту ссылку и работает с файлом напрямую. Ключи никуда не утекают, файл не гоняется через твой сервер, а по истечении срока ссылка становится бесполезной.
К этим четырём терминам полезно добавить ещё два слова, которые встретятся в настройках библиотеки. Endpoint — это адрес сервера хранилища, к которому обращается приложение; именно сменой endpoint и отличается подключение к разным провайдерам. Регион — это дата-центр (или группа дата-центров), где физически лежат твои файлы; регион выбирают поближе к пользователям, чтобы сократить задержки.
Вот как выглядит типичный цикл загрузки файла целиком:
flowchart TB
U["Пользователь выбирает файл"] --> A["Приложение генерирует presigned URL"]
A --> S["Файл загружается напрямую в S3-хранилище"]
S --> D["В базу данных записывается только ссылка"]
D --> P["При показе браузер забирает файл прямо из хранилища"]
Обрати внимание, чего на этой схеме нет: файл ни разу не проходит через базу данных. База хранит только текстовую строку-ссылку — а это её любимая работа. И обрати внимание, насколько разгружен твой сервер: он участвует только в выдаче коротких ссылок, а тяжёлые байты ходят между браузером и хранилищем напрямую.
Какое объектное хранилище выбрать: сравнение сервисов
Раз API везде одинаковый, выбор сводится к практическим вопросам: как платить, нужен ли VPN, где физически стоят серверы и сколько стоит трафик. Вот честная сравнительная таблица вариантов, о которых чаще всего спрашивают новички из России:
| Сервис | S3-совместимость | Оплата из РФ | Доступ без VPN | Кому подходит |
|---|---|---|---|---|
| AWS S3 | Оригинал, эталон | Нужна иностранная карта | Может требоваться VPN | Те, у кого уже есть зарубежная оплата и опыт |
| Cloudflare R2 | Да | Нужна иностранная карта | Может требоваться VPN | Проекты с большим трафиком раздачи: нет платы за исходящий трафик |
| Yandex Object Storage | Да | Да, рубли, счёт для самозанятых и ИП | Да | Основной вариант для большинства читателей курса |
| Selectel Объектное хранилище | Да | Да, рубли | Да | Альтернатива Яндексу, тоже российский провайдер |
| MinIO (свой сервер) | Да, это сама суть проекта | Бесплатно (платишь только за свой сервер) | Да | Эксперименты, локальная разработка, полный контроль |
Пара пояснений к таблице.
AWS S3 — это оригинал, с которого всё началось, и эталон совместимости: если что-то работает на AWS S3, оно почти наверняка заработает и у остальных. Но для разработчика из России путь сюда лежит через иностранную карту и обход ограничений — ради учебного проекта это лишние сложности.
Cloudflare R2 часто вспоминают в обсуждениях из-за необычной тарифной модели: сервис не берёт плату за исходящий трафик, то есть за раздачу файлов пользователям. У классических хранилищ трафик — отдельная и часто главная строка расходов, поэтому для приложения, которое отдаёт много картинок и видео, R2 даёт заметную экономию. Но оплата и доступ из России — с теми же зарубежными оговорками, что и у AWS.
Yandex Object Storage и Selectel — главный практический вывод таблицы. Регистрация с российским номером, оплата в рублях, закрывающие документы для самозанятых и ИП, никаких VPN и иностранных карт. Серверы в России — файлы быстро отдаются российским пользователям. Для первого приложения с аватарками и скриншотами этого более чем достаточно. Актуальные тарифы смотри на сайтах провайдеров: цены и условия меняются, а плата обычно складывается из трёх составляющих — объёма хранения, количества операций (загрузок и скачиваний) и исходящего трафика.
MinIO заслуживает отдельного абзаца. Это не облачный сервис, а программа с открытым кодом: поднимаешь на своём сервере — получаешь собственное S3-хранилище. Именно поэтому запрос «s3 хранилище minio» так популярен: люди ищут способ получить S3 без облака. MinIO удобен для локальной разработки — код пишешь и тестируешь дома против своего MinIO, а в боевом окружении переключаешься на облачный сервис, меняя только настройки. Для учебного проекта вроде «Змейки» это отличный способ потрогать S3 API бесплатно и без регистраций. Минус очевиден: своё хранилище — это свои заботы о дисках, бэкапах и доступности. Если твой сервер умрёт, спасать файлы придётся самому.
Как подключить S3-хранилище к своему приложению
Хорошая новость: тебе не нужно реализовывать протокол S3 руками. Схема подключения одинакова для всех сервисов и сводится к четырём шагам.
Шаг 1. Создай бакет. Регистрируешься у провайдера, в личном кабинете создаёшь bucket с понятным именем и оставляешь его приватным, если файлы не предназначены для всех. Выбирай регион поближе к своим пользователям — так файлы будут отдаваться быстрее.
Шаг 2. Получи ключи доступа. В том же личном кабинете генерируешь пару: access key (публичный идентификатор, как логин) и secret key (секретный ключ, как пароль). Этой парой будет подписывать запросы только твой сервер. Secret key показывают один раз — сразу сохрани его в надёжное место.
Шаг 3. Подключи библиотеку. Для JavaScript-проектов есть готовый npm-пакет — официальный aws-sdk (точнее, его модуль для S3) или совместимые альтернативы. Несмотря на название, библиотека работает с любым S3-совместимым сервисом: указываешь адрес хранилища (endpoint), регион, ключи — и дальше вызываешь готовые методы: загрузить файл, получить ссылку, удалить объект. Если пишешь приложение вместе с ИИ-ассистентом, задача формулируется буквально так: «подключи S3-совместимое хранилище, загружай аватарку пользователя через presigned URL и сохраняй ссылку на неё в базе». Ассистент подставит конкретный синтаксис под твой стек.
Шаг 4. В базу — только ссылку. После успешной загрузки хранилище подтверждает, что объект на месте. Приложение склеивает из бакета и ключа адрес файла и записывает эту текстовую строку в базу данных — в обычное текстовое поле рядом с пользователем. Сам файл в базе не появляется никогда. Когда нужно показать аватарку, приложение читает строку из базы и подставляет её в тег картинки — браузер скачивает файл напрямую из хранилища.
Один технический нюанс, о котором новички узнают из сообщений об ошибках: если браузер загружает файл напрямую в хранилище по presigned URL, в настройках бакета нужно разрешить запросы с домена твоего приложения — это называется CORS. Без этого разрешения браузер заблокирует загрузку из соображений безопасности. Настраивается один раз в личном кабинете провайдера.
Чтобы картина сложилась окончательно, вот весь серверный код загрузки аватарки в виде схемы — без привязки к конкретной библиотеке и синтаксису:
1. Браузер: «хочу загрузить файл ivan.png, 2 МБ»
2. Сервер проверяет: пользователь вошёл, формат — картинка, размер в лимите
3. Сервер генерирует presigned URL на загрузку в бакет, ключ "avatars/ivan.png"
4. Браузер получает ссылку и отправляет файл прямо в хранилище
5. Хранилище подтверждает загрузку
6. Сервер записывает в базу: user ivan → avatar = "avatars/ivan.png"
Шесть строк логики — и ни одного файла через сервер. Когда дойдёшь до реализации, ИИ-ассистент напишет из этой схемы рабочий код под твой проект: твоя задача — понимать шаги и следить, чтобы ключи оставались на сервере.
Типичные ошибки на этом этапе: попытаться проксировать все файлы через свой сервер «потому что так проще» (теряется весь смысл хранилища — сервер снова становится узким местом), сделать бакет публичным «чтобы точно открывалось» (личные файлы пользователей увидит кто угодно) и забыть ограничить размер и тип загружаемых файлов (пользователь зальёт десятикигабайтное видео вместо аватарки — а платить за хранение будешь ты). Проверяй формат и размер до генерации presigned URL, а лучше и после загрузки тоже.
Безопасность: куда девать ключи доступа
Access key и secret key от хранилища — это полномочия. Тот, кто ими завладел, может читать и удалять твои файлы, а главное — заливать свои, за хранение которых заплатишь ты. Известны случаи, когда слитые ключи использовали для размещения чужого контента, а владельцу приходил счёт на круглую сумму.
Правила простые и не обсуждаются:
- ключи никогда не попадают в код и не коммитятся в git — даже в «временный» коммит, даже в приватный репозиторий;
- ключи лежат в файле
.env, который добавлен в.gitignore, а на боевом сервере — в настройках переменных окружения платформы; - в браузерный (клиентский) код ключи не вшиваются ни в каком виде — всё, что попало в браузер, видно любому пользователю через «просмотр кода»; именно для этого и существуют presigned URL;
- если ключ всё-таки засветился — в коммите, скриншоте, переписке — немедленно отзови его в личном кабинете и сгенерируй новый; боты сканируют публичные репозитории на ключи постоянно, и счёт идёт на минуты.
Подробно про то, как правильно хранить пароли, токены и ключи, мы разбирали в статье про хранение секретов и токенов — если ещё не читал, самое время: ошибки с секретами самые дорогие из всех ошибок новичка.
Дополнительно: заведи для приложения отдельный ключ с минимальными правами — только запись и чтение в один конкретный бакет, без права удалять бакеты и управлять аккаунтом. Если такой ключ и утечёт, ущерб будет ограничен одним бакетом.
Когда S3-хранилище действительно нужно
Честный вывод: не каждому приложению нужно объектное хранилище, и подключать его «на вырост» — такая же ошибка, как класть файлы в базу.
S3-хранилище оправдано, когда в приложении появляются файлы, которые загружают сами пользователи: аватарки, скриншоты, фото, документы, игровые ассеты, аудио. Особенно когда таких файлов много и каждый весит больше нескольких мегабайт. Здесь без хранилища никак: база захлебнётся, а диск сервера не переживёт первого же обновления.
Есть и менее очевидный случай: файлы, которые создаёт само приложение. Если твоя «Змейка» генерирует пользователю красивую карточку с рекордом для соцсетей или сохраняет скриншоты лучших игр, это тоже накапливающиеся файлы, а не часть кода — им тоже место в хранилище, а не в папке проекта. Тот же принцип касается бэкапов, экспортов данных и любых отчётов, которые приложение собирает «на лету».
И наоборот: если у твоей «Змейки» есть пяток иконок интерфейса, логотип и пара фоновых картинок — ничего никуда выносить не надо. Эти файлы часть самого приложения, они меняются только вместе с кодом, и им самое место в публичной папке проекта, рядом с остальными файлами. Они попадут на сервер при обычном развёртывании и никуда не денутся.
Граница проходит по вопросу: «кто создаёт эти файлы?» Разработчик вместе с кодом — папка проекта. Пользователи в процессе работы — объектное хранилище плюс ссылка в базе.
Частые вопросы
Чем S3-хранилище отличается от обычного облачного диска вроде Google Drive?
Облачный диск сделан для человека: папки, общий доступ через интерфейс, синхронизация с компьютером. S3-хранилище сделано для программ: у него нет «интерфейса для людей», есть API, через который приложение кладёт и забирает файлы. Плюс тарификация под машинные нагрузки: гигабайты хранения и трафик раздачи, а не «место в аккаунте». Встроить Google Drive как файловое хранилище приложения — значит влезть в его ограничения и условия использования, которые для этого не предназначены.
Обязательно ли пользоваться именно Amazon S3?
Нет. Amazon S3 — лишь первый и самый известный сервис. Сегодня S3-совместимый API предлагают десятки провайдеров, включая российские Yandex Object Storage и Selectel, которые работают без VPN и принимают оплату в рублях. Код приложения от смены провайдера почти не меняется — обычно достаточно поменять адрес сервера и ключи.
Что такое presigned URL и зачем он нужен?
Это временная подписанная ссылка на один конкретный файл и одну операцию — загрузку или скачивание. Приложение генерирует её на сервере, где хранятся секретные ключи, и отдаёт браузеру пользователя. Браузер работает с файлом напрямую, ключи никому не показываются, а через заданное время ссылка перестаёт действовать. Это стандартный способ организовать загрузку и раздачу приватных файлов.
Можно ли поднять своё S3-хранилище бесплатно?
Да — для этого существует MinIO, программа с открытым кодом, реализующая S3 API. Её ставят на свой сервер или даже на домашний компьютер. Это популярный вариант для локальной разработки и экспериментов: код пишется против своего MinIO, а потом без переделок переключается на облачный сервис. Подробности — на официальном сайте проекта: min.io.
Что произойдёт, если я случайно закоммичу ключи в git?
Считай ключи скомпрометированными, даже если удалишь их следующим коммитом: история git сохраняет всё, а боты сканируют публичные репозитории на ключи постоянно. Правильное действие — немедленно отозвать ключ в личном кабинете провайдера, сгенерировать новый и прописать его в .env. И на будущее — проверить, что .env есть в .gitignore.
Сколько стоит S3-хранилище для небольшого приложения?
Плата складывается из объёма хранения, количества операций и исходящего трафика. Для учебного приложения с сотнями пользователей суммы обычно символические, а у некоторых провайдеров есть бесплатные лимиты на старт. Точные цифры меняются — актуальные тарифы смотри на сайте выбранного сервиса, там же обычно есть калькулятор стоимости.
Заключение
Файлы и база данных — разные виды нагрузки, и смешивать их не нужно. Запомни цепочку: сам файл живёт в S3-совместимом объектном хранилище, в базе данных лежит только текстовая ссылка на него, а сервер приложения остаётся «безгосударственным» — его можно пересоздать в любой момент, ничего не потеряв. Для новичка из России практичнее всего начать с российского S3-совместимого сервиса — регистрация простая, оплата в рублях, VPN не нужен, а API тот же самый, что у мирового эталона. А когда пользователи твоей «Змейки» начнут ставить себе аватарки, ты уже будешь знать, где этим аватаркам самое место.
И последнее. Не откладывай переезд файлов «на потом, когда вырастем»: архитектурные долги такого рода дорожают с каждым загруженным файлом, потому что потом придётся переносить всё накопленное под нажимом жалоб пользователей. Если в приложении уже есть или скоро появятся пользовательские файлы — заложи хранилище сразу. Это один из немногих случаев, когда «правильно с самого начала» стоит столько же, сколько «как-нибудь потом переделать».
Читай дальше
Все статьиНе просто статьи — тебя доведут до результата
В практикуме за 2499 ₽ рядом живая команда практикующих разработчиков и маркетологов: ведём по шагам до твоего работающего приложения. Не «ролики и сам разбирайся» — помогаем на каждом затыке.
Перейти к практикуму