feat(generator): глагол next-day — следующий модельный день от слепка
- Зачем:
- режим кормления стенда порциями: менти триггерит «следующий день»,
видит полный цикл DWH за один шаг (задача 13, вариант 2 — генерация
от слепка T_end).
- Что:
- новый ограниченный режим next-day: восстановление мира из state,
генерация ровно [T_end, T_end+24h), публикация данные -> state ->
манифест (манифест — точка фиксации, автоотката нет).
- операция next-day в DAG generator_control: своя предпроверка границы
вместо clean-guard, идемпотентность через параметр expected_t_end.
- цепочка границ — накопительное поле boundaries в манифесте, старый
формат читается как [T0, T_end]; импорт не изменён.
- новая проверка цепочки (make generated-history-chain-check): непарные
счётчики и однородность по каждой границе, явный статус нулевого
стыка, хвост за границей по всем четырём топикам, литералы в UTC
с микросекундами.
- документация OPERATIONS.md: глагол, предпроверка, восстановление
после сбоя, ограничение retention; в задаче 13 — решения двух слепых
ревью постановки и кода с аргументами отклонений.
- Проверка:
- make test: 204 теста генератора + 31 контракт корня, зелёные.
- make generated-history-chain-check: зелёный, 2 внутренние границы,
непарные счётчики нулевые; учебный цикл: DM 322 -> 10026 -> 19196
за два next-day подряд.
- make generated-history-runtime-check (регрессия задачи 20): зелёный.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,13 +23,19 @@ from airflow_clickhouse_plugin.hooks.clickhouse import ClickHouseHook
|
||||
from clickstream_generator.airflow_control import (
|
||||
KAFKA_BOOTSTRAP_SERVERS,
|
||||
assert_clickhouse_matches_manifest,
|
||||
assert_expected_t_end,
|
||||
assert_live_generator_not_running,
|
||||
assert_live_generator_not_running_for_next_day,
|
||||
assert_next_day_snapshot,
|
||||
assert_stand_clean,
|
||||
build_control_env,
|
||||
build_next_day_env,
|
||||
default_artifact_path,
|
||||
load_manifest_from_kafka,
|
||||
load_state_from_kafka,
|
||||
run_backfill,
|
||||
run_import,
|
||||
run_next_day,
|
||||
target_dag_trigger_error,
|
||||
validate_import_artifact,
|
||||
)
|
||||
@@ -101,6 +107,8 @@ def choose_operation(**context) -> str:
|
||||
return "check_etl_not_paused_before_backfill"
|
||||
if operation == "import":
|
||||
return "check_etl_not_paused_before_import"
|
||||
if operation == "next-day":
|
||||
return "check_etl_not_paused_before_next_day"
|
||||
if operation == "check":
|
||||
return "check_only"
|
||||
raise ValueError(f"Неизвестная операция: {operation}")
|
||||
@@ -143,6 +151,31 @@ def run_import_task(**context) -> None:
|
||||
run_import(env, artifact_path)
|
||||
|
||||
|
||||
def precheck_next_day(**context) -> None:
|
||||
"""Проверяет точку продолжения и готовит неизменные настройки мира."""
|
||||
assert_live_generator_not_running_for_next_day()
|
||||
manifest = load_manifest_from_kafka()
|
||||
state = load_state_from_kafka()
|
||||
assert_next_day_snapshot(manifest, state)
|
||||
assert_expected_t_end(
|
||||
str(_param(context, "expected_t_end") or "").strip(),
|
||||
str(manifest["model_t_end"]),
|
||||
)
|
||||
context["ti"].xcom_push(
|
||||
key="generator_env",
|
||||
value=build_next_day_env(manifest),
|
||||
)
|
||||
|
||||
|
||||
def run_next_day_task(**context) -> None:
|
||||
"""Генерирует следующий модельный день от проверенного state."""
|
||||
env = context["ti"].xcom_pull(
|
||||
task_ids="precheck_next_day",
|
||||
key="generator_env",
|
||||
)
|
||||
run_next_day(env)
|
||||
|
||||
|
||||
def check_manifest_task(**context) -> None:
|
||||
"""Сверяет витрину ClickHouse с manifest стартовой истории."""
|
||||
manifest = load_manifest_from_kafka()
|
||||
@@ -164,6 +197,8 @@ def assert_target_dag_not_paused(dag_id: str, session=None) -> None:
|
||||
raise AirflowException(error)
|
||||
|
||||
|
||||
# Context7, Airflow 2.10.5: Param поддерживает enum, а max_active_runs ограничивает
|
||||
# число одновременных DAG run. Поэтому форму и блокировку next-day держим в DAG.
|
||||
with DAG(
|
||||
dag_id="generator_control",
|
||||
description="Пульт стартовой истории генератора",
|
||||
@@ -178,11 +213,11 @@ with DAG(
|
||||
"operation": Param(
|
||||
"backfill",
|
||||
type="string",
|
||||
enum=["backfill", "import", "check"],
|
||||
enum=["backfill", "import", "next-day", "check"],
|
||||
title="Операция",
|
||||
description=(
|
||||
"Что сделать: создать историю, импортировать артефакт "
|
||||
"или проверить витрины."
|
||||
"Что сделать: создать историю, импортировать артефакт, "
|
||||
"добавить следующий день или проверить витрины."
|
||||
),
|
||||
),
|
||||
"profile": Param(
|
||||
@@ -219,6 +254,15 @@ with DAG(
|
||||
"Import: что читать; пусто — путь по умолчанию в data."
|
||||
),
|
||||
),
|
||||
"expected_t_end": Param(
|
||||
"",
|
||||
type="string",
|
||||
title="Ожидаемая граница next-day",
|
||||
description=(
|
||||
"Необязательный model_t_end до запуска. Защищает от "
|
||||
"повторной доливки того же дня."
|
||||
),
|
||||
),
|
||||
},
|
||||
) as dag:
|
||||
route = BranchPythonOperator(
|
||||
@@ -244,6 +288,15 @@ with DAG(
|
||||
python_callable=run_import_task,
|
||||
)
|
||||
|
||||
precheck_next_day_task = PythonOperator(
|
||||
task_id="precheck_next_day",
|
||||
python_callable=precheck_next_day,
|
||||
)
|
||||
next_day_task = PythonOperator(
|
||||
task_id="run_next_day",
|
||||
python_callable=run_next_day_task,
|
||||
)
|
||||
|
||||
check_etl_not_paused_before_backfill = PythonOperator(
|
||||
task_id="check_etl_not_paused_before_backfill",
|
||||
python_callable=assert_target_dag_not_paused,
|
||||
@@ -254,6 +307,11 @@ with DAG(
|
||||
python_callable=assert_target_dag_not_paused,
|
||||
op_kwargs={"dag_id": ETL_DAG_ID},
|
||||
)
|
||||
check_etl_not_paused_before_next_day = PythonOperator(
|
||||
task_id="check_etl_not_paused_before_next_day",
|
||||
python_callable=assert_target_dag_not_paused,
|
||||
op_kwargs={"dag_id": ETL_DAG_ID},
|
||||
)
|
||||
|
||||
trigger_etl = TriggerDagRunOperator(
|
||||
task_id="trigger_etl",
|
||||
@@ -284,11 +342,14 @@ with DAG(
|
||||
route >> [
|
||||
check_etl_not_paused_before_backfill,
|
||||
check_etl_not_paused_before_import,
|
||||
check_etl_not_paused_before_next_day,
|
||||
check_only,
|
||||
]
|
||||
check_etl_not_paused_before_backfill >> precheck_backfill_task
|
||||
check_etl_not_paused_before_import >> precheck_import_task
|
||||
check_etl_not_paused_before_next_day >> precheck_next_day_task
|
||||
precheck_backfill_task >> backfill_task >> trigger_etl
|
||||
precheck_import_task >> import_task >> trigger_etl
|
||||
precheck_next_day_task >> next_day_task >> trigger_etl
|
||||
trigger_etl >> check_after_etl >> done
|
||||
check_only >> done
|
||||
|
||||
Reference in New Issue
Block a user