Как считать стабильность API: исходные запросы, повторы и конечная успешность
Успех после повтора нельзя считать новым независимым успехом. Статья разделяет request_id, попытки и конечные состояния, объясняет границы 429, 503, тайм-аутов и идемпотентности, а локальный скрипт считает конечную успешность и усиление повторами.
Многие «тесты стабильности» считают повторно выполненный запрос успешным и получают результат лучше того, что видит пользователь. Чтобы ответить, насколько стабилен API, нужно хранить три уровня: исходный запрос, каждую попытку и конечное состояние. Иначе тайм-аут с последующим успехом превращает один нестабильный эпизод в одну строку «успех».
Сначала определите три единицы подсчёта
Назовите одно пользовательское или бизнес-действие request_id. Оно может породить несколько HTTP-попыток, но повтор не должен увеличивать число бизнес-запросов. Каждую отправку помечайте возрастающим attempt, а исходному запросу назначайте конечный статус: success, permanent_error или ambiguous.
Рекомендуется публиковать одновременно:
| Метрика | Расчёт | Назначение |
|---|---|---|
| Число исходных запросов | Число разных request_id | Сколько задач реально запустил пользователь |
| Число попыток | Число всех строк attempt | Видно повторные отправки и усиление нагрузки |
| Конечная успешность | Успешные request_id / исходные запросы | Доля задач, для которых получен итог |
| Усиление повторами | Попытки / исходные запросы | Показывает, скрывает ли повтор сбои или создаёт нагрузку |
| Неопределённые результаты | Соединение оборвалось и неизвестно, выполнил ли сервер задачу | Нужна ли сверка или идемпотентный запрос |
Нельзя публиковать только «успешность попыток». Она превращает вторую успешную попытку того же запроса в новую пару знаменателя и числителя и не показывает, насколько надёжной была первая попытка.
Какие сбои повторять, а когда остановиться
RFC 9110 считает GET, HEAD, PUT, DELETE, OPTIONS и TRACE идемпотентными методами, но это свойство описывает ожидаемый эффект и не гарантирует отсутствия внешних побочных эффектов при повторе.[1] Для POST клиент не должен автоматически отправлять запрос снова только из-за ошибки соединения, если протокол приложения не даёт идемпотентный ключ или иной способ доказать безопасность повтора.
Поэтому в журнале нужен retry_decision, а не правило «на 500 всегда повторять»:
- Ошибка DNS, установки соединения или чтения: сначала отметьте
ambiguousлибоretryable_error. Если запрос мог дойти до сервера, перед повтором нужен идемпотентный ключ, API проверки задачи или ручное подтверждение. - Ответ 429 или 503: прочитайте
Retry-Afterи подождите указанное число секунд или дату. Не заменяйте сигнал сервера фиксированным «спать одну секунду».[1] - Ответы 400, 401, 403 и 404 (ошибка модели или пути) обычно указывают на конфигурацию или права. Повтор без исправления причины лишь создаёт шум.
- Ошибка разбора: сохраните исходный статус, заголовки и место обрыва. Считайте её повторяемой только после подтверждения, что сервер не выполнил запрос и протокол это допускает.
Асинхронные задачи также требуют раздельной записи отправки, опроса и конечного состояния; пример есть в разборе задачи API со статусом 202. Эта статья добавляет влияние повторов на статистику. Для Webhook объединяйте ID события и идемпотентную обработку с разбором подписи и повторной доставки Webhook.
Минимальный формат записи
В каждой строке храните только обезличенные поля:
{
"request_id": "run-20261111-0007",
"attempt": 2,
"started_at": "2026-11-11T14:10:03+08:00",
"status": 503,
"outcome": "retryable_error",
"retry_after": 5,
"idempotency_key": "test-0007",
"provider_request_id": "redacted",
"model": "documented-model-id"
}
Не записывайте Authorization, полный промпт, Cookie или персональные данные из ответа. provider_request_id оставляйте только в виде разрешённого провайдером номера для обращения; при отсутствии номера используйте null, а не выдуманное значение.
Локальная проверка статистики
Скрипт не обращается к платному API. Он использует четыре искусственные записи: первый ответ одного запроса — 503, второй — успех, а результат третьего запроса неизвестен.
python api-stability-retry-metrics-2026-11-11.py
Ожидаемый результат: 3 исходных запроса, 4 попытки, конечная успешность 0.6667, усиление повторами 1.3333. Если считать четыре попытки независимыми наблюдениями, получится успешность попыток 0.5; это другая метрика и она не заменяет конечную успешность задач.
Проверки кода включают пустую выборку, неизвестный статус, непоследовательные номера попыток и обязательное начало нумерации с 1. В рабочем журнале добавьте часовой пояс, регион сети, версию клиента и окно теста, чтобы не смешивать разные условия.
Как спроектировать воспроизводимый тест
1. Зафиксируйте запросы и окна
Для каждой модели подготовьте короткие запросы без личных данных и запишите ID модели, параметры, версию клиента и окно времени. Выделите обычный и пиковый периоды, но не объединяйте результаты двух разных запросов в один. В каждом окне используйте один и тот же набор исходных запросов.
2. Заранее задайте предел повторов
Например, не более двух попыток для одного request_id; для 429/503 соблюдайте Retry-After, а при неизвестном результате POST остановитесь и сохраните доказательства. Предел не является обещанием провайдера и не доказывает безопасность бесконечных повторов.
3. Разделяйте четыре результата
В отчёте отдельно показывайте успех с первой попытки, успех после повтора, окончательную ошибку и неопределённый результат. Для успеха после повтора сохраняйте тип первой ошибки — иначе нельзя увидеть ухудшение в часы пик.
4. Следите за усилением повторов
Если 100 исходных запросов породили 180 попыток, коэффициент равен 1.8. Он может увеличить расходы, конкуренцию за соединения и нагрузку на upstream, даже когда конечная успешность высока. Сопоставляйте его с 429, 503 и тайм-аутами по каждому окну, чтобы понять, не создаёт ли стратегия повторов пробки.
5. Сохраняйте данные для сверки
Для ambiguous не делайте автоматический возврат или повторное списание. Сначала используйте ID запроса, API проверки задачи или выгрузку счёта; без доказательств оставляйте состояние «неизвестно». Для сверки расходов полезен разбор расхождений в выгрузке счёта API.
Распространённые ошибки интерпретации
| Наблюдение | Чего нельзя заключать сразу | Следующий шаг |
|---|---|---|
| После повтора пришёл 200 | Провайдер стабилен | Посмотреть первую ошибку и усиление |
| После тайм-аута нет списания | Запрос не выполнялся | Сверить ID запроса или счёт |
| 503 содержит Retry-After | Достаточно всегда ждать одну секунду | Разобрать секунды или дату и задать предел |
| Повтор POST успешен | Повтор не создал побочного эффекта | Проверить идемпотентный ключ или бизнес-запрос |
| Высокая конечная успешность в пик | В пиковый период нет деградации | Сравнить первые ошибки, TTFB и повторы |
Вывод
Надёжная единица теста — это «исходный запрос + последовательность попыток + конечное состояние». Сначала разделите 429, 503, тайм-аут и неизвестный результат, затем принимайте решение о повторе. Одновременный отчёт о конечной успешности и усилении повторами помогает отличить временное восстановление, повторную отправку и действительно стабильный сервис.
Источники
- RFC 9110: HTTP Semantics, разделы 9.2.2 и 10.2.3: идемпотентные методы и
Retry-After. - Документация HTTPX по тайм-аутам: различия тайм-аутов подключения, чтения, записи и пула соединений.