0042. Единый ярусный пайплайн синка фидов оцифровки
Копия ADR из репозитория:
backend/docs/adr/0042-unified-feed-sync-pipeline.md. Канонический текст — в репозитории; портал обновляется командойnpm run sync:adr.
- Статус: accepted
- Дата: 2026-07-28
- Связанные задачи: OP-217 (5.1 · Оцифровка × 5.6 · РНП)
- Дополняет: ADR-0016 (импорт оцифровки), ADR-0029 (высокочастотный синк РНП)
Контекст и проблема
Один и тот же импорт фидов orders/sales (WbImportService, общий курсор
wb_sync_cursors) гоняли два планировщика с разными окнами:
daily-digitization-sync(cron 03:00 МСК, lookback 7д, модульdigitization);rnp-orders-sales-sync(interval 30 мин, lookback 1д, модульrnp, ADR-0029).
Два планировщика в разных модулях дублировали проходы по одним данным и
конкурировали за 1/мин-бакеты кабинета. При этом РНП не имеет своих
сущностей заказов/продаж — читает wb_orders/wb_sales напрямую, а дневные
роллапы пересчитывает независимый rnp-merge-sync. Значит собственное расписание
синка фидов у РНП избыточно: свежесть можно обеспечить одним пайплайном в
оцифровке.
Решение
Свести оба планировщика в единый ярусный пайплайн модуля digitization
(очередь digitization-sync), два яруса как режимы одного расписания —
дискриминант tier в payload джобы-оркестратора:
increment— interval (дефолт 30 мин, envDIGITIZATION_INCREMENT_INTERVAL_MS, back-compat aliasRNP_SYNC_INTERVAL_MS), lookback 1д, курсорный вход (importForAccountScheduled, K страниц/прогон, двигает forward-only курсор) — внутридневная свежесть. Заменяетrnp-sync.backfill— cron 03:00 МСК, lookback 7д, пере-скан без курсора (importForAccountSystemбезresumeCursor/onFeedCursor, полный дренаж окна) — верификация полноты поверх инкремента; идемпотентный upsert заполняет пробелы капнутого инкремента, общий курсор не трогается. Заменяет ночной прогон digitization.
Расписание rnp-sync (сервис, процессор, очередь, константы) выпилено. РНП
продолжает читать wb_orders/wb_sales.
Anti-overlap (ровно один плановый поток фидов по кабинету)
- Оркестратор — детерминированный
jobIdреестра (ADR-0009); ярусы — разные имена расписаний → независимы. - Внутри яруса —
jobId = sync-account--{acc}--{tier}--{slot}(slot: инкремент — окно каденции; бэкфилл — день). - Между ярусами — фан-аут пропускает кабинет с уже «живой» плановой
sync-account-джобой любого яруса (activeScheduledFeedAccountIds) — снимает параллельный двойной проход при совпадении окон (03:00). - Уступка user-импорту (OP-214) сохранена в обоих ярусах.
Плановые джобы получают явные ретраи per-job (attempts=3, exp backoff) — сохраняем
устойчивость выпиленной очереди rnp-sync, не меняя семантику user-джоб той же очереди.
Отменено в части user-джоб (OP-220, ADR-0048). Унаследованный из
DEFAULT_JOB_OPTIONSbackoff 1/2/4/8 c оказался секундной лестницей против минутных окон WB: упавшая по 429 джоба возвращалась в тот же ещё закрытый бакет. Теперьimport-user-feedsиimport-user-reportтоже имеют явную per-job политику (attempts: 5, exp backoff от 60 c). Политика плановыхsync-accountне изменилась.
Рассмотренные альтернативы
- Две отдельные очереди/модуля (as-is). Отвергнуто: дублирование проходов и конкуренция за бакеты — исходная проблема OP-217.
- Один ярус (только инкремент), без ночного бэкфилла. Отвергнуто: капнутый инкремент (K страниц/прогон) при большой дельте теоретически оставляет пробелы; ночной пере-скан за 7д — дешёвая (раз в сутки, низкий приоритет) страховка полноты.
- Бэкфилл курсорным входом (как инкремент, но шире окно). Отвергнуто: forward-only курсор уже сдвинут инкрементом → «верификация» ничего бы не перепроверяла. Пере-скан без курсора — единственный способ реально закрыть пробелы.
Последствия
- Положительные: по кабинету в норме один плановый поток фидов; суммарный расход
statistics-запросов не выше «инкремент + ночной бэкфилл»; РНП и оцифровка питаются
одними таблицами без деградации свежести (30 мин); модуль РНП больше не владеет
расписанием фидов (чище границы). Метрики синка orders/sales переехали в
optima_digitization_feed_sync_*{tier}. - Отступления/цена: ночной бэкфилл пере-сканирует 7-дневное окно без курсора —
дороже курсорного прогона, но раз в сутки, низкий приоритет и уступает user-импорту и
инкременту; в рамках бюджета AC. Метка
orders-salesуходит из набораoptima_rnp_sync_*(дашборды/алерты по ней — перенести на новую метрику). - Superseded: решение ADR-0029 «отдельный модуль
rnpс собственным интервальным расписанием заказов/продаж» заменено этим ADR; переиспользование оцифровки и хранение вwb_orders/wb_sales(ADR-0016/0029) сохраняются.
Ссылки
Ресёрч бэкенд↔WB API 2026-07-24 (memory wb-api-research-2026-07); ADR-0009 (anti-overlap),
ADR-0016 (импорт), ADR-0029 (синк РНП), OP-212 (каденция), OP-214 (приоритет user-импорта),
OP-215 (персистентный курсор), OP-216 (concurrency). Доки: docs/digitization-feed-sync.md,
docs/rnp-schedules.md.