Перейти к основному содержимому

Ретраи. Как эффективно реализовать переповторы

Проблема: В распределенных системах временные сбои (сеть, БД, внешние сервисы) — норма. Простой вызов "повторить при ошибке" часто усугубляет проблему.

Для начала рассмотрим какие бывают ошибки

Виды ошибок

Не все ошибки равны: ретраить можно только временные сбои. Постоянные ошибки повторять бесполезно и вредно для ретраев

Повторяемые ошибки

Временные сбои, которые могут исчезнуть при повторной попытке. Типичные кейсы повторяемых ошибок:

Сетевые сбои

  • Таймауты TCP, ConnectionReset
    • Проблемы балансировщика, кратковременная недоступность сети

Ограничения ресурсов

  • HTTP 429 (Too Many Requests)
    • Превышение лимитов API (Rate Limiting)
      • Пример: Пользователь массово экспортирует данные → API временно ограничивает запросы

Ошибки серверов

  • HTTP 5xx (503, 504)
    • Перегрузка сервера, деградация БД
      • Пример: (HTTP 503) Сервис геокодирования перегружен в час пик

Конфликты данных

  • Деадлоки БД, оптимистичные блокировки
    • Конкурентные транзакции
      • Пример: деадлоки БД - два клиента одновременно редактируют один заказ

Постоянные ошибки

  • HTTP 400 (Bad Request)
    Клиент отправил невалидные данные (например, буквы в поле "Цена"). Повторы бесполезны.
  • HTTP 404 (Not Found)
    Ресурс удален (например, несуществующий ID товара). Повторы создают нагрузку.
  • HTTP 403 (Forbidden)
    Постоянное отсутствие прав (например, просмотр чужих заказов).

Стратегии повторов

Экспоненциальная задержка (Exponential Backoff)
Растущая задержка: 1с → 2с → 4с → 8с
Для снижение нагрузки на сбойный ресурс, дает ему время на восстановление.

Пример:
Пользователь оплачивает заказ → Платежный шлюз временно недоступен → Система повторяет через 0.5с, 1с, 2с → 95% платежей проходят со 2-3 попытки.

Джиттер (Jitter)
Когда к задержке добавляется случайное значение, чтобы 1000 запросов не повторились одновременно.
Формула: Фактическая задержка = Базовая задержка + random(0, 30% от задержки)
Для предотвращение синхронизации запросов

Пример:
Черная пятница: 10 000 корзин ожидают оплаты → Без джиттера: повтор всех 10к запросов одновременно → Коллапс платежной системы. С джиттером: запросы распределяются равномерно.

Комбинированные подходы

a) Retry-After + Backoff
Использование заголовка HTTP Retry-After для точного определения задержки.

HTTP/1.1 429 Too Many Requests Retry-After: 15 ← Ждать ровно 15 секунд

Когда использовать: При интеграции с внешними API (AWS, Azure, банки).

b) Адаптивные ретраи
Динамический расчет задержки на основе:

  • Истории ответов сервиса
  • Текущей нагрузки
  • SLA системы

Пример:
Система логирования увеличивает задержку с 1с до 10с при 1000 ошибок/мин.

Ограничение попыток Максимальное число ретраев (напр., 3-5). Это предотвращает бесконечные циклы.

После исчерпания попыток— фиксируем ошибку. Например:

  • Асинхронная обработка (отправка в очередь/Dead Letter Queue)
  • Уведомление мониторинга

Circuit Breaker ("Предохранитель" системы)

Это "автомат", который временно блокирует вызовы сбойного сервиса. Принцип: После N ошибок за период T, все последующие вызовы мгновенно завершаются ошибкой без реального вызова ресурса. Периодически проверяет "полуоткрытое" состояние.

Следует определить пороги срабатывания (N, T), время восстановления. Связать с SLA и поведением системы при отказе.

Состояния:

  • Closed: вызовы проходят
    Система работает нормально
  • Open: вызовы блокируются (ошибка без реального запроса)
    После 5 ошибок за 1 мин
  • Half-Open: Пропускает часть трафика для проверки восстановления

Кейс: Сервис отправки SMS падает → Circuit Breaker блокирует вызовы на 2 мин → Предотвращает:

  • Потерю денег за SMS
  • Перегрузку очереди сообщений
  • Каскадные сбои

Идемпотентность

