Перейти к основному содержимому

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)
Тип jobOne-off (bootstrap исторической глубины)
Triggerручной оператор (ввод сервиса)
Критичностьmedium (историческая глубина; live-ingest работает независимо)

Расписание И Locking

АспектПравило
Scheduleнет (one-off/manual)
Distributed lockdev-stats:facts-backfill (single-run, TTL = max duration)
Batch size≤ 1000 строк на батч
Max durationзависит от глубины окна (до 3 лет) — прогон по партициям toYYYYMM

Входная Модель BackfillFactsRequestModel

ПолеТипОбязательностьИсточникОписание
PeriodNullableRange<DateOnly>Даоператорокно ViewedAt/StartedAt для перезаливки (≤ TTL 3 года)
FactsIReadOnlyList<FactKind>Нетоператорview / session (default — оба)
Sourceenum (user-activity-history)Нетоператористочник истории (реплика/архив user-activity)

Алгоритм

  1. Взять lock dev-stats:facts-backfill; восстановить correlationId; проверить готовность dim_*.
  2. Итерировать окно Period по партициям toYYYYMM (stable ordering — ViewedAt).
  3. Читать историю просмотров/сессий из источника user-activity, обогащать иерархией из dim_property_object (argMax(*, UpsertedAt), без FINAL), классифицировать ViewerKind/ViewSource.
  4. Формировать строки с детерминированным SourceEventId = backfill:{PropertyObjectViewId} (не Guid конверта) — исключает пересечение с live-консюмером (см. «Идемпотентность»).
  5. Вставка батчей в property_object_view_facts/session_facts; checkpoint — партиция + курсор.
  6. По завершении окна — rebuild rollup-ов затронутых партиций (INSERT INTO ... SELECT ...State... FROM property_object_view_facts).
  7. Освободить lock; метрики (строк на партицию, длительность, lag).

Граница Транзакции

ЭлементПравило
Unit of workper batch (вставка фактов); без PostgreSQL-транзакции
Сохраняемое состояниефакты/сессии + rollup-партиции (ClickHouse); checkpoint — служебная таблица job-а (PostgreSQL)
External callsчтение истории user-activity (реплика/архив)
Частичная ошибкаresume с checkpoint партиции; append-only, dedup по ключу

Идемпотентность И Retry

СценарийПравило
Job restartresume с зафиксированной партиции/курсора
Duplicate itemSourceEventId = 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).

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы