Лабораторная 01: Сеть ненадёжна
Постройте API сервиса задач. Добавьте тайм-ауты, повторные запросы и ключи идемпотентности. Смоделируйте потерю ответа.
Код стенда, установка и шаблон отчёта
Лабораторная 1. Потерянный ответ не означает потерянную операцию
Создайте надёжный HTTP-контракт одного сервиса задач. После занятия вы должны уметь отличать повтор запроса от новой операции, обосновывать атомарную границу и воспроизводить неопределённый результат. Предварительно: HTTP, Python, Docker, лекции о моделях отказов и взаимодействии сервисов. Рабочая длительность — около 90 минут плюс самостоятельное завершение; это предложение автора, не регламент ВШЭ.
Подготовка и запуск
Нужны Python 3.12+, Docker и Compose. Общая инструкция описывает установку, контракт, изоляцию и cleanup. Команды выполняются из корня репозитория:
python3 -m venv /tmp/ds-course-venv
/tmp/ds-course-venv/bin/python -m pip install -r labs/distributed-systems/requirements.txt
PYTHONDONTWRITEBYTECODE=1 /tmp/ds-course-venv/bin/python labs/distributed-systems/runner.py --lab 1 --solution starter --report /tmp/lab01.json
Starter запускает реальный API и настоящий кластер etcd, но намеренно не проходит атомарность. Исправляйте starter/protocol.py; повторяйте ту же команду. Эталон доступен преподавателю через --solution reference. Проверки в tests/scenarios.py общие; их ослабление не является решением.
Задание
- Зафиксируйте область Idempotency-Key: один namespace запуска, 1–128 ASCII-символов, хранение на весь прогон. Тело —
{"amount": 1}с необязательнымlabel; детали в README. У одинакового ключа и одинакового JSON должна быть одна идентичность. Изменённое содержание —409. - Замените check-then-write на условную etcd-транзакцию. Task, idempotency и outbox должны получить одну ревизию: намерение публикации нужно сохранить уже сейчас для совместимости с Lab3. Брокер пока не требуется для завершения Lab1.
- Опишите, что клиент делает после timeout: использует тот же ключ и не выводит «запись не произошла».
GET /tasks/<id>читает linearizable состояние.
Три сценария
- Ответ потерян после commit. Runner отправляет
X-Lab-Drop-Response: true; API фиксирует данные и закрывает настоящее TCP-соединение. Первый ответ недоступен. Повтор возвращает200и исходный task_id; изменённый amount даёт409. - Конкурентные повторы. Двенадцать потоков одновременно посылают одинаковый запрос. Ровно один получает
201, одиннадцать —200; task_id один. Два конкурентных запроса с одним ключом и разными amount дают один201и один409. - Падение API. После подтверждённой записи процесс API получает SIGKILL и запускается заново. Повтор возвращает прежний task_id; задача читается из etcd, а не из памяти процесса.
Проверяемый инвариант: для одного ключа за срок хранения существует не более одной принятой операции; её task, idempotency и outbox фиксируются атомарно. Подтверждённая задача переживает падение API в заявленной модели. Это ещё не гарантия завершения worker и не глобальное «exactly once».
Что сдавать
- Diff starter с объяснением точки линеаризации и судьбы конкурента, проигравшего compare.
/tmp/lab01.jsonи минимальный фрагмент JSONL с task_id/trace_id; укажите версию, команду и число попыток.- Короткую последовательность «commit → потеря ответа → повтор» и ответ, почему
201нельзя вернуть двум конкурентам. - Отчёт: сохраните и неудачный прогон, затем сравните с исправленным. В Git не кладите venv, container volumes и полные диагностические логи.
Разбор и чтение
Сопоставьте границу операции с Lamport, Time, Clocks, and the Ordering of Events, и границу локальной атомарности с Helland, Life beyond Distributed Transactions. Исследовательские деки преподавателя по Lamport и Helland поддерживают упражнение; публичная методичка ссылается на первоисточники. Точный используемый механизм: etcd transactions.
На защите объясните, что изменится при удалении idempotency через час, при другой сериализации JSON и при замене локального эффекта внешним платёжным API. Расширение: смоделируйте два ключа для одного бизнес-намерения и покажите, почему транспортная идемпотентность не распознает их автоматически.
Восстановление и ограничения
Runner сам завершает только свой уникальный Compose project, включая volumes. Ctrl+C также запускает cleanup; после SIGKILL используйте сохранённый compose.env и инструкцию README. Все порты — loopback. Не используйте глобальный prune. Потеря всего компьютера, дисков большинства и Byzantine-сбои не проверяются. Timeout может означать неизвестный исход, а не отказ записи.