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

dim_layout_group (ClickHouse dimension)

Реплика-измерение «Группа планировок» в БД dev-stats. JOIN-free справочник для резолва имени группы и для фильтрации метрик по уровню LayoutGroup (уровень иерархии между проектом и планировкой, ADR-0057). Дом полей источника — chessboard: LayoutGroupEntity; здесь — только колонки, нужные dev-stats.

Назначение

dim_layout_group хранит текущий снапшот группы планировок (одна логическая строка на LayoutGroupId), реплицируемый из pin.chessboard.layout-group-upserted.v1. Даёт подпись графика (Name — «Студии», «Люкс с видом на море»), порядок отображения (SortOrder) и родительские id для восхождения по иерархии/фильтрации. Не факт, а стабильное измерение (ReplacingMergeTree).

Table

ПолеЗначение
Databasepin_dev_stats
Tabledim_layout_group
EngineReplacingMergeTree(UpsertedAt)
Owner servicedev-stats (DevStats.Background пишет, DevStats.Api читает; DDL — DevStats.Migrator)
Source eventspin.chessboard.layout-group-upserted.v1
Partition / Orderбез партиционирования (сотни строк); ORDER BY (LayoutGroupId)
Freshness SLA≤ 5 минут от события до видимости

Стабильное измерение, не append-only факт: последняя версия перезаписывает предыдущую. Онлайн-чтение — дубль-устойчивое, без FINAL: актуальная строка берётся argMax(col, UpsertedAt) c GROUP BY LayoutGroupId.

Columns

ColumnTypeNullableSource (поле LayoutGroupEntity)Partition/OrderPII
LayoutGroupIdUUIDнетIdorder (единственный ключ)нет
ProjectIdUUIDнетProjectId — родительnoneнет
DeveloperIdUUIDнетTenantId (= DeveloperId проекта) — вверх по иерархииnoneнет
NameStringнетName («Студии», «Люкс с видом на море»)noneнет
SortOrderInt32нетSortOrder — порядок отображения группnoneнет
IsArchivedUInt8нетIsArchived (soft-delete группы) — статус-эквивалентnoneнет
SourceVersionInt64нетVersion источника — монотонный guardnoneнет
UpsertedAtDateTime64(3)нетвремя события (CreatedAt)none (версия движка)нет

Минимальный набор для фильтрации/подписи. Счётчики (LayoutsCount) и диапазоны площадей (AreaMin/AreaMax) в dev-stats не нужны — не реплицируются. У группы нет статусного цикла (только активна/архив через soft-delete), поэтому вместо Status реплицируется флаг IsArchived из snapshot-а события.

Инварианты

  • INV-DIM-LG-1 (единственность): одна логическая строка на LayoutGroupId (единственный компонент ORDER BY); merge оставляет строку с максимальным UpsertedAt.
  • INV-DIM-LG-2 (родители денормализованы): ProjectId и DeveloperId реплицируются в строку группы для восхождения по иерархии/проверки scope без обращения к dim_project.
  • INV-DIM-LG-3 (не удаляется): строка живёт, пока существует группа; IsArchived = 1 помечает архив, но не удаляет (3-летние факты должны резолвить подпись/фильтр по уровню LayoutGroup).
  • INV-DIM-LG-4 (precedence): актуальность — по UpsertedAt (max побеждает); SourceVersion монотонна per-key, служит анти-out-of-order-диагностикой.
  • INV-DIM-LG-5 (статус-эквивалент): у группы нет статусного цикла — вместо Status реплицируется флаг IsArchived (активна/архив); SortOrder определяет порядок отображения групп.

Кросс-сервисный контракт

  • Ожидает от chessboard (layout-group-upserted): Id, ProjectId, TenantId (= DeveloperId), Name, SortOrder, IsArchived, Version; key = LayoutGroupId, at-least-once, атомарная публикация снапшота, additive-only для minor.
  • Предоставляет downstream (в пределах dev-stats): JOIN-free подпись (Name — «Студии», «Люкс с видом на море»), SortOrder и атрибуты фильтра метрик по уровню LayoutGroup. Измерение не на критическом пути обогащения факта: факт несёт денормализованный LayoutGroupId (из dim_property_object); dim_layout_group нужен для подписи/фильтра при чтении.
  • При отсутствии/устаревании: строки ещё нет → метрика уровня LayoutGroup резолвится по LayoutGroupId, подпись деградирует до id без ошибки; устаревшая доставка → берётся max(UpsertedAt).

Deduplication

  • Dedup key: LayoutGroupId; ReplacingMergeTree(UpsertedAt) оставляет строку с максимальным UpsertedAt.
  • Replay behavior: идемпотентный upsert; до merge — дубль-устойчивое чтение argMax(…, UpsertedAt) … GROUP BY LayoutGroupId (FINAL в online запрещён).
  • Version guard: событие применяется только при event.Version > SourceVersion; иначе пропуск.

Partition И TTL

  • Partition key: нет — групп сотни.
  • Order key: (LayoutGroupId) — точечный резолв и фильтр WHERE DeveloperId = актор AND ProjectId = … AND LayoutGroupId = ….
  • TTL/retention: нет — измерение живёт, пока существует группа; IsArchived = 1 помечает архив, строку не удаляет (3-летние факты должны резолвиться).

Consumers И Backfill

ProcessНазначениеCheckpoint
Приём pin.chessboard.layout-group-upserted.v1 (фоновый ingest)Проверка версии, маппинг snapshot → строка измерения, batch-upsert (≤ 1000 / 5 с)Kafka consumer offset (commit после вставки батча)
Backfill измеренийПовтор топика layout-group-upserted при вводе сервиса (идемпотентно)Kafka offset / диапазон LayoutGroupId

Security

  • Raw PII не хранится (группа планировок — справочный объект).
  • Доступ — роль застройщика, permission dev-stats.view, scope строго = свой DeveloperId; каждый запрос фильтрует DeveloperId = актор.
  • ClickHouse не operational source of truth: источник группы — chessboard.

Acceptance criteria

  • Happy upsert. Given layout-group-upserted с непустым Id, When обработан батч, Then в dim_layout_group есть строка с Name/SortOrder/IsArchived и родителями ProjectId/DeveloperId; чтение argMax(…, UpsertedAt) GROUP BY LayoutGroupId (без FINAL) отдаёт значения.
  • Идемпотентный повтор / устаревшая доставка. Given то же/старое событие доставлено повторно, When обработано, Then после merge логически одна строка; побеждает max(UpsertedAt).
  • Архив не удаляет. Given группа переведена в IsArchived = 1, When событие обработано, Then строка остаётся, факты 3-летней глубины резолвят подпись.
  • Деградация подписи. Given строки ещё нет при чтении метрики уровня LayoutGroup, Then метрика резолвится по LayoutGroupId, подпись показывает id без ошибки.
  • Scope. Given запрос застройщика, When фильтруется dim_layout_group, Then только строки DeveloperId = актор.

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

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

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