🌍 Добрай раніцы! 🇧🇾
TL;DR
- Три подхода к ретраю: простой, линейный и экспоненциальный
- Экспоненциальный backoff — оптимальный баланс повторов и паузы
- Jitter защищает от Thundering Herd четырьмя способами
- Не все ошибки нужно ретраить: 404, 401 — бессмысленно
🌍 Добрай раніцы! 🇧🇾
Недавно пришлось имплементировать retry-логику. Один из сторонних сервисов, от которого зависит проект, к сожалению, нестабилен 😑.
Вариантов реализации куча — уверен, что кто-то из вас сделал бы по-другому (или вообще без ретраев). Но в рамках моей задачи мне нужен был retry, и, как в очерке Максима Горького "Простой, как правда".
🪣 Варианты реализации
✔️ Simple retry
Функция принимает:
-
-
-
💡 Можно сделать ещё проще — убрать
✔️ Линейное увеличение задержки
✏️ Добавляем параметр
✔️ Экспоненциальный backoff
На мой взгляд — самый сбалансированный способ. Сначала быстрые ретраи, потом увеличение интервала, когда сервису нужно время "прийти в себя".
✏️ Базовая формула:
Поговорили о вариантах реализации, есть еще бонус который сделает вашу систему более способной не завалить только поднятый сервер внешной системы - Jitter ⚡️.
🎲 Jitter — добавляем случайность
Jitter можно применить к любому из вышеперечисленных способов. Есть такое понятие Thundering herd, я про него не рассказывал, как-нибудь следующий раз. Если кратко, у системы к которой мы обращаемся возникли проблемы, чудом они скэйлятся и поднимается новый узел, и в этот прекрасный момент, клиенты с одинаковым таймингом на ретрае делают запрос и роняют свеже поднятый сервачок, там вылетает память, превышается RPS и как следствие снова бобик сдох.
🪣 Варианты реализации Jitter
✔️ Percentage jitter
Добавляем случайную погрешность к задержке (±10 %):
✔️ Full jitter
Полностью случайная задержка до рассчитанного значения:
✔️ Decorrelated jitter
Новая задержка зависит от предыдущей, чтобы избежать синхронных всплесков:
✔️ Equal jitter
Все значения лежат в верхней половине диапазона:
✏️ Полезности
🟠 Слишком частые или долгие ретраи могут превратиться в
🟠 Не все ошибки нужно ретраить: например,
🟠 Бэкграунд-задачи могут иметь более длительный retry.
🟠 Всегда ставьте
🟠 Логируйте каждую попытку, потом проще понять что происходит.
🟠 Используйте
⬇️ Все примеры, наглядно, тута.
#BESTPRACTICES
Недавно пришлось имплементировать retry-логику. Один из сторонних сервисов, от которого зависит проект, к сожалению, нестабилен 😑.
Вариантов реализации куча — уверен, что кто-то из вас сделал бы по-другому (или вообще без ретраев). Но в рамках моей задачи мне нужен был retry, и, как в очерке Максима Горького "Простой, как правда".
🪣 Варианты реализации
✔️ Simple retry
Функция принимает:
-
fn — функцию, которую нужно вызвать-
delay — задержку между попытками-
retries — количество попыток.async function simpleRetry(fn, retries, delay) {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (error) {
if (i === retries - 1) throw error;
await sleep(delay);
}
}
}
💡 Можно сделать ещё проще — убрать
delay совсем и просто выполнять вызовы подряд (если это допустимо по логике).✔️ Линейное увеличение задержки
(1 с → 2 с → 3 с → 4 с …)
✏️ Добавляем параметр
step — шаг увеличения задержки.async function retryWithLinearDelay(fn, retries, baseDelay, step) {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === retries) throw error;
const delay = baseDelay + step * (attempt - 1);
await sleep(delay);
}
}
}
✔️ Экспоненциальный backoff
(1 с → 2 с → 4 с → 8 с …)
На мой взгляд — самый сбалансированный способ. Сначала быстрые ретраи, потом увеличение интервала, когда сервису нужно время "прийти в себя".
✏️ Базовая формула:
delay = baseDelay * (factor ^ retryNumber)async function retryWithExponentialDelay(fn, retries, baseDelay, factor = 2) {
for (let attempt = 0; attempt < retries; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === retries - 1) throw error;
const delay = baseDelay * Math.pow(factor, attempt);
await sleep(delay);
}
}
}Поговорили о вариантах реализации, есть еще бонус который сделает вашу систему более способной не завалить только поднятый сервер внешной системы - Jitter ⚡️.
🎲 Jitter — добавляем случайность
Jitter можно применить к любому из вышеперечисленных способов. Есть такое понятие Thundering herd, я про него не рассказывал, как-нибудь следующий раз. Если кратко, у системы к которой мы обращаемся возникли проблемы, чудом они скэйлятся и поднимается новый узел, и в этот прекрасный момент, клиенты с одинаковым таймингом на ретрае делают запрос и роняют свеже поднятый сервачок, там вылетает память, превышается RPS и как следствие снова бобик сдох.
🪣 Варианты реализации Jitter
✔️ Percentage jitter
Добавляем случайную погрешность к задержке (±10 %):
delay + (Math.random() * 2 - 1) * delay * 0.1;baseDelay → with jitter
1 → 0.9–1.1
2 → 1.8–2.2
4 → 3.6–4.4
8 → 7.2–8.8✔️ Full jitter
Полностью случайная задержка до рассчитанного значения:
Math.random() * delay;1 → (0–1)
2 → (0–2)
4 → (0–4)
8 → (0–8)
✔️ Decorrelated jitter
Новая задержка зависит от предыдущей, чтобы избежать синхронных всплесков:
Math.min(maxDelay, Math.random() * (3 * prevDelay - delay) + delay);
delay → prevDelay → range
1 → 1 → (1–2)
2 → 1.8 → (2–3.4)
4 → 2.9 → (4–6.7)
8 → 5.8 → (8–10)✔️ Equal jitter
Все значения лежат в верхней половине диапазона:
(delay / 2) + Math.random() * (delay / 2);1 → (0.5–1)
2 → (1–2)
4 → (2–4)
8 → (4–8)✏️ Полезности
🟠 Слишком частые или долгие ретраи могут превратиться в
infinity loop и повесить ваш процесс.🟠 Не все ошибки нужно ретраить: например,
404, 401.., их повтор не изменит результат.🟠 Бэкграунд-задачи могут иметь более длительный retry.
🟠 Всегда ставьте
maxRetries или общий timeout, чтобы не зависнуть навсегда.🟠 Логируйте каждую попытку, потом проще понять что происходит.
🟠 Используйте
circuit breaker — если внешний сервис падает, а вы продолжаете ретраить, то фактически помогаете его добить. Circuit breaker позволяет отключить вызовы на короткое время, дать сервису восстановиться и не создавать лавину запросов.⬇️ Все примеры, наглядно, тута.
#BESTPRACTICES

Хотите больше таких постов?
Подпишитесь на канал и читайте продолжение в Telegram.