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

property_object_view_daily (ClickHouse rollup / materialized view)

Дневной агрегат просмотров по иерархии застройщика × дата × тип зрителя. Нужен, чтобы графики за месяцы/годы (DevStats.Api) не сканировали сырые property_object_view_facts. ADR-0057 требует rollup-ы (в отличие от invest-analytics, где считают прямыми запросами). Онлайн-чтение — через *Merge-функции, без FINAL.

Назначение

property_object_view_dailyAggregatingMergeTree-таблица, наполняемая materialized view property_object_view_daily_mv над property_object_view_facts: на каждый вставленный факт MV эмитит частичные агрегатные состояния в целевую таблицу, где они схлопываются по ключу (иерархия, EventDate, ViewerKind). Хранит меры: число просмотров, уникальных зрителей, суммарную/среднюю длительность и повторные просмотры. Дедуп-устойчивость обеспечивают uniqState-меры (склейка open/closed строк одного просмотра по PropertyObjectViewId) и идемпотентность консюмера по FactId (MV не получает повтор одного факта).

Классификация

ПолеЗначение
Контекстdev-stats (ADR-0057)
Kindrollup — AggregatingMergeTree + materialized view над property_object_view_facts
Источниктаблица pin_dev_stats.property_object_view_facts (см. property-object-view-facts.md)
ХранилищеClickHouse, database pin_dev_stats, table property_object_view_daily, MV property_object_view_daily_mv
Tenant isolationDeveloperId — головной компонент ключа; фильтр обязателен и = актор

Table

ПолеЗначение
Databasepin_dev_stats
Tableproperty_object_view_daily (+ MV property_object_view_daily_mv)
EngineAggregatingMergeTree
Owner servicedev-stats (DevStats.Migrator создаёт таблицу и MV; наполнение — автоматически при вставке фактов)
Sourceproperty_object_view_facts (триггер MV на INSERT)
Transform version1
Freshness SLA≤ 5 минут (синхронно со вставкой фактов)

Columns

Ключ группировки (dimensions) + агрегатные состояния (measures). EventDate = toDate(ViewedAt).

ColumnTypeРольPartition/Order
DeveloperIdUUIDdimensionorder (1)
ProjectIdUUIDdimensionorder (2)
BuildingIdUUIDdimensionorder (3)
LayoutGroupIdUUIDdimensionorder (4)
LayoutIdUUIDdimensionorder (5)
PropertyObjectIdUUIDdimensionorder (6)
EventDateDatedimension = toDate(ViewedAt)PARTITION BY toYYYYMM(EventDate), order (7)
ViewerKindLowCardinality(String)dimension (Guest/Agent/Investor)order (8)
ViewsAggregateFunction(uniq, UUID)мера: число просмотров = uniqState(PropertyObjectViewId)none
UniqViewersAggregateFunction(uniq, UUID)мера: uniqState(coalesce(UserId, AnonymousId))none
DurationSumAggregateFunction(sum, Int32)мера: sumState(DurationSeconds)none
AvgDurationAggregateFunction(avg, Int32)мера: avgState(DurationSeconds)none

RepeatViews не хранится отдельной колонкой, а выводится при чтении: uniqMerge(Views) − uniqMerge(UniqViewers) — просмотры сверх первого на каждого уникального зрителя (частота повторных заходов). Это устойчиво к дублям и не требует оконных функций в MV.

Определение measures (dedup-tolerant)

  • Views = uniqState(PropertyObjectViewId) — «число просмотров» (канон: count) реализовано как uniq по PropertyObjectViewId, чтобы open + closed строки одного просмотра и at-least-once дубли считались один раз.
  • UniqViewers = uniqState(coalesce(UserId, AnonymousId)) — уникальные зрители (гости — по AnonymousId).
  • DurationSum = sumState(DurationSeconds) — open-строка вносит 0, поэтому сумма = реальному суммарному времени просмотров (консюмер идемпотентен по FactId → повтор closed не задваивается).
  • AvgDuration: хранится avgState(DurationSeconds); так как open-строки добавляют нули, API для показа берёт sumMerge(DurationSum) / uniqMerge(Views) (средняя длительность на просмотр), а avgMerge — как вспомогательный ряд. Оба варианта задокументированы, чтобы не путать нуль-смещение.

