Как мы строим интеграции: очереди, retry, мониторинг
Кейс

Как мы строим интеграции: очереди, retry, мониторинг

Потерянный заказ стоит дороже медленного. Упавшая синхронизация остатков в три часа ночи — это утренний звонок от клиента, которому продали несуществующий товар. Сбой без алерта — это день, два, неделя тихого накопления расхождений, пока кто-то не заметит вручную.

Мы строим интеграции между бизнес-системами: маркетплейсы, склады, CRM, таблицы, мессенджеры. За время работы выработали набор архитектурных принципов, которые повторяем из проекта в проект. Эта статья — не маркетинговый обзор, а технический разбор того, как именно устроены наши интеграции изнутри. Для тех, кто оценивает подрядчика не по лендингу, а по архитектуре.

Принцип 1: Очереди везде

Каждая наша интеграция использует очередь задач. Без исключений. Даже если кажется, что «тут всего десять заказов в день и можно обработать синхронно» — нет, нельзя. Потому что синхронная обработка означает: если API на той стороне ответил таймаутом, вся цепочка встала. А если ответил ошибкой — непонятно, выполнилась операция или нет.

Очередь решает три задачи:

  1. Последовательность. Заказы обрабатываются строго по одному. Не бывает ситуации, когда два процесса одновременно пытаются списать один и тот же товар.
  2. Персистентность. Если сервис упал и перезапустился — необработанные задачи остались в очереди. Ничего не потеряно.
  3. Наблюдаемость. В любой момент можно посмотреть: сколько задач в очереди, сколько обработано, сколько упало. Это не логи, которые нужно парсить — это состояние системы.

Node.js: BullMQ + Redis

Для серверных интеграций на Node.js мы используем BullMQ — библиотеку очередей поверх Redis. Redis обеспечивает персистентность: данные переживают перезапуск процесса. BullMQ даёт приоритеты, отложенные задачи, повторные попытки и dead letter queue из коробки.

Типичная конфигурация:

const queue = new Queue('orders', {
  connection: { host: 'localhost', port: 6379 },
  defaultJobOptions: {
    attempts: 5,
    backoff: { type: 'exponential', delay: 3000 },
    removeOnComplete: { age: 7 * 24 * 3600 },
    removeOnFail: false, // failed jobs остаются для разбора
  },
});

Обратите внимание: removeOnFail: false. Упавшие задачи не удаляются. Они остаются в dead letter queue, пока кто-то не разберётся, что произошло. Это принципиально: автоматизация, которая молча теряет данные, хуже ручной работы.

Google Apps Script: очередь поверх PropertiesService

В GAS нет ни Redis, ни внешних зависимостей. Мы строим очередь поверх PropertiesService — встроенного хранилища «ключ-значение» с лимитом 9 МБ на скрипт. Это не база данных, но для хранения массива задач с приоритетами и статусами — достаточно.

Структура задачи:

{
  id: "task_20260302_001",
  type: "check_spreadsheet",
  target: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms",
  status: "pending",     // pending | processing | done | failed
  attempts: 0,
  maxAttempts: 3,
  lastError: null,
  createdAt: 1709337600000
}

Очередь сериализуется в JSON и записывается в PropertiesService одним ключом. При каждом запуске скрипт читает очередь, берёт следующую задачу, обрабатывает, обновляет статус и записывает обратно. Примитивно по сравнению с BullMQ — но работает надёжно в рамках ограничений платформы.

Общая архитектура обработки

Независимо от платформы, архитектура одинаковая:

flowchart TD A["Источник событий\n(API polling / webhook / триггер)"] --> B["Очередь задач"] B --> C["Worker:\nвзять задачу из очереди"] C --> D{"Обработка\nуспешна?"} D -->|Да| E["Отметить как done\nЗаписать результат"] D -->|Нет| F{"Попытки\nисчерпаны?"} F -->|Нет| G["Exponential backoff\nВернуть в очередь"] F -->|Да| H["Dead Letter Queue\nАлерт в Telegram"] G --> C E --> I["Следующая задача"] I --> C

