diff --git a/Makefile b/Makefile index c5db886..6f15f1d 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ contract-test config-test test lint COMPOSE ?= docker compose -PROFILE ?= ci +PROFILE ?= daily-wave PYTEST_ARGS ?= -q -p no:cacheprovider PYTHON_CHECK_PATHS := airflow/dags configs generator scripts superset tests BASH_SCRIPTS := $(shell find scripts -type f -name '*.sh' | sort) diff --git a/README.md b/README.md index f9d8bb2..875eedb 100644 --- a/README.md +++ b/README.md @@ -59,14 +59,10 @@ Airflow-образ и подтягивает новые зависимости make generated-history-analytics ``` -По умолчанию это быстрый профиль `ci`: 6 часов модельного времени. Историю на -2 суток с суточной волной можно прогнать одной командой. В live-продолжении -`daily-wave` идёт с ×60: модельные сутки проходят примерно за 24 настенные -минуты. - -```bash -PROFILE=daily-wave make generated-history-analytics -``` +По умолчанию используется учебный профиль `daily-wave`: 2 суток с суточной +волной. В live-продолжении он идёт с ×60: модельные сутки проходят примерно +за 24 настенные минуты. Плоский профиль `ci` на 6 часов остаётся служебным +для автоматических тестов. Разовую длительность можно задать без ручного расчёта правой границы: diff --git a/airflow/dags/generator_control_dag.py b/airflow/dags/generator_control_dag.py index 3b80735..be66ed1 100644 --- a/airflow/dags/generator_control_dag.py +++ b/airflow/dags/generator_control_dag.py @@ -221,7 +221,7 @@ with DAG( ), ), "profile": Param( - sorted(PROFILES)[0], + "daily-wave", type="string", enum=sorted(PROFILES), title="Профиль", diff --git a/docs/OPERATIONS.md b/docs/OPERATIONS.md index 10e16f6..480fc12 100644 --- a/docs/OPERATIONS.md +++ b/docs/OPERATIONS.md @@ -204,7 +204,7 @@ make generator-logs | `GEN_MODEL_TIMEZONE` | Часовой пояс модельных часов для дневного коэффициента | `UTC` | | `GEN_MODEL_TIME_SPEED` | Сколько модельных секунд проходит за одну настенную секунду | `1` | | `GEN_RUN_MODE` | Режим: `live`, `backfill` или `next-day` | `live` | -| `GEN_LAUNCH_PROFILE` | Имя профиля запуска для логов | `ci` | +| `GEN_LAUNCH_PROFILE` | Имя профиля запуска для логов | `daily-wave` | | `GEN_STARTUP_HISTORY_ARTIFACT` | JSON-файл для экспорта стартовой истории в режиме `backfill` | пусто | | `GEN_STATE_ENABLED` | Сохранять state v3 между рестартами | `true` | | `GEN_STATE_RESET` | Сбросить state при старте | `false` | @@ -270,14 +270,10 @@ make generated-history-runtime-check LIVE_SECONDS=45 WAIT_STG_SECONDS=10 make generated-history-runtime-check ``` -По умолчанию команда использует быстрый профиль `ci`: 6 часов модельного -времени. Историю на 2 суток с суточной волной можно получить одной командой. -В live-продолжении `daily-wave` идёт с ×60 и тикает раз в секунду, поэтому -модельные сутки проходят примерно за 24 настенные минуты: - -```bash -PROFILE=daily-wave make generated-history-analytics -``` +По умолчанию команда использует учебный профиль `daily-wave`: 2 суток с +суточной волной. В live-продолжении он идёт с ×60 и тикает раз в секунду, +поэтому модельные сутки проходят примерно за 24 настенные минуты. Плоский +профиль `ci` на 6 часов остаётся служебным для автоматических тестов. Разовую длительность можно задать без ручного расчёта `GEN_MODEL_T_END`: @@ -306,7 +302,7 @@ Kafka-топики данных, state и manifest генератора. `make g Ручной backfill без всего аналитического контура: ```bash -PROFILE=daily-wave make generator-backfill +make generator-backfill ``` Перед записью команда проверяет с хоста, что Kafka data-топики и STG пустые. @@ -336,7 +332,7 @@ Live-продолжение стартует с этого state. Использ чистом стенде: она тоже проверяет, что Kafka data-топики и STG пустые. ```bash -PROFILE=daily-wave make generator-continue +make generator-continue sleep 5 make generator-down diff --git a/docs/TEST_PLAN.md b/docs/TEST_PLAN.md index 37f3e9e..df9bc8c 100644 --- a/docs/TEST_PLAN.md +++ b/docs/TEST_PLAN.md @@ -14,7 +14,7 @@ ## Общие принципы -- По умолчанию используем быстрый профиль стартовой истории (`PROFILE=ci`). +- Для smoke и CI явно задаём служебный профиль `ci`. - Полный прогон выполняем отдельно через `PROFILE=daily-wave`. - Основной ручной путь запуска — через Airflow DAG `generator_control`. - Консольный чистый прогон `make generated-history-analytics` остаётся коротким @@ -31,7 +31,7 @@ ```bash # Чистый быстрый прогон: backfill -> Kafka -> STG -> ODS -> DDS -> DM -> Superset -make generated-history-analytics +PROFILE=ci make generated-history-analytics # Поднять остальные UI-сервисы после чистого прогона make up diff --git a/docs/runbooks/startup-history.md b/docs/runbooks/startup-history.md index 81cc5c3..9f2eae4 100644 --- a/docs/runbooks/startup-history.md +++ b/docs/runbooks/startup-history.md @@ -79,20 +79,18 @@ STG уже непустые, DAG упадёт до записи и подска ## Экспорт -По умолчанию создаётся быстрый 6-часовой артефакт: +По умолчанию создаётся двухсуточный артефакт `daily-wave` с суточной волной: ```bash make startup-history-export ``` -Файл по умолчанию: `/tmp/clickstream-startup-history.json`. - -История на 2 суток с суточной волной. В live-продолжении этот профиль проживает -модельные сутки примерно за 24 настенные минуты: +Файл по умолчанию: `/tmp/clickstream-startup-history.json`. В live-продолжении +профиль `daily-wave` проживает модельные сутки примерно за 24 настенные минуты. +Для быстрой автоматической проверки явно задайте служебный профиль `ci`: ```bash -ARTIFACT=/tmp/clickstream-startup-history-2d.json \ -PROFILE=daily-wave \ +PROFILE=ci ARTIFACT=/tmp/clickstream-startup-history-ci.json \ make startup-history-export ``` @@ -122,10 +120,11 @@ CHECK_LIVE_SEAM=0 make generated-history-check compact-топики в Kafka. ClickHouse читает данные через свои Kafka-таблицы и Materialized View, затем batch строит ODS, DDS и DM. -Если артефакт создан не профилем `ci`, импорт запускайте с тем же профилем: +Импорт запускайте с тем же профилем, на котором создан артефакт. Для служебного +артефакта `ci` профиль нужно задать явно: ```bash -PROFILE=daily-wave ARTIFACT=/tmp/clickstream-startup-history-2d.json \ +PROFILE=ci ARTIFACT=/tmp/clickstream-startup-history-ci.json \ make startup-history-import ``` @@ -142,10 +141,10 @@ ClickHouse уже успел прочитать частичные сообще ## Live-продолжение После импорта запускайте live с тем же профилем, что был в артефакте. Для -артефакта `daily-wave`: +обычного артефакта `daily-wave` достаточно команды: ```bash -PROFILE=daily-wave make generator-continue +make generator-continue ``` У `daily-wave` скорость ×60. Долгий простой стенда создаёт большую дыру в diff --git a/generator/README.md b/generator/README.md index 389ab04..1d7a145 100644 --- a/generator/README.md +++ b/generator/README.md @@ -78,7 +78,7 @@ generator-service -> Kafka topics -> (потребители отдельно) | `GEN_MODEL_TIMEZONE` | Часовой пояс модельных часов для дневного коэффициента | `UTC` | | `GEN_MODEL_TIME_SPEED` | Сколько модельных секунд проходит за одну настенную секунду | `1` | | `GEN_RUN_MODE` | Режим генератора | `live` | -| `GEN_LAUNCH_PROFILE` | Имя профиля запуска для логов | `ci` | +| `GEN_LAUNCH_PROFILE` | Имя профиля запуска для логов | `daily-wave` | | `GEN_STARTUP_HISTORY_ARTIFACT` | JSON-файл для экспорта стартовой истории в режиме `backfill` | — | | `GEN_DATA_DIR` | Путь к JSONL файлам | `/data` | | `GEN_SEED` | Сид для воспроизводимости | — | @@ -126,14 +126,11 @@ make generated-history-analytics проверяет Superset metadata. Файлы `data/*.jsonl` при этом не грузятся в Kafka: они пока используются только как фактура для генератора. -По умолчанию используется быстрый профиль `ci`: 6 часов модельного времени. -Профиль `daily-wave` даёт 2 суток, чтобы была видна суточная волна. В live он -идёт с `GEN_MODEL_TIME_SPEED=60` и `GEN_TICK_SECONDS=1`: модельные сутки -проходят примерно за 24 настенные минуты. - -```bash -PROFILE=daily-wave make generated-history-analytics -``` +По умолчанию используется учебный профиль `daily-wave`: 2 суток с суточной +волной. В live он идёт с `GEN_MODEL_TIME_SPEED=60` и +`GEN_TICK_SECONDS=1`: модельные сутки проходят примерно за 24 настенные +минуты. Плоский профиль `ci` на 6 часов остаётся служебным для автоматических +тестов. Разовую длительность можно задать без ручного расчёта `GEN_MODEL_T_END`: diff --git a/generator/src/clickstream_generator/config.py b/generator/src/clickstream_generator/config.py index e870370..1b2a0f2 100644 --- a/generator/src/clickstream_generator/config.py +++ b/generator/src/clickstream_generator/config.py @@ -102,7 +102,7 @@ class Config: else None ) launch_profile: str = field( - default_factory=lambda: os.getenv("GEN_LAUNCH_PROFILE", "ci") + default_factory=lambda: os.getenv("GEN_LAUNCH_PROFILE", "daily-wave") ) def __post_init__(self): diff --git a/generator/src/clickstream_generator/launch.py b/generator/src/clickstream_generator/launch.py index 7659a9a..fb1328f 100644 --- a/generator/src/clickstream_generator/launch.py +++ b/generator/src/clickstream_generator/launch.py @@ -88,7 +88,7 @@ def parse_duration(value: str) -> timedelta: def build_launch_env( verb: str, *, - profile_name: str = "ci", + profile_name: str = "daily-wave", duration: str | None = None, overrides: dict[str, str] | None = None, ) -> dict[str, str]: @@ -161,7 +161,11 @@ def _parse_args(argv: list[str]) -> argparse.Namespace: ) parser.add_argument( "--profile", - default=os.getenv("PROFILE") or os.getenv("GEN_LAUNCH_PROFILE") or "ci", + default=( + os.getenv("PROFILE") + or os.getenv("GEN_LAUNCH_PROFILE") + or "daily-wave" + ), choices=sorted(PROFILES), ) parser.add_argument("--duration", default=os.getenv("GEN_HISTORY_DURATION")) diff --git a/generator/tests/test_config.py b/generator/tests/test_config.py index 2d67531..a805e53 100644 --- a/generator/tests/test_config.py +++ b/generator/tests/test_config.py @@ -119,14 +119,14 @@ class TestConfigDefaults: if orig_value is not None: os.environ["GEN_TICK_SECONDS"] = orig_value - def test_launch_profile_defaults_to_ci(self, monkeypatch, data_dir): - """По умолчанию выбран быстрый профиль запуска ci.""" + def test_launch_profile_defaults_to_daily_wave(self, monkeypatch, data_dir): + """По умолчанию выбран учебный профиль запуска daily-wave.""" monkeypatch.setenv("GEN_DATA_DIR", str(data_dir)) monkeypatch.delenv("GEN_LAUNCH_PROFILE", raising=False) config = Config() - assert config.launch_profile == "ci" + assert config.launch_profile == "daily-wave" def test_launch_profile_can_be_read_from_env(self, monkeypatch, data_dir): """Выбранный профиль запуска попадает в конфигурацию для логов.""" diff --git a/generator/tests/test_generator_control_dag_contract.py b/generator/tests/test_generator_control_dag_contract.py index 3412034..cd8517a 100644 --- a/generator/tests/test_generator_control_dag_contract.py +++ b/generator/tests/test_generator_control_dag_contract.py @@ -42,6 +42,25 @@ def _declared_param_types() -> dict[str, set[str]]: return param_types +def _declared_param_defaults() -> dict[str, object]: + param_defaults = {} + for node in ast.walk(_tree()): + if not isinstance(node, ast.Dict): + continue + for key, value in zip(node.keys, node.values, strict=True): + if not ( + isinstance(key, ast.Constant) + and isinstance(key.value, str) + and isinstance(value, ast.Call) + and isinstance(value.func, ast.Name) + and value.func.id == "Param" + and value.args + ): + continue + param_defaults[key.value] = ast.literal_eval(value.args[0]) + return param_defaults + + def test_dag_file_exists_and_uses_dynamic_profiles(): """DAG берёт варианты профилей из PROFILES, а не из ручного списка.""" text = DAG_PATH.read_text(encoding="utf-8") @@ -55,9 +74,11 @@ def test_dag_file_exists_and_uses_dynamic_profiles(): def test_trigger_form_has_expected_param_enums(): """Форма запуска ограничивает операции и профили.""" text = DAG_PATH.read_text(encoding="utf-8") + param_defaults = _declared_param_defaults() assert 'enum=["backfill", "import", "next-day", "check"]' in text assert "enum=sorted(PROFILES)" in text + assert param_defaults["profile"] == "daily-wave" assert '"duration": Param(' in text assert '"artifact_path": Param(' in text assert '"expected_t_end": Param(' in text diff --git a/generator/tests/test_launch.py b/generator/tests/test_launch.py index fa7660e..6b96b71 100644 --- a/generator/tests/test_launch.py +++ b/generator/tests/test_launch.py @@ -5,6 +5,17 @@ from datetime import datetime, timezone +def test_daily_wave_is_the_default_for_api_and_cli(monkeypatch): + """Ручные точки входа без профиля выбирают daily-wave.""" + from clickstream_generator.launch import _parse_args, build_launch_env + + monkeypatch.delenv("PROFILE", raising=False) + monkeypatch.delenv("GEN_LAUNCH_PROFILE", raising=False) + + assert build_launch_env("backfill")["GEN_LAUNCH_PROFILE"] == "daily-wave" + assert _parse_args(["backfill"]).profile == "daily-wave" + + def test_duration_2d_sets_model_t_end_from_t0(): """Длительность 2d считается от T0 без ручного GEN_MODEL_T_END.""" from clickstream_generator.launch import build_launch_env diff --git a/scripts/export_startup_history_artifact.sh b/scripts/export_startup_history_artifact.sh index a7b13a9..20c299d 100755 --- a/scripts/export_startup_history_artifact.sh +++ b/scripts/export_startup_history_artifact.sh @@ -10,7 +10,7 @@ REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" COMPOSE_BIN="${COMPOSE_BIN:-docker compose}" CLEAN_START="${CLEAN_START:-1}" ARTIFACT="${ARTIFACT:-/tmp/clickstream-startup-history.json}" -PROFILE="${PROFILE:-${GEN_LAUNCH_PROFILE:-ci}}" +PROFILE="${PROFILE:-${GEN_LAUNCH_PROFILE:-daily-wave}}" duration_args=() if [[ -n "${GEN_HISTORY_DURATION:-}" ]]; then duration_args=(--duration "${GEN_HISTORY_DURATION}") diff --git a/scripts/import_startup_history_artifact.sh b/scripts/import_startup_history_artifact.sh index 04436d9..8020699 100755 --- a/scripts/import_startup_history_artifact.sh +++ b/scripts/import_startup_history_artifact.sh @@ -9,7 +9,7 @@ REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" COMPOSE_BIN="${COMPOSE_BIN:-docker compose}" ARTIFACT="${ARTIFACT:-/tmp/clickstream-startup-history.json}" -PROFILE="${PROFILE:-${GEN_LAUNCH_PROFILE:-ci}}" +PROFILE="${PROFILE:-${GEN_LAUNCH_PROFILE:-daily-wave}}" duration_args=() if [[ -n "${GEN_HISTORY_DURATION:-}" ]]; then duration_args=(--duration "${GEN_HISTORY_DURATION}") diff --git a/scripts/run_generated_history_analytics.sh b/scripts/run_generated_history_analytics.sh index 586bcd9..40667c3 100644 --- a/scripts/run_generated_history_analytics.sh +++ b/scripts/run_generated_history_analytics.sh @@ -15,7 +15,7 @@ COMPOSE_BIN="${COMPOSE_BIN:-docker compose}" CLEAN_START="${CLEAN_START:-1}" WAIT_CLICKHOUSE_SECONDS="${WAIT_CLICKHOUSE_SECONDS:-60}" WAIT_STG_SECONDS="${WAIT_STG_SECONDS:-10}" -PROFILE="${PROFILE:-${GEN_LAUNCH_PROFILE:-ci}}" +PROFILE="${PROFILE:-${GEN_LAUNCH_PROFILE:-daily-wave}}" duration_args=() if [[ -n "${GEN_HISTORY_DURATION:-}" ]]; then duration_args=(--duration "${GEN_HISTORY_DURATION}") diff --git a/scripts/run_generator.sh b/scripts/run_generator.sh index c908d87..e142610 100644 --- a/scripts/run_generator.sh +++ b/scripts/run_generator.sh @@ -22,7 +22,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" COMPOSE_BIN="${COMPOSE_BIN:-docker compose}" -PROFILE="${PROFILE:-${GEN_LAUNCH_PROFILE:-ci}}" +PROFILE="${PROFILE:-${GEN_LAUNCH_PROFILE:-daily-wave}}" duration_args=() if [[ -n "${GEN_HISTORY_DURATION:-}" ]]; then duration_args=(--duration "${GEN_HISTORY_DURATION}") diff --git a/tests/test_generated_analytics_check_contract.py b/tests/test_generated_analytics_check_contract.py index 0ecc4c8..7fe8164 100644 --- a/tests/test_generated_analytics_check_contract.py +++ b/tests/test_generated_analytics_check_contract.py @@ -137,7 +137,7 @@ def test_generated_history_check_uses_actual_manifest_boundary_and_profile(): assert "kafka-manifest-summary" in script assert "manifest_model_t_end" in script assert "CH_MODEL_T_END=\"$(clickhouse_datetime_literal \"${manifest_model_t_end}\")\"" in script - assert "PROFILE ?= ci" in makefile + assert "PROFILE ?= daily-wave" in makefile assert "GEN_LAUNCH_PROFILE" in makefile assert "PROFILE" in makefile