Files

10 KiB
Raw Permalink Blame History

Лабораторная 4. S3-хранилище и вложения

Разделить метаданные и бинарное содержимое, добавить изображения в сообщения и сохранить доступ к ним при переключении API-реплики.

Это skeleton, а не готовый сервер. Здесь есть задание, контракт и публичные проверки. Реализацию приложения, Dockerfile и требуемую инфраструктуру пишет студент. Язык и framework свободные; UI не обязателен.

Карта курса · Процесс fork/PR · OpenAPI · Семантика API · Проверки · Отчёт · Примеры JSON · Памятка преподавателю

Откуда начинаем

Перенесите минимум lab3. Дополняются поля сообщения и endpoint вложений; прежние текстовые POST остаются валидными.

Архитектура: Клиент → LB → API × 2+ → PostgreSQL (метаданные) + приватное S3-хранилище (байты).

Что сделать

  1. Выберите один S3-совместимый сервер: MinIO, SeaweedFS или RustFS. Закрепите проверенную версию образа и опишите создание приватного bucket.
  2. Спроектируйте Attachment и связь с чатом/сообщением. S3 object key назначает сервер, filename используется только для отображения.
  3. Реализуйте синхронный binary upload PNG/JPEG с ограничениями, сохранение метаданных и авторизованный download через API.
  4. Включите S3 и persistent volumes в общий Compose/Swarm. Проверьте, что другая API-реплика скачивает файл после остановки загрузившей.
  5. Проверьте недоступность S3, чужой чат и restart всего стенда.

Оценка «3»: работающий минимум

  • Все обязательные возможности lab3 сохранены. S3-сервер находится в общем стенде; метаданные вложений живут в PostgreSQL, байты — в приватном bucket, не в БД или локальном каталоге API.
  • POST вложения возвращает 201 ready после сохранения; оригинал скачивается побайтно. Message с attachment_ids читается участниками чата. Публичный smoke проверяет checksum и отсутствие доступа у постороннего.
  • Принимаются реальные PNG/JPEG ≤10 MiB и ≤40 млн пикселей; MIME проверяется по содержимому, опасные filename отклоняются, лимиты применяются до неограниченного чтения/декодирования.
  • Upload → остановка обслужившей API-реплики → download через другую возвращает тот же файл. Restart с сохранением volumes сохраняет метаданные и оригинал.

Оценка «4»: уровень стажёра/джуна

Весь уровень 3 и все пункты ниже:

  • Тесты покрывают слишком большой файл, повреждённое изображение, подмену MIME, чужое attachment_id и привязку файла другого чата. Пользовательская ошибка не превращается в 500.
  • Прерывание загрузки и отказ между записью S3/БД не оставляют вечных неучтённых объектов: есть документированный механизм компенсации/сверки/очистки с безопасным grace period.
  • Лимиты/таймауты и потоковая обработка не позволяют одной загрузке занять неограниченную RAM. Проведён эксперимент ≥10 одновременных загрузок с измерением пика памяти.
  • Bucket bootstrap идемпотентен; API использует отдельные S3 credentials с ограниченными правами. Административная консоль не открыта в общей сети. Есть проверка private bucket вне API.

Оценка «5»: исследование и доказанный результат

Весь уровень 4 и один законченный трек на выбор. Приведите гипотезу, повторяемую методику, результаты и ограничения.

Трек 1. Прямой upload. Добавьте отдельный безопасный presigned-upload flow: короткая подпись, уникальный ключ, проверка принадлежности, размера/checksum при complete, обработка незавершённой загрузки. Сохраните обязательный proxy endpoint. Сравните трафик и RAM API при ≥2 размерах файлов, покажите негативные сценарии.

Трек 2. Согласованность двух хранилищ. Разработайте повторяемый reconciler для orphan/missing объектов и метаданных. Внедрите ошибки на обеих сторонах, покажите обнаружение и безопасное исправление с dry-run/повторным запуском. Докажите, что свежие и используемые вложения не удаляются; измерьте стоимость обхода.

Как начать и проверить

# Работает прямо в skeleton, Python 3.9+; приложение не запускается:
make check

# После реализации запустите свой сервер/стенд по REPORT.md.
# По умолчанию тест использует http://localhost:8080.
make test

.env.example, compose.example.yaml и contracts/infrastructure.json описывают настройки и роли компонентов. compose.example.yaml — список ролей с пустым services, не запускаемый стенд. Реализуйте свой compose.yaml/stack.yaml, закрепите версии образов, заполните локальный .env и опишите bootstrap/миграции. Все зависимости поднимаются из этого репозитория; ссылка «у меня БД уже установлена» не заменяет воспроизводимость.

# Compose-вариант после реализации:
docker compose --env-file .env config --quiet
docker compose --env-file .env up -d --build
# Дождитесь /health/ready; затем make test.
# Для Swarm укажите эквивалентные build/push/deploy-команды в REPORT.md.

Примеры запросов доступны в examples/requests.http, примеры JSON — в contracts/examples.json. Для полной проверки схемы (по желанию локально; CI делает её автоматически):

python3 -m venv .venv
.venv/bin/python -m pip install -r requirements-dev.txt
make validate PYTHON=.venv/bin/python

Smoke не выставляет оценку автоматически. Проверку сохранности/отказов запускайте явно по tests/README.md, остальные доказательства приведите в отчёте.

Сценарий защиты

  1. Загрузить fixture, отправить message с attachment_ids, скачать как второй участник и сравнить SHA-256.
  2. С третьим пользователем получить 404 для метаданных и bytes; direct unauthenticated S3 GET не отдаёт объект.
  3. Выполнить persistence и failover: общий тест начиная с lab4 сохраняет и повторно читает оригинал через API.
  4. Показать, что на API нет обязательного локального файла оригинала. Для 4/5 — отказы и эксперименты.

Что должно быть в PR

Дополненный стенд, настройка bucket/политики, миграции Attachment, тесты файлов, описание согласованности БД/S3 и результаты.

Заполните REPORT.md и шаблон PR, укажите целевую оценку/трек. Не меняйте обязательный контракт и публичные tests ради зелёного результата. Сохраняйте возможности предыдущих лабораторных в пределах объявленных переходов.

Вопросы на защите

  • Почему файл в Docker volume одной API-реплики не равен объектному хранилищу?
  • Когда можно честно вернуть 201?
  • Что произойдёт, если S3 PUT прошёл, а COMMIT в PostgreSQL — нет?
  • Почему случайный object key и приватный bucket не заменяют ACL приложения?

Первичные материалы