Какие HTTP коды ошибок имеет смысл ретраить, а какие нет
Что проверяют
Понимаете ли вы разницу между временной и постоянной ошибкой и помните ли про идемпотентность. Следом почти всегда спрашивают, можно ли ретраить POST и что будет, если ретраи стоят на нескольких уровнях сразу.
Как отвечать
Разделите коды на три группы:
- ретраить: сетевые ошибки и таймауты соединения,
408,429(с учётомRetry-After),502,503(тоже часто присылаетRetry-After),504. Это перегрузка, перезапуск или недоступный апстрим, повтор через паузу имеет шансы; - осторожно:
500. Это может быть сбой, а может быть детерминированный баг, который упадёт на каждой попытке, поэтому одна-две повторные попытки максимум. Сюда же409: повторять имеет смысл только после перечитывания состояния, а не тем же запросом; - не ретраить:
400,401,403,404,405,410,415,422,501. Повтор без изменения запроса даст тот же ответ. Исключение: при401можно один раз обновить токен и повторить.
Второе измерение это метод. GET, HEAD, PUT, DELETE идемпотентны по RFC 9110, а POST и PATCH нет: таймаут не значит, что сервер ничего не сделал, и повтор может создать второй заказ. Такие запросы повторяют только с ключом идемпотентности, обычно в заголовке Idempotency-Key.
Инструменты в Python: в requests это HTTPAdapter(max_retries=Retry(...)) из urllib3, где задают total, status_forcelist, backoff_factor, allowed_methods (по умолчанию POST в нём нет) и respect_retry_after_header. У httpx параметр retries транспорта повторяет только ошибки соединения, статусы придётся обрабатывать самим, например через tenacity. Паузы экспоненциальные с джиттером, плюс лимит попыток и общий дедлайн.
Где спотыкаются
Ретраят все 5xx на любых методах. Первый же таймаут на создании платежа превращается в двойное списание.
Ставят фиксированную паузу без джиттера. Тысячи клиентов повторяют запрос одновременно и добивают сервис, который пытался подняться.
Не замечают вложенных ретраев: по три попытки в клиенте, в сервисе и в шлюзе дают до 27 запросов на один клик. Ретраи должны жить на одном уровне.