재시도 간격을 점점 두 배씩 늘리는 방식(exponential backoff).
1차 실패 → 1초 대기 2차 실패 → 2초 대기 3차 실패 → 4초 대기
- 4차 실패 → 8초 대기 — (2ⁿ 으로 증가)
왜 간격을 늘리나
고정 간격으로 재시도하면 죽어 가는 서버에 계속 부하를 준다.
서버가 과부하로 느려짐
→ 100개 클라이언트가 1초마다 재시도
→ 초당 100건의 추가 부하가 계속 유입
→ 서버가 영영 회복하지 못한다
간격을 늘리면 시간이 갈수록 부하가 줄어 회복할 여유가 생긴다. 동시에 "일시적 장애라면 초반에 빨리 복구"라는 요구도 만족한다.
지터가 반드시 필요하다 — 이것이 핵심
지수 백오프만으로는 재시도가 동시에 몰린다.
장애 발생 (t=0) → 1000개 클라이언트가 동시에 실패
지터 없음: 전부 정확히 t=1초에 재시도 → 1000건 동시 → 또 죽음
- t=3초에 또 1000건 동시 → 또 죽음
- ⟳ 동기화된 파도가 반복된다 (thundering herd)
지터 있음: t=0.3, 0.7, 0.9, 1.2 … 로 흩어짐 → 부하가 평탄해진다
"장애가 복구되는 순간 재시도가 한꺼번에 몰려 다시 죽는다" — 실제 장애 사례에서 매우 흔한 패턴이다.
지터 방식
# Full Jitter — AWS 권장. 가장 균등하게 퍼진다
delay = random(0, min(cap, base * 2**n))
# Equal Jitter — 최소 대기는 보장하면서 분산
temp = min(cap, base * 2**n)
delay = temp/2 + random(0, temp/2)
# Decorrelated Jitter — 이전 대기 시간을 기준으로
delay = min(cap, random(base, prev * 3))
AWS의 실험에서 Full Jitter가 총 완료 시간과 서버 부하 모두에서 가장 좋았다. "무작위를 많이 넣을수록 좋다"가 결론이다.
반드시 함께 둘 것
| 항목 | 이유 |
|---|---|
| 최대 재시도 횟수 | 무한 재시도는 자원을 영원히 붙잡는다 |
| 최대 대기 시간(cap) | 2ⁿ은 금방 커진다. 10차면 1024초 |
| 전체 타임아웃(deadline) | 재시도 총합이 사용자 인내를 넘으면 무의미 |
재시도해도 되는 것과 안 되는 것 — 가장 중요한 판단
✅ 재시도 가능 : 타임아웃, 연결 실패, 503, 429, 일시적 네트워크 오류 ❌ 재시도 금지 : 400(잘못된 요청), 401, 403, 404, 422
- 몇 번을 해도 결과가 같다. 자원 낭비일 뿐
멱등하지 않은 요청은 특히 위험하다.
- POST /payments 요청 → 타임아웃
- 서버는 실제로 결제를 처리했는데 응답만 유실됐을 수 있다
- 재시도하면 이중 결제
→ 멱등성 키를 함께 보내 서버가 중복을 걸러내게 해야 한다. 재시도 로직과 멱등성은 항상 짝으로 설계한다.
서킷 브레이커와의 관계
- 재시도 — : 개별 요청 수준의 일시적 실패 흡수
- 서킷 — : 대상 전체가 망가졌다고 판단되면 아예 차단
서킷이 OPEN이면 재시도하지 않는다. 둘을 같이 쓸 때 재시도를 서킷 안쪽에 두면 실패 1건이 3건으로 부풀려져 서킷이 필요 이상으로 빨리 열린다.
429 Too Many Requests
서버가 Retry-After 헤더를 주면 그 값을 따르는 것이 우선이다.
자체 백오프 계산보다 서버가 아는 정보가 정확하다.