Фоновые задачи
Все тяжёлые и периодические операции — синхронизация с Wildberries, пересчёт витрин, мониторинг слотов, сбор метрик — выполняются в фоне на BullMQ (поверх Redis) внутри того же бэкенд-процесса, что и HTTP API (Бэкенд). Решения — ADR-0007, ADR-0009, ADR-0040, ADR-0042, ADR-0057.
Инфраструктура
| Элемент | Решение |
|---|---|
| Движок | @nestjs/bullmq; воркеры — провайдеры с @Processor(queue), очереди — BullModule.registerQueue в фичевых модулях |
| Соединения | BullMQ создаёт собственные ioredis-соединения (maxRetriesPerRequest: null); общий REDIS_CLIENT остаётся за кэшем и лимитерами |
| Изоляция сред | префикс ключей QUEUE_PREFIX (optima на prod, optima-stage на stage) |
| Дефолты джоб | attempts: 5, exponential backoff от 1 с, успешные хранятся 24 ч / 1000 шт., неудачные не удаляются (removeOnFail: false); очереди плановых синков задают свои 3 попытки |
| Идентификаторы | composeJobId(...parts) склеивает части через -- (BullMQ v5 запрещает : в custom jobId) |
| Контекст | каждая джоба исполняется в своём correlation-контексте — логи трассируются (Наблюдаемость) |
Реестр расписаний
Периодические расписания не используют @Cron/@Interval: каждый сервис регистрирует своё в
onModuleInit через ScheduleRegistryService.register({ name, trigger, queue, jobName, payloadFactory }).
Триггер — interval (мс) или cron (с таймзоной, почти везде Europe/Moscow).
Anti-overlap — на двух уровнях: оркестратор получает детерминированный jobId по «намеренному»
времени тика (в кластере одно окно — один прогон), а джоба кабинета — jobId с номером окна
каденции; повторная постановка того же ключа отбрасывается BullMQ. Повторы безопасны: импорты —
идемпотентный upsert.
Очереди и расписания
Время — московское (Europe/Moscow), если не указано иное.
Синхронизация данных WB
| Очередь | Расписание | Что делает |
|---|---|---|
digitization-sync | инкремент — interval 30 мин (DIGITIZATION_INCREMENT_INTERVAL_MS); бэкфилл — cron 0 3 * * * | заказы, продажи, возвраты (wb_orders/wb_sales); пользовательские импорты фидов и отчёта идут в ту же очередь |
rnp-finance-sync | cron 0 5 * * * | отчёт о реализации за 14 дней (wb_report_lines) |
rnp-ads-sync | interval 5 мин | реклама: статистика кампаний (ad_campaign_stats) |
rnp-sections-sync | interval 1 ч | воронка и конверсии (rnp_funnel_stats) |
catalog-sync | карточки — cron 0 4 * * *; цены — interval 30 мин; полный синк кабинета — по требованию | каталог и цены |
inventory-sync | cron 0 * * * * (каждый час) | остатки |
autobidder-sync | по требованию (POST /autobidder/sync → 202 + jobId) | рекламные данные кабинета; отдельная очередь, чтобы пользовательский запуск не ждал бэклог крона |
kb-wb-news-sync | cron 15 */3 * * * | новости WB в базу знаний (метод домена common, лимит WB — 1 запрос в минуту, берётся токен любого активного кабинета) |
Пересчёт витрин
| Очередь | Расписание | Что делает |
|---|---|---|
re-aggregate | по событию (приоритет 10 — ниже ingest; 3 попытки) | пересчёт дневных агрегатов (domain, accountId, day) |
rnp-merge-sync | cron 0 6 * * * | ночной пересчёт окна 3 дня и самовосстановление пропусков за 90 дней |
Движки и прикладные задачи
| Очередь | Расписание | Что делает |
|---|---|---|
autobidder-bid-engine | interval 60 с на кабинет, concurrency 1 | прогон движка ставок |
repricer-schedule | interval N часов на пару (кабинет, интервал правила); регистрируется на старте | прогон правил репрайсера |
booking-slot-monitor | interval 12 с | мониторинг слотов приёмки для заданий бронировщика |
pricing-bulk | по требованию | массовое изменение цен чанками через WB upload/task |
autoreply-sync | каждые 15 мин | загрузка новых отзывов/вопросов, сверка отвеченных, автогенерация ответов и жалоб по авто-сценариям/правилам |
autoreply-answer-send | по требованию | отправка ответа на отзыв/вопрос в WB (ретраи BullMQ) |
ab-test-metrics | cron 25 */3 * * * | сбор метрик A/B-тестов после синхронизации рекламы |
partition-manager | cron 0 3 * * 1 (UTC), еженедельно | создание партиций на 3 месяца вперёд и удаление по retention |
example-jobs | — | шаблон-образец для новых воркеров |
Значения заданы константами *.constants.ts в соответствующих модулях; каденции синков WB подобраны
под реальное обновление данных на стороне Wildberries (фиды заказов и продаж — раз в 30 минут,
отчёт о реализации — еженедельно).
Конкурентность и gate кабинета
- Concurrency. Шесть sync-очередей (
digitization-sync,rnp-ads-sync,rnp-finance-sync,inventory-sync,catalog-sync,autobidder-sync) обрабатываютSYNC_QUEUE_CONCURRENCYкабинетов параллельно (по умолчанию 5): лимиты WB — на кабинет, поэтому параллель между кабинетами легальна. Значение читается из окружения процесса при загрузке декоратора@Processor; в dev с.envберётся дефолт. Остальные очереди работают по одной джобе (concurrency = 1). - Gate кабинета (
WbAccountJobGate, ADR-0057). Ключwb:gate:{accountId}, владелец<очередь>:<jobId>, TTL 90 с с heartbeat раз в 30 с (падение процесса не оставляет вечный замок). К «общим» бакетам WB одновременно ходит одна джоба кабинета. Если gate занят — джоба уходит вdelayedна 15 с, попытка не расходуется. - Приоритеты. Пользовательские джобы (
import-user-feeds,import-user-report,autobidder-sync-user) обгоняют плановые: плановый владелец отдаёт gate на ближайшей паузе лимита WB, а плановые джобы не копят хвост — ждут не дольше окна каденции (реклама — 5 мин, инкремент — свой интервал, бэкфилл — 3 ч, финансы — 6 ч), затем завершаютсяskipped: account-busy. - Перенос по лимиту WB (ADR-0051). Если ожидание лимитера дольше 5 с, джоба не спит в воркере,
а переносится (
delayed) на нужную паузу — слот воркера освобождается для других кабинетов. Применяется в процессорах длинных синков. Подробнее — Интеграция с Wildberries. - Фан-аут пропускает кабинеты не в статусе «Активен» и кабинеты с живым пользовательским импортом.
Конвейер пересчёта витрины РНП
Витрина rnp_daily собирается только джобами очереди re-aggregate
(домены rnp-sales, rnp-ads, rnp-finance, каскадом — merge rnp). Какие дни пересчитать,
решает RnpRecomputeService (modules/rnp/rnp-recompute.service.ts):
| Источник | Что ставит | Дни |
|---|---|---|
| Плановый синк фидов оцифровки | rnp-sales | МСК-дни записанных заказов и продаж |
| Пользовательский импорт фидов | rnp-sales | весь период [dateFrom, сегодня] + задетые дни |
Импорт отчёта / rnp-finance-sync | rnp-finance | дни исходных заказов строк отчёта (srid → wb_orders.date, иначе rr_dt) |
rnp-merge-sync | все роллаперы | окно 3 дня + самовосстановление: дни за 90 дней, где есть заказы, а строк витрины нет |
Сбой постановки пересчёта не роняет импорт (данные уже в БД) — пропуск подберёт ночной merge-sync.
Дни вне существующих партиций rnp_daily отбрасываются. Благодаря этому у нового кабинета РНП
наполняется сразу после успешного импорта, а не через сутки. Модель данных — Данные,
правила объединения — ADR-0030; подробности расписаний — backend/docs/rnp-schedules.md.
Метрики и мониторинг
Метрики очередей (optima_queue_jobs, optima_queue_job_duration_ms), прогонов РНП и пересчётов
(optima_reaggregate_*, optima_rnp_sync_*, optima_digitization_feed_sync_*) отдаёт /metrics;
состояние очередей — Bull Board (/admin/queues). См. Наблюдаемость.