Background Job: DimensionBackfillJob
Назначение
Первичная загрузка реплик-измерений dim_* (dim_developer/dim_project/dim_building/
dim_layout_group/dim_layout/dim_property_object) из chessboard при вводе сервиса dev-stats в
эксплуатацию. Обязателен до включения ingest фактов: без наполненных измерений обогащение просмотра
иерархией (property_object_view_ended consumer) не найдёт dim_property_object и факты уйдут в отложенную
дозагрузку. Способ — повтор (replay) upsert-топиков chessboard с раннего offset; консюмеры измерений
идемпотентны (ReplacingMergeTree), поэтому job не пишет напрямую, а инициирует переигрывание.
Классификация
| Поле | Значение |
|---|
| Сервис | dev-stats (DevStats.Background) |
| Тип job | One-off (bootstrap; повтор при расхождении измерений) |
| Trigger | ручной оператор (ввод сервиса / recovery после «dimension gap») |
| Критичность | high (предусловие ingest фактов) |
Расписание И Locking
| Аспект | Правило |
|---|
| Schedule | нет (one-off/manual); не cron |
| Distributed lock | dev-stats:dimension-backfill (single-run, TTL = max duration) |
| Batch size | ≤ 1000 строк на батч (как у ingest-консюмеров) |
| Max duration | ограничено объёмом справочников chessboard (сотни тысяч dim_property_object) |
Входная Модель RebuildDimensionsRequestModel
| Поле | Тип | Обязательность | Источник | Описание |
|---|
Dimensions | IReadOnlyList<DimensionKind> | Нет | оператор | какие dim_* перезалить (default — все) |
FromOffset | KafkaOffsetSpec | Нет | оператор | earliest (default) / конкретный offset per-topic |
Алгоритм
- Взять lock
dev-stats:dimension-backfill; восстановить correlationId.
- Для каждого выбранного
dim_* — сбросить offset consumer-group измерения на FromOffset (earliest).
- Переиграть upsert-топик: консюмеры измерений применяют snapshot'ы (
ReplacingMergeTree(UpsertedAt),
version-guard event.Version > SourceVersion) — дубли схлопываются при merge.
- Дождаться, пока consumer-lag измерений дойдёт до текущего конца топика (checkpoint = offset).
- Освободить lock; метрики (строк на измерение, длительность).
Граница Транзакции
| Элемент | Правило |
|---|
| Unit of work | per batch (вставка измерения); PostgreSQL не участвует |
| Сохраняемое состояние | строки dim_* (ClickHouse); checkpoint — Kafka offset consumer-group |
| External calls | Kafka replay (идемпотентно) |
| Частичная ошибка | продолжить с последнего offset (at-least-once, dedup движком) |
Идемпотентность И Retry
| Сценарий | Правило |
|---|
| Job restart | resume с зафиксированного offset consumer-group измерения |
| Duplicate item | ReplacingMergeTree(UpsertedAt) + version-guard → логических дублей нет |
| Пересечение с live-консюмером | безопасно: тот же upsert идемпотентен |
| Падение посреди прогона | измерения, доехавшие до своего конца топика, уже наполнены; недоехавшие — продолжают с текущего offset; повторный запуск безопасен (idempotent replay) |
Частичный отказ одного dim_* | остальные измерения не блокируются; проблемное переигрывается отдельно (по Dimensions) |
Кросс-сервисные контракты
| Направление | Контракт |
|---|
Ожидает от chessboard | Upsert-топики pin.chessboard.{developer,project,building,layout-group,layout,property-object}-upserted.v1 replayable с раннего offset (retention достаточен для полного справочника). Каждое сообщение — самодостаточный snapshot с ключом Id и Version; порядок per-key гарантирован ключом сообщения. |
| Предоставляет | Наполненные реплики-измерения dim_* в pin_dev_stats — предусловие для обогащения фактов иерархией. Прямых событий job не публикует. |
| Взаимодействие с ingest фактов | Job инициирует переигрывание upsert-топиков; фактическую запись делают идемпотентные консюмеры измерений. Ingest фактов включается после подтверждения, что измерения догнали конец топика. |
Предусловия и постусловия
- Предусловия: сервис
dev-stats развёрнут, DDL измерений применён; ingest фактов ещё не включён (либо
включается после этого job-а); upsert-топики chessboard доступны для replay.
- Постусловия: для каждого выбранного
dim_* consumer-lag = 0 (реплика догнала конец топика на момент
чекпоинта); dim_property_object и вышестоящие измерения содержат актуальные строки, достаточные для
резолва иерархии факта; lock освобождён.
Наблюдаемость
- Logs: старт/финиш (по каждому
dim_*), сброс offset, достижение конца топика, ошибки.
- Metrics: строк на измерение, длительность прогона, текущий consumer-lag по группам измерений.
- Alerts: незавершённый job (lock удерживается дольше max duration); устойчивый ненулевой lag после
ожидаемого завершения; ошибки вставки измерений.
Acceptance Criteria
- Happy path (полный ввод): Дано — DDL применён, топики доступны,
Dimensions не задан (все);
Когда — job отрабатывает; Тогда — все dim_* догнали конец своих топиков (lag = 0), после чего ingest
фактов может быть включён без «dimension gap».
- Recovery одного измерения: Дано — обнаружен пропуск строк только в
dim_property_object; Когда —
job запущен с Dimensions = [PropertyObject]; Тогда — переигран только этот топик, прочие не тронуты.
- Идемпотентность повторного запуска: Дано — job уже отрабатывал; Когда — запущен повторно; Тогда —
логических дублей в
dim_* нет (ReplacingMergeTree + version-guard).
- Пересечение с live-консюмером: Дано — live-ingest измерений работает; Когда — job переигрывает тот
же топик; Тогда — результат идемпотентен, гонок нет.
- Одновременный запуск заблокирован: Дано — job уже держит lock
dev-stats:dimension-backfill;
Когда — стартует второй экземпляр; Тогда — второй не запускается (single-run).
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы