Задача 2 из 10
Хранилище и раздача картинок
Пользователи загружают фотографии, сервис хранит их и быстро отдаёт в нескольких размерах — как аватарки и картинки товаров в маркетплейсе.
Функциональные требования
- Загрузка изображения размером до 20 МБ
- Автоматические превью в трёх размерах
- Быстрая отдача картинки по постоянной ссылке
- Удаление изображения вместе со всеми превью
Нефункциональные требования
- 50 ТБ новых файлов в год
- Пик 500 загрузок/с, отдача 20 000 запросов/с
- Пользователи по всему миру, картинка открывается за 100 мс
- Превью готово в течение 10 секунд после загрузки
- Потеря оригинала недопустима
Нарисуйте архитектуру
Не гонитесь за красотой: на собеседовании от схемы нужно, чтобы по ней было видно путь запроса и где лежат данные. Сначала нарисуйте сами — эталон и разбор ниже.
Блок из палитры — добавить. Тащите мышкой или пальцем. Двойной клик по названию — переименовать. «Связь» — щёлкнуть по одному блоку, потом по другому: получится стрелка.
Схема сохраняется в этом браузере — вкладку можно закрыть и вернуться позже.
1 С чего начать, если ступор
- Не пропускайте файл через приложение: подпишите ссылку и пусть клиент грузит байты прямо в хранилище.
- Отдача картинок — работа CDN, а не вашего сервиса.
- Ресайз асинхронный: ответ на загрузку не должен ждать обработки.
- Метаданные (кто, когда, какие размеры готовы) — в БД, сами байты — в объектном хранилище.
- Продумайте «сирот»: загрузка началась и не завершилась, запись в БД есть, а файла нет.
2 Эталонная схема
Байты не ходят через приложение: клиент грузит файл прямо в хранилище по подписанной ссылке, а раздаёт картинки CDN. API держит только метаданные и ставит задачу на превью.
Это один из рабочих вариантов, а не единственно верный. Если у вас иначе, но вы можете объяснить почему — на собеседовании это ровно то, что нужно.
3 Разбор: что должно прозвучать
Загрузка без прокси через приложение
- Pre-signed URL: клиент кладёт файл прямо в хранилище, приложение только подписывает
- Ограничения зашиваются в подпись: тип, максимальный размер, срок действия
- Почему прогонять 20 МБ через gunicorn с одним воркером — плохая идея: воркер занят на всё время загрузки
Отдача и CDN
- CDN перед хранилищем закрывает и географию, и 20k RPS
- Длинный Cache-Control: файлы неизменяемы, имя меняется вместе с содержимым
- Инвалидация через новое имя файла, а не через purge всего кэша
Асинхронная обработка
- Очередь и воркеры: загрузка отвечает сразу, превью появляются позже
- Обработчик идемпотентен — повторная задача из очереди не должна ломать результат
- Что показывать, пока превью не готово: оригинал, заглушку или ресайз на лету
Метаданные и «сироты»
- Запись в БД со статусом (загружается / готово) и отметками готовых размеров
- Незавершённые загрузки убирает фоновая уборка по возрасту записи
- Событие о завершении загрузки от хранилища надёжнее, чем доверие клиенту
Удаление
- Мягкое удаление в БД + отложенная физическая уборка файлов
- Удалять нужно все производные, а не только оригинал
- Картинка может остаться в кэше CDN — с этим либо живут, либо инвалидируют явно
Куда копать дальше, если спросят
- Проверка содержимого, а не расширения файла
- Дедупликация по хешу: одна и та же картинка от тысячи пользователей
- Приватные картинки: подписанные ссылки на чтение и почему CDN тут мешает
- Стоимость хранения: холодные классы, ретеншен старых превью
Следующая задача: Ограничение частоты запросов
Маленькая задача с большим количеством граблей: алгоритм окна, общее состояние на 20 инстансов и атомарность.