[분산시스템] 타임아웃과 재시도 전략: 재시도 폭풍, 지수 백오프와 지터, 타임아웃 예산
![[분산시스템] 타임아웃과 재시도 전략: 재시도 폭풍, 지수 백오프와 지터, 타임아웃 예산](https://blog.kakaocdn.net/dna/bhtr9h/dJMb997HQNp/AAAAAAAAAAAAAAAAAAAAANcdfghnr302uFhVZG92TfSs9Jj6EoGWgw_E_d3CXJob/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1788188399&allow_ip=&allow_referer=&signature=zx4HHg%2BkBa3tz%2FBNmi34kvE0YMM%3D)
[분산시스템] 타임아웃과 재시도 전략: 재시도 폭풍, 지수 백오프와 지터, 타임아웃 예산
재시도는 분산 시스템의 기본 회복 수단인데, 잘못 쓰면 장애를 키우는 증폭기가 돼요. 과부하로 비틀거리는 서비스에 모든 호출자가 일제히 3배의 재시도 트래픽을 퍼붓는 — 재시도 폭풍이에요. 그 폭풍을 막는 표준 처방(지수 백오프+지터)과 짝꿍 개념인 타임아웃 예산을 짚으며 분산 패턴 시리즈를 마무리해요.
01. 재시도 폭풍, 선의가 DDoS가 된다

상대가 과부하로 실패하기 시작하면, 호출자들은 "일시 장애겠지" 하고 재시도해요. 전원이 3회씩 재시도하면 트래픽이 순간 3~4배가 돼요. 평소 부하도 못 버티던 상대는 더 깊이 무너지고 회복이 불가능해져요. 선의의 재시도가 사실상 DDoS가 되는 거예요.
고정 간격 재시도는 한 술 더 떠요 — 같은 순간 실패한 호출자들이 정확히 1초 후 일제히 다시 때리고 또 일제히... 부하가 파도처럼 박자를 맞춰 몰려요.
02. 처방, 백오프 + 지터 + 한도

- 지수 백오프 — 1초 → 2초 → 4초로 점점 물러나요. 일시 장애는 첫 재시도에 잡히고 진짜 장애엔 부하를 빠르게 줄여요.
- 지터(jitter) — 간격에 무작위를 섞어요(예: 0~백오프값 사이 랜덤). 호출자들의 박자를 흩어 파도를 평탄하게 만들어요. 캐시 TTL jitter와 정확히 같은 원리예요 — "동시에 몰리는 것"이 적이면 무작위가 약이에요.
- 횟수 제한 — 2~3회면 충분해요. 그걸로 안 되면 일시 장애가 아니라는 뜻이고 그때부턴 서킷 브레이커가 이어받아 아예 차단하는 게 맞아요. 재시도(짧은 장애용)와 서킷(긴 장애용)은 역할 분담이에요.
// Resilience4j Retry — 백오프 + 지터
RetryConfig config = RetryConfig.custom()
.maxAttempts(3) // 최초 호출 포함 3회 = 재시도는 2회
.intervalFunction(IntervalFunction.ofExponentialRandomBackoff(
Duration.ofMillis(500), 2.0, 0.5)) // 0.5s부터 2배씩, ±50% 지터
.retryExceptions(IOException.class, TimeoutException.class)
.ignoreExceptions(BusinessException.class) // 비즈니스 실패는 재시도 무의미
.build();
가장 중요한 전제 — 재시도는 멱등한 작업에만. "타임아웃 = 실패"가 아니라 "결과 모름"이라는 걸 기억하세요(멱등 API 편의 그 비대칭). 처리됐는데 응답만 유실된 호출을 재시도하면 중복 실행이에요. 결제 호출의 재시도는 Idempotency-Key가 깔려 있을 때만 안전하고 그게 없다면 재시도 대신 "상태 조회 후 판단"이 맞아요. 그리고 비즈니스 실패(잔액 부족)는 백 번 재시도해도 똑같으니 재시도 대상에서 빼요 — 카프카 에러 분류의 일시적/영구적 구분이 여기서도 그대로예요.
03. 타임아웃 예산, 바깥보다 안쪽이 짧아야
재시도와 한 몸인 게 타임아웃이에요. 타임아웃 없는 재시도는 "언제 실패할지"가 없어 성립하지 않고 타임아웃 값들이 서로 안 맞으면 또 다른 사고가 나요.

원칙은 예산 배분이에요. 게이트웨이가 3초에 끊는다면, 그 안의 내부 호출들 + 재시도 시간의 합이 3초 안에 들어와야 해요. 흔한 사고는 반대예요 — 안쪽 외부 API 타임아웃이 10초인데 바깥이 3초면, 클라이언트는 3초에 떠나서(어쩌면 벌써 재시도해서) 응답 받을 사람이 없는데 서버는 7초를 더 일해요. 자원 낭비에 중복 처리까지 겹치죠.
실무 규칙으로 정리하면:
- 모든 외부 호출에 명시적 타임아웃 — 기본값(무한대거나 너무 긴)을 믿지 않아요. HTTP 클라이언트의 connect/read 타임아웃, Redis, DB까지 전부요.
- 안쪽 합(재시도 포함) < 바깥 — 재시도 1회를 계획한 호출이라면 (타임아웃 × 2 + 백오프)가 예산 안에 들어와야 해요.
- 타임아웃은 p99 기준으로 — 평균이 아니라 정상 응답의 99퍼센타일보다 약간 크게. 너무 짧으면 멀쩡한 요청을 죽이고(자체 오탐), 너무 길면 장애 때 자원이 묶여요.
04. 어디서 재시도할 것인가
마지막 설계 질문 — 재시도를 어느 층에서 하느냐예요. 클라이언트도, 게이트웨이도, 내 서비스도, 메시지큐 컨슈머도 재시도를 하면 곱셈이 일어나요. 3회 × 3회 × 3회 = 27배 증폭이죠.
원칙은 "재시도는 한 층에서만"이에요. 보통 실패의 의미를 가장 잘 아는 층이 맡아요 — 외부 API 호출이면 그걸 부르는 서비스가, 메시지 처리면 컨슈머의 재시도+DLQ가요. 나머지 층은 재시도 없이 실패를 그대로 전달하고 최종 사용자 층에서만 "다시 시도" 버튼으로 사람의 판단에 맡겨요.
05. 자주 만나는 문제
장애가 났는데 트래픽이 오히려 몇 배로 뛰어요
재시도 폭풍이에요. 백오프+지터를 깔고 층층이 재시도가 중첩돼 있지 않은지(곱셈) 점검해요.
재시도 때문에 중복 처리가 생겨요
비멱등 작업의 재시도예요. Idempotency-Key(1편)를 깔거나 재시도 대신 조회-후-판단으로 바꿔요.
타임아웃을 줄였더니 멀쩡한 요청이 실패해요
p99보다 짧게 잡은 거예요. 실측 분포를 보고 정하고 느린 의존성 자체의 개선과 병행해요.
정리 — 그리고 시리즈를 닫으며
재시도의 공식은 멱등 전제 + 지수 백오프 + 지터 + 2~3회 한도 + (그 너머는 서킷)이고 타임아웃은 전 구간 명시 + 안쪽이 바깥보다 짧게예요. 그리고 재시도는 한 층에서만 해요.
이걸로 분산 패턴 시리즈가 마무리돼요. 멱등 API(중복을 무해하게), 아웃박스(전달을 확실하게), 사가(흐름의 정합성), 서킷 브레이커(장애 격리), 그리고 타임아웃과 재시도(회복의 규율) — 다섯 조각이 서로를 전제하며 맞물려요. 그 토대는 결국 카프카·Redis·JPA·MySQL 시리즈에서 다진 한 가지 감각, 분산된 것들 사이의 틈을 의심하고 그 틈을 설계로 메우는 일이에요.
다음 글부터는 이런 큰 그림이 아니라 코드 한 줄짜리 함정들이에요. 돈 계산에 double을 쓰면 안 되는 이유부터 시작해요.
출처: AWS Builders' Library — Timeouts, retries, and backoff with jitter · Resilience4j — Retry
'백엔드 > 분산 & 운영' 카테고리의 다른 글
| [분산시스템] 서킷 브레이커 패턴: Resilience4j 설정, fallback 설계, 연쇄 장애 차단 (0) | 2026.08.11 |
|---|---|
| [분산시스템] 사가(Saga) 패턴: 보상 트랜잭션, 코레오그래피 vs 오케스트레이션 (0) | 2026.08.10 |
| [분산시스템] 아웃박스 패턴: DB 커밋과 Kafka 발행을 원자적으로 묶는 법 (0) | 2026.08.07 |
| [분산시스템] 멱등 API 설계: Idempotency-Key로 중복 결제 막기 (0) | 2026.08.06 |
📚 같이 보면 좋은
"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 일정액의 수수료를 제공받습니다."