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

Talent Price Sync: сведён к consumer-у chessboard-price-changed (решение, не отдельный consumer)

Документ-решение о канале поступления цен из Talent Chessboard в invest-analytics. Отдельный consumer «синка цен из Talent» не создаётся: постоянный поток цен уже идёт через событие pin.chessboard.property-object-price-changed.v1, и его обрабатывает chessboard-price-changed.md; первичная (разовая) загрузка накопленных цен — background job, а не consumer. Ниже — обоснование и разграничение двух путей.

Metadata

ПолеЗначение
Статусdraft (решение зафиксировано; job первичной загрузки — будущий док)
Owning teamBackend PIN (инвест-аналитика)
Область действияКанал поступления цен Talent Chessboard → pin_analytics.property_object_price_history_facts
ОбоснованиеУточняет требование «цены поступают из Talent Chessboard при обновлении объекта» с учётом границ контекстов (service-boundaries.md: Talent Chessboard → chessboardinvest-analytics)

Решение

ПутьМеханизмДокумент
Постоянный поток изменений ценConsumer chessboard-price-changed на pin.chessboard.property-object-price-changed.v1 (ChangeSource = talent-sync)chessboard-price-changed.md
Первичная загрузка при вводе сервисаРазовый background job price-history-backfill (ChangeSource = backfill, OldPrice = NULL), чтение текущих цен объектов недвижимости из Talent Chessboard API, checkpoint по диапазону PropertyObjectId в PG../jobs/price-history-backfill.md (будущий док)

Прямого подключения invest-analytics к Talent Chessboard (API-поллинг, CDC, отдельный топик Talent) в постоянном режиме нет.

Обоснование сведения к одному consumer-у

  1. Границы контекстов. Интеграция с Talent Chessboard — ответственность контекста chessboard (docs/00-overview/service-boundaries.md: «Объекты/цены Talent → Talent Chessboard → chessboard, invest-analytics»). chessboard синхронизирует объекты недвижимости с Talent и на каждое изменение Price — в том числе пришедшее синком из Talent — надёжно публикует событие pin.chessboard.property-object-price-changed.v1 (docs/06-services/chessboard/domain/property-object.md). Требование «цены получаем из Talent Chessboard при обновлении объекта» выполняется транзитивно, без второго канала.
  2. Один источник истины на запись. Два независимых пути (событие chessboard + прямой синк Talent) писали бы одно изменение цены дважды с разными SourceEventId — dedup ReplacingMergeTree по FactId = SourceEventId такие «дубли по смыслу» не схлопнул бы. Один пишущий поток исключает класс ошибок целиком.
  3. Simplicity-first (ADR-0056). Отдельный интеграционный клиент Talent в invest-analytics — это второй HTTP/CDC-контракт, второй маппинг, второй мониторинг и второй runbook без измеренной необходимости: лаг событийного пути укладывается в freshness SLA факта ≤ 5 минут.
  4. Консистентность измерений. Событие chessboard несёт снапшот измерений (DeveloperId/ProjectId/ BuildingId/LayoutGroupId/LayoutId/AreaTotal — зависимость к ТЗ chessboard, см. chessboard-price-changed.md); прямой синк из Talent потребовал бы собственного разрешения измерений (реплика справочников либо синхронные запросы) — отвергнуто решением №5 в ../README.md.

Почему первичная загрузка — job, а не consumer

  • Топик pin.chessboard.property-object-price-changed.v1 на момент ввода invest-analytics не содержит истории «до начала публикации»: стартовые цены существующих объектов недвижимости взять из потока неоткуда — нужна разовая выгрузка (Talent Chessboard API, см. ../domain/price-history-fact.md, «Consumers И Backfill»).
  • Это конечная, перезапускаемая задача с checkpoint-ом по диапазону PropertyObjectId (таблица состояния job-а в PostgreSQL), а не бесконечный поток — по классификации это background-job, не consumer.
  • Разграничение с потоком: ChangeSource = backfill, OldPrice = NULL, SourceEventId = backfill:{PropertyObjectId}:{ChangedAt:O} — коллизии с Guid EventId события невозможны; повторный прогон backfill идемпотентен по тому же FactId-dedup.

Диаграмма каналов

Гарантии и проверяемость решения

Решение держится на четырёх проверяемых инвариантах — их нарушение = сигнал к пересмотру:

  • Единственный писатель ленты. В ленту pin_analytics.property_object_price_history_facts пишут только два процесса одного контекста — потоковый consumer и разовый backfill-job; прямой записи из API, других сервисов или Talent нет. Проверка: источник строки читается по колонке ChangeSource ∈ {talent-sync, backfill}, иных значений быть не должно.
  • Непересекающиеся namespace-ы ключа дедупликации. У потока SourceEventId = EventId события (Guid), у backfill — backfill:{PropertyObjectId}:{ChangedAt:O}; пространства не пересекаются, поэтому FactId-dedup ReplacingMergeTree корректно схлопывает повторы внутри канала и не «слепляет» строки разных каналов.
  • Одно изменение цены — один FactId. Поскольку изменение приходит ровно одним путём (событие chessboard), одно бизнес-изменение цены не может получить два разных SourceEventId — класс «дублей по смыслу», которые dedup не схлопнул бы, исключён конструктивно.
  • Freshness событийного пути. Лаг «изменение в Talent → видимость в API» укладывается в SLA факта ≤ 5 мин (батч consumer ≤ 1000 строк / 5 с). Систематическое нарушение — триггер условия (б) пересмотра.

Пересмотр решения

Отдельный канал синка цен из Talent появится только если: (а) chessboard перестанет быть транзитом цен (изменение границ — через ADR), либо (б) измеренный лаг событийного пути систематически нарушает freshness SLA ≤ 5 минут и не лечится настройкой consumer-а. До этого любой запрос «добавить прямой синк» отклоняется со ссылкой на этот документ.

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

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

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