Интеграция работала спокойно, потом очередь выросла, а worker начали получать QUERY_LIMIT_EXCEEDED. Самая плохая реакция — немедленно повторить каждый запрос. Через секунду все процессы снова бьют в портал одновременно, и временный всплеск превращается в постоянный шторм.
Первое, что я бы проверил, — сколько вызовов реально уходит к одному порталу за секунду и кто их отправляет. Счётчик только по HTTP-коду ничего не объясняет. Нужны portal key, метод, worker, попытка и время.
Соберите минутный профиль нагрузки
Webhook secret в журнал не нужен. Сделайте стабильный внутренний ID портала и группируйте вызовы по секунде. Отдельно считайте методы чтения и записи. Смотрите объект time ответа и поле operating, если оно присутствует.
function retryDelay(attempt) {
const base = Math.min(30_000, 500 * (2 ** attempt));
const jitter = Math.floor(Math.random() * 300);
return base + jitter;
}
async function callB24(job) {
const response = await request(job);
if (response.error === 'QUERY_LIMIT_EXCEEDED') {
if (job.attempt >= 6) throw new Error('retry_exhausted');
return queue.later(job, retryDelay(job.attempt));
}
return response;
}
Это пример внешнего worker, а не код внутри Битрикс24. Число попыток и максимум задержки выбирайте по цене операции. Jitter нужен, чтобы сто задач не проснулись в одну миллисекунду.
Один портал — один управляемый поток
- Назначьте каждому порталу стабильный
portal_key. - Поставьте REST-вызовы в очередь до отправки, а не после первой ошибки.
- Примените общий limiter по portal key для всех worker.
- Для чтения используйте пагинацию и выбирайте только нужные поля.
- На ошибке лимита увеличивайте задержку; после успеха снижайте её постепенно.
- Для записи сохраняйте бизнес-ключ операции до первого вызова.
| Симптом | Проверка | Исправление |
|---|---|---|
| Ошибки ровно в начале минуты | Планировщики всех worker | Разнести задания и добавить jitter |
| Лимит только у одного портала | Группа по portal key | Отдельный limiter |
| После retry появились дубли | Бизнес-ID записи | Идемпотентность write-операции |
| Долго отвечает один метод | time и operating | Сузить select/filter, разбить batch |
PASS и FAIL
- PASS: на графике видна ограниченная интенсивность отдельно по каждому порталу.
- PASS: повторные задания распределяются по времени, число попыток конечно.
- PASS: повтор write-задания не создаёт вторую сущность.
- FAIL: каждый процесс имеет собственный limiter и не знает о соседях.
- FAIL: после ошибки вся очередь повторяется без паузы.
Ошибки, которые встречаются в production
Limiter стоит внутри одного worker. Второй процесс, cron и ручной импорт продолжают отправлять запросы. Координация должна быть общей для источников одного портала.
Retry одинаковый для чтения и создания. Повтор чтения обычно безопасен. Повтор создания сделки после сетевого обрыва может дать дубль, если первый ответ потерян.
Batch считают бесплатным. Внутри него остаются операции и нагрузка. Большой batch с тяжёлыми фильтрами способен ухудшить картину.
Логируют полный webhook URL. Вместе с диагностикой в лог попадает секрет. Храните маскированный portal key.
Чек-лист
- Есть метрики по portal key, методу и worker.
- Один общий limiter управляет конкурентностью.
- Backoff растёт и содержит jitter.
- Retry имеет максимум попыток и dead-letter статус.
- Write-операции защищены бизнес-ключом.
- Webhook secrets не попадают в логи.
Как не потерять задания во время backoff
Задержка сама по себе создаёт новую проблему: очередь может расти быстрее, чем успевает разгружаться. Считайте возраст самого старого задания, длину очереди и скорость обработки. Если возраст постоянно растёт, limiter защищает портал, но бизнес-процесс уже не успевает. Тогда сокращайте число REST-вызовов на одну операцию, объединяйте чтение и убирайте повторные запросы одних данных.
Не держите worker заблокированным через длинный sleep. Верните задание в очередь с полем run_at. Так процесс возьмёт другую работу, а delayed job останется видимой. Для конечной ошибки используйте отдельный статус и уведомление, а не бесконечный цикл.
Минимальные метрики очереди
- вызовы в секунду по portal key;
- число QUERY_LIMIT_EXCEEDED по методу;
- возраст самого старого задания;
- p50 и p95 времени REST-ответа;
- распределение номера попытки;
- число заданий в dead-letter состоянии.
Есть ещё одна ловушка: повтор после сетевого таймаута. Клиент не получил ответ, но Битрикс24 мог выполнить операцию. Перед повторным созданием найдите объект по вашему внешнему бизнес-ключу. Если он уже существует, завершите job с найденным CRM-ID. PASS: повтор восстанавливает связь с созданной сущностью. FAIL: каждый timeout заканчивается новой сделкой.
После исправления не снимайте limiter одним движением. Поднимайте конкурентность ступенями и наблюдайте хотя бы несколько полных циклов фоновой обработки.
Что делать, если не помогло
Остановите один источник на короткое контрольное окно и сравните интенсивность. Если график не изменился, запросы идут из другого worker или приложения. Если ваша очередь ровная, но QUERY_LIMIT_EXCEEDED остаётся, проверьте другие сервисы на том же IP и портале. Для прав и scope используйте диагностику REST 403, а для длинных выборок — безопасную пагинацию. CTR1 может разобрать и доработать интеграцию Битрикс24 без слепого увеличения пауз.