Инварианты

КодИнвариантСмысл
INV-RD-1 DerivedFromFactsRollup детерминированно выводится из property_object_view_facts через MV; пересборка по диапазону партиций даёт тот же результатrollup не source of truth — восстановим из фактов
INV-RD-2 DedupTolerantMeasuresВсе меры устойчивы к дублям и паре open/closed (склейка по PropertyObjectViewId); at-least-once не задваиваетнет расхождения с фактами
INV-RD-3 KeyCompleteКлюч (иерархия, EventDate, ViewerKind) заполнен полностью (наследуется из факта, INV-LV-3)нет строк с «дырявой» иерархией
INV-RD-4 RepeatNonNegativeRepeatViews = uniqMerge(Views) − uniqMerge(UniqViewers) ≥ 0 (просмотров всегда ≥ уникальных зрителей)производная неотрицательна по определению
INV-RD-5 OpenZeroDurationOpen-строка вносит 0 в DurationSum; средняя длительность на показ = DurationSum / Viewsнуль-смещение open-строк не искажает суммарное/среднее время
INV-RD-6 SyncTtlTTL и глубина синхронны с фактами (EventDate + 3 года)rollup не переживает и не отстаёт от источника

Согласованность и промежуточное состояние

  • Rollup наполняется синхронно со вставкой фактов (MV на INSERT) — окно freshness то же, что у фактов (≤ 5 мин). Отдельного лага rollup над фактами нет.
  • Текущий (сегодняшний) день может «доуточняться»: по мере прихода closed-фактов растут длительности, по мере late-dimension enrichment — счётчики прошедших дней (идемпотентно). Ряд за завершённые прошлые дни стабилен.
  • Rollup не потребляет события напрямую — только производен от property_object_view_facts; все кросс-сервисные ожидания (иерархия из dim_*, поля просмотра) замкнуты на уровне факта.

Deduplication

  • AggregatingMergeTree схлопывает частичные состояния по ключу ORDER BY; финальные значения дают uniqMerge/sumMerge/avgMerge. uniq-меры делают счётчики устойчивыми к дублированию (open/closed, at-least-once).
  • FINAL в online-запросах запрещён — вместо него всегда *Merge + GROUP BY по нужному срезу ключа (правило аналитического чтения).

Partition И TTL

  • Partition key: toYYYYMM(EventDate).
  • Order key (подсказка по индексам): (DeveloperId, ProjectId, BuildingId, LayoutGroupId, LayoutId, PropertyObjectId, EventDate, ViewerKind) — та же иерархия фильтров, что и в property_object_view_facts. Свёртка «по проекту за месяц» = префикс-скан (DeveloperId, ProjectId) + партиции; детализация по зданию/группе/планировке/объекту недвижимости — удлинение префикса; разбивка по типу зрителя — хвостовой ViewerKind. EventDate перед ViewerKind даёт временные ряды по дням внутри выбранного уровня иерархии.
  • TTL: EventDate + INTERVAL 3 YEAR (синхронно с фактами).

Consumers И Backfill

ProcessНазначениеCheckpoint
MV property_object_view_daily_mvАвтонаполнение при каждом INSERT в property_object_view_facts (частичные агрегатные состояния)— (детерминированно от фактов)
Rebuild (будущий ../background/facts-backfill.md)INSERT INTO property_object_view_daily SELECT ...State... FROM property_object_view_facts по диапазону партиций после backfill/схемных правокДиапазон toYYYYMM(EventDate)

Security

  • Доступ — роль застройщика + permission dev-stats.view; фильтр DeveloperId обязателен и = актор (fail-closed). Агрегаты не раскрывают персональные данные — только счётчики и типы зрителей.
  • Прямой доступ к ClickHouse отсутствует; чтение — через DevStats.Api.

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

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

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