docs(glossary): четыре термина переименованы, world.md и stand.md вычитаны
- Зачем:
- справочники читает человек со стороны, а «пульт мира», «опись мира»,
«работник» и «сырьё» звучали шуткой или калькой времён разработки
(тикет #214, выбор владельца).
- Что:
- CONTEXT.md: статьи «Паспорт мира», «Даги модельного времени»,
«Шаговый даг», «Сырые данные», старые слова в строках «Избегать»;
пометки в ADR 0005, 0008, 0009 без переписывания решений.
- замены по README, справочникам, путеводителю, лабам, спекам,
комментариям дагов и SQL, тегу дага и панели Grafana; имена в коде
не тронуты.
- world.md и stand.md: 35 находок вычитки внесены, регистр выровнен.
- Проверка:
- make config-test; make lint в корне и в generator/; make test в
generator/ (437 passed); скрипт ссылок из #212 пуст; grep старых
слов вне ADR, research и CONTEXT.md пуст.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QBCtU38qSHg5DvgtnKXcW
This commit is contained in:
1 parent
86ad8afaf2
commit
59d8c83b17
54 files changed
+794
-776
No files matched your search
+75
-73
@@ -1,12 +1,13 @@
|
||||
# Мир и его пульт
|
||||
# Мир и модельное время
|
||||
|
||||
Модельное время стенда со стороны оператора: как рождается мир, что двигает
|
||||
его ось, кто играет дни и где хранится позиция.
|
||||
Доводы об устройстве пульта — в [ADR 0009](../adr/0009-world-control.md), о
|
||||
безопасном повторном подъеме и жизненном цикле мира — в
|
||||
[ADR 0013](../adr/0013-world-lifecycle.md). Почему последний день стартового
|
||||
мира неполон и что хранит опись, разобрано в [справочнике стартового
|
||||
мира](orders/start-world.md) и [справочнике описи](orders/inventory.md).
|
||||
Оператор управляет модельным временем стенда. Здесь разобрано, как рождается
|
||||
мир, что двигает его ось, какие даги играют дни и где хранится позиция. Доводы
|
||||
об устройстве дагов модельного времени — в [ADR
|
||||
0009](../adr/0009-world-control.md), о безопасном повторном подъёме и жизненном
|
||||
цикле мира — в [ADR 0013](../adr/0013-world-lifecycle.md). Почему последний день
|
||||
стартового мира неполон и что хранит паспорт, разобрано в [справочнике
|
||||
стартового мира](orders/start-world.md) и [справочнике
|
||||
паспорта](orders/inventory.md).
|
||||
|
||||
## Стартовый мир
|
||||
|
||||
@@ -15,7 +16,7 @@
|
||||
генератора и играет в `hits` первые восемь дней модельного времени — понедельник
|
||||
по понедельник, 401 185 событий. Следом второй запуск генератора отправляет в
|
||||
`orders` семь слепков — за дни 0…6. Даг ждёт приёма обоих источников и только
|
||||
тогда ставит позицию мира в `8`. Так у всякой лабы есть данные, и всегда одни и
|
||||
тогда ставит позицию мира в `8`. Так у каждой лабы есть данные, и всегда одни и
|
||||
те же.
|
||||
|
||||
Последний день намеренно неполон: события дня 7 уже приехали, а его слепок
|
||||
@@ -28,21 +29,21 @@
|
||||
образ и не заливает данные повторно. Изменённую схему применяют адресно либо
|
||||
явной командой `make rebuild-storage`.
|
||||
|
||||
## Опись мира
|
||||
## Паспорт мира
|
||||
|
||||
Сам мир в git не хранится — он чистая функция зерна, и держать его в
|
||||
репозитории значило бы держать там кэш. Вместо него лежит **опись мира**,
|
||||
Сам мир в git не хранится: он вычисляется из зерна, поэтому готовая копия была
|
||||
бы лишь кэшем. Вместо него лежит **паспорт мира**,
|
||||
[`data/world-inventory.json`](../../data/world-inventory.json): зерно, версия
|
||||
генератора, хеш каталога товаров и по строке на каждый день — дата, число
|
||||
событий и хеш его байтов. У дней с закрытым окном там же лежат счётчики
|
||||
классов расхождений по заказам. Опись отвечает на единственный вопрос: тот ли
|
||||
это мир, что был вчера.
|
||||
событий и хеш его байтов. У дней с закрытым окном там же лежат счётчики классов
|
||||
расхождений по заказам. Паспорт отвечает на единственный вопрос: тот ли это мир,
|
||||
что был вчера.
|
||||
|
||||
Спрашивают её двое. `make test` в `generator/` сверяет опись с тем, что
|
||||
собирается из кода сегодня: правка генератора меняет мир, и опись надо
|
||||
пересобрать — `make inventory` там же. `make check-clickhouse` в корне сверяет
|
||||
с описью то, что доехало до `ods.event`, подневно. Хеш дня можно пересчитать и
|
||||
руками — это обычный `sha256sum` файла, который пишет файловый приёмник:
|
||||
Спрашивают его двое. `make test` в `generator/` сверяет паспорт с тем, что
|
||||
собирается из кода сегодня: правка генератора меняет мир, и паспорт надо
|
||||
пересобрать — `make inventory` там же. `make check-clickhouse` в корне по дням
|
||||
сверяет паспорт с данными, поступившими в `ods.event`. Хеш дня можно пересчитать
|
||||
и руками — это обычный `sha256sum` файла, который пишет файловый приёмник:
|
||||
|
||||
```bash
|
||||
uv run --project generator python -m clickstream_generator batch \
|
||||
@@ -52,18 +53,19 @@ sha256sum tmp/day0.jsonl
|
||||
|
||||
## Как запустить генератор
|
||||
|
||||
События производит генератор из [`generator/`](../../generator/). Модельный день —
|
||||
функция зерна и номера дня, поэтому один и тот же день всегда даёт те же
|
||||
события: обрыв лечится повтором. Позицию на оси генератор не помнит — какой
|
||||
день играть, решает зовущий.
|
||||
События производит генератор из [`generator/`](../../generator/). Модельный день
|
||||
— функция зерна и номера дня, поэтому один и тот же день всегда даёт те же
|
||||
события: обрыв лечится повтором. Позицию на оси генератор не помнит — номер дня
|
||||
передаёт вызывающая команда.
|
||||
|
||||
Режима два, и различаются они темпом. **Пакетный** гонит день подряд, без пауз:
|
||||
так заливается мир и так переигрывается день после обрыва. **Живой** держит темп
|
||||
модельного времени — по умолчанию ×60: модельные сутки за 24 реальные минуты,
|
||||
суточная волна разворачивается на глазах, отставание видно в логе.
|
||||
Режима два, и различаются они темпом. В **пакетном** режиме события дня
|
||||
отправляются подряд, без пауз: так заливается мир и так переигрывается день
|
||||
после обрыва. **Живой** держит темп модельного времени — по умолчанию ×60:
|
||||
модельные сутки за 24 реальные минуты, суточная волна разворачивается на глазах,
|
||||
отставание видно в журнале.
|
||||
|
||||
На стенде генератор ходит своим образом — разовой службой, которую поднимают
|
||||
и убирают на один прогон. На каждый режим по цели:
|
||||
На стенде генератор запускается из отдельного образа как разовая служба. После
|
||||
прогона её убирают. Для каждого режима есть своя цель:
|
||||
|
||||
```bash
|
||||
# день D0 целиком в топик hits
|
||||
@@ -80,24 +82,24 @@ make generate-live GENERATOR_DAY=3 GENERATOR_SPEED=1000
|
||||
| `GENERATOR_LIMIT` | потолок событий на прогон; только `generate-batch` | нет: день целиком |
|
||||
| `GENERATOR_SPEED` | ускорение модельного времени; только `generate-live` | ×60, задано в генераторе |
|
||||
|
||||
Куда отправлять, обеим целям задаёт сам стенд: внутри сети Compose это брокер
|
||||
Адрес назначения для обеих целей задаёт стенд: внутри сети Compose это брокер
|
||||
`kafka:9092` и топик `hits`.
|
||||
|
||||
Образ цели не пересобирают: нет образа — Compose соберёт его сам, есть —
|
||||
возьмёт как есть. Пересобрать намеренно, после правки `generator/Dockerfile`
|
||||
или зависимостей, — отдельной командой:
|
||||
При обычном запуске образ генератора не пересобирается. Если образа нет, Compose
|
||||
соберёт его; если есть, использует готовый. После правки `generator/Dockerfile`
|
||||
или зависимостей образ пересобирают отдельной командой:
|
||||
|
||||
```bash
|
||||
docker compose --profile generator build generator
|
||||
```
|
||||
|
||||
Тот же образ несёт заливка стартового мира. Перед такой заливкой
|
||||
`world_initialize` сам собирает его из текущего дерева; на готовом мире
|
||||
повторный `make up` до сборки не доходит.
|
||||
Для заливки стартового мира нужен тот же образ. Перед такой заливкой
|
||||
`world_initialize` собирает его из текущего дерева; на готовом мире повторный
|
||||
`make up` до сборки не доходит.
|
||||
|
||||
Разведено это нарочно: собрать образ и запустить контейнер — разные действия, и
|
||||
цель запуска, молча пересобирающая образ, стирает между ними границу. Что образ
|
||||
устарел, видно по собственному прогону — это обратная связь, а не ловушка.
|
||||
Сборка образа и запуск контейнера разделены намеренно. Если цель запуска сама
|
||||
пересобирает образ, различие между этими действиями исчезает. Следующий запуск
|
||||
покажет, что образ устарел.
|
||||
|
||||
Посмотреть на события, не поднимая стенд, помогает файловый приёмник: одно
|
||||
событие — одна строка. Флаг `--limit` берёт начало дня вместо целого дня —
|
||||
@@ -108,21 +110,21 @@ uv run --project generator python -m clickstream_generator batch \
|
||||
--day 0 --limit 100 --file tmp/day0.jsonl
|
||||
```
|
||||
|
||||
Несколько дней подряд играет один запуск: `--days 8`. Всё, что принимается
|
||||
флагами, принимается и переменными окружения — так генератор позовёт даг
|
||||
этапа 5; полный список у `--help`.
|
||||
За один запуск можно проиграть несколько дней подряд: `--days 8`. Всё, что
|
||||
принимается флагами, принимается и переменными окружения — так генератор позовёт
|
||||
даг этапа 5; полный список у `--help`.
|
||||
|
||||
**Значения по умолчанию есть не у всех параметров, и это сделано специально.**
|
||||
Зерно, число дней и темп живого дня описывают модель мира — их генератор знает
|
||||
сам. День на оси, приемник и имя топика описывают стенд, на котором генератор
|
||||
запустили: если их не задать, он откажется работать и вернет ненулевой код еще
|
||||
до первого события. Поэтому значение дня по умолчанию живет в `Makefile`: день
|
||||
задает тот, кто запускает, а не тот, кого запускают.
|
||||
сам. День на оси, приёмник и имя топика относятся к конкретному запуску
|
||||
генератора на стенде: если их не задать, он откажется работать и вернёт
|
||||
ненулевой код ещё до первого события. Поэтому значение дня по умолчанию живёт в
|
||||
`Makefile`: день задаёт тот, кто запускает, а не тот, кого запускают.
|
||||
|
||||
## Пульт мира
|
||||
## Даги модельного времени
|
||||
|
||||
Считать дни самому необязательно. В Airflow живёт **пульт мира**: два дага
|
||||
создают его начальное состояние, а три двигают ось дальше.
|
||||
Считать дни самому необязательно. В Airflow живут **даги модельного времени**:
|
||||
два дага создают начальное состояние мира, а три двигают ось дальше.
|
||||
|
||||
| Даг | Что делает |
|
||||
| --- | --- |
|
||||
@@ -130,33 +132,33 @@ uv run --project generator python -m clickstream_generator batch \
|
||||
| `world_recreate` | удаляет только прикладной мир и запускает его создание заново |
|
||||
| `world_next_day` | играет следующие дни пачкой; сколько — параметр запуска, по умолчанию один |
|
||||
| `world_live_day` | играет следующий день в темпе модельного времени, около двадцати четырёх минут |
|
||||
| `world_live` | выключатель: пока снят с паузы, дёргает `world_live_day` день за днём |
|
||||
| `world_live` | выключатель: пока снят с паузы, запускает `world_live_day` день за днём |
|
||||
|
||||
Работник не только играет день: следом он отправляет слепок заказов за
|
||||
сыгранное и дёргает отдельный даг, `orders_ingest`, — тот забирает приехавший
|
||||
слепок из топика `orders` в сырьё, — и дожидается его конца. Так две половины
|
||||
стенда идут в ногу: упал приём — краснеет и работник, а позиция остаётся на
|
||||
месте ([ADR 0008](../adr/0008-order-ingestion.md)).
|
||||
Сыграв день, шаговый даг отправляет слепок заказов и запускает даг
|
||||
`orders_ingest`. Тот забирает слепок из топика `orders` в `stg`; шаговый даг
|
||||
ждёт его завершения. Так две половины стенда идут в ногу: упал приём — падает и
|
||||
шаговый даг, а позиция остаётся на месте ([ADR
|
||||
0008](../adr/0008-order-ingestion.md)).
|
||||
|
||||
Позицию хранит переменная Airflow `world_position` — номер первого несыгранного
|
||||
дня. Ставит её работник, сыгравший день, и только по успеху: оборванный прогон
|
||||
позицию не двигает, и следующий запуск играет тот же день заново. Отсутствующая
|
||||
переменная означает, что мир ещё не создавали; `-1` — очистка не закончена,
|
||||
`0` — очистка закончена, а начальная генерация ещё не завершена. Работники
|
||||
начинают играть только с позиции `8` и больше.
|
||||
дня. Шаговый даг записывает позицию только после успешного завершения дня:
|
||||
оборванный прогон позицию не двигает, и следующий запуск играет тот же день
|
||||
заново. Отсутствующая переменная означает, что мир ещё не создавали; `-1` —
|
||||
очистка не закончена, `0` — очистка закончена, а начальная генерация ещё не
|
||||
завершена. Шаговые даги начинают играть только с позиции `8` и больше.
|
||||
|
||||
В форме запуска можно выбрать только число дней. Первый день работник берет из
|
||||
`world_position`. Поля «Logical date» и «Run ID» спрятаны в «Advanced Options».
|
||||
Они нужны самому Airflow и на выбор дня не влияют. Сыгранные дни и новую
|
||||
позицию работник пишет в журнал шага `remember_played`.
|
||||
В форме запуска можно выбрать только число дней. Первый день шаговый даг берёт
|
||||
из `world_position`. Поля «Logical date» и «Run ID» спрятаны в «Advanced
|
||||
Options». Они нужны самому Airflow и на выбор дня не влияют. Сыгранные дни и
|
||||
новую позицию шаговый даг пишет в журнал шага `remember_played`.
|
||||
|
||||
Выключатель создаётся на паузе. Снимите — мир поедет сам; поставите обратно —
|
||||
встанет на границе модельных суток, доиграв начатый день. Форма пульта и доводы
|
||||
целиком — [ADR 0009](../adr/0009-world-control.md).
|
||||
встанет на границе модельных суток, доиграв начатый день. Форма дагов времени и
|
||||
доводы целиком — [ADR 0009](../adr/0009-world-control.md).
|
||||
|
||||
**У этого решения есть цена: планировщику Airflow отдан сокет Docker** —
|
||||
иначе ему не поднять контейнер генератора. Доступ к сокету равен правам root
|
||||
на машине; для локального учебного стенда это приемлемо, но знать об этом
|
||||
нужно. Доступ дает не само монтирование сокета, а членство в группе `docker`:
|
||||
у нее на каждой машине свой GID, он записан в `.env`, а узнать его можно
|
||||
командой `getent group docker`.
|
||||
**У этого решения есть цена: планировщик Airflow подключён к сокету Docker.**
|
||||
Без него планировщик не сможет запустить контейнер генератора. Доступ к сокету
|
||||
равен правам root на машине; для локального учебного стенда это приемлемо, но
|
||||
знать об этом нужно. Доступ даёт не само монтирование сокета, а членство в
|
||||
группе `docker`: у неё на каждой машине свой GID. Он записан в `.env`, а узнать
|
||||
его можно командой `getent group docker`.
|
||||
Reference in new issue
Block a user