Главная/Проекты/Проект

Проект 3. Multi-service stack

Выполнять после: раздела 10
Опирается на разделы: 07, 08, 09, 10
Ориентировочное время: 12 часов
Оценивание: rubric, проходной результат 70


Задача

Собрать систему из пяти компонентов вокруг сервиса из проекта 2.

КомпонентРоль
apiHTTP-сервис из проекта 2
workerФоновая обработка накопленных событий
migrateПрименение миграций схемы
dbPostgreSQL
cacheRedis

Проект впервые требует проектировать систему, а не отдельный container. Отсюда и главная его особенность: большинство ошибок здесь — не в коде, а в порядке, зависимостях и границах.

Что переносится из проекта 2

Хранилище там было отделено протоколом Storage — ради именно этой замены. MemoryStorage уступает место PostgresStorage, а обработчики запросов не меняются вовсе. Если в проекте 2 интерфейс не выделен, начните с него: иначе перенос превратится в переписывание.


Требования

Обязательные

ТребованиеПроверяется
1Пять сервисов; роль каждого явно ограниченаChecklist 1
2Healthcheck у каждого сервиса, кроме одноразовогоChecklist 2
3Зависимости по готовности, а не по запускуChecklist 2
4Миграции применяются до старта api и workerChecklist 3
5Повторный запуск миграций ничего не делаетChecklist 3
6Две сети; база и кэш недоступны снаружиChecklist 4
7Именованные тома; данные переживают downChecklist 5
8Ограничения ресурсов у всех сервисовChecklist 6
9Интеграционные тесты с настоящей базойChecklist 7
10Отдельные конфигурации для разработки и тестовChecklist 8
11Секрет базы не лежит в compose.yamlChecklist 9

Требования, невыполнение которых снимает работу

  • depends_on без условия готовности — приложение стартует раньше базы;
  • пароль базы записан в compose.yaml открытым текстом;
  • тесты «проходят» при отсутствующей базе, потому что молча пропускаются;
  • данные теряются при docker compose down без -v.

Архитектура

Порядок старта

text
db (healthy) ─┐
              ├──→ migrate (completed) ──→ api
cache (healthy)┘                        └─→ worker

Три разных условия, и каждое нужно:

УсловиеГдеПочему именно оно
service_healthydb, cacheЗапущен ≠ принимает соединения
service_completed_successfullymigrateСхема должна существовать до первого запроса
db, cacheНи от кого не зависят

Ошибка, которую требование 3 предотвращает: depends_on в форме списка ждёт запуска container'а. PostgreSQL после старта container'а несколько секунд инициализирует кластер. Симптом плавающий: на прогретой машине успевает, на холодной нет.

Миграции как одноразовая задача

migrate — не сервис. У него restart: "no", нет healthcheck, и он обязан завершиться с кодом 0.

Три свойства, которые нужно обеспечить:

СвойствоЗачем
Порядок задаётся именем файлаОбход каталога не гарантирует порядок
Повторный запуск ничего не делаетdocker compose up вызывается многократно
Две копии не мешают друг другуВ оркестраторе задача может запуститься дважды

Третье решается консультативной блокировкой (pg_advisory_lock). Без неё две копии применят одну миграцию дважды.

Границы сетей

text
хост ──→ [frontend] ──→ api
                          │
                          ▼
              [backend, internal: true]
                 db   cache   worker   migrate

internal: true у backend означает, что container'ы этой сети не имеют выхода наружу. Сервис api состоит в обеих: он единственный, кто принимает запросы снаружи.

Очередь: два варианта, и выбор надо обосновать

Задача «обработать накопленные события» решается двумя способами:

СпособПлюсМинус
Очередь в RedisОтделена от базы, быстраяЕщё одно место, где теряются задачи
Очередь в самой базе (FOR UPDATE SKIP LOCKED)Транзакция общая с даннымиНагрузка на базу

Эталонное решение выбирает второй: SKIP LOCKED позволяет нескольким обработчикам брать разные строки, не ожидая друг друга и не обрабатывая строку дважды, а признак обработки лежит в той же транзакции, что и сами данные. Redis остаётся для кэша.

Выбор обратный тоже обоснован — но обосновать его нужно письменно.


Пошаговый план

Шаг 1. Миграции

Раннер и три файла схемы. Проверить: порядок, повторный запуск, изменение уже применённого файла.

Опора: урок 9.4.

Шаг 2. Хранилище в PostgreSQL

Реализовать протокол Storage поверх пула соединений. Имя колонки в GROUP BY подставляется в SQL — нужен белый список.

Опора: урок 7.2.

Шаг 3. Обработчик

Цикл с пачками, реакция на SIGTERM, дорабатывание текущей пачки.

Шаг 4. Compose

Пять сервисов, две сети, тома, ограничения, три вида зависимостей.

Опора: урок 9.7.

Шаг 5. Конфигурации разработки и тестов

Отдельные файлы, а не профили: набор отличий велик.

Опора: урок 9.6.

Шаг 6. Интеграционные тесты

С настоящей базой. Отдельно решить, что делает набор тестов, если базы нет.

Опора: урок 15.3.


Критерии завершения

  1. docker compose up -d из чистого состояния десять раз подряд — без единого падения api.
  2. docker compose run --rm migrate второй раз печатает «новых миграций нет».
  3. docker compose exec api python -c "import socket; socket.create_connection(('db',5432),2)" работает, а с хоста подключение к базе не проходит.
  4. Запись, docker compose down, up, чтение — данные на месте.
  5. docker compose down -v, up — данных нет, схема создана заново.
  6. docker compose -f compose.yaml -f compose.test.yaml run --rm tests проходит.
  7. Тот же прогон без базы завершается с ошибкой, а не с «пропущено».
  8. grep -ri password compose.yaml не находит значения.

Седьмой пункт — самый содержательный. Разберите его прежде, чем считать проект законченным.


Дополнительные задания

ЗаданиеЧто добавляет
1Блокировка при миграциях; проверить двумя копиямиПоведение в оркестраторе
2Отказ при изменении уже применённой миграцииРасхождение схем между средами
3Проверить, что два обработчика не берут одни строкиSKIP LOCKED на практике
4Переключатель «база обязательна» для конвейера«Не проверено» ≠ «проверено»
5Резервное копирование тома и проверка восстановленияУрок 7.6
6Замерить время старта стека из чистого состоянияЦена условий готовности

Задания 1–4 реализованы в эталонном решении; 5 и 6 — нет.


Навигация

Список проверок →
Эталонное решение → — открывать после собственной попытки
← Предыдущий проект: FastAPI service
Вернуться к проектам
Главное оглавление

Markdown на GitHub ↗