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

Consumer: Handbook Sync (pin-chessboard-handbook-sync)

Консолидированный документ Kafka-консюмера локальной реплики справочников в контексте chessboard. Один consumer group обрабатывает четыре compacted-топика pin.handbook.*.v1 (страны, регионы, города, валюты); топик языков не потребляется — у chessboard нет ссылок на языки. Поля реплики и правила её ведения HR-1..HR-7 — дом handbook-replica.md; контракты входных событий — дома handbook (country-upserted, region-upserted, city-upserted, currency-upserted) — здесь не переописываются. Retry/DLQ/replay-политика группы описана здесь единожды на все потоки.

Назначение

Consumer group pin-chessboard-handbook-sync поддерживает в chessboard локальную реплику справочников chessboard.handbook_* — единственную локальную реплику справочников в PIN (ADR-0063 п.6): валидация ссылок CountryId/RegionId/CityId/CurrencyId на write-path проектов и цен выполняется без сетевого вызова в handbook. Вторая обязанность — refresh денормализованных имён RegionName/CityName проектов при переименованиях в справочнике с переопубликацией project-upserted затронутых проектов (HR-6), чтобы проекции catalog догнали новое имя. На топики Talent chessboard не подписывается (ADR-0063 п.1).

Owning-сервис и триггер

ПолеЗначение
Сервис/контекстchessboard
Consumer grouppin-chessboard-handbook-sync
Producerконтекст handbook (talent-handbook-sync, admin archive/restore, пересинхронизация)
Топик 1pin.handbook.countries.v1 — полный snapshot страны
Топик 2pin.handbook.regions.v1 — полный snapshot региона
Топик 3pin.handbook.cities.v1 — полный snapshot города (вложенные снапшоты Country/Region не сохраняются)
Топик 4pin.handbook.currencies.v1 — полный snapshot валюты
ContractVersionожидается 1 для всех событий; при несовпадении — warn-and-proceed + метрика
Порядокkey = Id записи → строгий порядок per-record; порядок между топиками не гарантирован — компенсируется логическими ссылками без FK (HR-4)
DLQpin.chessboard.handbook-sync-dlq.v1

Маппинг «событие → эффект» (бизнес-термины):

ПотокСобытие (топик)Эффект
Acountry-upsertedUpsert HandbookCountryReplica
Bregion-upsertedUpsert HandbookRegionReplica; при смене Name — refresh RegionName проектов + re-publish project-upserted (HR-6)
Ccity-upsertedUpsert HandbookCityReplica; при смене Name — refresh CityName проектов + re-publish project-upserted (HR-6)
Dcurrency-upsertedUpsert HandbookCurrencyReplica
language-upserted (pin.handbook.languages.v1)Не потребляется — ссылок на языки в chessboard нет

Контекст события

  • Из метаданных события доступны: MessageId (идентификатор для дедупликации), OccurredAt, идентификатор записи справочника (key). Актор — система синка (не пользовательский контекст); реплика платформенная, tenant-фильтр не применяется, PII нет.
  • Ключ идемпотентности: chessboard-handbook-sync:{eventType}:{MessageId}, eventType ∈ {country, region, city, currency}; бизнес-идемпотентность — version-guard HR-2 (payload.ReplicaVersion > хранимой версии; регион — payload.LastEventAt > хранимого LastEventAt).
  • Корреляция сквозного трейса восстанавливается из метаданных события; при refresh HR-6 пробрасывается в переопубликованные project-upserted.

Входные данные

Payload каждого события — полный snapshot записи (контракты — дома handbook, ссылки выше). Chessboard читает только поля, хранимые репликой (таблицы полей — handbook-replica.md): ShortName/DefaultCurrencyId/DefaultLanguageId страны; вложенные снапшоты Country/Region внутри payload city-события игнорируются — актуальные имена родительской географии ведутся собственными строками реплики по их топикам.

Алгоритм