Каждая задача проходит один и тот же путь: очередь, обработка, результат или ошибка. Ошибки не глотаются — они либо повторяются, либо попадают в dead letter queue и вызывают алерт. Третьего не дано.

Принцип 2: Retry как система, а не костыль

«Если упало — попробуй ещё раз» — это не стратегия. Это надежда, замаскированная под код. Настоящий retry — это система с чёткими правилами: когда повторять, сколько ждать, когда сдаваться.

Exponential backoff

Первая попытка — через 3 секунды. Вторая — через 9. Третья — через 27. Пятая — через 243 секунды, то есть через 4 минуты. Это exponential backoff: интервал между попытками растёт экспоненциально.

Зачем: если API на той стороне перегружен, забрасывать его запросами каждые 3 секунды — значит усугублять проблему. Нарастающая пауза даёт сервису время восстановиться.

Circuit breaker

Если конкретный endpoint стабильно возвращает ошибки — мы перестаём к нему обращаться на время. Это circuit breaker: после N последовательных ошибок «цепь размыкается», и все запросы к этому endpoint’у автоматически отклоняются на протяжении заданного периода. Через период — одна пробная попытка. Если успешна — цепь замыкается, работа возобновляется.

В нашем проекте мониторинга 700 таблиц circuit breaker критически важен. Если одна таблица вызывает ошибку 500 у Google API, без circuit breaker скрипт будет тратить драгоценные секунды из 6-минутного лимита на бесполезные повторные обращения к ней. С circuit breaker — проблемная таблица откладывается, остальные 699 обрабатываются штатно.

Dead letter queue

Когда все попытки исчерпаны — задача не удаляется и не логируется в файл, который никто не читает. Она уходит в dead letter queue (DLQ). DLQ — это отдельная очередь для задач, которые не удалось обработать автоматически.

Каждая задача в DLQ содержит: исходные данные, историю попыток, последнюю ошибку и время. По задачам в DLQ приходит алерт в Telegram. Разработчик (или клиент, если настроили ему доступ) видит конкретную проблему и может либо исправить причину и перезапустить задачу, либо обработать вручную.

Дерево решений при ошибке

flowchart TD A["Ошибка при обработке"] --> B{"Тип ошибки?"} B -->|"429 / 503 / timeout"| C["Retryable:\nвременная ошибка"] B -->|"400 / 404 / validation"| D["Non-retryable:\nошибка в данных"] B -->|"500 / unknown"| E["Условно retryable:\nмаксимум 3 попытки"] C --> F{"Circuit breaker\nоткрыт?"} F -->|Нет| G["Exponential backoff\nвернуть в очередь"] F -->|Да| H["Ждать cooldown\nperiod"] H --> G G --> I{"Попытки < max?"} I -->|Да| J["Повторить"] I -->|Нет| K["Dead Letter Queue"] D --> K E --> I K --> L["Алерт в Telegram\nс деталями ошибки"]

Ключевой момент: не все ошибки одинаковы. Ошибка 429 (rate limit) — это «подожди и попробуй снова». Ошибка 400 (bad request) — это «данные невалидны, повторять бесполезно». Ошибка 500 — неизвестность, даём три попытки и сдаёмся. Система различает эти случаи и реагирует по-разному.

Принцип 3: Мониторинг мониторинга

Лог, который никто не читает — это шум, а не мониторинг. Мы строим мониторинг по трём уровням.

Уровень 1: Структурированные логи

Каждая операция логируется с контекстом: ID транзакции, тип операции, входные данные, результат, время выполнения. Не console.log("error"), а структурированная запись, по которой можно восстановить полную историю обработки конкретного заказа или задачи.

logger.info({
  transactionId: 'ord_20260302_0847',
  operation: 'create_order',
  source: 'flowwow',
  target: 'moysklad',
  duration: 1240,
  status: 'success',
  details: { orderId: 'FW-78291', moyskladId: 'MS-41023' }
});

