docs(architecture): операторские разделы README переехали в справочники #213
No files matched your search
@@ -61,7 +61,7 @@ make check-services
|
|||||||
экземпляра, внешние порты, учебные учётные данные и ключи. Одно значение образца
|
экземпляра, внешние порты, учебные учётные данные и ключи. Одно значение образца
|
||||||
верно не везде — `DOCKER_GID`: у группы `docker` на каждой машине свой номер, и
|
верно не везде — `DOCKER_GID`: у группы `docker` на каждой машине свой номер, и
|
||||||
свой подскажет `getent group docker`. Пока он не сойдётся, стенд работает весь,
|
свой подскажет `getent group docker`. Пока он не сойдётся, стенд работает весь,
|
||||||
кроме пульта мира — о нём ниже. `compose.yaml` не
|
кроме [пульта мира](docs/architecture/world.md#пульт-мира). `compose.yaml` не
|
||||||
дублирует их умолчаниями: если значения нет в `.env`, Compose сразу предложит
|
дублирует их умолчаниями: если значения нет в `.env`, Compose сразу предложит
|
||||||
скопировать образец. Внутренние адреса, имена топиков и версии образов описывают
|
скопировать образец. Внутренние адреса, имена топиков и версии образов описывают
|
||||||
сам стенд, поэтому записаны литералами в `compose.yaml` и Dockerfile.
|
сам стенд, поэтому записаны литералами в `compose.yaml` и Dockerfile.
|
||||||
@@ -71,56 +71,8 @@ make check-services
|
|||||||
все локальные данные стенда, а следующий `make up` создаст их с новыми
|
все локальные данные стенда, а следующий `make up` создаст их с новыми
|
||||||
значениями.
|
значениями.
|
||||||
|
|
||||||
`make up` собирает локальные образы Airflow, Superset и Grafana, поднимает весь
|
Что делает `make up`, кого спрашивает каждая проверка и как устроен смоук,
|
||||||
стенд и ждёт здорового состояния долгоживущих контейнеров. В образ Airflow
|
описано в [карте проверок](docs/architecture/testing.md).
|
||||||
добавлены закреплённые клиенты ClickHouse и Kafka. Одноразовые `airflow-init` и
|
|
||||||
`superset-init` завершаются с кодом 0. Первый обновляет схему Airflow,
|
|
||||||
подготавливает администратора и два подключения к `clickhouse-01`. Второй
|
|
||||||
обновляет Superset, создаёт администратора и импортирует подключение к
|
|
||||||
`clickhouse-02`. На ноутбуке в режиме энергосбережения приёмочный подъём с
|
|
||||||
пустых томов занял около десяти минут, повторный на живом стенде — около трёх.
|
|
||||||
|
|
||||||
`make smoke` за секунды спрашивает, собран ли стенд: зависимости машины,
|
|
||||||
здоровье контейнеров, устройство keeper, ответ Kafka с машины через отображённый
|
|
||||||
порт, четыре цели Prometheus, два источника Grafana, компоненты Airflow и
|
|
||||||
подготовленное подключение к `clickhouse-01`. В конце проверка спрашивает у
|
|
||||||
Docker, не убивало ли ядро что-нибудь в долгоживущих контейнерах за нехватку
|
|
||||||
памяти и не включалась ли политика перезапуска: убитый контейнер Docker
|
|
||||||
поднимает сам, и проверка состояния об этом промолчит.
|
|
||||||
|
|
||||||
Двенадцать проверок здоровья сразу после `make up --wait` повторяют то, чего
|
|
||||||
Compose уже дождался: у каждой долгоживущей службы есть своя `healthcheck`.
|
|
||||||
Оставлены они потому, что первый вопрос к стенду всё равно «всё ли живо», а
|
|
||||||
ответ на него стоит меньше секунды. Устройство keeper — другое дело: он должен
|
|
||||||
работать от пользователя `clickhouse`, с пределом в 262144 открытых файла и со
|
|
||||||
своим томом под данные. Всё это объявлено в `compose.yaml`, но здоровым keeper
|
|
||||||
выглядит и без этого, поэтому смоук спрашивает у живого контейнера, дошли ли
|
|
||||||
объявленные настройки до процесса.
|
|
||||||
|
|
||||||
Kafka по той же причине спрашивают снаружи. Её `healthcheck` обращается к
|
|
||||||
брокеру изнутри контейнера и по внутреннему слушателю, поэтому здоровой Kafka
|
|
||||||
остаётся и тогда, когда объявленный наружу адрес ведёт не туда. Клиент с машины
|
|
||||||
в этом случае подключается, получает метаданные и молча виснет на адресе,
|
|
||||||
которого с его стороны не существует. Смоук идёт тем же путём, что и будущий
|
|
||||||
генератор: стучится в отображённый порт и просит список топиков.
|
|
||||||
|
|
||||||
`make check-services` проверяет то, ради чего приходится ждать службу: работу с
|
|
||||||
топиком Kafka с машины — создан, найден, удалён, — ручной запуск пробников
|
|
||||||
`test_clickhouse` и `test_kafka`, вход в Superset, его метаданные и подключение
|
|
||||||
к `clickhouse-02`. Первый пробник создаёт таблицы на обеих нодах и читает через
|
|
||||||
`Distributed` на ноде 2 строку из локальной таблицы ноды 1. Второй пишет в Kafka
|
|
||||||
и читает свой маркер. Временный топик проверки с машины и запуски DAG удаляются;
|
|
||||||
постоянный топик пробника сохраняется, а старые записи чистит Kafka.
|
|
||||||
|
|
||||||
`make check-clickhouse` запускает отдельную глубокую проверку ClickHouse:
|
|
||||||
описание кластера, макросы, связь с keeper, `ReplicatedMergeTree`,
|
|
||||||
`Distributed`, очередь распределённых DDL и очистку временных таблиц. Девятая
|
|
||||||
из десяти проверок спрашивает словарь товаров на обеих нодах. Последняя —
|
|
||||||
единственная на настоящих событиях: она подневно сверяет стартовый мир с описью
|
|
||||||
и при расхождении говорит, где искать — в событиях или в браке.
|
|
||||||
|
|
||||||
`make config-test` проверяет Compose, синтаксис файлов DAG и пробельные ошибки
|
|
||||||
в diff без запуска стенда.
|
|
||||||
|
|
||||||
Обычный рабочий цикл — `make config-test` и `make smoke`; остальные цели гоняют
|
Обычный рабочий цикл — `make config-test` и `make smoke`; остальные цели гоняют
|
||||||
тогда, когда правка их касается. Что утверждает каждая проверка, нужен ли ей
|
тогда, когда правка их касается. Что утверждает каждая проверка, нужен ли ей
|
||||||
@@ -137,168 +89,21 @@ Airflow его явную пересборку: удалит прикладны
|
|||||||
|
|
||||||
## Стартовый мир
|
## Стартовый мир
|
||||||
|
|
||||||
Стенд поднимается не пустым: `make up` через REST API запускает даг Airflow
|
Стартовый мир и его опись описаны в
|
||||||
`world_initialize`. Тот создаёт прикладные топики и схему, собирает образ
|
[справочнике мира](docs/architecture/world.md).
|
||||||
генератора и играет в `hits` первые восемь дней модельного времени — понедельник
|
|
||||||
по понедельник, 401 185 событий. Следом второй запуск генератора отправляет в
|
|
||||||
`orders` семь слепков — за дни 0…6. Даг ждёт приёма обоих источников и только
|
|
||||||
тогда ставит позицию мира в `8`. Так у всякой лабы есть данные, и всегда одни и
|
|
||||||
те же.
|
|
||||||
|
|
||||||
Последний день намеренно неполон: события дня 7 уже приехали, а его слепок
|
|
||||||
уедет только с первым ходом мира. Поэтому на свежем стенде правый край графика
|
|
||||||
выручки отстаёт от кликстрима и дозаполняется по мере движения мира — так
|
|
||||||
виден разный темп потокового трекера и ночной выгрузки магазина.
|
|
||||||
|
|
||||||
Повторный `make up` безопасен: `world_initialize` видит готовую позицию,
|
|
||||||
сообщает в журнале, что мир уже существует, и не применяет DDL, не собирает
|
|
||||||
образ и не заливает данные повторно. Изменённую схему применяют адресно либо
|
|
||||||
явной командой `make rebuild-storage`.
|
|
||||||
|
|
||||||
Сам мир в git не хранится — он чистая функция зерна, и держать его в
|
|
||||||
репозитории значило бы держать там кэш. Вместо него лежит **опись мира**,
|
|
||||||
[`data/world-inventory.json`](data/world-inventory.json): зерно, версия
|
|
||||||
генератора, хеш каталога товаров и по строке на каждый день — дата, число
|
|
||||||
событий и хеш его байтов. У дней с закрытым окном там же лежат счётчики
|
|
||||||
классов расхождений по заказам. Опись отвечает на единственный вопрос: тот ли
|
|
||||||
это мир, что был вчера.
|
|
||||||
|
|
||||||
Спрашивают её двое. `make test` в `generator/` сверяет опись с тем, что
|
|
||||||
собирается из кода сегодня: правка генератора меняет мир, и опись надо
|
|
||||||
пересобрать — `make inventory` там же. `make check-clickhouse` в корне сверяет
|
|
||||||
с описью то, что доехало до `ods.event`, подневно. Хеш дня можно пересчитать и
|
|
||||||
руками — это обычный `sha256sum` файла, который пишет файловый приёмник:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
uv run --project generator python -m clickstream_generator batch \
|
|
||||||
--day 0 --file tmp/day0.jsonl
|
|
||||||
sha256sum tmp/day0.jsonl
|
|
||||||
```
|
|
||||||
|
|
||||||
## Как позвать генератор
|
## Как позвать генератор
|
||||||
|
|
||||||
События производит генератор из [`generator/`](generator/). Модельный день —
|
Режимы и команды запуска генератора описаны в
|
||||||
функция зерна и номера дня, поэтому один и тот же день всегда даёт те же
|
[справочнике мира](docs/architecture/world.md#как-позвать-генератор).
|
||||||
события: обрыв лечится повтором. Позицию на оси генератор не помнит — какой
|
|
||||||
день играть, решает зовущий.
|
|
||||||
|
|
||||||
Режима два, и различаются они темпом. **Пакетный** гонит день подряд, без пауз:
|
|
||||||
так заливается мир и так переигрывается день после обрыва. **Живой** держит темп
|
|
||||||
модельного времени — по умолчанию ×60: модельные сутки за 24 реальные минуты,
|
|
||||||
суточная волна разворачивается на глазах, отставание видно в логе.
|
|
||||||
|
|
||||||
На стенде генератор ходит своим образом — разовой службой, которую поднимают
|
|
||||||
и убирают на один прогон. На каждый режим по цели:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# день D0 целиком в топик hits
|
|
||||||
make generate-batch
|
|
||||||
# первая сотня событий дня D3
|
|
||||||
make generate-batch GENERATOR_DAY=3 GENERATOR_LIMIT=100
|
|
||||||
# день D3 живьём, ускорение ×1000
|
|
||||||
make generate-live GENERATOR_DAY=3 GENERATOR_SPEED=1000
|
|
||||||
```
|
|
||||||
|
|
||||||
| Переменная | Что задаёт | Умолчание |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `GENERATOR_DAY` | номер дня на оси мира (D0 — первый) | `0`, задано в `Makefile` |
|
|
||||||
| `GENERATOR_LIMIT` | потолок событий на прогон; только `generate-batch` | нет: день целиком |
|
|
||||||
| `GENERATOR_SPEED` | ускорение модельного времени; только `generate-live` | ×60, задано в генераторе |
|
|
||||||
|
|
||||||
Куда отправлять, обеим целям задаёт сам стенд: внутри сети Compose это брокер
|
|
||||||
`kafka:9092` и топик `hits`.
|
|
||||||
|
|
||||||
Образ цели не пересобирают: нет образа — Compose соберёт его сам, есть —
|
|
||||||
возьмёт как есть. Пересобрать намеренно, после правки `generator/Dockerfile`
|
|
||||||
или зависимостей, — отдельной командой:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose --profile generator build generator
|
|
||||||
```
|
|
||||||
|
|
||||||
Тот же образ несёт заливка стартового мира. Перед такой заливкой
|
|
||||||
`world_initialize` сам собирает его из текущего дерева; на готовом мире
|
|
||||||
повторный `make up` до сборки не доходит.
|
|
||||||
|
|
||||||
Разведено это нарочно: собрать образ и запустить контейнер — разные действия, и
|
|
||||||
цель запуска, молча пересобирающая образ, стирает между ними границу. Что образ
|
|
||||||
устарел, видно по собственному прогону — это обратная связь, а не ловушка.
|
|
||||||
|
|
||||||
Посмотреть на события, не поднимая стенд, помогает файловый приёмник: одно
|
|
||||||
событие — одна строка. Флаг `--limit` берёт начало дня вместо целого дня —
|
|
||||||
тому, кто смотрит на конвейер, ждать полсотни тысяч событий незачем.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
uv run --project generator python -m clickstream_generator batch \
|
|
||||||
--day 0 --limit 100 --file tmp/day0.jsonl
|
|
||||||
```
|
|
||||||
|
|
||||||
Несколько дней подряд играет один запуск: `--days 8`. Всё, что принимается
|
|
||||||
флагами, принимается и переменными окружения — так генератор позовёт даг
|
|
||||||
этапа 5; полный список у `--help`.
|
|
||||||
|
|
||||||
**Умолчания есть не у всего, и это нарочно.** Зерно, число дней и темп живого
|
|
||||||
дня описывают модель мира — их генератор знает сам. День на оси, приёмник и имя
|
|
||||||
топика описывают стенд, на котором его запустили: не назвали — отказ и ненулевой
|
|
||||||
код возврата, ещё до первого события. Поэтому умолчание дня и живёт в
|
|
||||||
`Makefile`: день называет тот, кто запускает, а не тот, кого запускают.
|
|
||||||
|
|
||||||
### Пульт мира
|
### Пульт мира
|
||||||
|
|
||||||
Считать дни самому необязательно. В Airflow живёт **пульт мира**: два дага
|
Даги пульта, позиция мира и форма запуска описаны в
|
||||||
создают его начальное состояние, а три двигают ось дальше.
|
[справочнике мира](docs/architecture/world.md#пульт-мира).
|
||||||
|
|
||||||
| Даг | Что делает |
|
|
||||||
| --- | --- |
|
|
||||||
| `world_initialize` | создаёт стартовый мир, если его ещё нет; повторный запуск ничего не меняет |
|
|
||||||
| `world_recreate` | удаляет только прикладной мир и запускает его создание заново |
|
|
||||||
| `world_next_day` | играет следующие дни пачкой; сколько — параметр запуска, по умолчанию один |
|
|
||||||
| `world_live_day` | играет следующий день в темпе модельного времени, около двадцати четырёх минут |
|
|
||||||
| `world_live` | выключатель: пока снят с паузы, дёргает `world_live_day` день за днём |
|
|
||||||
|
|
||||||
Работник не только играет день: следом он отправляет слепок заказов за
|
|
||||||
сыгранное и дёргает отдельный даг, `orders_ingest`, — тот забирает приехавший
|
|
||||||
слепок из топика `orders` в сырьё, — и дожидается его конца. Так две половины
|
|
||||||
стенда идут в ногу: упал приём — краснеет и работник, а позиция остаётся на
|
|
||||||
месте ([ADR 0008](docs/adr/0008-order-ingestion.md)).
|
|
||||||
|
|
||||||
Позицию хранит переменная Airflow `world_position` — номер первого несыгранного
|
|
||||||
дня. Ставит её работник, сыгравший день, и только по успеху: оборванный прогон
|
|
||||||
позицию не двигает, и следующий запуск играет тот же день заново. Отсутствующая
|
|
||||||
переменная означает, что мир ещё не создавали; `-1` — очистка не закончена,
|
|
||||||
`0` — очистка закончена, а начальная генерация ещё не завершена. Работники
|
|
||||||
начинают играть только с позиции `8` и больше.
|
|
||||||
|
|
||||||
В форме запуска можно выбрать только число дней. Первый день работник берет из
|
|
||||||
`world_position`. Поля «Logical date» и «Run ID» спрятаны в «Advanced Options».
|
|
||||||
Они нужны самому Airflow и на выбор дня не влияют. Сыгранные дни и новую
|
|
||||||
позицию работник пишет в журнал шага `remember_played`.
|
|
||||||
|
|
||||||
Выключатель создаётся на паузе. Снимите — мир поедет сам; поставите обратно —
|
|
||||||
встанет на границе модельных суток, доиграв начатый день. Форма пульта и доводы
|
|
||||||
целиком — [ADR 0009](docs/adr/0009-world-control.md).
|
|
||||||
|
|
||||||
**Плата названа вслух: планировщику Airflow отдан сокет докера** — иначе
|
|
||||||
контейнер генератора ему не поднять. Доступ к сокету равен праву root на
|
|
||||||
машине; для локального учебного стенда размен принят, но знать о нём надо.
|
|
||||||
Открывает дверь не монтирование, а членство в группе: GID группы `docker` у
|
|
||||||
каждой машины свой, живёт в `.env` и подсматривается командой
|
|
||||||
`getent group docker`.
|
|
||||||
|
|
||||||
## Состав и доступ
|
## Состав и доступ
|
||||||
|
|
||||||
- `clickhouse-01` — инициатор DDL и точка подключения Airflow;
|
|
||||||
- `clickhouse-02` — точка подключения Superset;
|
|
||||||
- `clickhouse-keeper` — координатор кластера;
|
|
||||||
- `kafka` — один брокер KRaft;
|
|
||||||
- `kafka-exporter` — экспортёр метрик Kafka для Prometheus;
|
|
||||||
- `postgres-metadata` — один Postgres с отдельными базами и пользователями
|
|
||||||
`airflow` и `superset`;
|
|
||||||
- `airflow-apiserver`, `airflow-scheduler` и `airflow-dag-processor` —
|
|
||||||
Airflow 3.3 с LocalExecutor, без triggerer;
|
|
||||||
- `superset` — интерфейс и подготовленное подключение ClickHouse;
|
|
||||||
- `prometheus` и `grafana` — сбор и просмотр метрик ClickHouse и Kafka.
|
|
||||||
|
|
||||||
С настройками из `.env.example` порты доступны только с локальной машины:
|
С настройками из `.env.example` порты доступны только с локальной машины:
|
||||||
|
|
||||||
- нода 1 — `http://127.0.0.1:28123`, нативный порт `29000`;
|
- нода 1 — `http://127.0.0.1:28123`, нативный порт `29000`;
|
||||||
@@ -309,95 +114,8 @@ uv run --project generator python -m clickstream_generator batch \
|
|||||||
- Prometheus — `http://127.0.0.1:29090`;
|
- Prometheus — `http://127.0.0.1:29090`;
|
||||||
- Grafana — `http://127.0.0.1:23000`, пользователь `admin`, пароль `admin`.
|
- Grafana — `http://127.0.0.1:23000`, пользователь `admin`, пароль `admin`.
|
||||||
|
|
||||||
В ClickHouse семь пользователей и шесть ролей:
|
Службы, пользователи и роли, применение настроек и результаты сверки
|
||||||
|
конфигурации описаны в [справочнике стенда](docs/architecture/stand.md).
|
||||||
- `etl` подключает рабочие даги Airflow; роль `etl_writer` читает и пишет слои
|
|
||||||
хранилища;
|
|
||||||
- `lifecycle` применяет канонический DDL и пересоздаёт прикладные базы; роль
|
|
||||||
`lifecycle_owner` не даёт эти административные права рабочим дагам;
|
|
||||||
- `bi` подключает Superset; роль `bi_reader` читает ODS, DDS, DM и справочники
|
|
||||||
`dic`, а запросы Superset обращаются к публичным `_v`;
|
|
||||||
- `analyst` предназначен для подключения человека и читает все слои и
|
|
||||||
справочники;
|
|
||||||
- `grafana` с ролью `monitoring_reader` подключает Grafana и читает все слои,
|
|
||||||
системные таблицы и данные со всех реплик кластера;
|
|
||||||
- `dict` читает только базу `dic` и пароля не имеет: им словарь товаров ходит
|
|
||||||
за своей подложкой;
|
|
||||||
- `default` остаётся служебным: им ходят проверки здоровья и скрипты внутри
|
|
||||||
контейнеров, но не приложения.
|
|
||||||
|
|
||||||
Почему пользователи объявлены файлом, а ноды доверяют общему секрету, — в
|
|
||||||
[ADR 0007](docs/adr/0007-clickhouse-access.md).
|
|
||||||
Пользователи и роли объявлены в `infra/clickhouse/users.d/access.xml`. Пароли и
|
|
||||||
общий секрет нод живут в `.env` и передаются в конфигурацию через окружение;
|
|
||||||
Superset получает пароль `bi`, а Grafana — пароль `grafana` тем же путём.
|
|
||||||
Значения для локального стенда есть в `.env.example`.
|
|
||||||
|
|
||||||
Изменения значений ClickHouse в `.env` и файлов настройки серверов в
|
|
||||||
`infra/clickhouse/config.d/` и `infra/clickhouse/users.d/` применяются
|
|
||||||
пересозданием нод:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose up --force-recreate --wait clickhouse-01 clickhouse-02
|
|
||||||
```
|
|
||||||
|
|
||||||
Команда возвращает управление, когда обе ноды снова здоровы; именованные тома
|
|
||||||
при этом сохраняются. `docker compose restart` оставит прежнее окружение и
|
|
||||||
может оставить старую версию отдельно смонтированного файла.
|
|
||||||
|
|
||||||
Пароли ClickHouse и интерфейсов, пароли Postgres, ключи Airflow и Superset,
|
|
||||||
отсутствие проверки доступа у Kafka и Prometheus — намеренно простые и явно
|
|
||||||
ненастоящие настройки локального учебного стенда. Это не пример настройки
|
|
||||||
защиты: не копируйте значения из `.env.example` в рабочую среду. Все
|
|
||||||
опубликованные порты привязаны только к `127.0.0.1`; Postgres наружу не
|
|
||||||
опубликован.
|
|
||||||
|
|
||||||
В бою перед репликами ClickHouse обычно был бы балансировщик. Здесь в каждом
|
|
||||||
шарде одна реплика, поэтому балансировать нечего. Балансировщик и топология
|
|
||||||
2×2 намеренно не входят в стенд.
|
|
||||||
|
|
||||||
Конфигурация сверена 30 июля 2026 года с официальной документацией ClickHouse:
|
|
||||||
[настройками сервера](https://clickhouse.com/docs/operations/server-configuration-parameters/settings),
|
|
||||||
[Keeper](https://clickhouse.com/docs/guides/oss/deployment-and-scaling/keeper/),
|
|
||||||
[ReplicatedMergeTree](https://clickhouse.com/docs/engines/table-engines/mergetree-family/replication),
|
|
||||||
[ON CLUSTER](https://clickhouse.com/docs/sql-reference/distributed-ddl) и
|
|
||||||
[Distributed](https://clickhouse.com/docs/engines/table-engines/special/distributed).
|
|
||||||
Описание кластера задаётся через `remote_servers`, макросы — через `macros`,
|
|
||||||
подключение к keeper — через `zookeeper`; путь `ReplicatedMergeTree` содержит
|
|
||||||
`{shard}` и `{replica}`, а `Distributed` получает имя кластера, базу, локальную
|
|
||||||
таблицу и ключ шардирования. Макросы выбраны, чтобы один DDL через `ON CLUSTER`
|
|
||||||
создавал отдельный путь каждого шарда без вписанных вручную значений. Для
|
|
||||||
образа зафиксирован точный текущий
|
|
||||||
[LTS-выпуск 26.3.17.56](https://github.com/ClickHouse/ClickHouse/releases/tag/v26.3.17.56-lts);
|
|
||||||
серверы и keeper используют один образ. Настройки Kafka 4.3.1 сверены с
|
|
||||||
[примером односерверного KRaft](https://github.com/apache/kafka/blob/4.3.1/docker/examples/docker-compose-files/single-node/plaintext/docker-compose.yml).
|
|
||||||
Секция метрик взята из конфигурации закреплённого образа ClickHouse и проверена
|
|
||||||
на серверах и keeper. Подготовка источников Grafana сверена с
|
|
||||||
[официальным описанием автоматической настройки](https://grafana.com/docs/grafana/latest/administration/provisioning/)
|
|
||||||
и [документацией плагина ClickHouse](https://grafana.com/docs/plugins/grafana-clickhouse-datasource/latest/configure/).
|
|
||||||
Prometheus собирает встроенные метрики двух серверов и keeper, а через
|
|
||||||
`kafka-exporter` — состояние Kafka. Поверх этого в Grafana поднимается дашборд
|
|
||||||
«Данные»: свежесть, поток, брак, сходимость Kafka с ClickHouse, отставание
|
|
||||||
чтения и дневной слепок заказов. Устройство зоны — в [справочнике
|
|
||||||
мониторинга](docs/architecture/monitoring/README.md), разбор панелей — в
|
|
||||||
[описании дашборда](docs/architecture/monitoring/data.md); правил оповещения
|
|
||||||
пока нет.
|
|
||||||
|
|
||||||
Airflow закреплён на 3.3.0. Состав обязательных процессов, LocalExecutor,
|
|
||||||
публичный `airflow.sdk`, API здоровья и SimpleAuthManager сверены с
|
|
||||||
[архитектурой Airflow 3.3](https://airflow.apache.org/docs/apache-airflow/3.3.0/core-concepts/overview.html),
|
|
||||||
[публичным интерфейсом](https://airflow.apache.org/docs/apache-airflow/3.3.0/public-airflow-interface.html)
|
|
||||||
и [описанием здоровья](https://airflow.apache.org/docs/apache-airflow/3.3.0/administration-and-deployment/logging-monitoring/check-health.html).
|
|
||||||
Для ClickHouse установлен официальный провайдер Airflow: SQL-файлы исполняет
|
|
||||||
общий `SQLExecuteQueryOperator`, программные запросы идут через
|
|
||||||
`ClickHouseHook`. Провайдер работает поверх закреплённого
|
|
||||||
`clickhouse-connect`. Официальный провайдер Kafka сам использует
|
|
||||||
`confluent-kafka`; отдельное подключение и его обёртки здесь не нужны, поэтому
|
|
||||||
клиент Kafka добавлен в образ напрямую.
|
|
||||||
Superset закреплён на 6.1.0; драйвер `clickhouse-connect`, форма
|
|
||||||
`clickhousedb://` и драйвер Postgres сверены с
|
|
||||||
[документацией подключений Superset](https://superset.apache.org/user-docs/6.1.0/databases/)
|
|
||||||
и [настройкой базы метаданных](https://superset.apache.org/admin-docs/6.1.0/configuration/configuring-superset/).
|
|
||||||
|
|
||||||
## Что здесь будет
|
## Что здесь будет
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
# Стенд: службы, доступ, настройка
|
||||||
|
|
||||||
|
Здесь описано, из каких служб собран стенд, кто к кому подключается и как
|
||||||
|
настройки доходят до контейнеров. Состав каркаса обоснован в
|
||||||
|
[ADR 0001](../adr/0001-stand-services.md), доступ к ClickHouse — в
|
||||||
|
[ADR 0007](../adr/0007-clickhouse-access.md). Адреса и пароли интерфейсов
|
||||||
|
остаются в [корневом README](../../README.md#состав-и-доступ).
|
||||||
|
|
||||||
|
## Службы
|
||||||
|
|
||||||
|
- `clickhouse-01` — инициатор DDL и точка подключения Airflow;
|
||||||
|
- `clickhouse-02` — точка подключения Superset;
|
||||||
|
- `clickhouse-keeper` — координатор кластера;
|
||||||
|
- `kafka` — один брокер KRaft;
|
||||||
|
- `kafka-exporter` — экспортёр метрик Kafka для Prometheus;
|
||||||
|
- `postgres-metadata` — один Postgres с отдельными базами и пользователями
|
||||||
|
`airflow` и `superset`;
|
||||||
|
- `airflow-apiserver`, `airflow-scheduler` и `airflow-dag-processor` —
|
||||||
|
Airflow 3.3 с LocalExecutor, без triggerer;
|
||||||
|
- `superset` — интерфейс и подготовленное подключение ClickHouse;
|
||||||
|
- `prometheus` и `grafana` — сбор и просмотр метрик ClickHouse и Kafka.
|
||||||
|
|
||||||
|
## Пользователи и роли ClickHouse
|
||||||
|
|
||||||
|
В ClickHouse семь пользователей и шесть ролей:
|
||||||
|
|
||||||
|
- `etl` подключает рабочие даги Airflow; роль `etl_writer` читает и пишет слои
|
||||||
|
хранилища;
|
||||||
|
- `lifecycle` применяет канонический DDL и пересоздаёт прикладные базы; роль
|
||||||
|
`lifecycle_owner` не даёт эти административные права рабочим дагам;
|
||||||
|
- `bi` подключает Superset; роль `bi_reader` читает ODS, DDS, DM и справочники
|
||||||
|
`dic`, а запросы Superset обращаются к публичным `_v`;
|
||||||
|
- `analyst` предназначен для подключения человека и читает все слои и
|
||||||
|
справочники;
|
||||||
|
- `grafana` с ролью `monitoring_reader` подключает Grafana и читает все слои,
|
||||||
|
системные таблицы и данные со всех реплик кластера;
|
||||||
|
- `dict` читает только базу `dic` и пароля не имеет: им словарь товаров ходит
|
||||||
|
за своей подложкой;
|
||||||
|
- `default` остаётся служебным: им ходят проверки здоровья и скрипты внутри
|
||||||
|
контейнеров, но не приложения.
|
||||||
|
|
||||||
|
Почему пользователи объявлены файлом, а ноды доверяют общему секрету, — в
|
||||||
|
[ADR 0007](../adr/0007-clickhouse-access.md).
|
||||||
|
Пользователи и роли объявлены в `infra/clickhouse/users.d/access.xml`. Пароли и
|
||||||
|
общий секрет нод живут в `.env` и передаются в конфигурацию через окружение;
|
||||||
|
Superset получает пароль `bi`, а Grafana — пароль `grafana` тем же путём.
|
||||||
|
Значения для локального стенда есть в `.env.example`.
|
||||||
|
|
||||||
|
## Применение настроек
|
||||||
|
|
||||||
|
Изменения значений ClickHouse в `.env` и файлов настройки серверов в
|
||||||
|
`infra/clickhouse/config.d/` и `infra/clickhouse/users.d/` применяются
|
||||||
|
пересозданием нод:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up --force-recreate --wait clickhouse-01 clickhouse-02
|
||||||
|
```
|
||||||
|
|
||||||
|
Команда возвращает управление, когда обе ноды снова здоровы; именованные тома
|
||||||
|
при этом сохраняются. `docker compose restart` оставит прежнее окружение и
|
||||||
|
может оставить старую версию отдельно смонтированного файла.
|
||||||
|
|
||||||
|
## Секреты и порты
|
||||||
|
|
||||||
|
Пароли ClickHouse и интерфейсов, пароли Postgres, ключи Airflow и Superset,
|
||||||
|
отсутствие проверки доступа у Kafka и Prometheus — намеренно простые и явно
|
||||||
|
ненастоящие настройки локального учебного стенда. Это не пример настройки
|
||||||
|
защиты: не копируйте значения из `.env.example` в рабочую среду. Все
|
||||||
|
опубликованные порты привязаны только к `127.0.0.1`; Postgres наружу не
|
||||||
|
опубликован.
|
||||||
|
|
||||||
|
## Одна реплика на шард
|
||||||
|
|
||||||
|
В бою перед репликами ClickHouse обычно был бы балансировщик. Здесь в каждом
|
||||||
|
шарде одна реплика, поэтому балансировать нечего. Балансировщик и топология
|
||||||
|
2×2 намеренно не входят в стенд.
|
||||||
|
|
||||||
|
## Что проверено
|
||||||
|
|
||||||
|
**Конфигурация сверена 30 июля 2026 года** с официальной документацией
|
||||||
|
ClickHouse:
|
||||||
|
[настройками сервера](https://clickhouse.com/docs/operations/server-configuration-parameters/settings),
|
||||||
|
[Keeper](https://clickhouse.com/docs/guides/oss/deployment-and-scaling/keeper/),
|
||||||
|
[ReplicatedMergeTree](https://clickhouse.com/docs/engines/table-engines/mergetree-family/replication),
|
||||||
|
[ON CLUSTER](https://clickhouse.com/docs/sql-reference/distributed-ddl) и
|
||||||
|
[Distributed](https://clickhouse.com/docs/engines/table-engines/special/distributed).
|
||||||
|
Описание кластера задаётся через `remote_servers`, макросы — через `macros`,
|
||||||
|
подключение к keeper — через `zookeeper`; путь `ReplicatedMergeTree` содержит
|
||||||
|
`{shard}` и `{replica}`, а `Distributed` получает имя кластера, базу, локальную
|
||||||
|
таблицу и ключ шардирования. Макросы выбраны, чтобы один DDL через `ON CLUSTER`
|
||||||
|
создавал отдельный путь каждого шарда без вписанных вручную значений. Для
|
||||||
|
образа зафиксирован точный текущий
|
||||||
|
[LTS-выпуск 26.3.17.56](https://github.com/ClickHouse/ClickHouse/releases/tag/v26.3.17.56-lts);
|
||||||
|
серверы и keeper используют один образ. Настройки Kafka 4.3.1 сверены с
|
||||||
|
[примером односерверного KRaft](https://github.com/apache/kafka/blob/4.3.1/docker/examples/docker-compose-files/single-node/plaintext/docker-compose.yml).
|
||||||
|
Секция метрик взята из конфигурации закреплённого образа ClickHouse и проверена
|
||||||
|
на серверах и keeper. Подготовка источников Grafana сверена с
|
||||||
|
[официальным описанием автоматической настройки](https://grafana.com/docs/grafana/latest/administration/provisioning/)
|
||||||
|
и [документацией плагина ClickHouse](https://grafana.com/docs/plugins/grafana-clickhouse-datasource/latest/configure/).
|
||||||
|
Prometheus собирает встроенные метрики двух серверов и keeper, а через
|
||||||
|
`kafka-exporter` — состояние Kafka. Поверх этого в Grafana поднимается дашборд
|
||||||
|
«Данные»: свежесть, поток, брак, сходимость Kafka с ClickHouse, отставание
|
||||||
|
чтения и дневной слепок заказов. Устройство зоны — в [справочнике
|
||||||
|
мониторинга](monitoring/README.md), разбор панелей — в
|
||||||
|
[описании дашборда](monitoring/data.md); правил оповещения
|
||||||
|
пока нет.
|
||||||
|
|
||||||
|
Airflow закреплён на 3.3.0. Состав обязательных процессов, LocalExecutor,
|
||||||
|
публичный `airflow.sdk`, API здоровья и SimpleAuthManager сверены с
|
||||||
|
[архитектурой Airflow 3.3](https://airflow.apache.org/docs/apache-airflow/3.3.0/core-concepts/overview.html),
|
||||||
|
[публичным интерфейсом](https://airflow.apache.org/docs/apache-airflow/3.3.0/public-airflow-interface.html)
|
||||||
|
и [описанием здоровья](https://airflow.apache.org/docs/apache-airflow/3.3.0/administration-and-deployment/logging-monitoring/check-health.html).
|
||||||
|
Для ClickHouse установлен официальный провайдер Airflow: SQL-файлы исполняет
|
||||||
|
общий `SQLExecuteQueryOperator`, программные запросы идут через
|
||||||
|
`ClickHouseHook`. Провайдер работает поверх закреплённого
|
||||||
|
`clickhouse-connect`. Официальный провайдер Kafka сам использует
|
||||||
|
`confluent-kafka`; отдельное подключение и его обёртки здесь не нужны, поэтому
|
||||||
|
клиент Kafka добавлен в образ напрямую.
|
||||||
|
Superset закреплён на 6.1.0; драйвер `clickhouse-connect`, форма
|
||||||
|
`clickhousedb://` и драйвер Postgres сверены с
|
||||||
|
[документацией подключений Superset](https://superset.apache.org/user-docs/6.1.0/databases/)
|
||||||
|
и [настройкой базы метаданных](https://superset.apache.org/admin-docs/6.1.0/configuration/configuring-superset/).
|
||||||
@@ -76,6 +76,15 @@
|
|||||||
Airflow. В цену целей она не входит. На готовом мире подъём больше не
|
Airflow. В цену целей она не входит. На готовом мире подъём больше не
|
||||||
пересчитывает данные: `world_initialize` зеленеет без изменений.
|
пересчитывает данные: `world_initialize` зеленеет без изменений.
|
||||||
|
|
||||||
|
`make up` собирает локальные образы Airflow, Superset и Grafana, поднимает весь
|
||||||
|
стенд и ждёт здорового состояния долгоживущих контейнеров. В образ Airflow
|
||||||
|
добавлены закреплённые клиенты ClickHouse и Kafka. Одноразовые `airflow-init` и
|
||||||
|
`superset-init` завершаются с кодом 0. Первый обновляет схему Airflow,
|
||||||
|
подготавливает администратора и два подключения к `clickhouse-01`. Второй
|
||||||
|
обновляет Superset, создаёт администратора и импортирует подключение к
|
||||||
|
`clickhouse-02`. На ноутбуке в режиме энергосбережения приёмочный подъём с
|
||||||
|
пустых томов занял около десяти минут, повторный на живом стенде — около трёх.
|
||||||
|
|
||||||
`make rebuild-storage` тоже не проверка. Команда через Airflow удаляет
|
`make rebuild-storage` тоже не проверка. Команда через Airflow удаляет
|
||||||
прикладные базы ClickHouse, топики и позиции групп Kafka, заново применяет DDL,
|
прикладные базы ClickHouse, топики и позиции групп Kafka, заново применяет DDL,
|
||||||
заливает стартовый мир и возвращает его пульт к концу стартовых дней. Службы и
|
заливает стартовый мир и возвращает его пульт к концу стартовых дней. Службы и
|
||||||
@@ -237,6 +246,45 @@ Kafka → STG → ODS, и живёт он в `make check-clickhouse`.
|
|||||||
скриптов нет. Отдельной проверкой этот вход тоже не считается: то же самое
|
скриптов нет. Отдельной проверкой этот вход тоже не считается: то же самое
|
||||||
утверждает смоук.
|
утверждает смоук.
|
||||||
|
|
||||||
|
### Что спрашивает смоук
|
||||||
|
|
||||||
|
`make smoke` за секунды спрашивает, собран ли стенд: зависимости машины,
|
||||||
|
здоровье контейнеров, устройство keeper, ответ Kafka с машины через отображённый
|
||||||
|
порт, четыре цели Prometheus, два источника Grafana, компоненты Airflow и
|
||||||
|
подготовленное подключение к `clickhouse-01`.
|
||||||
|
|
||||||
|
Двенадцать проверок здоровья сразу после `make up --wait` повторяют то, чего
|
||||||
|
Compose уже дождался: у каждой долгоживущей службы есть своя `healthcheck`.
|
||||||
|
Оставлены они потому, что первый вопрос к стенду всё равно «всё ли живо», а
|
||||||
|
ответ на него стоит меньше секунды. Устройство keeper — другое дело: он должен
|
||||||
|
работать от пользователя `clickhouse`, с пределом в 262144 открытых файла и со
|
||||||
|
своим томом под данные. Всё это объявлено в `compose.yaml`, но здоровым keeper
|
||||||
|
выглядит и без этого, поэтому смоук спрашивает у живого контейнера, дошли ли
|
||||||
|
объявленные настройки до процесса.
|
||||||
|
|
||||||
|
Kafka по той же причине спрашивают снаружи. Её `healthcheck` обращается к
|
||||||
|
брокеру изнутри контейнера и по внутреннему слушателю, поэтому здоровой Kafka
|
||||||
|
остаётся и тогда, когда объявленный наружу адрес ведёт не туда. Клиент с машины
|
||||||
|
в этом случае подключается, получает метаданные и молча виснет на адресе,
|
||||||
|
которого с его стороны не существует. Смоук идёт тем же путём, что и будущий
|
||||||
|
генератор: стучится в отображённый порт и просит список топиков.
|
||||||
|
|
||||||
|
### Что спрашивают глубокие проверки
|
||||||
|
|
||||||
|
`make check-services` проверяет то, ради чего приходится ждать службу: работу с
|
||||||
|
топиком Kafka с машины — создан, найден, удалён, — ручной запуск пробников
|
||||||
|
`test_clickhouse` и `test_kafka`, вход в Superset, его метаданные и подключение
|
||||||
|
к `clickhouse-02`. Первый пробник создаёт таблицы на обеих нодах и читает через
|
||||||
|
`Distributed` на ноде 2 строку из локальной таблицы ноды 1. Второй пишет в Kafka
|
||||||
|
и читает свой маркер. Временный топик проверки с машины и запуски DAG удаляются;
|
||||||
|
постоянный топик пробника сохраняется, а старые записи чистит Kafka.
|
||||||
|
|
||||||
|
`make check-clickhouse` запускает отдельную глубокую проверку ClickHouse:
|
||||||
|
описание кластера, макросы, связь с keeper, `ReplicatedMergeTree`,
|
||||||
|
`Distributed`, очередь распределённых DDL и очистку временных таблиц. Девятая
|
||||||
|
из десяти проверок спрашивает словарь товаров на обеих нодах. Последняя —
|
||||||
|
подневный счёт событий против описи, о нем сказано выше.
|
||||||
|
|
||||||
## Что проверено
|
## Что проверено
|
||||||
|
|
||||||
**Калибровка заказной стороны 30 августа 2026 года при исполнении #194.**
|
**Калибровка заказной стороны 30 августа 2026 года при исполнении #194.**
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
# Мир и его пульт
|
||||||
|
|
||||||
|
Модельное время стенда со стороны оператора: как рождается мир, что двигает
|
||||||
|
его ось, кто играет дни и где хранится позиция.
|
||||||
|
Доводы об устройстве пульта — в [ADR 0009](../adr/0009-world-control.md), о
|
||||||
|
безопасном повторном подъеме и жизненном цикле мира — в
|
||||||
|
[ADR 0013](../adr/0013-world-lifecycle.md). Почему последний день стартового
|
||||||
|
мира неполон и что хранит опись, разобрано в [справочнике стартового
|
||||||
|
мира](orders/start-world.md) и [справочнике описи](orders/inventory.md).
|
||||||
|
|
||||||
|
## Стартовый мир
|
||||||
|
|
||||||
|
Стенд поднимается не пустым: `make up` через REST API запускает даг Airflow
|
||||||
|
`world_initialize`. Тот создаёт прикладные топики и схему, собирает образ
|
||||||
|
генератора и играет в `hits` первые восемь дней модельного времени — понедельник
|
||||||
|
по понедельник, 401 185 событий. Следом второй запуск генератора отправляет в
|
||||||
|
`orders` семь слепков — за дни 0…6. Даг ждёт приёма обоих источников и только
|
||||||
|
тогда ставит позицию мира в `8`. Так у всякой лабы есть данные, и всегда одни и
|
||||||
|
те же.
|
||||||
|
|
||||||
|
Последний день намеренно неполон: события дня 7 уже приехали, а его слепок
|
||||||
|
уедет только с первым ходом мира. Поэтому на свежем стенде правый край графика
|
||||||
|
выручки отстаёт от кликстрима и дозаполняется по мере движения мира — так
|
||||||
|
виден разный темп потокового трекера и ночной выгрузки магазина.
|
||||||
|
|
||||||
|
Повторный `make up` безопасен: `world_initialize` видит готовую позицию,
|
||||||
|
сообщает в журнале, что мир уже существует, и не применяет DDL, не собирает
|
||||||
|
образ и не заливает данные повторно. Изменённую схему применяют адресно либо
|
||||||
|
явной командой `make rebuild-storage`.
|
||||||
|
|
||||||
|
## Опись мира
|
||||||
|
|
||||||
|
Сам мир в git не хранится — он чистая функция зерна, и держать его в
|
||||||
|
репозитории значило бы держать там кэш. Вместо него лежит **опись мира**,
|
||||||
|
[`data/world-inventory.json`](../../data/world-inventory.json): зерно, версия
|
||||||
|
генератора, хеш каталога товаров и по строке на каждый день — дата, число
|
||||||
|
событий и хеш его байтов. У дней с закрытым окном там же лежат счётчики
|
||||||
|
классов расхождений по заказам. Опись отвечает на единственный вопрос: тот ли
|
||||||
|
это мир, что был вчера.
|
||||||
|
|
||||||
|
Спрашивают её двое. `make test` в `generator/` сверяет опись с тем, что
|
||||||
|
собирается из кода сегодня: правка генератора меняет мир, и опись надо
|
||||||
|
пересобрать — `make inventory` там же. `make check-clickhouse` в корне сверяет
|
||||||
|
с описью то, что доехало до `ods.event`, подневно. Хеш дня можно пересчитать и
|
||||||
|
руками — это обычный `sha256sum` файла, который пишет файловый приёмник:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run --project generator python -m clickstream_generator batch \
|
||||||
|
--day 0 --file tmp/day0.jsonl
|
||||||
|
sha256sum tmp/day0.jsonl
|
||||||
|
```
|
||||||
|
|
||||||
|
## Как позвать генератор
|
||||||
|
|
||||||
|
События производит генератор из [`generator/`](../../generator/). Модельный день —
|
||||||
|
функция зерна и номера дня, поэтому один и тот же день всегда даёт те же
|
||||||
|
события: обрыв лечится повтором. Позицию на оси генератор не помнит — какой
|
||||||
|
день играть, решает зовущий.
|
||||||
|
|
||||||
|
Режима два, и различаются они темпом. **Пакетный** гонит день подряд, без пауз:
|
||||||
|
так заливается мир и так переигрывается день после обрыва. **Живой** держит темп
|
||||||
|
модельного времени — по умолчанию ×60: модельные сутки за 24 реальные минуты,
|
||||||
|
суточная волна разворачивается на глазах, отставание видно в логе.
|
||||||
|
|
||||||
|
На стенде генератор ходит своим образом — разовой службой, которую поднимают
|
||||||
|
и убирают на один прогон. На каждый режим по цели:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# день D0 целиком в топик hits
|
||||||
|
make generate-batch
|
||||||
|
# первая сотня событий дня D3
|
||||||
|
make generate-batch GENERATOR_DAY=3 GENERATOR_LIMIT=100
|
||||||
|
# день D3 живьём, ускорение ×1000
|
||||||
|
make generate-live GENERATOR_DAY=3 GENERATOR_SPEED=1000
|
||||||
|
```
|
||||||
|
|
||||||
|
| Переменная | Что задаёт | Умолчание |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `GENERATOR_DAY` | номер дня на оси мира (D0 — первый) | `0`, задано в `Makefile` |
|
||||||
|
| `GENERATOR_LIMIT` | потолок событий на прогон; только `generate-batch` | нет: день целиком |
|
||||||
|
| `GENERATOR_SPEED` | ускорение модельного времени; только `generate-live` | ×60, задано в генераторе |
|
||||||
|
|
||||||
|
Куда отправлять, обеим целям задаёт сам стенд: внутри сети Compose это брокер
|
||||||
|
`kafka:9092` и топик `hits`.
|
||||||
|
|
||||||
|
Образ цели не пересобирают: нет образа — Compose соберёт его сам, есть —
|
||||||
|
возьмёт как есть. Пересобрать намеренно, после правки `generator/Dockerfile`
|
||||||
|
или зависимостей, — отдельной командой:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose --profile generator build generator
|
||||||
|
```
|
||||||
|
|
||||||
|
Тот же образ несёт заливка стартового мира. Перед такой заливкой
|
||||||
|
`world_initialize` сам собирает его из текущего дерева; на готовом мире
|
||||||
|
повторный `make up` до сборки не доходит.
|
||||||
|
|
||||||
|
Разведено это нарочно: собрать образ и запустить контейнер — разные действия, и
|
||||||
|
цель запуска, молча пересобирающая образ, стирает между ними границу. Что образ
|
||||||
|
устарел, видно по собственному прогону — это обратная связь, а не ловушка.
|
||||||
|
|
||||||
|
Посмотреть на события, не поднимая стенд, помогает файловый приёмник: одно
|
||||||
|
событие — одна строка. Флаг `--limit` берёт начало дня вместо целого дня —
|
||||||
|
тому, кто смотрит на конвейер, ждать полсотни тысяч событий незачем.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run --project generator python -m clickstream_generator batch \
|
||||||
|
--day 0 --limit 100 --file tmp/day0.jsonl
|
||||||
|
```
|
||||||
|
|
||||||
|
Несколько дней подряд играет один запуск: `--days 8`. Всё, что принимается
|
||||||
|
флагами, принимается и переменными окружения — так генератор позовёт даг
|
||||||
|
этапа 5; полный список у `--help`.
|
||||||
|
|
||||||
|
**Умолчания есть не у всего, и это нарочно.** Зерно, число дней и темп живого
|
||||||
|
дня описывают модель мира — их генератор знает сам. День на оси, приёмник и имя
|
||||||
|
топика описывают стенд, на котором его запустили: не назвали — отказ и ненулевой
|
||||||
|
код возврата, ещё до первого события. Поэтому умолчание дня и живёт в
|
||||||
|
`Makefile`: день называет тот, кто запускает, а не тот, кого запускают.
|
||||||
|
|
||||||
|
## Пульт мира
|
||||||
|
|
||||||
|
Считать дни самому необязательно. В Airflow живёт **пульт мира**: два дага
|
||||||
|
создают его начальное состояние, а три двигают ось дальше.
|
||||||
|
|
||||||
|
| Даг | Что делает |
|
||||||
|
| --- | --- |
|
||||||
|
| `world_initialize` | создаёт стартовый мир, если его ещё нет; повторный запуск ничего не меняет |
|
||||||
|
| `world_recreate` | удаляет только прикладной мир и запускает его создание заново |
|
||||||
|
| `world_next_day` | играет следующие дни пачкой; сколько — параметр запуска, по умолчанию один |
|
||||||
|
| `world_live_day` | играет следующий день в темпе модельного времени, около двадцати четырёх минут |
|
||||||
|
| `world_live` | выключатель: пока снят с паузы, дёргает `world_live_day` день за днём |
|
||||||
|
|
||||||
|
Работник не только играет день: следом он отправляет слепок заказов за
|
||||||
|
сыгранное и дёргает отдельный даг, `orders_ingest`, — тот забирает приехавший
|
||||||
|
слепок из топика `orders` в сырьё, — и дожидается его конца. Так две половины
|
||||||
|
стенда идут в ногу: упал приём — краснеет и работник, а позиция остаётся на
|
||||||
|
месте ([ADR 0008](../adr/0008-order-ingestion.md)).
|
||||||
|
|
||||||
|
Позицию хранит переменная Airflow `world_position` — номер первого несыгранного
|
||||||
|
дня. Ставит её работник, сыгравший день, и только по успеху: оборванный прогон
|
||||||
|
позицию не двигает, и следующий запуск играет тот же день заново. Отсутствующая
|
||||||
|
переменная означает, что мир ещё не создавали; `-1` — очистка не закончена,
|
||||||
|
`0` — очистка закончена, а начальная генерация ещё не завершена. Работники
|
||||||
|
начинают играть только с позиции `8` и больше.
|
||||||
|
|
||||||
|
В форме запуска можно выбрать только число дней. Первый день работник берет из
|
||||||
|
`world_position`. Поля «Logical date» и «Run ID» спрятаны в «Advanced Options».
|
||||||
|
Они нужны самому Airflow и на выбор дня не влияют. Сыгранные дни и новую
|
||||||
|
позицию работник пишет в журнал шага `remember_played`.
|
||||||
|
|
||||||
|
Выключатель создаётся на паузе. Снимите — мир поедет сам; поставите обратно —
|
||||||
|
встанет на границе модельных суток, доиграв начатый день. Форма пульта и доводы
|
||||||
|
целиком — [ADR 0009](../adr/0009-world-control.md).
|
||||||
|
|
||||||
|
**Плата названа вслух: планировщику Airflow отдан сокет докера** — иначе
|
||||||
|
контейнер генератора ему не поднять. Доступ к сокету равен праву root на
|
||||||
|
машине; для локального учебного стенда размен принят, но знать о нём надо.
|
||||||
|
Открывает дверь не монтирование, а членство в группе: GID группы `docker` у
|
||||||
|
каждой машины свой, живёт в `.env` и подсматривается командой
|
||||||
|
`getent group docker`.
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
Пульт играет модельный день, а дневной конвейер раскладывает данные по слоям.
|
Пульт играет модельный день, а дневной конвейер раскладывает данные по слоям.
|
||||||
Глава поможет увидеть их связь и роль каждого дага. Пока главы нет, смотрите
|
Глава поможет увидеть их связь и роль каждого дага. Пока главы нет, смотрите
|
||||||
[справочник конвейера](../architecture/etl.md) и
|
[справочник конвейера](../architecture/etl.md) и
|
||||||
[раздел «Пульт мира» корневого README](../../README.md#пульт-мира).
|
[справочник мира и его пульта](../architecture/world.md).
|
||||||
|
|
||||||
### Мониторинг: куда смотреть
|
### Мониторинг: куда смотреть
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -77,7 +77,9 @@
|
|||||||
|
|
||||||
## Команды
|
## Команды
|
||||||
|
|
||||||
Проиграть день — [быстрый старт](../README.md#как-позвать-генератор) и `--help`.
|
Проиграть день — раздел [«Как позвать
|
||||||
|
генератор»](../docs/architecture/world.md#как-позвать-генератор) справочника
|
||||||
|
мира и `--help`.
|
||||||
|
|
||||||
Из этого каталога — цели генератора живут здесь, а не в корне:
|
Из этого каталога — цели генератора живут здесь, а не в корне:
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user