본문 바로가기
Backend2026년 7월 27일6분 읽기

재시도 지수 백오프와 지터(Jitter) — 재시도 폭풍을 막는 법

YS
김영삼
조회 4
재시도 지수 백오프와 지터(Jitter) — 재시도 폭풍을 막는 법

지수 백오프(exponential backoff)는 요청이 실패했을 때 재시도 간격을 1초, 2초, 4초… 처럼 지수적으로 늘려가는 재시도 전략이고, 지터(jitter)는 그 간격에 무작위성을 더해 여러 클라이언트가 동시에 재시도하는 걸 흩어놓는 기법입니다. 둘은 거의 항상 세트로 씁니다.

재시도는 분산 시스템에서 가장 흔하면서도 가장 위험하게 다뤄지는 기능입니다. 잘 쓰면 일시적 장애를 사용자 모르게 넘기지만, 잘못 쓰면 죽어가는 서버에 재시도 폭탄을 던져 아예 못 일어나게 만듭니다. 이걸 재시도 폭풍(retry storm)이라고 부릅니다.

고정 간격 재시도가 만드는 재앙

가장 순진한 재시도는 "실패하면 1초 뒤 다시"입니다. 클라이언트가 하나면 괜찮아요. 문제는 수천 대일 때입니다. 서버가 잠깐 흔들려 모두가 동시에 실패하면, 정확히 1초 뒤에 모두가 동시에 다시 몰려옵니다. 겨우 숨 돌리려던 서버는 이 동기화된 파도에 다시 쓰러집니다. 이 현상이 썬더링 herd입니다.

몇 번 데인 뒤에야 알았습니다. 재시도의 목적은 "빨리 다시 시도"가 아니라 "부하를 시간에 걸쳐 흩뿌리기"라는 걸요.

지수 백오프 — 간격을 벌린다

실패가 이어질수록 간격을 지수적으로 늘립니다. 서버가 회복할 시간을 점점 더 많이 줍니다.

// base=100ms, 시도 n회차의 대기 = base * 2^(n-1)
// 1회: 100ms, 2회: 200ms, 3회: 400ms, 4회: 800ms ...
function backoff(attempt, base = 100, cap = 10000) {
  return Math.min(cap, base * 2 ** (attempt - 1));
}

cap(상한)을 꼭 두세요. 안 그러면 10회차쯤엔 수십 초를 기다리게 됩니다. 그리고 이것만으론 부족합니다. 모든 클라이언트가 같은 지수 곡선을 그리면, 파도가 조금 넓어질 뿐 여전히 동기화돼 몰려옵니다.

지터 — 파도를 부순다

여기에 무작위성을 넣으면 클라이언트들의 재시도 시점이 흩어집니다. AWS가 정리한 방식 중 Full Jitter가 가장 널리 쓰입니다.

function fullJitter(attempt, base = 100, cap = 10000) {
  const exp = Math.min(cap, base * 2 ** (attempt - 1));
  return Math.random() * exp;   // 0 ~ exp 사이 균등 난수
}
방식대기 시간 계산특징
No Jitterexp동기화된 파도 그대로
Full Jitterrandom(0, exp)가장 넓게 분산, 추천
Equal Jitterexp/2 + random(0, exp/2)최소 대기 보장

AWS의 실험에서도 Full Jitter가 서버 부하와 완료 시간 양쪽에서 가장 좋았습니다. 나도 실무에선 특별한 이유가 없으면 Full Jitter를 기본값으로 둡니다.

재시도하기 전에 물어야 할 것들

백오프 이전에 더 중요한 판단이 있습니다. 애초에 재시도해도 되는 요청인가?

  • 멱등한가 — GET·PUT·DELETE는 대체로 안전하지만, POST는 중복 처리 위험이 있습니다. 멱등성 키 없이 결제 POST를 재시도하면 이중 결제가 납니다.
  • 재시도할 가치가 있는 에러인가 — 5xx·타임아웃·커넥션 리셋은 재시도할 만합니다. 하지만 4xx(400·401·404·422)는 다시 보내도 똑같이 실패합니다. 그런 건 재시도하지 마세요.
  • 서버가 힌트를 줬는가 — 429나 503 응답의 Retry-After 헤더를 존중하세요. 서버가 "N초 뒤에 와"라고 알려주면 그걸 따르는 게 예의이자 효율입니다.

서킷 브레이커와 함께

재시도만으론 부족한 순간이 옵니다. 대상이 완전히 죽었다면, 재시도는 그저 죽은 문을 계속 두드리는 것일 뿐입니다. 이때는 서킷 브레이커가 회로를 열어 일정 시간 동안 시도 자체를 멈춰 회복할 틈을 줘야 합니다. "재시도 + 백오프 + 지터 + 서킷 브레이커"가 회복 탄력성의 기본 4종 세트라고 생각하면 됩니다.

자주 묻는 질문

최대 재시도 횟수는 몇 번이 적당한가요?

정답은 없지만 대개 3~5회면 충분합니다. 그 이상 실패하면 일시적 장애가 아니라 진짜 장애일 가능성이 높습니다. 무한 재시도는 절대 하지 마세요. 전체 재시도에 걸리는 총 시간 상한(예: 30초)도 함께 정하는 게 좋습니다.

지터를 넣으면 재시도가 느려지지 않나요?

개별 요청은 평균적으로 조금 빨라질 수도, 늦어질 수도 있습니다. 하지만 시스템 전체로 보면 서버가 덜 무너지므로 전체 완료 시간은 오히려 짧아집니다. 한 요청의 최선이 아니라 전체의 안정을 사는 겁니다.

클라이언트와 서버 중 어디서 재시도해야 하나요?

둘 다 위험이 있습니다. 여러 계층(클라이언트 → 게이트웨이 → 서비스 → DB)이 각자 재시도하면 재시도가 곱셈으로 증폭됩니다. 3층이 각각 3번이면 최대 27번이죠. 그래서 재시도는 한 계층에만 두거나, "재시도 예산(retry budget)"으로 전체 재시도 비율을 제한하는 방식을 권합니다.

타임아웃 없이 재시도만 걸어도 되나요?

안 됩니다. 타임아웃이 없으면 첫 요청이 영원히 안 끝나 재시도가 시작조차 못 합니다. 재시도는 반드시 요청 타임아웃과 짝을 이뤄야 의미가 있습니다.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록