Когда клиент спрашивает «а что случилось с заказом FW-78291?» — мы открываем логи по transactionId и видим всю цепочку: получен в 03:47, проверка остатков в 03:47, принят в 03:48, создан в МойСклад в 03:48. Или: получен в 03:47, ошибка 503 при проверке остатков, повтор через 3 секунды, повтор через 9 секунд, успех, далее штатно.

Уровень 2: Алерты в Telegram

Логи — для разбора постфактум. Алерты — для немедленной реакции. Мы отправляем уведомления в Telegram при:

  • Задача попала в dead letter queue
  • Circuit breaker разомкнулся (endpoint недоступен)
  • Очередь растёт быстрее, чем обрабатывается (backpressure)
  • Сервис перезапустился

Формат алерта содержит всё необходимое для первичной диагностики: что случилось, когда, с какими данными, сколько попыток было. Не «ошибка в системе», а конкретика, по которой можно действовать.

Уровень 3: Watchdog

Мониторинг сам может сломаться. Триггер в GAS может молча перестать срабатывать. Процесс на VPS может зависнуть. Если мониторинг умер — кто об этом сообщит?

Watchdog — это отдельный процесс, единственная задача которого — проверять, что основная система работает. В GAS это второй скрипт, который раз в 15 минут проверяет: обновилась ли метка последнего запуска основного скрипта? Если метка устарела — watchdog шлёт алерт в Telegram.

На VPS это может быть отдельный healthcheck-endpoint или systemd watchdog. Идея одна: мониторинг мониторинга — это не перестраховка, это необходимость. Мы убедились в этом на практике, когда GAS-триггер молча не сработал два дня подряд — и без watchdog’а об этом бы никто не узнал.

Принцип 4: Идемпотентность

Если retry автоматический — каждая операция обязана быть идемпотентной. Это значит: повторное выполнение с теми же данными даёт тот же результат, без побочных эффектов.

Пример: сервис создаёт заказ в МойСклад. Запрос ушёл, ответ не пришёл — таймаут. Заказ создался или нет? Неизвестно. Retry создаёт заказ ещё раз. Без идемпотентности — у клиента два одинаковых заказа.

Решение: каждый заказ создаётся с уникальным внешним ID (например, ID заказа из Flowwow). Перед созданием сервис проверяет: есть ли в МойСклад заказ с таким внешним ID? Если есть — пропускаем. Если нет — создаём. Повторный вызов безопасен.

Этот же принцип работает со списанием остатков, обновлением цен, отправкой уведомлений. Любая операция, которая может быть повторена автоматически, должна быть спроектирована так, чтобы повторение было безопасным. Это не «хорошая практика» — это требование к архитектуре системы с автоматическими retry.

Как это выглядит на практике

Все принципы выше — не теория. Вот два реальных проекта, в которых они работают:

Flowwow + МойСклад. Сервис на Node.js + BullMQ. Polling Flowwow API каждые 30 секунд. Очередь обработки заказов с exponential backoff и dead letter queue. Структурированные логи по каждому заказу. Telegram-алерты при сбоях. В пиковый день (8 марта) обработал 330 заказов без единого ручного вмешательства. Подробный разбор.

Мониторинг 700+ Google Таблиц. Google Apps Script с самописной очередью поверх PropertiesService. Checkpoint-resume для обхода 6-минутного лимита. Circuit breaker для проблемных таблиц. Watchdog-скрипт для мониторинга самого мониторинга. Проверяет все 700+ таблиц каждые 5 минут и шлёт сводку в Telegram. Подробный разбор.

В обоих проектах — одни и те же принципы, разные реализации. BullMQ vs PropertiesService, Redis vs JSON-сериализация, systemd vs GAS-триггеры. Инструменты разные — архитектурные решения одинаковые.

Чего мы не делаем

Честный раздел. Мы не используем:

