Материал

Практика backend: API и диагностика

Здесь самостоятельные задания с учебными данными. Сначала сделайте свою попытку, затем проверьте крайние случаи и откройте подсказку. Время — ориентир; для проекта учитывайте отдельное время на тесты и оформление.

Как выбрать практику · Карта промахов

Прокрутите таблицу по горизонтали →

Задание Этап Ориентир
Ревью заказа из другого ресторана 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 в логах.

Вернуться к этапу