feat(generator): инкрементальные счётчики manifest без перечитки Kafka
- Зачем:
- world_next_day перечитывал всю историю топиков Kafka ради
накопительных счётчиков — время прогона росло с возрастом мира
(issue #5, находка F9).
- Что:
- счётчики засеваются при import из уже прочитанного артефакта и при
backfill из потока; next-day продвигает их только событиями нового
дня, полного чтения Kafka больше нет;
- катящаяся контрольная сумма — сумма SHA-256 событий по модулю 2^256
(инкремент равен полному пересчёту), старый формат артефакта
принимается без изменений;
- точные множества click_id/user_domain_id вынесены из manifest в
цепочку контент-адресуемых фрагментов (<=10 000 ID, SHA-256-цепочка,
отдельный топик counter_chunks) — потолок сообщения Kafka не грозит,
предел 900 000 байт проверяется явно с понятной ошибкой;
- порядок записи всюду: фрагменты -> manifest -> state; старое локальное
состояние отклоняется с подсказкой перезапустить import;
- документация manifest/state обновлена (ARCHITECTURE, OPERATIONS,
runbook startup-history).
- Проверка:
- make test (216+31) и make lint зелёные;
- живая приёмка на чистом стенде: import 235 с; три прогона
world_next_day — 716/718/716 с (плоское время, O(нового дня));
мир 3->6 дней, 561 942 события; make generated-history-chain-check —
все порции и стыки однородны;
- тест равенства инкремента и полного пересчёта:
test_incremental_counters_equal_full_recompute.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -670,7 +670,12 @@ INSERT INTO dm.daily_traffic SELECT * FROM dm.v_daily_traffic;
|
||||
|
||||
**DAG `world_next_day`** без параметров пакетно добавляет следующий модельный
|
||||
день, запускает `etl_pipeline` с `full_refresh` и сверяет manifest. У него задано
|
||||
расписание каждые 30 минут, но DAG создаётся на паузе и не выполняет пропущенные интервалы.
|
||||
расписание каждые 30 минут, но DAG создаётся на паузе и не выполняет пропущенные
|
||||
интервалы. Счётчики manifest продолжаются из compact-топиков state/manifest и
|
||||
обновляются событиями нового дня; старые data-топики Kafka не перечитываются.
|
||||
Точные множества идентификаторов лежат отдельными неизменяемыми фрагментами, а
|
||||
основная запись manifest хранит только числа и ссылку на их цепочку. Полная
|
||||
пересборка ETL при этом сохраняется намеренно.
|
||||
|
||||
**Подключение к ClickHouse:**
|
||||
- Connection: `clickhouse_default`
|
||||
|
||||
+11
-10
@@ -82,7 +82,6 @@ Backfill/import требуют чистый стенд: пустые data-топ
|
||||
- Один запуск восстанавливает мир из state, добавляет 24 модельных часа,
|
||||
запускает `etl_pipeline` с полной пересборкой и сверяет витрины с manifest.
|
||||
- Расписание задано каждые 30 минут, но DAG создаётся на паузе; `catchup=False`.
|
||||
Не включайте расписание до внедрения накопительных счётчиков manifest.
|
||||
- `max_active_runs=1` не даёт двум доливкам выполняться параллельно.
|
||||
|
||||
`world_next_day` работает на непустом стенде и не использует проверку чистоты.
|
||||
@@ -107,13 +106,15 @@ data-топики, state, manifest. Автоматического отката
|
||||
не повторяйте доливку поверх возможного хвоста. Очистите стенд и переимпортируйте
|
||||
последний исправный портативный артефакт, затем повторите запуск `world_next_day`.
|
||||
|
||||
Текущая версия пересчитывает накопительные счётчики и контрольные суммы по всей
|
||||
доступной истории data-топиков Kafka. Поэтому время выполнения и расход памяти
|
||||
каждой доливки растут вместе с историей. Стандартный срок хранения Kafka тоже
|
||||
ограничивает долгую работу стенда. Для ручного учебного цикла запускайте
|
||||
соседние дни без долгих пауз. Перед включением расписания отдельная задача
|
||||
должна выбрать одно из решений: бессрочное хранение data-топиков или новый
|
||||
формат накопительного состояния manifest.
|
||||
`next-day` не перечитывает старые data-топики Kafka. Основная запись manifest
|
||||
хранит суммы, катящуюся контрольную сумму и SHA-256-ссылку на цепочку точных
|
||||
множеств. Сами `click_id` и `user_domain_id` разбиты на небольшие неизменяемые
|
||||
фрагменты в отдельном compact-топике. Новый день записывает только новый
|
||||
фрагмент, поэтому размер одной записи не растёт с возрастом мира, а загрузка
|
||||
основного manifest не сканирует всю цепочку.
|
||||
State ссылается на основную запись manifest, чтобы две точки продолжения нельзя
|
||||
было случайно смешать. Если локальный state создан старым кодом и раздела в нём
|
||||
нет, повторно запустите `world_init` с операцией `import` на чистом стенде.
|
||||
|
||||
Перед запуском `etl_pipeline` пульт проверяет, что DAG не стоит на паузе. Если
|
||||
стоит, задача падает сразу с подсказкой снять паузу в UI или командой:
|
||||
@@ -294,8 +295,8 @@ GEN_HISTORY_DURATION=2d make generated-history-analytics
|
||||
что state совпадает с manifest, и стартует ровно с `T_end` без настенной дельты.
|
||||
|
||||
Новый backfill записывает `boundaries=[T0, T_end]`. Каждый успешный `next-day`
|
||||
добавляет одну границу, сдвигает `model_t_end` и пересчитывает накопительные
|
||||
счётчики и контрольные суммы по всей истории.
|
||||
добавляет одну границу, сдвигает `model_t_end` и дополняет накопительные
|
||||
счётчики только событиями нового дня. Полной перечитки Kafka в этом пути нет.
|
||||
|
||||
Если историю нужно сохранить в файл и восстановить на чистом стенде без новой
|
||||
генерации, используйте [runbook стартовой истории](./runbooks/startup-history.md).
|
||||
|
||||
@@ -78,7 +78,8 @@ make startup-history-import
|
||||
5. Когда нужен ещё один модельный день, запустите `world_next_day` с пустой формой.
|
||||
|
||||
`world_next_day` имеет расписание каждые 30 минут, но по умолчанию стоит на паузе.
|
||||
Не включайте расписание до внедрения накопительных счётчиков manifest.
|
||||
Один запуск добавляет один модельный день; `max_active_runs=1` не допускает
|
||||
параллельных доливок.
|
||||
|
||||
## Операции сопровождающего
|
||||
|
||||
@@ -157,6 +158,21 @@ CHECK_LIVE_SEAM=0 make generated-history-check
|
||||
compact-топики в Kafka. ClickHouse читает данные через свои Kafka-таблицы и
|
||||
Materialized View, затем batch строит ODS, DDS и DM.
|
||||
|
||||
Во время импорта генератор за один проход по событиям создаёт локальное
|
||||
накопительное состояние manifest. Эталонный файл не меняется: в нём этого
|
||||
служебного раздела нет. Основная запись manifest в Kafka хранит суммы, катящуюся
|
||||
контрольную сумму и ссылку на цепочку точных множеств. `click_id` и
|
||||
`user_domain_id` лежат небольшими неизменяемыми фрагментами в отдельном
|
||||
compact-топике. State хранит SHA-256-ссылку на основную запись.
|
||||
`backfill` создаёт такое состояние сразу по ходу генерации.
|
||||
|
||||
`world_next_day` восстанавливает эти числа и добавляет только события нового
|
||||
дня, не читая старую историю Kafka и не перезаписывая прежние фрагменты.
|
||||
Контрольная сумма складывает 256-битные
|
||||
SHA-256-отпечатки записей по модулю 2^256, поэтому результат не зависит от
|
||||
границ пакетов и совпадает с полным пересчётом. Если старый локальный state не
|
||||
содержит ссылку на накопительные числа, очистите стенд и повторите `import`.
|
||||
|
||||
Импорт запускайте с тем же профилем, на котором создан артефакт. Для служебного
|
||||
артефакта `ci` профиль нужно задать явно:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user