Files
clickstream-ch-kafka-supers…/.scratch/generator-model-time-startup-history/issues/04-state-v2-model-resume.md
T
ddadmin fcb06da16e feat(generator): добавлено восстановление state по модельному времени
- Зачем:
  - рестарт генератора должен продолжать поток от модельной точки без дублей и смешивания state разных настроек.
- Что:
  - state v2 хранит модельную и настенную метки, скорость, timezone, T0 и seed.
  - live-восстановление считает модельную точку по настенной дельте и проверяет совместимость config.
  - добавлен путь восстановления от T_end и тесты короткого и долгого простоя.
- Проверка:
  - make generator-test.
  - ClickHouse-сценарии короткого и долгого восстановления state.
  - reviewer gate issue 04 пройден после исправления совместимости state.
2026-06-14 18:47:34 +03:00

115 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Status: ready-for-human
# Восстановление state v2 от модельной точки
## Parent
`.scratch/generator-model-time-startup-history/PRD.md`
## What to build
Привести восстановление state v2 к модельному времени. Один и тот же слепок
должен уметь восстанавливаться в двух разных случаях: после сбоя, где время
действительно прошло, и при старте из стартовой истории, где продолжение идёт
от `T_end` без искусственного разрыва.
Срез должен доказать поведение не только локальными тестами состояния, но и
данными в ClickHouse: активные визиты продолжаются или закрываются по правилам
модельного времени.
## Acceptance criteria
- [x] State сохраняет достаточно данных, чтобы после сбоя вычислить модельную
точку возобновления при ×K.
- [x] Восстановление после короткого модельного сбоя продолжает активные визиты
и досылает созревшие события с исходными модельными метками.
- [x] Восстановление после долгого модельного сбоя закрывает сильно просроченные
активные визиты без досылки остатка.
- [x] Восстановление из стартовой истории использует `T_end` как точку
возобновления и не обрывает активные визиты из-за настенного простоя.
- [x] Визит, переживший восстановление, остаётся однородным: события до и после
восстановления не меняют контекст визита и не создают второй путь генерации
внутри одного `click_id`.
- [x] Повреждённый или несовместимый state не валит сервис: генератор стартует
с чистого листа и пишет предупреждение.
- [x] ClickHouse-проверка подтверждает, что на стыке восстановления нет дублей
событий и нет разрыва `click_id` внутри продолжающегося визита.
- [x] После реализации выполнено саморевью worker-а и отдельное reviewer-ревью,
потому что задача меняет state/serialization и сервисное восстановление.
- [x] Если исправление после ревью меняет формат state или способ восстановления,
выполнен повторный reviewer-круг.
## Blocked by
- `.scratch/generator-model-time-startup-history/issues/03-model-speed-and-day-factor.md`
- Review gate из `PRD.md`: сквозной инвариант времени после задачи 3
## Решение
- State v2 теперь хранит `model_timestamp`, `wall_timestamp`,
`model_time_speed`, `model_timezone`, `model_t0` и `gen_seed`.
- Старый v2 без этих полей считается несовместимым: `from_dict_safe` возвращает
`None`, сервис пишет предупреждение и стартует с чистого состояния.
- Live-восстановление считает точку продолжения по формуле
`model_timestamp + max(0, wall_now_utc - wall_timestamp) * model_time_speed`.
- Перед live-восстановлением сервис сверяет state с текущим `Config` по
`gen_seed`, `model_t0`, `model_timezone` и `model_time_speed`. Несовпадение
считается несовместимым state: сервис пишет warning и стартует с чистого
состояния.
- Восстановление из стартовой истории вынесено в минимальный метод
`GeneratorService.restore_from_startup_history(state, model_t_end)`: он берёт
переданный `T_end` напрямую и не применяет wall-дельту.
- Сохранение state после успешного тика пишет модельную точку следующего тика,
а не настенное время. Это убирает повторный старт с уже обработанной модельной
точки.
## Проверки
- Точечный прогон после проверки reviewer-а:
`docker run --rm -v ... generator:test pytest tests/test_state.py::TestGeneratorState tests/test_state.py::TestGeneratorStateValidation::test_from_dict_safe_returns_state_on_valid tests/test_state.py::TestGeneratorStateValidation::test_from_dict_safe_returns_none_on_invalid_gen_seed tests/test_service.py::TestGeneratorServiceStateV2 -v`
— 15 passed.
- `make generator-test` — 126 passed.
- `uv run pytest ...` и `uv run python -m pytest ...` не запускались до тестов:
в текущем uv-окружении нет `pytest`.
- `make lint` недоступен: в `Makefile` нет такой цели.
## ClickHouse-проверка
Выполнены два чистых стендовых сценария. Оба начинались со сброса ClickHouse,
Kafka и state генератора.
1. Короткое восстановление, `GEN_MODEL_TIME_SPEED=1`.
Первый запуск с `GEN_STATE_RESET=true` записал один тик, второй запуск с
`GEN_STATE_RESET=false` восстановился из state и продолжил активные визиты.
Проверка ClickHouse:
- `dds.event`: `rows=332`, `uniq_events=332`, `duplicate_events=0`.
- `continued_from_first_tick=67` для визитов, начатых на
`2026-01-01 10:00:00`.
- Для этих `click_id` нет смены `user_domain_id`, `device_type`, `os_name`,
`geo_country`, `geo_region_name` и `geo_timezone`.
2. Долгое восстановление, `GEN_MODEL_TIME_SPEED=3600`.
Первый запуск с `GEN_STATE_RESET=true` записал один тик на
`2026-01-01 10:00:00`, второй запуск с `GEN_STATE_RESET=false`
восстановился на модельной точке `2026-01-03T22:51:51.552000+00:00`.
Проверка ClickHouse:
- `dds.event`: `rows=200`, `uniq_events=200`, `duplicate_events=0`.
- диапазон `event_ts`: от `2026-01-01 10:00:00.000000` до
`2026-01-03 22:51:51.552000`.
- `continued_old_clicks=0`: старые `click_id` с первого тика не продолжились
после долгой модельной паузы.
## Риски
- Старые записи state v2 без модельной связки больше не восстанавливаются. Это
намеренно: без этих полей нельзя корректно посчитать точку продолжения.
- Полноценный манифест и backfill не реализованы: добавлен только минимальный
интерфейс восстановления от переданного `T_end`.
- `restore_from_startup_history` пока принимает state и `T_end` напрямую, без
проверки manifest. Это риск issue 05, где появится полноценная стартовая
история и запрет смешивания артефактов.
- `GeneratorState.__post_init__` оставляет in-memory совместимость для прямого
создания state в тестах и коде. JSON/Kafka путь остаётся строгим: отсутствие
новых полей не маскируется и ведёт к fresh start.
- После reviewer-fix формат state снова изменился (`gen_seed`). Повторный
reviewer-круг выполнен, новых блокирующих находок нет.