0029. Высокочастотный сбор заказов/продаж РНП поверх оцифровки
Копия ADR из репозитория:
backend/docs/adr/0029-rnp-orders-sales-sync.md. Канонический текст — в репозитории; портал обновляется командойnpm run sync:adr.
- Статус: accepted
- Дата: 2026-06-29
- Связанные задачи: OP-79 (5.6 · РНП), переиспользует OP-54 (оцифровка)
Контекст и проблема
ТЗ 5.6: заказы и продажи собираются ежеминутно для оперативной аналитики РНП.
Оцифровка (OP-54) уже импортирует те же фиды WB (/api/v1/supplier/orders,
/api/v1/supplier/sales) в wb_orders/wb_sales идемпотентным upsert, и для неё
есть ежедневный планировщик (OP-58, cron 03:00). Вопросы:
- Заводить ли отдельные сущности РНП для заказов/продаж?
- Как организовать ежеминутный сбор?
- Как обеспечить «инкрементальность без дублей» и лимиты WB?
Решение
- Переиспользуем оцифровку, не дублируя сущности: РНП-сбор вызывает
WbImportService.importForAccountSystem(wbAccountId, dateFrom). Заказы/продажи остаются вwb_orders/wb_sales(поnmId, готовы к объединению по артикулу — OP-82). Так формулируют и соседние тикеты (OP-80/81 — «переиспользовать»). - Отдельный модуль
rnpс интервальным расписанием (OP-42, 1 мин) → фан-аут-джоба → джоба импорта на кабинет (зеркало OP-58/OP-70). Очередь и имена расписания изолированы от оцифровки. - Дедупликация — идемпотентный upsert импорта (по
saleId/srid); повтор за пересекающееся окно безопасен. Окно —lookback = 1 день(подхват свежих записей и правокlastChangeDate, которые WB вносит задним числом).
Последствия и known limitations (follow-up)
- Окно vs курсор. Текущая реализация на каждом тике запрашивает суточное окно
(digitization
flag=0, постранично), а не «дельту с последнего курсора». Это даёт корректные данные (идемпотентность убирает дубли), но при ежеминутном опросе создаёт повторную нагрузку на WB. Follow-up: курсорный инкремент (хранитьlastChangeDate-курсор на кабинет и запрашивать только новее) — когда понадобится снизить нагрузку. - Лимиты WB. Соблюдение лимитов делегировано WB-клиенту/лимитеру (OP-34).
Эндпоинты статистики WB лимитированы жёстко (~1 запрос/мин); при росте числа
кабинетов и объёмов потребуется per-endpoint лимит для
statistics:/supplier/*и согласование с ежедневной оцифровкой за общую корзину кабинета. Follow-up в рамках OP-34/нагрузочного тюнинга, вне scope OP-79. - Anti-overlap. Наложение прогонов отсекается детерминированным
jobId(расписание — по окну; фан-аут — поaccount:minute-slot). Импорт кабинета, длящийся дольше минуты, теоретически может пересечься со следующим тиком; данные при этом не портятся (идемпотентный upsert), цена — лишний расход лимита. - Партиционирование fact-таблиц (ADR-0008, месячные партиции) — отдельный тикет OP-91; здесь не вводится.