События dev-stats
Produced-события — нет
dev-stats — leaf-аналитика (ADR-0057): не порождает интеграционных и доменных событий. Метрики
отдаются только по read-only API области developers; потребители читают их через HTTP, не через шину.
Ingest-консюмеры пишут в ClickHouse батчами, без промежуточной транзакции в PostgreSQL — см.
../consumers/property-object-view-ended.md.
Consumed-события (наполнение фактов/измерений/воронки)
Все входящие — Kafka, батч-ingestion (batch-вставка, идемпотентность по FactId/SourceEventId,
freshness SLA ≤ 5 мин). Топики согласованы с продюсерами (producer↔consumer, ADR-0058).
| Топик | Назначение | Consumer |
|---|---|---|
pin.user-activity.property-object-view-ended.v1 | closed-факт просмотра → property_object_view_facts | property-object-view-ended.md |
pin.user-activity.action-tracked.v1 | open-факт (ViewPropertyObject) + вход воронки каталога | action-tracked.md |
pin.user-activity.session-ended.v1 | сессия → session_facts | session-ended.md |
pin.chessboard.property-object-upserted.v1 | реплика dim_property_object | property-object-upserted.md |
pin.chessboard.developer-upserted.v1 | реплика dim_developer | developer-upserted.md |
pin.chessboard.project-upserted.v1 | реплика dim_project | project-upserted.md |
pin.chessboard.building-upserted.v1 | реплика dim_building | building-upserted.md |
pin.chessboard.layout-group-upserted.v1 | реплика dim_layout_group | layout-group-upserted.md |
pin.chessboard.layout-upserted.v1 | реплика dim_layout | layout-upserted.md |
pin.favorites.favorite-added.v1 | этап воронки Favorites | favorite-added.md |
pin.selections.selection-item-added.v1 | этап воронки Selections | selection-item-added.md |
pin.crm.lead-created.v1 | этап воронки Leads | lead-created.md |
pin.crm.booking-created.v1 | этап воронки Bookings | booking-created.md |
pin.crm.booking-paid.v1 | этап воронки BookingsPaid | booking-paid.md |
pin.crm.deal-created.v1 | этап воронки Deals | deal-created.md |
pin.crm.deal-completed.v1 | этап воронки DealsCompleted | deal-completed.md |
Гарантии потребления (общие для всех топиков)
| Свойство | Правило |
|---|---|
| Доставка | at-least-once; повтор возможен всегда — обрабатывается идемпотентно (см. ниже) |
| Идемпотентность | по бизнес-ключу целевой строки: факты — FactId = SourceEventId (ReplacingMergeTree); этапы воронки — uniqState(<бизнес-ключ этапа>) (AggregatingMergeTree); дополнительно отсев повторного SourceEventId/MessageId в батче |
| Порядок | не требуется: rollup/фактовые движки сливают частичные/дублирующие состояния по ключу независимо от порядка доставки; open/closed одного просмотра склеиваются по PropertyObjectViewId (argMax по IngestionTime), не по порядку |
| Freshness (окно eventual-consistency) | ≤ 5 мин от события до видимости в API (batch-вставка ≤ 1000 строк / 5 c). В этом окне метрики могут отставать; чтение всегда консистентно к тому, что уже вставлено |
| Checkpoint | Kafka offset, commit строго после подтверждённой batch-вставки (сбой между вставкой и commit → повторная доставка → дубль схлопывается) |
| ContractVersion | при несовпадении версии — warn-and-proceed (обрабатываем совместимую часть, лог-предупреждение), не остановка партиции |
Что dev-stats ожидает от продюсеров (входные контракты)
Ключ партиционирования и обязательные поля — на стороне продюсера; dev-stats — только потребитель. При нарушении обязательных полезных полей строка идёт в poison-DLQ (см. consumer-доки), партиция продолжается.
| Семейство топиков | Продюсер | Обязательные поля, на которые опирается dev-stats | Идемпотентный ключ | Партиционирование (ожидание) |
|---|---|---|---|---|
pin.user-activity.property-object-view-ended.v1 | user-activity | PropertyObjectViewId, PropertyObjectId, SessionId, AnonymousId, UserId?, ViewSource, ViewedAt, LastActivityAt, DurationSeconds (≥0) | SourceEventId (= FactId) | по PropertyObjectViewId/SessionId (локальность склейки) |
pin.user-activity.action-tracked.v1 | user-activity | ActionType, PropertyObjectViewId, PropertyObjectId, ViewedAt (для ViewPropertyObject) | SourceEventId | — |
pin.user-activity.session-ended.v1 | user-activity | SessionId, UserId?, AnonymousId, StartedAt, LastActivityAt, DurationSeconds | SourceEventId | по SessionId |
pin.chessboard.*-upserted.v1 | chessboard | Id уровня + вышестоящие id пути + UpsertedAt (версия) | Id (last-wins по UpsertedAt) | по Id узла |
pin.favorites.favorite-added.v1 | favorites | FavoriteId, PropertyObjectId, OccurredAt | FavoriteId | по PropertyObjectId |
pin.selections.selection-item-added.v1 | selections | SelectionItemId, PropertyObjectId, OccurredAt | SelectionItemId | по PropertyObjectId |
pin.crm.lead-created.v1 | crm | LeadId, PropertyObjectId, OccurredAt | LeadId | по PropertyObjectId |
pin.crm.booking-created.v1 / booking-paid.v1 | crm | BookingId, PropertyObjectId, OccurredAt | BookingId | по PropertyObjectId |
pin.crm.deal-created.v1 / deal-completed.v1 | crm | DealId, PropertyObjectId, OccurredAt | DealId | по PropertyObjectId |
Событие несёт PropertyObjectId, но не иерархию — dev-stats обязан обогатить факт/этап воронки путём
DeveloperId → … → LayoutId из реплики dim_property_object перед вставкой (JOIN-free аналитика).
Поведение при отсутствии/устаревании входных данных
| Ситуация | Поведение dev-stats | Наблюдаемый эффект |
|---|---|---|
Реплика dim_property_object/dim_* ещё не пришла (late-dimension) | сообщение паркуется (deferred re-enrich с backoff, ≤ N попыток); при устойчивом промахе → DLQ + алерт «dimension gap» | факт не теряется, но появляется с задержкой обогащения |
dim_* устарела (старая версия) | обогащение по последней известной версии измерения (argMax по UpsertedAt) | подпись/иерархия может быть слегка устаревшей до догоняющего upsert |
Нижний этап воронки лагает (crm догоняет очередь) | верхние этапы считаются независимо (частичные состояния сливаются по ключу) | воронка показывает верхние этапы полностью, нижний — как «предварительно» |
Unknown wire-значение ViewSource в payload | → Unknown + warning (не DLQ) | просмотр учтён, источник помечен «не определён» |
Структурно битый/невалидный payload (нет обязательного id, DurationSeconds < 0, ViewedAt в будущем) | poison → DLQ + error-лог, партиция продолжается | одна строка в карантине; остальной поток не блокируется |
Версионирование потребляемых контрактов
- Additive (новое опциональное поле продюсера, напр.
ViewerRoleв просмотре) — bumpContractVersionне требуется; dev-stats начинает использовать поле по мере появления (см. viewer-classification.md, Assumption v1). - Breaking (удаление/переименование/смена типа обязательного поля, смена ключа партиционирования) — согласуется с продюсером и требует скоординированного rollout консюмера.
- Несовпадение
ContractVersionпри доставке — warn-and-proceed (совместимая часть обрабатывается), поэтому additive-изменения продюсера не роняют ingestion.
Связанные документы
- Консюмеры: ../consumers/; факты/rollup: ../facts/.
- Первичная загрузка: ../background/dimension-backfill.md, ../background/facts-backfill.md.
- ADR: ADR-0057, ADR-0058 (переименование топиков), ADR-0056.