Background Job: FactsBackfillJob
Назначение
Разовая перезаливка исторических фактов просмотров/сессий в ClickHouse pin_dev_stats из истории
user-activity при вводе сервиса dev-stats (ADR-0057: «отслеживать 3 года»). Заполняет
property_object_view_facts и session_facts за прошлый период, по которому события уже не переигрываются
из Kafka (retention топика короче TTL фактов). После backfill фактов rollup-ы
(property_object_view_daily, funnel_daily) пересобираются MV/rebuild по затронутым партициям.
Предусловие — наполненные измерения (dimension-backfill.md), иначе обогащение
иерархией не резолвится.
Классификация
| Поле | Значение |
|---|
| Сервис | dev-stats (DevStats.Background) |
| Тип job | One-off (bootstrap исторической глубины) |
| Trigger | ручной оператор (ввод сервиса) |
| Критичность | medium (историческая глубина; live-ingest работает независимо) |
Расписание И Locking
| Аспект | Правило |
|---|
| Schedule | нет (one-off/manual) |
| Distributed lock | dev-stats:facts-backfill (single-run, TTL = max duration) |
| Batch size | ≤ 1000 строк на батч |
| Max duration | зависит от глубины окна (до 3 лет) — прогон по партициям toYYYYMM |
Входная Модель BackfillFactsRequestModel
| Поле | Тип | Обязательность | Источник | Описание |
|---|
Period | NullableRange<DateOnly> | Да | оператор | окно ViewedAt/StartedAt для перезаливки (≤ TTL 3 года) |
Facts | IReadOnlyList<FactKind> | Нет | оператор | view / session (default — оба) |
Source | enum (user-activity-history) | Нет | оператор | источник истории (реплика/архив user-activity) |
Алгоритм
- Взять lock
dev-stats:facts-backfill; восстановить correlationId; проверить готовность dim_*.
- Итерировать окно
Period по партициям toYYYYMM (stable ordering — ViewedAt).
- Читать историю просмотров/сессий из источника
user-activity, обогащать иерархией из dim_property_object
(argMax(*, UpsertedAt), без FINAL), классифицировать ViewerKind/ViewSource.
- Формировать строки с детерминированным
SourceEventId = backfill:{PropertyObjectViewId} (не Guid
конверта) — исключает пересечение с live-консюмером (см. «Идемпотентность»).
- Вставка батчей в
property_object_view_facts/session_facts; checkpoint — партиция + курсор.
- По завершении окна — rebuild rollup-ов затронутых партиций (
INSERT INTO ... SELECT ...State... FROM property_object_view_facts).
- Освободить lock; метрики (строк на партицию, длительность, lag).
Граница Транзакции
| Элемент | Правило |
|---|
| Unit of work | per batch (вставка фактов); без PostgreSQL-транзакции |
| Сохраняемое состояние | факты/сессии + rollup-партиции (ClickHouse); checkpoint — служебная таблица job-а (PostgreSQL) |
| External calls | чтение истории user-activity (реплика/архив) |
| Частичная ошибка | resume с checkpoint партиции; append-only, dedup по ключу |
Идемпотентность И Retry
| Сценарий | Правило |
|---|
| Job restart | resume с зафиксированной партиции/курсора |
| Duplicate item | SourceEventId = backfill:{PropertyObjectViewId} — ReplacingMergeTree схлопывает повтор |
| Пересечение с live-ingest | исключено схемой ключей: у консюмера SourceEventId = Guid EventId конверта, у backfill — префикс backfill: |
| Падение посреди окна | resume с checkpoint партиции/курсора; уже вставленные партиции не переписываются заново без нужды (append-only + dedup) |
| Повторный запуск того же окна | идемпотентен: SourceEventId = backfill:{…} схлопывает повтор; rollup-партиции пересобираются из фактов (idempotent rebuild) |
Кросс-сервисные контракты
| Направление | Контракт |
|---|
Ожидает от user-activity | Доступный источник истории просмотров/сессий (реплика/архив) за окно Period (до 3 лет), запрашиваемый по PropertyObjectViewId/ViewedAt/StartedAt. Событий из Kafka за это окно уже нет (retention топика короче TTL фактов) — потому нужен прямой источник. |
Ожидает от измерений (chessboard) | Наполненные dim_* (dimension-backfill) — обогащение иерархией при перезаливке; при пропуске строки исторический факт не резолвит иерархию. |
| Предоставляет | Исторические строки property_object_view_facts/session_facts за окно + пересобранные rollup-партиции (property_object_view_daily, funnel_daily). Прямых событий job не публикует. |
| Порядок ввода | 1) dimension-backfill (измерения); 2) facts-backfill (факты по партициям); 3) rebuild rollup-ов затронутых партиций. Live-ingest работает независимо и параллельно (ключи не пересекаются). |
Предусловия и постусловия
- Предусловия: измерения наполнены (dimension-backfill.md); источник истории
user-activity доступен за окно Period; Period ≤ TTL (3 года); DDL фактов/rollup-ов применён.
- Постусловия: за окно
Period факты просмотров/сессий присутствуют в ClickHouse с детерминированными
SourceEventId = backfill:{…} (без пересечения с live-ingest); rollup-партиции затронутого диапазона
пересобраны и согласованы с фактами; checkpoint зафиксирован; lock освобождён.
Наблюдаемость
- Logs: старт/финиш по партициям
toYYYYMM, прогресс курсора, rebuild rollup-ов, ошибки чтения источника/вставки.
- Metrics: строк на партицию, длительность прогона, lag относительно окна, число partition-ов с rebuild.
- Alerts: незавершённый job (lock дольше max duration); ошибки чтения источника истории; расхождение
контрольных сумм факт/rollup после rebuild.
Acceptance Criteria
- Happy path (историческая глубина): Дано — измерения наполнены, источник доступен,
Period = 3 года;
Когда — job отрабатывает; Тогда — факты за окно вставлены, rollup-партиции пересобраны, метрики ЛК за
прошлый период отдаются корректно.
- Resume после падения: Дано — job упал на партиции
M; Когда — перезапущен; Тогда — продолжает с
checkpoint партиции M/курсора, ранее вставленные партиции не дублируются.
- Изоляция от live-ingest: Дано — live-консюмер пишет closed-факты параллельно; Когда — backfill
вставляет строки того же просмотра; Тогда — пересечения нет (разные схемы
SourceEventId), двойного
счёта нет.
- Идемпотентность повторного запуска окна: Дано — окно уже залито; Когда — job запущен на то же окно;
Тогда — логических дублей нет, rollup пересобирается идемпотентно.
- Пропуск измерения: Дано — для части объектов нет строк
dim_property_object; Когда — backfill идёт;
Тогда — такие факты не резолвят иерархию (сигнал «dimension gap») — сначала догоняется
dimension-backfill, затем окно перезаливается.
- Одновременный запуск заблокирован: Дано — job держит lock
dev-stats:facts-backfill; Когда —
стартует второй экземпляр; Тогда — второй не запускается (single-run).
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы