Политики повторов и backoff
Повторы (retries) помогают пережить временные сбои, но при неправильной настройке вызывают лавины трафика, дубли операций и каскадные падения. Надежная политика ретраев всегда начинается с дедлайнов/таймаутов, учитывает идемпотентность и использует backoff + джиттер.
1) Базовые принципы
1. Сначала таймаут/дедлайн, потом ретрай. Повтор без предела времени лишь удлиняет отказ.
2. Ретрай — только для безопасных/идемпотентных операций. Для небезопасных — через idempotency-keys и транзакционные гарантии.
3. Backoff обязателен. Экспоненциальный либо GCRA-подобный с джиттером, чтобы рассинхронизировать волны.
4. Ограничивайте количество попыток и общую бюджет-времени. Не выходите за SLO пользователя.
5. Уважайте сигналы инфраструктуры. `429/503` + `Retry-After`, состояние circuit breaker, лимиты очередей.
2) Таймауты и дедлайны (deadline propagation)
Таймаут запроса < SLO сервиса, дедлайн распространяется вниз по цепочке (HTTP заголовки/gRPC context).
Композиция таймаутов: суммарно (первая попытка + backoff + последующие) ≤ пользовательский дедлайн.
Разные таймауты для чтений/записей: записи короче и строже; чтения допускают hedging.
3) Идемпотентность и безопасные повторы
Чтения (GET/идемпотентные RPC): безопасно повторять при `5xx`, `UNAVAILABLE`, сетевых таймаутах.
Записи:- Используйте `Idempotency-Key` (HTTP) или request-ID на границе; сервер должен дедуплицировать.
- Пишите идемпотентные обработчики: «upsert», «at-least-once» + компенсации (сага).
- Внешние платежи/взаиморасчеты — только с идемпотентными ключами и журналом операций.
4) Алгоритмы backoff
Exponential: `base 2^attempt`, ограниченный `max_backoff`.
Decorrelated Jitter (full/equal jitter): случайность в диапазоне для рассинхронизации клиентов.
GCRA/Token-Bucket-подобные задержки: согласованы с rate limits.
Границы: `initial_backoff` (50–200 мс чтения; 200–500 мс записи), `max_backoff` (1–5 с), `max_elapsed` (например, 3–10 с).
Рекомендуемый шаблон: экспоненциальный backoff + full jitter.
5) Политики принятия решения «повторять/не повторять»
Повторяем при:- Сетевых ошибках/таймаутах, `429` (с уважением `Retry-After`), «мягких» `5xx` (`502/503/504`), gRPC `UNAVAILABLE/DEADLINE_EXCEEDED`.
- `4xx` (кроме `409/429/408` в некоторых сценариях), бизнес-ошибках, `401/403`, валидационных ошибках, явном `DoNotRetry` флаге.
- `409 Conflict` — иногда повтор с задержкой после консенсуса/локов.
- `404` для eventually consistent чтений — одно-двухкратный ретрай с малым backoff.
6) Concurrency caps и «шторм ретраев»
Ограничьте одновременные ретраи per-client/per-tenant/per-endpoint.
Суммарный лимит попыток на запрос (например, 2–3).
Тормозите «горячие» эндпойнты через admission control, чтобы не раздувать очереди.
7) Взаимодействие с Circuit Breaker и лимитами
Если CB open, не выполняйте ретраи напрямую — идите в fallback или ждите `half-open` проб.
При `429` — уважайте `Retry-After`; при его отсутствии — применяйте «мягкий» backoff.
Ретраи могут увеличивать нагрузку; применяйте адаптивные пороги (снижайте `max_attempts` при инциденте).
8) Протоколы и контракты
HTTP
Коды: `408/429/5xx`.
Заголовки: `Retry-After`, семейство `RateLimit-`, `Idempotency-Key`, `Request-Id`.
Клиент должен передавать `X-Request-Timeout`/`Deadline-At` (если у вас так принято).
gRPC
Используйте контекст с дедлайном; уважайте `UNAVAILABLE`, `DEADLINE_EXCEEDED`, полисы ретраев на метод.
Для идемпотентных RPC — включайте ретраи; для мутаций — только при поддержке идемпотентности.
9) Очереди, фоновые задачи и интеграции
At-least-once обработчики → идемпотентные действия, дедупликация по ключу.
Delay queue для backoff между попытками (например, 5s/30s/2m).
Dead-letter queue (DLQ) с лимитом попыток и ручной обработкой.
Outbox/CDC — чтобы повторы не нарушали транзакционную целостность.
10) Hedging vs Retries
Hedging (зеркальный запрос) полезен для высококритичных чтений при хвостовой латентности.
Ограничьте: не чаще X% запросов, задержка «старт-грейс» (например, p95-латентности), отмена проигравших.
Не применяйте hedging к операциям записи без сильной идемпотентности.
11) Телеметрия и наблюдаемость
Теги: `tenant_id`, `endpoint`, `attempt`, `decision` (retry/skip), `reason`, `backoff_ms`, `deadline_ms`, `idempotency_key`.
Метрики: доля ретраев, успешность после ретраев, p95/p99 «end-to-end», количество превышенных дедлайнов, срабатывания CB.
Логи аудита: верхние N «шумных» ключей/эндпойнтов, корреляция с 429/503.
12) Тестирование и хаос
Профили: «пила» (burst-затишье), «шторм» (массовые таймауты), «липкие» ошибки (каждый N-й), хвосты латентности.
Отказы стора лимитера/кэша/очереди, clock-skew.
Проверка, что общая длительность (attempts + backoff) укладывается в SLO.
13) Псевдокод политики
pseudo handle(req, deadline):
attempt = 0 backoff = initial()
while attempt < MAX_ATTEMPTS and now() < deadline:
attempt += 1 with timeout(per_attempt_timeout(deadline, attempt)):
try:
resp = call(req)
if isRetryableStatus(resp): raise Retryable(resp. status)
return resp except Retryable as e:
if circuit. isOpen(dep) or! isIdempotent(req): break sleep(jitter(backoff))
backoff = min(exp(backoff), MAX_BACKOFF)
except NonRetryable:
break return fail_or_fallback(req)
14) Конфигурационный шаблон (пример)
yaml retries:
default:
max_attempts: 3 initial_backoff_ms: 150 max_backoff_ms: 2000 strategy: exponential_full_jitter respect_retry_after: true per_attempt_timeout_fraction: 0. 4 # 40% of remaining deadline hedging:
enabled: false read_heavy:
max_attempts: 4 initial_backoff_ms: 80 max_backoff_ms: 1200 hedging:
enabled: true start_after_p95_ms: 300 max_extra_requests_ratio: 0. 05 write_strict:
max_attempts: 2 initial_backoff_ms: 250 max_backoff_ms: 1000 idempotency_required: true
limits:
concurrent_retries_per_tenant: 100 concurrent_retries_per_endpoint: 20
15) Чек-лист перед продом
- Дедлайны распространяются сквозь вызовы; суммарная длительность ≤ SLO.
- Алгоритм backoff с джиттером; параметры валидированы нагрузочным тестом.
- Идемпотентность: ключи/журналы/компенсации для записей.
- Ретрай-политики различаются для чтений и записей; respect `Retry-After`.
- Конкурентность ретраев и общее число попыток ограничены.
- Интеграция с circuit breaker и rate limits настроена.
- Телеметрия: теги, метрики, логи причин; дашборды p95/p99, доля успехов после ретраев.
- Тесты «штормов» и хвостов латентности, DLQ для задач.
- Документация для клиентов: коды/заголовки, примеры backoff и отмены.
16) Типичные ошибки
Повторы без таймаутов/дедлайнов — «вечные ожидания».
Фиксированные паузы без джиттера — синхронные волны и DDoS-самосаботаж.
Ретраи небезопасных записей без идемпотентности — дубли и рассинхронизация.
Игнорирование `Retry-After` и сигналов CB — эскалация инцидента.
Узкие места в очередях из-за отсутствия caps и admission control.
Отсутствие телеметрии причин/решений ретраев — «слепой полет».
17) Быстрые рецепты
Публичные чтения API: 3 попытки, `initial=100ms`, `max=1s`, full jitter, hedging до 5% трафика.
Критичные записи (платеж): 1–2 попытки макс, строгий таймаут, обязательный `Idempotency-Key`, без hedging.
Внешние интеграции: уважать `429/Retry-After`, `max_attempts=3`, `max_backoff=2–5s`, лимиты на исходящий поток.
Фоновые задачи: delay-backoff (5s → 30s → 2m), DLQ, идемпотентные обработчики.
Заключение
Хорошая политика повторов — это баланс между скорым восстановлением и контролируемым отказом. Дедлайны, идемпотентность, экспоненциальный backoff с джиттером, ограничение конкуренции и уважение сигналов инфраструктуры превращают ретраи из «шторма» в инструмент повышения надежности и удержания SLO.