재시도와 서킷은 같은 장애를 다루지만 서로 다른 층의 제어입니다. 재시도는 한 논리 요청이 만드는 물리 시도 수와 시간을 제한하고, 서킷은 관찰된 실패를 근거로 새 호출을 빠르게 차단한 뒤 제한된 probe로 회복을 확인합니다. 둘을 함께 설계할 때는 호출 수뿐 아니라 deadline, 동시성, 멱등성, 집계 분모를 함께 고정해야 합니다.
계층별 재시도 횟수와 하위 호출 증폭
상위 호출이 최초 시도를 포함해 최대 3번 실행되고, 각 실행에서 하위 client도 최대 3번 실행되면 한 논리 요청은 3×3=9번의 하위 호출을 만들 수 있습니다. 각 상위 실행이 독립 하위 API 5개를 모두 호출한다면 이 수는 9×5=45번까지 늘어납니다. 여기서 “3번 재시도”가 최초 시도 뒤 3번인지, 최초를 포함한 총 3번인지 먼저 정해야 하며, 그 해석에 따라 상위·하위 호출 상한과 45라는 계산의 적용 여부가 달라집니다.
시도 ID와 논리 요청 ID를 분리하고 실패한 호출·상태 조회·보상·fallback도 같은 하위 자원을 쓰면 전체 예산에 포함합니다. 각 계층이 몰래 재시도하지 않게 책임 계층·최대 횟수·전체 deadline을 공유합니다.
예산을 상위 총 시도 3 × 하위 총 시도 3 × 독립 하위 API 5로 잡으면 최악 물리 호출은 45입니다. 여기에 상태 조회나 보상 호출이 같은 자원을 사용하면 45에 포함해야 합니다. 시험에서 서킷이 빠른 거절을 늘렸는데 하위 호출 수가 줄지 않는다면 이미 실행 중인 요청·큐·다른 계층의 재시도가 남아 있는지 먼저 확인합니다.
Backoff 간격과 Jitter 분산
한 예로 k번째 재시도의 cap을 min(maxDelay, base×2^k)로 정하고 full jitter를 0~cap에서 선택할 수 있습니다. exponential 곱셈 overflow와 너무 작은 남은 deadline을 검사합니다. 방식마다 평균·분산이 다르므로 지터라는 이름만으로 같은 성능을 기대하지 않습니다.
| 정책 | 결과 | 남는 조건 |
|---|---|---|
| 같은 고정 간격 | 동기화된 호출 파도 | 회복 순간 재폭주 |
| 같은 지수 간격 | 호출 빈도 감소 | 여전히 같은 시각에 몰림 |
| 지수 cap + jitter | 시도 시점 분산 | 전체 호출 수·동시성 상한 필요 |
| retry budget | 허용 물리 시도 제한 | 포기·사용자 결과 정책 |
서버 Retry-After를 존중하고 입력 오류·권한 거절·영구 실패는 무조건 반복하지 않습니다. 결과 불확실 변경은 backoff만이 아니라 같은 멱등 키·결과 조회가 필요합니다. 모든 client에 같은 고정 seed를 쓰면 지터도 상관될 수 있어 운영 난수와 재현 테스트 난수를 구분합니다. 취소된 대기 timer·요청 상태도 정리합니다.
서킷 상태 전이와 지속 장애 차단
closed에서는 호출 결과를 관찰하고, 실패 기준을 넘으면 open으로 새 호출을 빠르게 거절합니다. 정한 회복 시점에는 half-open으로 제한된 시험만 허용하고 성공률·지연을 보고 단계적으로 정상 호출을 늘립니다. 시간이 지났다고 전체 트래픽을 한 번에 통과시키지 않습니다.
그림을 글로 읽기
- Closed · 정상 호출·관측
- Open · 새 호출 차단
- Half-open · 제한 시험
- Closed · 정상 호출·관측 → Open · 새 호출 차단: 실패 기준 충족
- Open · 새 호출 차단 → Half-open · 제한 시험: 회복 시험 시점
- Half-open · 제한 시험 → Closed · 정상 호출·관측: 대표 성공·지연 확인
- Half-open · 제한 시험 → Open · 새 호출 차단: 재실패
서킷은 이미 실행 중인 느린 I/O·무한 큐·풀 고갈을 자동 해결하지 않습니다. deadline·실행 동시성·대기 바이트·bulkhead가 별도로 필요합니다. open 거절은 하위에서 실제 실패한 시도와 다른 지표입니다.
실패 분류와 집계 단위
하위 5xx·연결 실패·timeout은 의존성 장애 신호일 수 있지만 사용자 입력 오류·권한 거부는 다른 원인입니다. client 취소도 실제 원인에 따라 분류합니다. 최소 표본·시간 창·slow call 비율을 정하고 낮은 트래픽에서 우연한 몇 실패로 전체 차단하지 않게 합니다.
예를 들어 한 사용자 호출이 재시도까지 포함해 하위 API를 세 번 호출하면, 재시도 바깥의 breaker는 이 세 번을 하나의 논리 결과로 세고 재시도 안쪽의 breaker는 세 물리 시도를 각각 셉니다. 따라서 두 위치에서 나온 실패율은 분모가 달라 같은 수치처럼 비교하면 안 됩니다. breaker를 endpoint·지역·tenant·작업 유형 중 어디까지 공유할지도 정해야 하며, 범위가 너무 넓으면 한 기능 장애가 다른 정상 기능까지 막습니다.
Health 성공과 실제 쓰기 회복의 대표성
작은 health 응답만 성공하고 큰 응답·특정 지역·DB 쓰기는 실패할 수 있습니다. 안전한 대표 호출을 작은 수·동시성으로 시험하고 충분한 표본과 지연 기준을 확인합니다. 실제 결제 같은 시험은 별도 격리·멱등성·승인 없이 만들지 않습니다. 먼저 제한된 실제 허용 트래픽과 점진 확대를 사용하는 선택도 가능합니다.
closed 세대에서 시작한 느린 성공이 open→half-open으로 바뀐 새 상태를 무조건 closed로 덮지 않게 호출에 breaker generation을 붙입니다. 같은 세대의 시험 결과만 해당 전이에 반영하고 실패·성공 중복 완료를 한 번 소비합니다.
공유 상태 저장소 장애와 로컬 상한
전역 breaker는 인스턴스 전체 신호를 모을 수 있지만 그 저장소가 또 의존성입니다. 장애 때 모든 요청을 무제한 허용하지 않도록 로컬 breaker·동시성·deadline을 유지하고 마지막 정상 상태의 최대 stale·기능별 축소를 정합니다. 상태 저장소 오류와 실제 하위 API 오류를 구분합니다.
복구 뒤 모든 인스턴스가 동시에 probe하지 않게 제한·jitter를 둡니다. 추천은 명시된 오래된 결과를 줄 수 있지만 결제 확인 불능을 승인 성공으로 위장할 수는 없습니다. 불확정 결과·재조회·운영 대사를 남깁니다.
실제 시도 수와 사용자 완료의 동시 시험
합성 하위 서버로 일시 오류·지속 실패·입력 오류·느린 성공·half-open 오탐·공유 저장소 장애를 나눕니다. 논리 요청당 시도 수·하위 순간 호출률·active·대기·빠른 거절·실제 성공·재개 뒤 재폭주를 확인합니다. 본문은 회복 제어 설계이며 실제 외부 서비스에 부하를 가한 결과는 아닙니다.