Все четыре потока симметричны; дельты — time-based guard региона (HR-2) и refresh имён (HR-6, только регионы/города).

  1. Контекст события. Метаданные (MessageId, key); сверка ContractVersion = 1 — при несовпадении warn-and-proceed + метрика; идемпотентность по ключу — дубль → лог skipped-duplicate, ack.
  2. Version-guard (HR-2). Запись есть и версия payload не больше хранимой (ReplicaVersion; регион — LastEventAt) → skip + метрика stale, ack. Компакция топика и пересинхронизация handbook дают «скачки» версии — каждое событие самодостаточно.
  3. Upsert (HR-3, HR-4, HR-5). Создание/перезапись всех полей записи реплики полным snapshot-ом (отсутствие optional-поля — «значение очищено»); ссылки CountryId/RegionId — логические, без проверки существования (нерезолвимость допустима, полнота гео-цепочки проверяется на write-path проектов); IsArchived применяется значением payload — физических удалений нет; SyncedAt = now.
  4. Refresh имён (HR-6, потоки B/C). Если применённый upsert изменил Name относительно хранимого: у всех проектов с этим RegionId/CityId, чьё RegionName/CityName отличается от нового имени, — поле обновляется, версия проекта растёт, для каждого затронутого проекта переопубликуется project-upserted (полный snapshot). Условие «отличается» делает refresh идемпотентным. Archive/restore без смены имени и события стран/валют refresh не триггерят.
  5. Согласованность. Запись реплики + отметка идемпотентности + refresh затронутых проектов + их события — одно согласованное изменение; offset подтверждается только после фиксации.
  6. Результат. Успех → ack; transient-сбой → retry по политике (таблица ниже); неустранимый дефект payload → DLQ + алерт.

Диаграмма (поток C — city с refresh имени)

Идемпотентность и replay

  • По событию: дедупликация по MessageId — повторная доставка after-commit-crash не меняет состояние.
  • По бизнес-правилу: version-guard HR-2 — устаревшее событие пропускается; refresh HR-6 идемпотентен (обновляются только проекты с отличающимся именем).
  • Replay: да, безопасен. Bootstrap и восстановление — чтение всех четырёх compacted-топиков с offset 0 (HR-7); re-emit при пересинхронизации handbook — идемпотентный no-op, кроме записей с фактическими изменениями. Точечный replay DLQ — после разбора дефекта с командой handbook.

Retry и DLQ

DLQ-топик группы: pin.chessboard.handbook-sync-dlq.v1 (исходное событие + заголовки x-dlq-reason, x-dlq-error-code, x-source-topic, x-original-offset).

СбойRetryDLQРучное действие
Инфраструктурный (PostgreSQL/шина недоступны, timeout)Бесконечный exponential backoff 1s→60s с паузой партиции (событие не теряется)НетВосстановить зависимость
Гонка refresh HR-6 с конкурентной правкой проекта (оптимистическая блокировка)3 попытки немедленно с перечитыванием проекта, далее backoffПосле исчерпания + алертРасследование гонки; replay из DLQ
Дефект payload (контрактное нарушение: пустое Name, неположительный Id)Нет (non-retriable)Да + алертРазбор с командой handbook (дефект producer-а) → replay из DLQ
Несовпадение ContractVersion— (warn-and-proceed, не сбой)НетСигнал смены контракта — согласовать миграцию на .v2
Гео-согласованность вне зоны ответственности консюмера

Гео-согласованность на консюмере не проверяется (HR-4): рассогласование цепочки — забота write-path проектов (CHESSBOARD_CITY_REGION_MISMATCH и др. — handbook-replica.md), а дефекты данных источника отсекаются синком handbook до публикации в pin.handbook.*.

Наблюдаемость и безопасность

  • Logs (structured, по событию): consumed / skipped-duplicate / skipped-stale / applied / name-refreshed (с числом затронутых проектов) / dead-lettered с полями MessageId, тип записи, Id, версия payload. PII нет.
  • Metrics: chessboard_handbook_sync_lag_seconds (по типу записи), chessboard_handbook_sync_stale_events_total, chessboard_handbook_sync_dlq_total, chessboard_handbook_sync_contract_mismatch_total, chessboard_handbook_name_refresh_projects_total.
  • Traces: consume-span → span применения (+ span refresh HR-6); корреляция пробрасывается в переопубликованные project-upserted.
  • Alerts: DLQ > 0; лаг применения > 5 мин при ненулевом потоке (SLA применения < 1 мин); contract_mismatch > 0.
  • Безопасность: consumer работает под system-actor; реплика не tenanted; изменения проектов при refresh штампуются system-actor-ом консюмера.

Пред/постусловия

  • До обработки события. Реплика может не содержать целевую запись (upsert); родительские записи географии могут ещё не приехать (HR-4 — не блокирует). Порядок per-record гарантирован ключом топика.
  • После обработки применённого события. Запись реплики отражает последний snapshot handbook (ReplicaVersion/LastEventAt, IsArchived, SyncedAt обновлены); при смене имени региона/города — денормализованные имена всех затронутых проектов обновлены и по каждому переопубликован project-upserted; offset ack — только после фиксации. Устаревшее/дублирующее событие — состояние не изменено, ack.

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

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

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