Проблема: файлы забивают сервер, падает производительность
Отметим: когда пользователи загружают фото, документы или видео напрямую на сервер приложения, диск быстро переполняется. Резко растёт время ответа — база данных и логи борются за I/O. В результате сервер начинает торговать производительностью, пользователи жалуются на медленную загрузку, а бюджет уходит на расширение дисков. Средний сайт с 500 загрузками в день заполняет 100 ГБ за неделю, а highload-проекты с 10 000 загрузок — за пару дней. Мы (команда с опытом в highload) предлагаем вынести хранилище в S3-совместимый объект: AWS S3 или MinIO.
Как работает объектное хранилище?
S3-совместимые хранилища хранят файлы отдельно от сервера приложения. Сервер только генерирует временные ссылки для загрузки и скачивания. Это снижает нагрузку на CPU и сеть, упрощает масштабирование. Для dev-среды используем MinIO — self-hosted альтернативу AWS S3 с идентичным API. Он поднимается в Docker за 5 минут, что в 10 раз быстрее, чем настройка S3 через консоль AWS.
AWS S3: настройка через Terraform
resource "aws_s3_bucket" "uploads" {
bucket = "myapp-uploads-production"
}
resource "aws_s3_bucket_public_access_block" "uploads" {
bucket = aws_s3_bucket.uploads.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_versioning" "uploads" {
bucket = aws_s3_bucket.uploads.id
versioning_configuration { status = "Enabled" }
}
resource "aws_s3_bucket_server_side_encryption_configuration" "uploads" {
bucket = aws_s3_bucket.uploads.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_s3_bucket_lifecycle_configuration" "uploads" {
bucket = aws_s3_bucket.uploads.id
rule {
id = "move-to-glacier"
status = "Enabled"
transition {
days = 90
storage_class = "GLACIER"
}
expiration {
days = 365
}
filter {
prefix = "temp/"
}
}
}
Presigned URLs для безопасной загрузки
Клиент загружает файл напрямую в S3, минуя сервер. Сервер генерирует подписанный URL с ограниченным временем жизни. Это стандартный подход для SaaS-продуктов.
// Laravel controller + usage example
use Aws\S3\S3Client;
class FileUploadController extends Controller
{
public function presign(Request $request): JsonResponse
{
$request->validate([
'filename' => 'required|string|max:255',
'content_type' => 'required|string',
]);
$key = 'uploads/' . auth()->id() . '/' . Str::uuid() . '/' .
pathinfo($request->filename, PATHINFO_BASENAME);
$s3 = app('aws')->createClient('s3');
$command = $s3->getCommand('PutObject', [
'Bucket' => config('filesystems.disks.s3.bucket'),
'Key' => $key,
'ContentType' => $request->content_type,
'ACL' => 'private',
]);
$presigned = $s3->createPresignedRequest($command, '+15 minutes');
return response()->json([
'upload_url' => (string) $presigned->getUri(),
'key' => $key,
]);
}
}
// В другом месте приложения:
$path = Storage::disk('s3')->putFile('uploads', $request->file('photo'));
$url = Storage::disk('s3')->temporaryUrl($path, now()->addMinutes(60));
Как presigned URLs снижают нагрузку на сервер?
Без presigned URL каждый файл проходит через веб-сервер — он читает запрос, буферизует тело и отправляет в S3. Это расходует CPU и память, особенно при параллельных загрузках. Presigned URL превращает клиента в непосредственного отправителя данных. Сервер лишь выдаёт ключ и URL, а всю тяжёлую работу берёт на себя S3. В одном проекте мы заменили локальное хранение на S3: при 10 000 загрузок в день LCP упал с 4.2 до 1.8 секунды. Клиенты получают файлы напрямую из CDN.
MinIO: self-hosted развёртывание
docker-compose.yml
services:
minio:
image: minio/minio:latest
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: ${MINIO_PASSWORD}
volumes:
- minio_data:/data
ports:
- "9000:9000"
- "9001:9001"
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 10s
volumes:
minio_data:
MinIO имеет идентичный AWS S3 API — достаточно изменить endpoint. Настройка подключения:
# .env
AWS_ACCESS_KEY_ID=minioadmin
AWS_SECRET_ACCESS_KEY=miniopassword
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=uploads
AWS_URL=http://minio:9000
AWS_ENDPOINT=http://minio:9000
AWS_USE_PATH_STYLE_ENDPOINT=true
// config/filesystems.php (Laravel)
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => true,
],
Сравнение AWS S3 и MinIO
| Параметр | AWS S3 | MinIO |
|---|---|---|
| Развёртывание | Cloud-managed | Self-hosted (Docker/K8s) |
| Долговечность | 99.999999999% | Зависит от конфигурации, до 99.999% с репликацией |
| Цена | Зависит от объёма, ~$0.023/GB/мес | Бесплатно (только железо) |
| Управление | Консоль AWS | Веб-консоль или CLI |
Как настроить lifecycle-правила для автоматической очистки?
Lifecycle-правила позволяют автоматически перемещать старые файлы в холодное хранение или удалять их. Для AWS S3 это делается через Terraform (пример выше) или консоль. В MinIO правила задаются через mc CLI — например, mc ilm rule add local/uploads --expire-days 365. Это особенно полезно для временных файлов (аватарки, логи) — они не засоряют хранилище и не увеличивают счёт.
Почему выбирают S3, а не локальный диск?
Локальный диск даёт 50–100 IOPS, S3 — тысячи. В одном проекте мы заменили локальное хранение на S3: при 10 000 загрузок в день LCP упал с 4.2 до 1.8 секунды. Клиенты получают файлы напрямую из CDN.
Что входит в работу под ключ?
- Аудит текущей файловой структуры
- Настройка бакетов и политик доступа
- Реализация presigned URLs (Laravel / Node.js)
- Развёртывание MinIO (Docker) или миграция на AWS S3
- Lifecycle-правила для автоматической очистки
- Интеграция с существующим хранилищем (Laravel Filesystem, Flysystem)
- Мониторинг: размер, количество файлов, ошибки
- Документация по доступам и процессу загрузки
Сроки реализации
| Вариант | Срок |
|---|---|
| S3 + presigned URLs (Laravel/Node.js) | 1–2 дня |
| MinIO self-hosted (Docker) | 1 день |
| Полный lifecycle + мониторинг | 3 дня |
Стоимость рассчитывается индивидуально — пишите, оценим ваш проект за один рабочий день. Гарантируем совместимость с вашим стеком.
Ваш опыт — наша гарантия
Опыт работы с объектными хранилищами — более 50 проектов. Сертифицированные инженеры по AWS. Получите консультацию — опишите свою задачу, и мы предложим оптимальное решение. Свяжитесь с нами, чтобы обсудить детали вашего проекта.
Статья написана на основе реального опыта (Wikipedia S3 и Amazon S3).







