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:
2026-07-22 23:47:02 +03:00
co-authored by Claude Fable 5
parent f4971e94ca
commit 103ac021c8
12 changed files with 845 additions and 54 deletions
+17 -1
View File
@@ -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` профиль нужно задать явно: