Phase 5레슨 22
Retry & Timeout
Exponential Backoff, Jitter, Timeout Budget, Hedging — 실패한 요청을 어떻게 재시도할 것인가
💡ELI5·
전화 재시도와 Retry Storm
서버 장애 (1초): 1000개 요청 → 실패 → 1초 후 1000개 재시도 → 또 실패 → 2초 후 1000개... → 서버가 복구되려는 순간 또 재시도 폭주 → 영영 복구 불가 해결 (Backoff + Jitter): 요청1: 1.2초 후 재시도 요청2: 1.8초 후 재시도 요청3: 2.1초 후 재시도 ← 분산됨 → 서버가 틈틈이 회복 가능
💡 비유 — 친구에게 전화가 안 걸려서 100명이 동시에 1초 간격으로 재다이얼하면, 기지국이 폭주해서 아무도 못 건다. 각자 랜덤한 시간에 재다이얼하면 기지국이 여유를 갖고 처리할 수 있다.
🔬Deep Dive·
Exponential Backoff + Jitter
// 잘못된 재시도: 고정 간격 (동기화 폭주)
async function retryBad(fn) {
for (let i = 0; i < 3; i++) {
try { return await fn(); }
catch { await sleep(1000); } // 모든 클라이언트가 1초 후 동시 재시도
}
}
// 올바른 재시도: Exponential Backoff + Full Jitter
async function retryGood(fn) {
const base = 1000; // 1초
const cap = 30000; // 최대 30초
for (let i = 0; i < 5; i++) {
try { return await fn(); }
catch (e) {
if (i === 4) throw e;
const exp = Math.min(cap, base * 2 ** i);
const jitter = Math.random() * exp; // 0 ~ exp 사이 무작위
await sleep(jitter);
}
}
}| Jitter 방식 | 공식 | 특징 |
|---|---|---|
| Full Jitter | random(0, base * 2^n) | 가장 분산이 큼, AWS 권장 |
| Equal Jitter | base/2 + random(0, base/2^n / 2) | 최소 대기 시간 보장 |
| Decorrelated Jitter | random(base, prev_sleep * 3) | 이전 대기 시간 기반 |
🔬Deep Dive·
Timeout Budget
사용자 요청 (총 5초 예산) ├── API Gateway: 4.5초 │ ├── 인증: 0.5초 │ └── 주문 서비스: 3.5초 │ ├── DB 쓰기: 1.5초 │ └── 결제 서비스 호출: 2초 └── 응답 직렬화: 0.5초 하위 타임아웃 합 ≤ 상위 타임아웃 → 결제가 2초 초과하면 주문 서비스가 끊어냄 (사용자 대기 방지)
하위 서비스의 타임아웃이 상위보다 길면, 상위가 먼저 타임아웃되어 사용자는 에러를 보지만 하위는 계속 처리 → 자원 낭비. 반드시 하위 ≤ 상위.
⚖️Trade-off·
재시도의 Trade-off
| 이점 | 위험 |
|---|---|
| 일시적 장애 자동 복구 | Retry Storm으로 장애 악화 |
| 사용자 경험 개선 (재시도 후 성공) | 비멱등 연산 시 중복 부작용 |
| 꼬리 지연 감소 (Hedging) | 서버 부하 증가 (Hedging 2배) |
| 재시도 중첩으로 지연 누적 |
❓ 체크포인트 질문
- 1.단순 재시도가 위험한 이유는?
- 2.Exponential Backoff에 Jitter가 필요한 이유는?
- 3.Timeout Budget이 무엇인가?
- 4.Hedging Request가 무엇이고 언제 유용한가?
- 5.재시도하면 안 되는 상황은?