Здесь самостоятельные задания с учебными данными. Сначала сделайте свою попытку, затем проверьте крайние случаи и откройте подсказку. Время — ориентир; для проекта учитывайте отдельное время на тесты и оформление.
Как выбрать практику · Карта промахов
Прокрутите таблицу по горизонтали →
| Задание | Этап | Ориентир |
|---|---|---|
| Ревью заказа из другого ресторана | Web-фреймворк | 50 мин |
| Две брони на последний столик | Web-фреймворк | 60 мин |
| Рецепты и ингредиенты в ORM | Web-фреймворк | 45 мин |
| Меню выросло до 61 запроса | Web-фреймворк | 40 мин |
| Кухня получила один заказ дважды | Async и фоновые задачи | 45 мин |
| Образ собирается заново, API ждёт базу | Production basics | 35 мин |
| Кэш не должен отдавать чужую бронь | HTTP и API | 35 мин |
Ревью заказа из другого ресторана
Проведите ревью фрагмента обработчика: найдите проблемы и предложите исправление. Ресторан выбирается в URL, сумма должна считаться по каталогу сервера, повтор запроса после таймаута не создаёт второй заказ. Товар может отсутствовать или принадлежать другому ресторану. Опишите также, что произойдёт при сбое публикации после записи в БД.
@app.post("/restaurants/{restaurant_id}/orders")
async def place_order(restaurant_id: int, body: dict, user=Depends(current_user)):
dish = await db.fetchrow(
f"SELECT * FROM dishes WHERE id = {body['dish_id']}"
)
await db.execute(
"INSERT INTO orders(user_id, dish_id, amount) VALUES ($1, $2, $3)",
user.id, dish["id"], body["amount"]
)
await publish({"type": "order_created", "user_id": user.id})
return {"ok": True}
Проверка результата
- Параметризованный SQL, валидация входа и проверка принадлежности блюда показаны отдельно.
- Цена и сумма не принимаются как доверенные значения клиента.
- Описаны атомарность локальных записей, повтор запроса и повтор доставки события.
Подсказка — после своей попытки
Проверьте каждую границу доверия и каждый шаг, который может завершиться независимо.
Усложнение: Напишите HTTP-тест для валидного пользователя, пытающегося заказать блюдо другого ресторана.
Две брони на последний столик
У ресторана один свободный столик на слот. Сделайте endpoint бронирования и импорт пачки броней. Правило импорта: либо сохраняется вся пачка, либо ни одна запись. Два запроса могут выполняться одновременно. «Закрыт для бронирования» — бизнес-статус слота; блокировка строки БД — временная синхронизация транзакций. Не подменяйте одно другим. Выберите явную политику ожидания DB-lock, HTTP-ответы и повтор запроса с тем же ключом.
Проверка результата
- Два конкурентных клиента получают ровно одну новую бронь.
- Ошибка в середине пачки откатывает все её изменения.
- Повтор одного ключа возвращает тот же результат; один ключ с другим payload отклоняется.
Подсказка — после своей попытки
Проверка свободного места и его занятие должны иметь общую гарантию конкурентной корректности.
Усложнение: Сравните условный UPDATE, ограничение уникальности и блокировку строки на двух транзакциях.
Рецепты и ингредиенты в ORM
Спроектируйте SQLAlchemy-модели Dish, Ingredient и RecipeLine. Строка рецепта связывает блюдо с ингредиентом и хранит положительное количество в граммах. Один ингредиент не повторяется внутри одного блюда. Добавьте признак аллергена у ингредиента. Архивирование блюда сохраняет рецепт; удаление ингредиента из используемого рецепта запрещено. Загрузите меню и аллергены каждого блюда без отдельного запроса на каждую строку.
Проверка результата
- DDL содержит FK, уникальность пары и CHECK количества.
- Проверено создание, повтор ингредиента, архивирование и запрещённое удаление.
- Число запросов измерено на меню из 30 блюд.
Подсказка — после своей попытки
Связь содержит собственное количество, поэтому одной таблицы с двумя FK недостаточно для модели данных.
Усложнение: Верните блюда без ингредиентов и объясните влияние JOIN на такие строки.
Меню выросло до 61 запроса
После добавления фотографий и аллергенов выдача меню стала выполнять 61 SQL-запрос на 30 блюд. При росте таблицы бронирований замедлился другой endpoint — последние брони ресторана. Есть только числа запросов и p95, связи между проблемами пока не доказано. Составьте план диагностики обоих случаев и минимальный эксперимент для каждого. После исправления покажите число запросов, план выполнения и изменение p95 на одинаковой нагрузке.
Проверка результата
- Не ставьте индекс как универсальный ответ на N+1.
- Отделены число запросов, стоимость одного запроса и ожидание соединения.
- Зафиксированы вход, объём данных и критерий успешного улучшения.
Подсказка — после своей попытки
Сначала найдите, на каком шаге появляется дополнительный запрос на блюдо.
Усложнение: Проверьте страницу без фотографий и страницу без аллергенов раздельно.
Кухня получила один заказ дважды
Заказ сохранён в PostgreSQL, но отправка события завершилась таймаутом. Клиент повторил запрос, доставчик повторил отправку, а потребитель упал после создания кухонного талона до подтверждения сообщения. Спроектируйте создание заказа и обработку события так, чтобы повтор не создавал ещё один талон. Различайте id запроса, id заказа и id события. Гарантии брокера не должны заменять проверку бизнес-эффекта.
Проверка результата
- Описаны локальная транзакция заказа и события, доставка с повторами и дедупликация потребителя.
- Есть проверка падения до и после создания талона.
- Порядок разных событий одного заказа обсуждён отдельно от дедупликации.
Подсказка — после своей попытки
Запись эффекта и отметка обработки события должны быть согласованы.
Усложнение: Добавьте отмену заказа, которая приходит раньше старого события создания.
Образ собирается заново, API ждёт базу
Изменение одной строки приложения заново запускает установку зависимостей в Docker. Одновременно API после увеличения числа экземпляров часто ждёт соединение PostgreSQL. Разберите эти проблемы отдельно: предложите порядок слоёв сборки и бюджет соединений. У каждого экземпляра пул на 20 соединений; экземпляров 6, бюджет базы для приложения — 80. Учитывайте служебные процессы и раскатку старых и новых экземпляров одновременно.
Проверка результата
- 120 потенциальных соединений превышают бюджет ещё до воркеров и раскатки.
- Изменение только Python-файла использует кэш слоя зависимостей.
- Измерены сборка, ожидание пула и загрузка БД.
Подсказка — после своей попытки
Число экземпляров умножает настройки каждого пула.
Усложнение: Проверьте rollout с максимальным одновременным числом экземпляров.
Кэш не должен отдавать чужую бронь
Endpoint личных броней требует входа. После включения общего HTTP-кэша другой посетитель увидел чужой список. Спроектируйте правила кэширования отдельно для публичного меню и личных броней. Для публичного меню используйте версию меню как основу ETag и обработайте If-None-Match. Для личных данных сначала выберите безопасный контракт, затем обоснуйте ключ кэша и допустимое хранение, если оно вообще нужно.
Проверка результата
- Неизменное меню даёт 304 без тела по корректному условному запросу.
- Изменение меню меняет ETag.
- Проверены два пользователя, запрос без входа и смена пользователя.
Подсказка — после своей попытки
Публичный ресурс и персональный ресурс имеют разные границы доступа.
Усложнение: Разберите кэширование ошибок и ограничения на персональные URL в логах.