Files
clickstream-ch-kafka-supers…/.scratch/generator-model-time-startup-history/issues/04-state-v2-model-resume.md
T
ddadminandClaude Fable 5 85f076e205 docs(generator): проведён триаж бэклога фичи, задачи 01-06 закрыты
- Зачем:
  - follow-up задачи после ревью 2026-06-14 лежали без триажа; решения по
    артефакту, громкому отказу и интерфейсу приняты 2026-07-04 и должны
    попасть в задачи до передачи исполнителю.
- Что:
  - задачи 07 (миграция курса) и 08 (артефакт) поменяны местами — номера
    отражают порядок; ссылки обновлены.
  - 07 (артефакт + runbook) дооформлен: импорт строго через Kafka (напрямую
    в ClickHouse не пишет), громкий отказ при несовместимом state с правкой
    спеки, граница runbook «использование, не устройство»; ready-for-agent.
  - 08 дооформлен: устройство генератора вне пути менти, реальный объём
    (make data во всех уроках 00-05), демо вне скоупа; ready-for-agent.
  - новые задачи: 11 глаголы/длительность/профили (после 07, до 12),
    12 Airflow-DAG как пульт (приоритет поднят), 13 доливка (после 09).
  - задачи 01-06 переведены в done (стояли ошибочные ready-for-human);
    PRD фичи дополнен списком задач 7-13 с порядком и зависимостями.
- Проверка:
  - head -1 .scratch/generator-model-time-startup-history/issues/*.md;
    grep по старым именам файлов ничего не находит вне handoff.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 16:39:40 +03:00

8.4 KiB
Raw Blame History

Status: done

Восстановление state v2 от модельной точки

Parent

.scratch/generator-model-time-startup-history/PRD.md

What to build

Привести восстановление state v2 к модельному времени. Один и тот же слепок должен уметь восстанавливаться в двух разных случаях: после сбоя, где время действительно прошло, и при старте из стартовой истории, где продолжение идёт от T_end без искусственного разрыва.

Срез должен доказать поведение не только локальными тестами состояния, но и данными в ClickHouse: активные визиты продолжаются или закрываются по правилам модельного времени.

Acceptance criteria

  • State сохраняет достаточно данных, чтобы после сбоя вычислить модельную точку возобновления при ×K.
  • Восстановление после короткого модельного сбоя продолжает активные визиты и досылает созревшие события с исходными модельными метками.
  • Восстановление после долгого модельного сбоя закрывает сильно просроченные активные визиты без досылки остатка.
  • Восстановление из стартовой истории использует T_end как точку возобновления и не обрывает активные визиты из-за настенного простоя.
  • Визит, переживший восстановление, остаётся однородным: события до и после восстановления не меняют контекст визита и не создают второй путь генерации внутри одного click_id.
  • Повреждённый или несовместимый state не валит сервис: генератор стартует с чистого листа и пишет предупреждение.
  • ClickHouse-проверка подтверждает, что на стыке восстановления нет дублей событий и нет разрыва click_id внутри продолжающегося визита.
  • После реализации выполнено саморевью worker-а и отдельное reviewer-ревью, потому что задача меняет state/serialization и сервисное восстановление.
  • Если исправление после ревью меняет формат 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-круг выполнен, новых блокирующих находок нет.