Kafka. Kafka — это про миллионы сообщений в секунду, кластеры брокеров, consumer groups. Наши интеграции обрабатывают десятки-сотни событий в минуту. BullMQ + Redis — это ровно тот масштаб: надёжно, просто, без overhead’а на администрирование кластера. Если нужна Kafka — значит, задача переросла нашу специализацию.

Микросервисную архитектуру. Одна интеграция — один сервис. Монолит. Не потому что не умеем, а потому что для интеграции между двумя системами Service Mesh, API Gateway и межсервисное взаимодействие — это оверинжиниринг. Монолит проще деплоить, дебажить и поддерживать. Когда задача — связать Flowwow с МойСклад, а не построить платформу из 40 сервисов, монолит — правильный выбор.

Гарантию 100% uptime. Внешние API падают. Google API возвращает 500. Flowwow не отвечает на запросы. Мы не контролируем чужие сервисы. Что мы гарантируем: система обнаружит сбой, отправит алерт и автоматически восстановится, когда внешний сервис вернётся в строй. Ни один заказ не потеряется. Но задержка на время сбоя — будет.

Технический стек

КомпонентNode.js-проектыGAS-проекты
ЯзыкTypeScriptJavaScript (V8)
Очередь задачBullMQ + RedisСвоя реализация поверх PropertiesService
Retry-стратегияExponential backoff, circuit breakerExponential backoff, circuit breaker
Dead letter queueBullMQ DLQ (Redis)Отдельный массив в PropertiesService
ЛогированиеСтруктурированный JSON-логLogger + сводная таблица
АлертыTelegram Bot APITelegram Bot API
Мониторингsystemd watchdog, healthcheckОтдельный GAS-скрипт (watchdog)
PollingsetInterval / cronTime-based trigger (5 мин)
Обход лимитовCheckpoint-resume (6 мин лимит GAS)
ДеплойVPS, systemd, rsyncGoogle Apps Script IDE

Частые вопросы

Какие очереди задач подходят для интеграций?

Для серверных интеграций на Node.js мы рекомендуем BullMQ + Redis. Это зрелая библиотека с поддержкой приоритетов, повторных попыток, отложенных задач и dead letter queue. Redis обеспечивает персистентность — задачи не теряются при перезапуске. Для проектов на Google Apps Script — собственная реализация поверх PropertiesService, потому что внешние зависимости в GAS недоступны.

Как правильно обрабатывать ошибки внешних API?

Разделять ошибки на retryable (429, 503, timeout) и non-retryable (400, 404). Для retryable — exponential backoff с ограничением по количеству попыток. Для non-retryable — сразу в dead letter queue с алертом. Отдельно — circuit breaker для endpoint’ов, которые стабильно возвращают ошибки. Главное: каждая ошибка должна быть видимой. Молча проглоченная ошибка в интеграции — это данные, которые не дошли.

Почему polling, а не вебхуки?

Не все API поддерживают вебхуки. Flowwow, например, не отправляет уведомления о новых заказах — только pull-модель. Google Apps Script тоже не поддерживает входящие HTTP-вызовы в полноценном виде. Polling надёжнее: он работает вне зависимости от инфраструктуры на стороне API, не требует публичного endpoint’а и проще в отладке. Минус — задержка между событием и обработкой. При polling каждые 30 секунд максимальная задержка — 30 секунд. Для бизнес-интеграций это приемлемо.

Читайте также


Если вы выбираете подрядчика для интеграции и хотите понять, как именно будет устроена система — напишите нам. Покажем архитектуру на примере вашей задачи, до начала работ.

С вами была команда GoogleSheets.ru, мы строим интеграции, которые не теряют данные.

Не хотите разбираться сами?

Если читали статью и поняли, что руками уже не справляетесь — напишите. Оценим задачу бесплатно и предложим решение.

140+ реализованных проектов
Google Products Expert в команде

Хотите такой же результат?

Расскажите о задаче — предложим решение и покажем релевантные кейсы.

Написать в Telegram

Подписывайтесь — делимся скриптами, кейсами и лайфхаками