API gateway для OpenAI: timeout и retry без двойного действия
Запрос «API gateway для OpenAI timeout и retry без дублей» появляется, когда команда пытается сделать интерфейс устойчивее, но рискует повторно отправить письмо, создать заказ или изменить данные. Timeout не доказывает, что работа не началась; он только сообщает, что клиент не получил подтверждение в свой бюджет времени. Нужны локальная операция, правило сверки и явная граница повторов. RussiaAPI — независимый сторонний API gateway; материал не обещает exactly-once, фиксированную задержку, лимит, стоимость или поведение конкретного endpoint.
Отделите deadline клиента от результата операции
Deadline ограничивает, сколько времени ваш клиент готов ждать, но не описывает итог на сервере. После его истечения локальная операция получает состояние unknown_outcome, пока приложение не проверит безопасные признаки завершения. Не превращайте timeout в «ошибку, которую всегда можно повторить»: для side effect такое правило создаёт дубликаты.
Храните локальный operation ID до внешнего вызова и возвращайте его в интерфейс. При повторном запросе приложение ищет эту операцию, а не создаёт новую. Журналу достаточно ID, tenant, версии адаптера, времени, счётчика попыток и категории результата. Не добавляйте полный payload, prompt, Authorization или ключи ради диагностики.
Сделайте idempotency свойством приложения
Idempotency key связывает повтор с локальной бизнес-операцией, а не доказывает функцию внешнего API. Генерируйте его сервером, привязывайте к tenant и ожидаемому действию, храните ограниченный срок и не передавайте как секрет в браузерный лог. Перед новым вызовом проверьте, что ключ относится к тому же объекту и не завершён другим путём.
Не предполагается, что любой маршрут принимает конкретный header или гарантирует exactly-once. Если ваша интеграция использует ключ, подтвердите актуальный контракт отдельно. Даже без поддержки заголовка локальная таблица операций и атомарный переход состояния предотвращают наиболее частую ошибку: два независимых клика создают две бизнес-команды.
Ограничьте retry бюджетом и условиями
Retry допустим только для заранее определённой операции без уже начатого побочного действия либо после доказуемой сверки. Задайте максимальное число попыток, общий deadline и backoff с jitter. Когда бюджет исчерпан, верните понятный статус и прекратите автоматизацию. Бесконечный retry увеличивает нагрузку и не делает ответ достовернее.
Явная ошибка формы, запрет прав, неизвестное поле или несоответствие схемы не должны уходить в тот же retry-цикл, что и временная сеть. Сначала классифицируйте локально. Если причина не ясна, оставьте операцию для review и соберите синтетический fixture; не меняйте параметры наугад и не используйте повтор как обход проверки.
Сверяйте результат до следующего действия
Для каждого значимого процесса определите, какой безопасный факт подтверждает состояние: локальная запись, собственная очередь, статус операции или ручная проверка. Только после такой сверки можно предложить пользователю следующий шаг. Текст модели или отсутствие ошибки в клиенте не заменяют доменное подтверждение.
Если подтверждения нет, пользователь видит «нужна проверка», а не ложный успех. Это особенно важно для платежей, доступа, публикации и внешних уведомлений. Сверка должна читать минимальные данные, проверять tenant и версию объекта; она не должна раскрывать чужой результат или превращаться в повторную отправку.
Проверьте конкуренцию и порядок событий
Два worker могут одновременно увидеть одну операцию. Используйте атомарный переход статуса, блокировку или оптимистическую версию записи, чтобы side effect запускался один раз по правилам приложения. Поздний callback или повторная доставка проверяет operation ID и ожидаемое состояние, иначе отправляется в review.
Не полагайтесь на порядок сообщений как на единственный источник истины. Сеть может задержать событие, клиент — перезагрузиться, а worker — повторить задачу. Храните revision, разрешённые переходы и короткую историю аудита. Такой журнал описывает вашу логику, а не гарантии внешнего сервиса.
Покройте ошибочные ветки тестами
Синтетический набор включает timeout до ответа, timeout после записи, два параллельных повтора, чужой tenant, истёкший ключ, неизвестный результат и невалидный payload. Каждый тест должен доказывать отсутствие сетевого вызова там, где он запрещён, отсутствие секрета в журнале и предсказуемое состояние интерфейса.
Дополните сценарий применением idempotency key, общим deadline budget и retry/backoff для 429. Перед production подтвердите маршруты, модельные alias и договорные ограничения своего аккаунта.
Безопасный server-side пример
Пример показывает только локальную логику приложения. Он не делает сетевой вызов, не содержит реальных ключей и не доказывает поддержку endpoint. Перед подключением рабочего маршрута сверяйте актуальный договор и API-контракт.
export function scheduleRetry(operation, now) {
if (!operation?.id || operation.status === 'unknown_outcome') {
return { action: 'reconcile', nextAt: null };
}
if (operation.attempts >= 2 || operation.idempotent !== true) {
return { action: 'stop', nextAt: null };
}
return { action: 'retry', nextAt: now + 1_000, operationId: operation.id };
}Критерий готовности
Сценарий можно расширять только после того, как проверка формы входа, прав, локального статуса и отсутствия секрета в журнале прошла на синтетических данных. Неизвестный ответ — причина остановить конкретный адаптер и провести сверку, а не причина ослабить ограничение.
Граница ответственности и данных
Не передавайте в браузер, обычный журнал, тикет или статью API-ключи, заголовок Authorization, cookie, ключи поставщиков, полный prompt либо персональные данные. Для расследования используйте минимальные технические метаданные и синтетические fixtures.
Контроль перед выпуском
Перед production проверьте обезличенный сценарий на staging, права tenant, границу побочного действия, понятный статус для пользователя и ручную ветку для неизвестного результата. После изменения модели, SDK, маршрута или схемы повторите проверку: старый прогон не подтверждает новый контракт.
Для выбора маршрута откройте RussiaAPI и сверяйте действующие условия в каталоге. Используйте только ключ своего аккаунта на сервере. RussiaAPI не является официальным представителем упомянутых поставщиков; доступность функций, цена, лимиты и правила обработки данных требуют отдельного подтверждения.
Полезные материалы
Google SRE: cascading failures · AWS Builders Library: timeouts and retries
FAQ
Доказывает ли timeout, что запрос не выполнен?
Нет. Он показывает только, что клиент не получил подтверждение в свой deadline. Сохраните локальную операцию, покажите неизвестный итог и сначала выполните безопасную сверку, особенно если действие влияет на деньги, доступ или публикацию.
Нужен ли idempotency key для любого запроса?
Нужен там, где повтор может создать повторное бизнес-действие. Ключ связывает локальную операцию с безопасным повтором; он не должен считаться обещанием exactly-once со стороны внешнего маршрута и требует проверки текущего контракта.
Что делать после исчерпания retry budget?
Остановить автоматические попытки, вернуть понятный статус и направить операцию на сверку или ручную очередь. Не меняйте payload наугад и не создавайте новую операцию, пока неизвестен итог первой.
Следующий шаг
Продолжите с материалами: Idempotency key при повторе · Deadline budget AI API · Retry/backoff после 429. Затем протестируйте интеграцию на staging до production-трафика.