Оптовая платформа цифровых товаров

Retry/backoff паттерны для B2B-интеграций 2026: Polly, axios-retry, tenacity

Production retry/backoff паттерны для B2B-интеграций: exponential + jitter, circuit breaker и примеры кода на 3 языках.

Retry/backoff паттерны для B2B-интеграций 2026: Polly, axios-retry, tenacity

Retry — это не «обернул в try/catch и в while-loop». Правильная стратегия зависит от типа ошибки, expected SLA upstream-сервиса, и текущего load на recovery. Эта статья — практический справочник retry-паттернов для B2B-интеграций с примерами production-кода.

1. Базовая формула: exponential + jitter

function delay(attempt: number, baseMs = 500, capMs = 30000): number {
  const exp = Math.min(baseMs * Math.pow(2, attempt), capMs);
  const jitter = Math.random() * exp * 0.5; // ±50%
  return exp / 2 + jitter; // «full jitter» AWS-style
}
// attempt 0: 250–750ms
// attempt 1: 500–1500ms
// attempt 2: 1000–3000ms
// attempt 5: 8000–24000ms

«Full jitter» (формула выше) — AWS-recommended; снижает peak load на recovery лучше, чем «equal jitter».

2. Когда ретраить и когда нет

HTTP status Retryable? Comment
5xx Yes Server-side transient
429 Yes Respect Retry-After header
408 Yes Client timeout
4xx (rest) No Permanent client error
Network error Yes DNS, conn reset, TLS
Timeout Yes But careful — может ужe completed

Особый случай для timeouts на POST: повторяйте только с idempotency key, иначе создадите дубли.

3. axios-retry (Node.js)

import axios from 'axios';
import axiosRetry from 'axios-retry';

const client = axios.create({ baseURL: 'https://api.foxreload.com', timeout: 30000 });
axiosRetry(client, {
  retries: 5,
  retryDelay: (count) => axiosRetry.exponentialDelay(count) + Math.random() * 1000,
  retryCondition: (err) => {
    if (!err.response) return true; // network
    const s = err.response.status;
    return s >= 500 || s === 429 || s === 408;
  },
  shouldResetTimeout: true,
});

shouldResetTimeout: true — обязательно: иначе одна 30s-timeout съест общий retry budget.

4. tenacity (Python)

from tenacity import retry, stop_after_attempt, wait_exponential_jitter, retry_if_exception_type
import httpx

@retry(
    stop=stop_after_attempt(6),
    wait=wait_exponential_jitter(initial=0.5, max=30, jitter=2),
    retry=retry_if_exception_type((httpx.TransportError, httpx.HTTPStatusError)),
    reraise=True,
)
def create_order(payload, idem_key):
    r = httpx.post('https://api.foxreload.com/v1/orders',
                   json=payload,
                   headers={'Idempotency-Key': idem_key},
                   timeout=30)
    if r.status_code >= 500 or r.status_code == 429:
        r.raise_for_status()
    return r.json()

5. Polly (C#/.NET)

var policy = Policy
    .HandleResult<HttpResponseMessage>(r => (int)r.StatusCode >= 500 || (int)r.StatusCode == 429)
    .Or<HttpRequestException>()
    .WaitAndRetryAsync(5, attempt =>
        TimeSpan.FromMilliseconds(500 * Math.Pow(2, attempt)
            + Random.Shared.Next(0, 500)));

var circuit = Policy
    .HandleResult<HttpResponseMessage>(r => (int)r.StatusCode >= 500)
    .CircuitBreakerAsync(5, TimeSpan.FromSeconds(60));

var combined = Policy.WrapAsync(circuit, policy);
var resp = await combined.ExecuteAsync(() => client.PostAsync("/v1/orders", body));

6. Circuit breaker — когда retry уже не помогает

Если upstream фейлит 50%+ за 30 секунд — открывайте breaker и фейлите сразу:

import CircuitBreaker from 'opossum';
const breaker = new CircuitBreaker(callFoxreload, {
  errorThresholdPercentage: 50,
  resetTimeout: 60_000,
  rollingCountTimeout: 30_000,
});
breaker.fallback(() => ({ fromCache: true }));

Это спасает downstream от лавины retry-traffic во время outage.

CTA

FoxReload API возвращает Retry-After на 429 и uses HTTP/2 для всех endpoints. Полные retry-recommendations — в onboarding-документе после получения доступа.

Часто задаваемые вопросы

Сколько retry attempts оптимально для B2B API?
5–8 attempts, с total budget < 5 minutes. Больше — клиент видит timeout, меньше — теряете заказы при transient errors. FoxReload retry-эндпоинты лимитированы 8 retries за 24h. Для real-time эндпоинтов (catalog, balance) — 3 retries за 30s.
Что НЕ нужно ретраить?
Любой 4xx кроме 408 (timeout) и 429 (rate limit). 400 bad request, 401 unauthorized, 403 forbidden, 404 not found, 422 validation error — это permanent errors. Retry их = бесконечный цикл без шанса успеха.
Зачем добавлять jitter в backoff?
Без jitter все клиенты ретраят синхронно (через 30s, 60s, 120s) — это thundering herd на recovery. Jitter ±30% размазывает retry-волны по 25–35s, 50–70s, 100–140s. Снижает peak load на recovery в 4–6 раз.
Полезен ли circuit breaker если уже есть retry?
Да. Retry защищает от единичных ошибок (network blip). Circuit breaker защищает от sustained outage — после 50% failures за 30s breaker открывается, дальнейшие requests fail immediately, не нагружают upstream. Recovery: half-open canary через 60s.
Получить доступ к FoxReload API

Похожие статьи