Безопасная интеграция сторонних API: таймауты, повторы и circuit breaker
Безопасная интеграция сторонних API, которая поддерживает работу приложения при простоях. Узнайте про таймауты, повторы, circuit breaker и быстрые проверки.

Почему сторонние API могут заблокировать ваши основные потоки
Сторонний API может ломаться не только очевидным «провалом». Самая частая проблема — затормаживание: запросы зависают, ответы приходят с опозданием, и ваше приложение продолжает ждать. Если такие вызовы находятся на критическом пути, небольшая проблема вне вас накапливается внутри системы.
Так локальное замедление превращается в полный простой. Потоки или воркеры застревают в ожидании, очереди растут, транзакции в базе данных держатся дольше, а новые запросы начинают таймаутиться. Вскоре даже страницы, которые не используют внешний API, кажутся сломанными, потому что система перегружена ожидающей работой.
Последствия реальны. Ненадёжный провайдер идентификации блокирует регистрацию и вход. Таймаут платежного шлюза замораживает оформление заказа, и пользователи не уверены, списали ли им деньги. Задержка в доставке сообщений останавливает сброс пароля и подтверждения заказов, что вызывает новые повторы и обращения в поддержку.
Цель проста: изолировать внешние ошибки, чтобы основные потоки продолжали работать. Это может означать разрешение пользователю оформить заказ с последующей проверкой платежа или позволение регистрации, даже если письмо‑приветствие не отправилось.
Практическая метрика успеха: когда провайдер медлит или недоступен, ваше приложение должно по‑прежнему отвечать быстро и ясно, а зона поражения оставаться небольшой. Например: большинство ключевых запросов укладывается в обычный бюджет задержки, ошибки ограничены функциями, зависящими от этого API, пользователи видят понятный статус (в очереди, ожидает, попробуйте позже), и восстановление происходит автоматически, когда провайдер возвращается.
Режимы отказов, к которым стоит подготовиться
Большинство отказов предсказуемы, даже если время их появления неточно. Назовите их заранее, и вы сможете решить, что стоит повторять, что прекращать, и что показывать пользователю.
Распространённые категории:
- Всплески латентности (запросы внезапно выполняются в 10× дольше)
- Преходящие ошибки сервера или сети (таймауты, 502/503, сбросы соединений)
- Ограничения по скорости и исчерпание квот (429, дневные лимиты)
- Проблемы с авторизацией и разрешениями (истёкшие ключи, отозванный доступ)
- Плохие или неожиданные данные (отсутствующие поля, неправильные форматы, частичные ответы)
Не все ошибки равнозначны. Преходящие проблемы часто стоит повторить, потому что следующий вызов может пройти (сетевая «вспышка», таймауты, 502/503 и некоторые 429 после ожидания). Постоянные проблемы вряд ли самоустранатся (неверные учётные данные, неверные конечные точки, malformed‑запросы, отказы по правам).
Обращение с каждой ошибкой как с одинаковой превращает маленький инцидент в простой. Повторы постоянных ошибок тратят время, быстрее приводят к лимитам и создают бэклог, который замедляет всё остальное. Полное отсутствие повторов при преходящих ошибках заставляет пользователя делать повторные действия и теряет работу, которая могла бы завершиться через мгновение.
Особое внимание уделяйте потокам, где пауза ощущается как поломка: оформление заказа, вход, сброс пароля и уведомления (email/SMS/push). Двухсекундный всплеск в маркетинговом API раздражает. Двухсекундный всплеск в авторизации платежа блокирует доход.
Полезный тест: «Нужен ли этот вызов, чтобы прямо сейчас завершить основную задачу пользователя?» Если да — нужны жёсткие таймауты, аккуратные повторы и понятный путь при ошибке. Если нет — переместите его в очередь и держите приложение отзывчивым.
Таймауты: выберите предел и придерживайтесь его
Таймаут — это максимум времени, который вы готовы ждать, прежде чем остановиться и двигаться дальше. Без чёткого предела один медленный провайдер может накопить ожидающие запросы и блокировать важную работу.
Полезно разделять два вида ожидания:
- Connect timeout: сколько вы будете пытаться установить соединение.
- Read timeout: сколько вы будете ждать ответа после установления соединения.
Выбор чисел — не про идеальность, а про соответствие человеческому терпению и вашим рабочим сценариям.
- Если пользователь смотрит на индикатор загрузки, обычно требуется быстрый ответ и понятный следующий шаг.
- Если это фоновая задача (например синхронизация счетов ночью), можно дать больше времени, но всё равно нужен потолок, чтобы задача не висела бесконечно.
Практический способ выбрать таймауты — отталкиваться от опыта:
- Сколько пользователь может ждать, прежде чем нужно показать понятное сообщение?
- Если этот вызов сейчас упадёт, можно ли повторить позже или использовать запасной вариант?
- Сколько таких вызовов выполняется при пиковых нагрузках?
Компромисс реален. Слишком долгие таймауты занимают потоки, воркеры и соединения с БД. Слишком короткие — создают ложные отказы и вызывают лишние повторы.
Повторы, которые не усугубляют простои
Повторы помогают, когда ошибка, вероятно, временная: краткий сетевой сбой, сброс DNS или единичный 500/502/503. В таких случаях вторая попытка может пройти, и пользователь ничего не заметит.
Риск — «шторм повторов». Когда многие клиенты одновременно терпят неудачу и все повторяют сразу, они могут перегрузить провайдера (и ваши собственные воркеры). Backoff и jitter предотвращают это.
Бюджет повторов заставляет вас быть реалистичными. Держите количество попыток низким и ограничьте общее время, чтобы ключевые потоки не застревали, ожидая чужой сервис.
Безопасный «рецепт» по умолчанию
- Повторяйте только несколько раз (обычно 1–3 попытки всего, в зависимости от сценария).
- Используйте экспоненциальный backoff (например 200 мс, 500 мс, 1 с) плюс случайный jitter.
- Ограничьте общее время на повторы (обычно несколько секунд для пользовательских сценариев).
- Для каждой попытки задавайте отдельный таймаут, а не один большой для всех попыток.
Не повторяйте предсказуемые ошибки клиента, такие как 400/422 валидация, 401/403 проблемы авторизации или 404. Они почти наверняка снова упадут и просто добавят нагрузку.
Ещё одно правило: повторяйте запросы, изменяющие состояние (POST/PUT), только если у вас есть идемпотентность, иначе вы рискуете двойными списаниями или дубликатами записей.
Идемпотентность: делайте повторы безопасными в реальных сценариях
Идемпотентность означает, что повторный запуск одного и того же запроса даёт тот же конечный результат. Это важно, потому что повторы — нормальная вещь: сети падают, серверы перезапускаются, клиенты таймаутятся. Без идемпотентности «полезный» повтор создаст дубликаты или денежные проблемы.
Представьте покупку: платёжный API медлит, ваше приложение таймаутится и повторяет запрос. Если первый вызов всё же прошёл, повтор может создать второе списание. Та же проблема при создании заказа, старте подписки, отправке письма/SMS, возврате средств или создании тикета в поддержку.
Решение — прикреплять ключ идемпотентности (или request ID) к каждому «выполнить что‑то» вызову. Он должен быть уникален для пользовательского действия, а не для попытки. Провайдер (или ваш сервис) использует этот ключ, чтобы обнаружить дубликаты и вернуть тот же результат вместо повторного выполнения операции.
Относитесь к ключу идемпотентности как к части модели данных, а не к заголовку, о котором надеются, что никто не забудет.
Паттерн, который работает в продакшне
Генерируйте один ключ, когда пользователь начинает действие (например нажал «Оплатить»), затем сохраняйте его вместе с локальной записью.
При каждой попытке:
- Отправляйте тот же ключ.
- Сохраняйте итоговый результат (успех, код ошибки, ID списания).
- Если уже есть записанный результат, возвращайте его вместо повторного выполнения.
Если вы — «провайдер» для внутренних вызовов, введите такое же поведение на сервере.
Circuit breaker: прекратите вызывать API, когда он падает
Circuit breaker — это аварийный выключатель. Когда внешний сервис начинает падать, вы перестаёте его вызывать на короткий период, вместо того чтобы накапливать запросы, которые, скорее всего, таймаутятся.
Обычно у breaker‑а три состояния:
- Closed: запросы идут как обычно.
- Open: вызовы блокируются на период охлаждения.
- Half‑open: после окна охлаждения небольшое число тестовых вызовов проверяет восстановление сервиса.
Когда breaker открыт, ваше приложение должно вести себя предсказуемо. Если API валидации адреса недоступен во время регистрации, примите адрес и пометьте для последующей проверки. Если проверка риска платежа упала, поставьте заказ в очередь на ручную проверку или временно отключите эту опцию и объясните причину.
Выбирайте пороги, соответствующие влиянию на пользователей:
- подряд идущие ошибки (например 5 неудач подряд)
- высокий процент ошибок за короткое окно
- много медленных ответов (таймауты)
- определённые коды статуса (например повторяющиеся 503)
Держите окна охлаждения короткими (секунды‑минута) и ограничьте число пробных вызовов при half‑open. Цель — прежде всего защитить ключевые потоки, а затем быстро восстановиться.
Резервные варианты и очереди: держите приложение удобным
Когда внешний API медлит или недоступен, ваша цель — сохранить работу пользователя. Это значит иметь план Б, честно показывающий, что произошло.
Запасные варианты: выберите «достаточно хороший» опыт
Fallback — это то, что приложение делает, когда API не отвечает вовремя. Варианты: использовать кэшированные данные, перейти в деградированный режим (скрыть несущественные виджеты, отключить опциональные действия), попросить пользователя ввести данные вручную (ручной ввод адреса) или показать понятное сообщение с дальнейшими шагами.
Будьте честны: не говорите, что операция выполнена, если это не так.
Очереди: сделайте позже то, что не обязательно сейчас
Если работа не должна завершиться в рамках пользовательского запроса, отправьте её в очередь и ответьте быстро. Частые кандидаты: отправка писем, синхронизация в CRM, генерация отчётов и отправка аналитики.
Быстро терпите неудачу для критичных действий. Если API не обязателен для завершения оформления заказа (или создания аккаунта), не блокируйте запрос. Примите заказ, поставьте внешнюю операцию в очередь и позже выполните сверку. Если API обязателен (например авторизация платежа), быстро верните понятную ошибку и не держите пользователя в ожидании.
То, что видит пользователь, должно соответствовать бэку: понятный статус (завершено, ожидает, не удалось), слово, которое вы можете сдержать (чек сейчас, подтверждение позже), возможность повтора и видимая запись в UI (лог активности, индикатор ожидания).
Лимиты скорости и нагрузка: избегайте проблем, созданных вами
Rate limits — это способ провайдера сказать: «Вы можете вызывать нас, но не слишком часто». Вы столкнётесь с ними раньше, чем думаете: всплески трафика, одновременный запуск фоновых задач или баг, делающий цикл на ошибках.
Начните с контроля числа создаваемых запросов. Пакетуйте, когда можно, кэшируйте ответы даже на 30–60 секунд, если это безопасно, и реализуйте throttling на стороне клиента, чтобы приложение не посылало всплески быстрее, чем допускает провайдер.
При получении 429 Treat it как сигнал замедлиться:
- Уважайте заголовок
Retry-After, если он есть. - Добавляйте jitter, чтобы многие воркеры не повторяли в один момент.
- Ограничьте повторы для 429, чтобы не застрять в бесконечных циклах.
- Усильте откат при повторяющихся 429.
- Логируйте это как метрику, чтобы заметить паттерны до пользователей.
Ограничьте также конкурентность. Одна рабочая задача (например синхронизация контактов) не должна занимать все слоты воркеров и «голодать» критичные потоки вроде входа или оформления заказа. Отдельные пулы или лимиты по фичам помогают.
Пошагово: безопасный рецепт интеграции по умолчанию
Каждый сторонний вызов требует плана на случай отказа. Вам не нужна идеальность. Вам нужно предсказуемое поведение, когда у провайдера плохой день.
1) Классифицируйте вызов (обязательно сейчас или можно подождать)
Решите, что происходит, если вызов сейчас упадёт. Налоговый расчёт при оформлении может быть обязательным. Синхронизация маркетингового контакта обычно может подождать. Этот выбор задаёт остальную логику.
2) Задайте таймауты и бюджет повторов
Выберите таймауты для каждого типа вызова и держите их консистентными. Затем установите бюджет повторов, чтобы вы не шли в атаку по медленному API.
- Обязательное, пользователь ждёт: короткий таймаут, 0–1 повтор.
- Можно подождать, фоновая задача: более долгий таймаут, несколько повторов с backoff.
- Никогда не повторять бесконечно: ограничьте общее время на задачу.
3) Сделайте повторы безопасными с идемпотентностью и отслеживанием
Если запрос может что‑то создать или снять деньги, добавьте ключ идемпотентности и храните запись запроса. Если запрос на платёж таймаутится, повтор не должен списать деньги дважды. Отслеживание также помогает поддержке ответить: «Прошло ли это?»
4) Добавьте circuit breaker и поведение резервного варианта
Когда ошибки растут, перестаньте вызывать провайдера на короткий период. Для обязательных вызовов покажите понятный путь «Попробуйте снова». Для отложенных — поставьте задачу в очередь и обработайте позже.
5) Мониторьте базовые метрики
Отслеживайте задержку, процент ошибок и события открытий/закрытий breaker‑а. Аллертьте на устойчивые изменения, а не на единичные всплески.
Общие ошибки, которые превращают маленькую проблему в простой
Большинство простоев API не начинаются большими. Они становятся большими, потому что приложение реагирует худшим образом: слишком долго ждёт, повторяет слишком агрессивно и занимает те же воркеры, которые должны держать всё работоспособным.
Эти паттерны вызывают каскады:
- Повторы каждой ошибки, включая 4xx (неверные запросы, просроченная авторизация, отсутствие прав).
- Очень длинные таймауты «чтобы наверняка», которые тихо потребляют потоки, соединения с БД или раннеры задач, пока не исчерпаете ресурсы.
- Повторы create‑операций без ключей идемпотентности, приводящие к двойным списаниям, дублирующим отгрузкам или повторным записям.
- Неправильно настроенные circuit breaker‑ы, которые либо никогда не восстанавливаются, либо постоянно флапают.
- Отнесение частичных простоев как полного отказа вместо деградации только затронутой функции.
Маленькие фиксы предотвращают большие простои: повторяйте только вероятно преходящие ошибки (таймауты, некоторые 429, некоторые 5xx) и лимитируйте попытки с backoff и jitter; держите таймауты короткими и осознанными; требуйте идемпотентности для операций, создающих ресурс или снимающих деньги; и проектируйте деградацию отдельных фич.
Быстрая проверка перед релизом
Прежде чем отправлять интеграцию в прод, пройдитесь с менталитетом «что если сломается». Если вы не можете ответить «да» на пункт, считайте это блокирующим для релиза ключевых потоков вроде регистрации, оформления заказа или отправки сообщений.
- Временные лимиты заданы явно (connect timeout и read/response timeout).
- Повторы ограничены (малый бюджет, backoff, jitter и общий предел времени).
- Повторы безопасны для реальных действий (ключи идемпотентности или явная дедупликация).
- Есть breaker и план Б (fallback, деградированный режим или очередь).
- Вы видите проблемы рано (латентность, процент ошибок и здоровье зависимостей по провайдеру и endpoint).
Если платёжный провайдер начинает таймаутить, правильное поведение: «оформление всё ещё грузится, пользователь получает понятное сообщение, и вы не зависаете в ожидании», а не «всё висит, пока не таймаутнется».
Пример: защита оформления заказа при ненадёжном провайдере
Представьте оформление заказа, которое вызывает три сервиса: платежный API для списания, налоговый API для расчёта налогов и email API для отправки чека.
Платёжный вызов — единственный, который должен быть синхронным. Проблемы с налогами или почтой не должны блокировать покупку.
Когда налоговый API медлит
Допустим налоговый API иногда занимает 8–15 секунд. Если чекaут ждёт, пользователи уходят, а ваши воркеры заблокированы.
Более безопасный поток:
- Установите жёсткий таймаут (например 800 мс–2 с) и быстро сдавайте ошибку.
- Повторите не более одного раза, только если это безопасно, с jitter.
- При таймауте используйте кэшированную ставку или последнюю известную таблицу для региона покупателя.
- Если нельзя использовать кэш по закону, пометьте заказ как «ожидает налога» и поставьте на перерасчёт в очередь.
Результат: меньше брошенных корзин и меньше зависших заказов при медленном налоговом провайдере.
Когда email API недоступен
Письмо‑чек важно, но оно не должно блокировать снятие средств. Если email API падает, circuit breaker должен открыться после нескольких быстрых ошибок и остановить вызовы на короткий период.
Вместо отправки письма в режиме inline, поставьте задачу «отправить чек» в очередь с ключом идемпотентности (например order_id + email_type). Если провайдер упал, очередь в фоне будет повторять отправку, а клиент увидит успешную покупку.
Результат: меньше тикетов поддержки о пропавших подтверждениях и отсутствие потери дохода из‑за падения ненужного внешнего вызова.
Следующие шаги: безопасный rollout по приложению
Выберите один поток, который больше всего болит при поломке (checkout, signup, выставление счетов) и сделайте его эталонной интеграцией. Затем копируйте те же значения повсеместно.
Простой порядок внедрения:
- Задайте таймауты и быстро сдавайте понятную ошибку.
- Добавьте повторы с backoff, но только для retryable ошибок.
- Введите идемпотентность, чтобы повторы не списывали дважды и не дублировали записи.
- Добавьте circuit breaker, чтобы плохой провайдер не мог заблокировать ключевой поток.
Запишите ваши значения по умолчанию и держите их скучными: один connect timeout, один request timeout, макс. число повторов, диапазон backoff, окно охлаждения breaker‑а и правила, что считается retryable.
Проведите тренировку отказа перед расширением на следующий поток. Форсируйте таймауты (или заблокируйте провайдера в тестовой среде), затем убедитесь, что пользователь видит полезное сообщение, запасные пути работают, а очереди не накапливают бесконечно повторяющиеся задачи.
Если вы быстро запускаете новые продукты, имеет смысл превратить эти надёжные значения в переиспользуемый шаблон. Для команд, использующих Koder.ai (koder.ai), это часто означает определить таймаут, политику повторов, идемпотентность и правила breaker‑а один раз, а затем применять тот же паттерн при создании и итерации новых сервисов.
FAQ
Почему медленный сторонний API может вывести моё приложение из строя?
Для каждого вызова задавайте явные тайм-ауты подключения и чтения. Для сценариев, где пользователь ждёт ответа, выбирайте короткий лимит, затем возвращайте понятный результат или переносите второстепенную работу в очередь.
При каких ошибках API стоит повторять запрос?
Повторяйте запросы при тайм-аутах, сбросе соединения и некоторых ошибках 5xx, потому что они могут быстро исчезнуть. Не повторяйте некорректные запросы, запросы с неверными учётными данными, ошибки доступа или обращения к отсутствующим эндпоинтам.
Сколько раз стоит повторять API-запрос?
Не делайте много повторных попыток, обычно достаточно от одной до трёх всего. Используйте экспоненциальную задержку со случайным разбросом и ограничивайте общее время повторных попыток.
Как предотвратить двойное списание, если платёжный запрос завершился по тайм-ауту?
Используйте ключ идемпотентности для действий, которые создают записи, отправляют сообщения, оформляют возвраты или списывают деньги. Для каждой попытки в рамках одного действия пользователя используйте один и тот же ключ.
Что делает автоматический выключатель?
Автоматический выключатель временно прекращает вызовы после повторяющихся ошибок или тайм-аутов. Он защищает ваши воркеры и даёт провайдеру восстановиться, прежде чем вы снова отправите ограниченное число тестовых запросов.
Что стоит перенести в фоновую очередь?
Сохраняйте отзывчивость оформления заказа и входа, изолируя необязательные вызовы. Отправляйте в очередь письма с чеками, обновления CRM и аналитику, а не ждите их завершения во время пользовательского запроса.
Как приложению обрабатывать ответ 429 с ограничением частоты запросов?
Учитывайте значение Retry-After от провайдера, если оно есть, снижайте параллелизм и добавляйте случайную задержку перед повторной попыткой. Кэшируйте или объединяйте безопасные запросы на чтение, чтобы приложение делало меньше вызовов.
Что должны видеть пользователи, когда внешний сервис не работает?
Показывайте фактический статус: выполнено, ожидает обработки или не выполнено. Предлагайте пользователям практический следующий шаг, например повторить оплату или позже проверить заказ, который ещё обрабатывается.
Что нужно отслеживать для сторонних API?
Отслеживайте задержку, долю ошибок, долю тайм-аутов, ответы с ограничением частоты запросов и события автоматического выключателя для каждого провайдера и эндпоинта. Настройте оповещения о продолжительных изменениях, чтобы раньше замечать проблемы.
Как протестировать интеграцию с API перед релизом?
Проверьте тайм-ауты, частичные ответы, 429, ошибки 5xx и повторные запросы на запись. Убедитесь, что резервный сценарий работает, количество задач в очереди не растёт бесконтрольно, а повторные попытки не создают дубликаты.