Идемпотентность: операция должна безопасно переносить многократное выполнение (отдавать одинаковый результат). Следует требовать идемпотентности от операций, которые будут ретраиться (POST/PUT запросы, сообщения).

Долгие ретраи могут испортить UX. Для пользовательских запросов можно использовать короткие лимиты попыток или асинхронные паттерны.

Без идемпотентности выходят ошибки. Например:

  • Двойное списание денег при повторе платежа
  • 2 одинаковых заказа при сбое сети

Пример как реализовать идемпотентность:

  • Уникальные ID запросов { "payment_id": "123e4567-e89b-12d3", "amount": 100 }
  • Проверка статуса перед повторным выполнением
  • Версионирование данных в БД
  • Таймауты: общие таймауты на операцию (включая ретраи) должны быть значительно меньше таймаутов вызывающей стороны.

Кейс: Повтор платежа → Система проверяет payment_id → Если уже выполнен → возвращает текущий статус.

Логирование и Мониторинг

Детальное Логирование: фиксировать факт ретрая, номер попытки, причину ошибки, задержку. Отделять логи ретраев от исходных ошибок.

Примеры обязательных метрик:

  • Количество ретраев (total, по сервисам/ошибкам).
  • Количество успешных ретраев (восстановленных ошибок).
  • Количество срабатываний Circuit Breaker.
  • Процент запросов, завершившихся ошибкой после всех ретраев. Эти метрики показывают стабильность системы, эффективность стратегии и узкие места.

Антипаттерны: Чего избегать

a) Ретраи без ограничений
Чем опасно: Полный отказ системы при перманентной ошибке.

b) Ретраи для неидемпотентных операций
Пример: POST /cart/checkout → Повтор → 2 одинаковых заказа.

c) Каскадные ретраи

Сервис А вызывает Б → Б ретраит вызов к В → А ретраит вызов к Б.
Итог: Экспоненциальный рост нагрузки (10x).

Интеграция с Observability

a) Трассировка
Добавлять в логи:

  • retry_attempt: 2/5
  • retry_delay: 2.3s
  • root_error: DB_DEADLOCK

b) Дашборды
Обязательные графики:

  • Успешные ретраи vs Финальные ошибки
  • Топ сервисов по срабатыванию Circuit Breaker
  • Среднее время восстановления после сбоев

c) Алёрты

  • Circuit Breaker в состоянии OPEN > 5 мин
  • Эффективность ретраев < 70%

Чеклист для аналитика

При проектировании сервиса задавайте:

  1. Какие ошибки ретраить? (Список кодов + примеры из логики)
  2. Параметры стратегии:
    • max_retries = ?
    • base_delay = ?
    • jitter = ? %
  3. Circuit Breaker:
    • Порог срабатывания (например, 5/60s)
    • Таймаут восстановления (например, 30s)
  4. Как гарантировать идемпотентность?
    • Механизм (ID запросов/версии/чеки)
    • Генерирует клиент или сервер?
  5. Логирование:
    • Какие метрики выводить?
    • Где хранить логи ретраев?

Материалы

  1. Хороший ретрай, плохой ретрай, или История одного падения
  2. Отложенные ретраи силами RabbitMQ
  3. Как на практике работать над перфомансом веб-приложения: опыт Авто.ру
  4. Retry в Go: От граблей к дзену отказоустойчивости
  5. Server-side rendering и практики работы с запросами
  6. Лучшие практики создания отказоустойчивых систем
  7. Всё, что я знаю о хорошем системном дизайне
  8. 220 платежей в секунду: выдержать нельзя упасть
  9. Повторная обработка событий, полученных из Kafka
  10. Exponential Backoff или как «не завалить сервер»
  11. Логика повтора выполнена правильно: реализация экспоненциального увеличения задержки для надежных систем
  12. Управление ретраями и повторной обработкой сообщений в Kafka и RabbitMQ
  13. Лаги, джиттер и потеря пакетов: откуда берутся проблемы с неткодом и как их решать
  14. Полезные свойства джиттера

Видео

  1. Общение микросервисов: Retry против Circuit Breaker. Сергей Рогатнев, Контур
  2. Exponential Backoff | Microservices/Distributed Systems Strategy | Zerodha System Design(eng)

Книги

Паттерны проектирования API - Джей Гивакс (Часть IV. Безопасность).