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

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