feat(generator): эталонный мир в git и import по умолчанию

- Зачем:
  - менти собирал мир генерацией (минуты и десятки минут CPU); теперь
    готовый трёхдневный мир загружается импортом за ~2 минуты, и числа
    у всех менти совпадают число-в-число (issue #3).
- Что:
  - артефакт data/startup_history/reference-world.json.xz в git:
    3 модельных дня daily-wave, ~850 МБ JSON → 32 МБ xz;
  - чтение и запись артефакта понимают .xz потоково (lzma); пустое поле
    artifact_path в пульте и make startup-history-import читают эталон;
  - длительность профиля daily-wave стала 3d — в тон эталонному миру;
  - предпроверка чистого стенда ставит зависимости генератора через uv;
    экспорт и импорт разведены отдельными переменными Makefile;
  - доки и runbook обновлены; новые контрактные тесты: xz round-trip
    и дефолтные пути артефакта.
- Проверка:
  - make test (210 + 31) и make lint зелёные; импорт на чистом стенде
    за 2м03с, manifest совпал (280437 событий), Superset-проверка
    зелёная; независимое ревью — APPROVED.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 18:49:02 +03:00
co-authored by Claude Fable 5
parent b87dde79b7
commit 10ee1a1cb6
21 changed files with 165 additions and 35 deletions
+4 -2
View File
@@ -9,6 +9,8 @@
contract-test config-test test lint contract-test config-test test lint
COMPOSE ?= docker compose COMPOSE ?= docker compose
STARTUP_HISTORY_EXPORT_ARTIFACT ?= $(or $(ARTIFACT),/tmp/clickstream-startup-history.json)
STARTUP_HISTORY_IMPORT_ARTIFACT ?= $(or $(ARTIFACT),data/startup_history/reference-world.json.xz)
PROFILE ?= daily-wave PROFILE ?= daily-wave
PYTEST_ARGS ?= -q -p no:cacheprovider PYTEST_ARGS ?= -q -p no:cacheprovider
PYTHON_CHECK_PATHS := airflow/dags configs generator scripts superset tests PYTHON_CHECK_PATHS := airflow/dags configs generator scripts superset tests
@@ -63,11 +65,11 @@ generated-history-chain-check:
# Сгенерировать стартовую историю и сохранить портативный артефакт # Сгенерировать стартовую историю и сохранить портативный артефакт
startup-history-export: startup-history-export:
COMPOSE_BIN="$(COMPOSE)" bash ./scripts/export_startup_history_artifact.sh ARTIFACT="$(STARTUP_HISTORY_EXPORT_ARTIFACT)" COMPOSE_BIN="$(COMPOSE)" bash ./scripts/export_startup_history_artifact.sh
# Воспроизвести портативный артефакт стартовой истории в Kafka # Воспроизвести портативный артефакт стартовой истории в Kafka
startup-history-import: startup-history-import:
COMPOSE_BIN="$(COMPOSE)" bash ./scripts/import_startup_history_artifact.sh ARTIFACT="$(STARTUP_HISTORY_IMPORT_ARTIFACT)" COMPOSE_BIN="$(COMPOSE)" bash ./scripts/import_startup_history_artifact.sh
# Сверить ClickHouse после импорта с manifest артефакта # Сверить ClickHouse после импорта с manifest артефакта
startup-history-check: startup-history-check:
+2 -2
View File
@@ -59,7 +59,7 @@ Airflow-образ и подтягивает новые зависимости
make generated-history-analytics make generated-history-analytics
``` ```
По умолчанию используется учебный профиль `daily-wave`: 2 суток с суточной По умолчанию используется учебный профиль `daily-wave`: 3 суток с суточной
волной. В live-продолжении он идёт с ×60: модельные сутки проходят примерно волной. В live-продолжении он идёт с ×60: модельные сутки проходят примерно
за 24 настенные минуты. Плоский профиль `ci` на 6 часов остаётся служебным за 24 настенные минуты. Плоский профиль `ci` на 6 часов остаётся служебным
для автоматических тестов. для автоматических тестов.
@@ -162,7 +162,7 @@ flowchart LR
- [Запуск и эксплуатация](./docs/OPERATIONS.md) — сценарий запуска, параметры DAG-ов, - [Запуск и эксплуатация](./docs/OPERATIONS.md) — сценарий запуска, параметры DAG-ов,
мониторинг, частые проблемы. мониторинг, частые проблемы.
- [Runbook стартовой истории](./docs/runbooks/startup-history.md) — экспорт, - [Runbook стартовой истории](./docs/runbooks/startup-history.md) — экспорт,
импорт и live-продолжение из готового артефакта. импорт эталонного мира из Git по умолчанию и live-продолжение.
- [Карта репозитория](./docs/REPO_MAP.md) — где какие файлы и что менять. - [Карта репозитория](./docs/REPO_MAP.md) — где какие файлы и что менять.
- [Курс «Кликстрим на ClickHouse»](./docs/course/README.md) — учебная программа на этом - [Курс «Кликстрим на ClickHouse»](./docs/course/README.md) — учебная программа на этом
стенде. стенде.
+1 -1
View File
@@ -252,7 +252,7 @@ with DAG(
title="Артефакт", title="Артефакт",
description=( description=(
"Backfill: куда сохранить файл; пусто — не сохранять. " "Backfill: куда сохранить файл; пусто — не сохранять. "
"Import: что читать; пусто — путь по умолчанию в data." "Import: что читать; пусто — эталонный мир из репозитория."
), ),
), ),
"expected_t_end": Param( "expected_t_end": Param(
+6
View File
@@ -0,0 +1,6 @@
# Эталонный мир
Здесь хранится `reference-world.json.xz` — готовая история за три модельных дня.
Файл собирает сопровождающий проекта по инструкции в
[`docs/runbooks/startup-history.md`](../../docs/runbooks/startup-history.md).
Ожидаемый размер сжатого файла — около 32 МБ.
Binary file not shown.
+4 -3
View File
@@ -69,7 +69,8 @@ volumes или live-генератором. Для стыка backfill/live от
- `profile` — список берётся из `PROFILES` генератора; - `profile` — список берётся из `PROFILES` генератора;
- `duration``6h`, `2d` и т.п.; пусто означает длительность профиля; - `duration``6h`, `2d` и т.п.; пусто означает длительность профиля;
- `seed`, `model_time_speed` — необязательные переопределения мира; - `seed`, `model_time_speed` — необязательные переопределения мира;
- `artifact_path` — для `backfill` путь сохранения, для `import` путь чтения; - `artifact_path` — для `backfill` путь сохранения; для `import` путь чтения.
При пустом поле импортируется эталонный мир из репозитория;
- `expected_t_end` — необязательная ожидаемая граница перед `next-day`. - `expected_t_end` — необязательная ожидаемая граница перед `next-day`.
При расхождении запуск показывает ожидаемое и фактическое значения. При расхождении запуск показывает ожидаемое и фактическое значения.
@@ -252,7 +253,7 @@ CHECK_LIVE_SEAM=1 GEN_LIVE_CHECK_MINUTES=10 make generated-history-check
``` ```
Для commit gate issue 17 есть короткий runtime-путь без полного `daily-wave` на Для commit gate issue 17 есть короткий runtime-путь без полного `daily-wave` на
2 суток и без Superset UI: 3 суток и без Superset UI:
```bash ```bash
make generated-history-runtime-check make generated-history-runtime-check
@@ -270,7 +271,7 @@ make generated-history-runtime-check
LIVE_SECONDS=45 WAIT_STG_SECONDS=10 make generated-history-runtime-check LIVE_SECONDS=45 WAIT_STG_SECONDS=10 make generated-history-runtime-check
``` ```
По умолчанию команда использует учебный профиль `daily-wave`: 2 суток с По умолчанию команда использует учебный профиль `daily-wave`: 3 суток с
суточной волной. В live-продолжении он идёт с ×60 и тикает раз в секунду, суточной волной. В live-продолжении он идёт с ×60 и тикает раз в секунду,
поэтому модельные сутки проходят примерно за 24 настенные минуты. Плоский поэтому модельные сутки проходят примерно за 24 настенные минуты. Плоский
профиль `ci` на 6 часов остаётся служебным для автоматических тестов. профиль `ci` на 6 часов остаётся служебным для автоматических тестов.
+1 -1
View File
@@ -124,7 +124,7 @@ curl -s -u admin:admin "http://localhost:3000/api/dashboards/uid/airflow-overvie
### B.1 Полная стартовая история и ETL ### B.1 Полная стартовая история и ETL
```bash ```bash
# История на 2 суток с видимой суточной волной # История на 3 суток с видимой суточной волной
PROFILE=daily-wave make generated-history-analytics PROFILE=daily-wave make generated-history-analytics
make up make up
``` ```
+38 -5
View File
@@ -29,11 +29,43 @@
| Профиль | Для чего | Длительность | Live-ход | | Профиль | Для чего | Длительность | Live-ход |
|---------|----------|--------------|----------| |---------|----------|--------------|----------|
| `ci` | Быстрая проверка и CI | `6h` | ×1, тик 60 с | | `ci` | Быстрая проверка и CI | `6h` | ×1, тик 60 с |
| `daily-wave` | История с видимой суточной волной | `2d` | ×60, тик 1 с | | `daily-wave` | История с видимой суточной волной | `3d` | ×60, тик 1 с |
Длительность можно переопределить через `GEN_HISTORY_DURATION`, например `2d`. Длительность можно переопределить через `GEN_HISTORY_DURATION`, например `2d`.
Команда сама считает `GEN_MODEL_T_END` от `GEN_MODEL_T0`. Команда сама считает `GEN_MODEL_T_END` от `GEN_MODEL_T0`.
## Эталонный мир
В репозитории хранится готовый мир за три модельных дня:
`data/startup_history/reference-world.json.xz`. Это обычный JSON-артефакт,
сжатый xz. Его добавляют прямо в Git, без Git LFS.
Сопровождающий проекта собирает файл одним запуском из корня репозитория:
```bash
ARTIFACT="$PWD/data/startup_history/reference-world.json.xz" \
make startup-history-export
```
Команда выполняет backfill с профилем `daily-wave` и сразу пишет xz-файл.
Несжатый JSON занимает около 850 МБ, сжатый файл — около 32 МБ. После проверки
файл можно добавить обычной командой:
```bash
git add data/startup_history/reference-world.json.xz
```
При импорте DAG дважды читает и распаковывает артефакт: во время предпроверки и
перед записью в Kafka. Это увеличивает время импорта, но не меняет результат.
Менти в форме `generator_control` выбирает `import` и оставляет
`artifact_path` пустым. Тогда читается эталонный мир из репозитория. Из консоли
тот же импорт запускается без указания пути:
```bash
make startup-history-import
```
## Пульт в Airflow ## Пульт в Airflow
Основной ручной путь — DAG `generator_control` в Airflow UI: Основной ручной путь — DAG `generator_control` в Airflow UI:
@@ -58,7 +90,8 @@
- `duration` можно оставить пустым, тогда берётся длительность профиля. - `duration` можно оставить пустым, тогда берётся длительность профиля.
- `seed` и `model_time_speed` — необязательные переопределения мира. - `seed` и `model_time_speed` — необязательные переопределения мира.
- `artifact_path`: для `backfill` — куда сохранить файл; пусто — не сохранять. - `artifact_path`: для `backfill` — куда сохранить файл; пусто — не сохранять.
Для `import` — что читать; пусто — `/opt/airflow/data/startup-history-import.json`. Для `import` — что читать; пусто — эталонный мир из репозитория:
`/opt/airflow/data/startup_history/reference-world.json.xz`.
Backfill и import работают только на чистом стенде. Если Kafka-топики данных или Backfill и import работают только на чистом стенде. Если Kafka-топики данных или
STG уже непустые, DAG упадёт до записи и подскажет `make clean`. Консольные STG уже непустые, DAG упадёт до записи и подскажет `make clean`. Консольные
@@ -79,7 +112,7 @@ STG уже непустые, DAG упадёт до записи и подска
## Экспорт ## Экспорт
По умолчанию создаётся двухсуточный артефакт `daily-wave` с суточной волной: По умолчанию создаётся трёхсуточный артефакт `daily-wave` с суточной волной:
```bash ```bash
make startup-history-export make startup-history-export
@@ -104,11 +137,11 @@ make clean
docker compose up -d clickhouse kafka docker compose up -d clickhouse kafka
make ddl make ddl
ARTIFACT=/tmp/clickstream-startup-history.json make startup-history-import make startup-history-import
sleep 10 sleep 10
make transform make transform
ARTIFACT=/tmp/clickstream-startup-history.json make startup-history-check ARTIFACT=data/startup_history/reference-world.json.xz make startup-history-check
CHECK_LIVE_SEAM=0 make generated-history-check CHECK_LIVE_SEAM=0 make generated-history-check
``` ```
@@ -105,7 +105,7 @@ ADR-0005 решил отвязать время генератора от реа
- `reset` — подставляет `GEN_RUN_MODE=live` и `GEN_STATE_RESET=true`. - `reset` — подставляет `GEN_RUN_MODE=live` и `GEN_STATE_RESET=true`.
Профиль `ci` даёт быстрый 6-часовой прогон и остаётся на ×1 с тиком 60 с. Профиль `ci` даёт быстрый 6-часовой прогон и остаётся на ×1 с тиком 60 с.
Профиль `daily-wave` даёт 2 суток, чтобы была видна суточная волна, а в live Профиль `daily-wave` даёт 3 суток, чтобы была видна суточная волна, а в live
идёт с `GEN_MODEL_TIME_SPEED=60` и `GEN_TICK_SECONDS=1`: модельные сутки идёт с `GEN_MODEL_TIME_SPEED=60` и `GEN_TICK_SECONDS=1`: модельные сутки
проходят примерно за 24 настенные минуты. Пара «тик 1 с, скорость ×60» выбрана, проходят примерно за 24 настенные минуты. Пара «тик 1 с, скорость ×60» выбрана,
чтобы модельный шаг тика остался 60 секунд. Так событийный бюджет и форма волны чтобы модельный шаг тика остался 60 секунд. Так событийный бюджет и форма волны
@@ -60,8 +60,8 @@
1. **Эталонный мир**: 3 модельных дня на профиле `daily-wave` (суточная 1. **Эталонный мир**: 3 модельных дня на профиле `daily-wave` (суточная
волна видна, есть «средний» день и сравнение день-к-дню). Собирает волна видна, есть «средний» день и сравнение день-к-дню). Собирает
мейнтейнер один раз через `backfill`; хранится в git по фиксированному мейнтейнер один раз через `backfill`; хранится в git по фиксированному
пути `data/startup_history/` сжатым `xz` (~13 МБ; замер 2026-07-19: пути `data/startup_history/` сжатым `xz` (факт 2026-07-22: ~850 МБ
`xz -9e` жмёт артефакт в 48 раз). Формат не меняется: `import` требует сырой JSON, ~32 МБ после `xz -9e`). Формат не меняется: `import` требует
`raw_topics`, сжатие снимает вопрос размера. Паттерн-образец — `raw_topics`, сжатие снимает вопрос размера. Паттерн-образец —
`airflow-greenplum-solution` (сид `demo.sql.xz` в git, стрим-распаковка `airflow-greenplum-solution` (сид `demo.sql.xz` в git, стрим-распаковка
при загрузке). при загрузке).
+1 -1
View File
@@ -126,7 +126,7 @@ make generated-history-analytics
проверяет Superset metadata. Файлы `data/*.jsonl` при этом не грузятся в Kafka: проверяет Superset metadata. Файлы `data/*.jsonl` при этом не грузятся в Kafka:
они пока используются только как фактура для генератора. они пока используются только как фактура для генератора.
По умолчанию используется учебный профиль `daily-wave`: 2 суток с суточной По умолчанию используется учебный профиль `daily-wave`: 3 суток с суточной
волной. В live он идёт с `GEN_MODEL_TIME_SPEED=60` и волной. В live он идёт с `GEN_MODEL_TIME_SPEED=60` и
`GEN_TICK_SECONDS=1`: модельные сутки проходят примерно за 24 настенные `GEN_TICK_SECONDS=1`: модельные сутки проходят примерно за 24 настенные
минуты. Плоский профиль `ci` на 6 часов остаётся служебным для автоматических минуты. Плоский профиль `ci` на 6 часов остаётся служебным для автоматических
@@ -335,12 +335,13 @@ def target_dag_trigger_error(dag_id: str, dag_model) -> str | None:
def default_artifact_path(operation: str) -> str: def default_artifact_path(operation: str) -> str:
"""Возвращает путь артефакта по умолчанию в общем томе data.""" """Возвращает путь артефакта по умолчанию в общем томе data."""
filename = ( if operation == "import":
"startup-history-import.json" return str(
if operation == "import" Path(AIRFLOW_DATA_DIR)
else "startup-history.json" / "startup_history"
/ "reference-world.json.xz"
) )
return str(Path(AIRFLOW_DATA_DIR) / filename) return str(Path(AIRFLOW_DATA_DIR) / "startup-history.json")
@contextmanager @contextmanager
@@ -47,7 +47,7 @@ PROFILES = {
}, },
), ),
"daily-wave": LaunchProfile( "daily-wave": LaunchProfile(
duration="2d", duration="3d",
env={ env={
"GEN_SEED": "4242", "GEN_SEED": "4242",
"GEN_MODEL_T0": "2026-01-01T00:00:00+00:00", "GEN_MODEL_T0": "2026-01-01T00:00:00+00:00",
@@ -5,6 +5,7 @@ from __future__ import annotations
import argparse import argparse
import hashlib import hashlib
import json import json
import lzma
import logging import logging
from copy import deepcopy from copy import deepcopy
from datetime import datetime, timezone from datetime import datetime, timezone
@@ -353,6 +354,15 @@ def write_startup_history_artifact(path: str | Path, artifact: dict) -> None:
"""Пишет артефакт в JSON-файл.""" """Пишет артефакт в JSON-файл."""
target = Path(path) target = Path(path)
target.parent.mkdir(parents=True, exist_ok=True) target.parent.mkdir(parents=True, exist_ok=True)
if target.suffix == ".xz":
with lzma.open(
target,
"wt",
encoding="utf-8",
preset=9 | lzma.PRESET_EXTREME,
) as output:
json.dump(artifact, output, ensure_ascii=True, indent=2)
return
target.write_text( target.write_text(
json.dumps(artifact, ensure_ascii=True, indent=2), json.dumps(artifact, ensure_ascii=True, indent=2),
encoding="utf-8", encoding="utf-8",
@@ -361,7 +371,11 @@ def write_startup_history_artifact(path: str | Path, artifact: dict) -> None:
def load_startup_history_artifact(path: str | Path) -> dict: def load_startup_history_artifact(path: str | Path) -> dict:
"""Читает артефакт из JSON-файла.""" """Читает артефакт из JSON-файла."""
return json.loads(Path(path).read_text(encoding="utf-8")) source = Path(path)
if source.suffix == ".xz":
with lzma.open(source, "rt", encoding="utf-8") as input_file:
return json.load(input_file)
return json.loads(source.read_text(encoding="utf-8"))
def validate_startup_history_artifact( def validate_startup_history_artifact(
+13 -1
View File
@@ -10,6 +10,18 @@ from types import SimpleNamespace
import pytest import pytest
def test_default_artifact_paths_keep_import_and_backfill_separate():
"""Import читает эталонный мир, а backfill пишет в прежний файл."""
from clickstream_generator.airflow_control import default_artifact_path
assert default_artifact_path("import") == (
"/opt/airflow/data/startup_history/reference-world.json.xz"
)
assert default_artifact_path("backfill") == (
"/opt/airflow/data/startup-history.json"
)
def test_build_control_env_uses_profile_duration_and_airflow_data_dir(): def test_build_control_env_uses_profile_duration_and_airflow_data_dir():
"""Пульт строит env для backfill из профиля и каталога Airflow.""" """Пульт строит env для backfill из профиля и каталога Airflow."""
from clickstream_generator.airflow_control import build_control_env from clickstream_generator.airflow_control import build_control_env
@@ -47,7 +59,7 @@ def test_import_env_requires_artifact_path_and_uses_backfill_contract():
assert env["GEN_RUN_MODE"] == "backfill" assert env["GEN_RUN_MODE"] == "backfill"
assert env["GEN_STATE_RESET"] == "true" assert env["GEN_STATE_RESET"] == "true"
assert env["GEN_HISTORY_DURATION"] == "2d" assert env["GEN_HISTORY_DURATION"] == "3d"
def test_next_day_env_uses_world_settings_and_current_manifest_boundary(): def test_next_day_env_uses_world_settings_and_current_manifest_boundary():
@@ -247,6 +247,22 @@ def test_console_generator_paths_precheck_clean_stand_before_writes():
) )
def test_reference_artifact_is_import_default_only():
"""Экспорт не может по умолчанию перезаписать эталонный мир."""
makefile = (REPO_ROOT / "Makefile").read_text(encoding="utf-8")
export_script = (
REPO_ROOT / "scripts" / "export_startup_history_artifact.sh"
).read_text(encoding="utf-8")
import_script = (
REPO_ROOT / "scripts" / "import_startup_history_artifact.sh"
).read_text(encoding="utf-8")
assert "STARTUP_HISTORY_EXPORT_ARTIFACT" in makefile
assert "STARTUP_HISTORY_IMPORT_ARTIFACT" in makefile
assert "/tmp/clickstream-startup-history.json" in export_script
assert "data/startup_history/reference-world.json.xz" in import_script
def test_console_continue_checks_state_or_clean_stand_before_live_start(): def test_console_continue_checks_state_or_clean_stand_before_live_start():
"""Continue не стартует новый live-мир на грязном стенде без state.""" """Continue не стартует новый live-мир на грязном стенде без state."""
run_generator = (REPO_ROOT / "scripts" / "run_generator.sh").read_text( run_generator = (REPO_ROOT / "scripts" / "run_generator.sh").read_text(
+3 -3
View File
@@ -26,15 +26,15 @@ def test_duration_2d_sets_model_t_end_from_t0():
assert env["GEN_MODEL_T_END"] == "2026-01-03T00:00:00+00:00" assert env["GEN_MODEL_T_END"] == "2026-01-03T00:00:00+00:00"
def test_daily_wave_profile_uses_two_days_by_default(): def test_daily_wave_profile_uses_three_days_by_default():
"""Профиль daily-wave даёт историю с суточной волной.""" """Профиль daily-wave даёт историю с суточной волной."""
from clickstream_generator.launch import build_launch_env from clickstream_generator.launch import build_launch_env
env = build_launch_env("backfill", profile_name="daily-wave") env = build_launch_env("backfill", profile_name="daily-wave")
assert env["GEN_LAUNCH_PROFILE"] == "daily-wave" assert env["GEN_LAUNCH_PROFILE"] == "daily-wave"
assert env["GEN_HISTORY_DURATION"] == "2d" assert env["GEN_HISTORY_DURATION"] == "3d"
assert env["GEN_MODEL_T_END"] == "2026-01-03T00:00:00+00:00" assert env["GEN_MODEL_T_END"] == "2026-01-04T00:00:00+00:00"
assert env["GEN_MODEL_TIME_SPEED"] == "60" assert env["GEN_MODEL_TIME_SPEED"] == "60"
assert env["GEN_TICK_SECONDS"] == "1" assert env["GEN_TICK_SECONDS"] == "1"
@@ -4,12 +4,55 @@
from dataclasses import replace from dataclasses import replace
from datetime import datetime, timezone from datetime import datetime, timezone
import json
import lzma
import pytest import pytest
from generator import GeneratorState from generator import GeneratorState
def test_plain_json_roundtrip_keeps_existing_serialization(tmp_path):
"""Обычный JSON по-прежнему пишется без сжатия и читается обратно."""
from clickstream_generator.startup_history_artifact import (
load_startup_history_artifact,
write_startup_history_artifact,
)
artifact = {"text": "мир", "nested": {"value": 42}}
path = tmp_path / "artifact.json"
write_startup_history_artifact(path, artifact)
assert path.read_text(encoding="utf-8") == json.dumps(
artifact,
ensure_ascii=True,
indent=2,
)
assert load_startup_history_artifact(path) == artifact
def test_xz_artifact_roundtrip(tmp_path):
"""XZ-артефакт потоково пишется и читается без изменения данных."""
from clickstream_generator.startup_history_artifact import (
load_startup_history_artifact,
write_startup_history_artifact,
)
artifact = {"text": "мир", "nested": {"value": 42}}
path = tmp_path / "artifact.json.xz"
write_startup_history_artifact(path, artifact)
assert load_startup_history_artifact(path) == artifact
with lzma.open(path, "rt", encoding="utf-8") as input_file:
assert input_file.read() == json.dumps(
artifact,
ensure_ascii=True,
indent=2,
)
def _state() -> GeneratorState: def _state() -> GeneratorState:
import random import random
+4 -2
View File
@@ -26,7 +26,8 @@ fi
if [[ "${CHECK_MODE}" == "clean" ]]; then if [[ "${CHECK_MODE}" == "clean" ]]; then
echo "Предпроверка: live-генератор не должен быть запущен." echo "Предпроверка: live-генератор не должен быть запущен."
PYTHONPATH="${REPO_ROOT}/generator/src" \ PYTHONPATH="${REPO_ROOT}/generator/src" \
uv run python -m clickstream_generator.stand_clean \ uv run --with-requirements generator/requirements.txt \
python -m clickstream_generator.stand_clean \
--mode live \ --mode live \
--live-metrics-url "${GENERATOR_METRICS_URL_HOST}" --live-metrics-url "${GENERATOR_METRICS_URL_HOST}"
fi fi
@@ -54,7 +55,8 @@ fi
KAFKA_BOOTSTRAP_SERVERS="${KAFKA_BOOTSTRAP_SERVERS_HOST}" \ KAFKA_BOOTSTRAP_SERVERS="${KAFKA_BOOTSTRAP_SERVERS_HOST}" \
GEN_DATA_DIR="${REPO_ROOT}/data" \ GEN_DATA_DIR="${REPO_ROOT}/data" \
PYTHONPATH="${REPO_ROOT}/generator/src" \ PYTHONPATH="${REPO_ROOT}/generator/src" \
uv run python -m clickstream_generator.stand_clean \ uv run --with-requirements generator/requirements.txt \
python -m clickstream_generator.stand_clean \
--mode "${CHECK_MODE}" \ --mode "${CHECK_MODE}" \
--kafka-bootstrap-servers "${KAFKA_BOOTSTRAP_SERVERS_HOST}" \ --kafka-bootstrap-servers "${KAFKA_BOOTSTRAP_SERVERS_HOST}" \
--stg-counts "${stg_counts}" --stg-counts "${stg_counts}"
+1 -1
View File
@@ -27,7 +27,7 @@ clickhouse_datetime_literal() {
[[ -s "${ARTIFACT}" ]] || fail "артефакт не найден или пуст: ${ARTIFACT}" [[ -s "${ARTIFACT}" ]] || fail "артефакт не найден или пуст: ${ARTIFACT}"
ARTIFACT_DIR="$(dirname "${ARTIFACT}")" ARTIFACT_DIR="$(cd "$(dirname "${ARTIFACT}")" && pwd)"
ARTIFACT_FILE="$(basename "${ARTIFACT}")" ARTIFACT_FILE="$(basename "${ARTIFACT}")"
manifest_summary="$(${COMPOSE_BIN} run --rm --no-deps \ manifest_summary="$(${COMPOSE_BIN} run --rm --no-deps \
+2 -2
View File
@@ -8,7 +8,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
COMPOSE_BIN="${COMPOSE_BIN:-docker compose}" COMPOSE_BIN="${COMPOSE_BIN:-docker compose}"
ARTIFACT="${ARTIFACT:-/tmp/clickstream-startup-history.json}" ARTIFACT="${ARTIFACT:-data/startup_history/reference-world.json.xz}"
PROFILE="${PROFILE:-${GEN_LAUNCH_PROFILE:-daily-wave}}" PROFILE="${PROFILE:-${GEN_LAUNCH_PROFILE:-daily-wave}}"
duration_args=() duration_args=()
if [[ -n "${GEN_HISTORY_DURATION:-}" ]]; then if [[ -n "${GEN_HISTORY_DURATION:-}" ]]; then
@@ -31,7 +31,7 @@ if [[ ! -s "${ARTIFACT}" ]]; then
exit 1 exit 1
fi fi
ARTIFACT_DIR="$(dirname "${ARTIFACT}")" ARTIFACT_DIR="$(cd "$(dirname "${ARTIFACT}")" && pwd)"
ARTIFACT_FILE="$(basename "${ARTIFACT}")" ARTIFACT_FILE="$(basename "${ARTIFACT}")"
echo "=== Импорт стартовой истории ===" echo "=== Импорт стартовой истории ==="