대규모 트래픽과 불안정한 외부 의존성 속에서 서비스 신뢰성을 지키려면, 실패를 없애기보다 예측 가능한 방식으로 다룰 수 있어야 합니다. 네 가지 핵심 패턴 서킷 브레이커, 리트라이, 백오프, 아이템포턴시를 적절히 묶으면 에러율과 p95/p99 꼬리 지연을 동시에 낮추고, 사용자는 “멈추지 않는 서비스”를 체감하게 됩니다.
1) 서킷 브레이커: “지금은 보내지 마”
의존 대상(외부 API/DB/브로커)의 오류율이나 지연이 임계치를 넘으면, 호출을 잠시 차단(Open) 했다가 상태가 회복되면 부분 허용(Half-Open) 을 거쳐 정상(Closed) 으로 복귀시키는 장치입니다.
실무에선 최근 10~30초 윈도우에서 오류율 5~10% 이상, 또는 p95 지연 급등을 기준으로 Open 하도록 잡습니다. 한 번 열리면 최소 유지 시간을 둬 플래핑을 막고, Half-Open에서는 소수의 탐색 요청만 통과시켜 회복을 확인합니다.
중요한 건 폴백입니다. 캐시된 값(stale), 기본 응답, 요약 데이터, 큐잉 등 사용자 친화적 대안을 준비해야 “차단”이 “장애”로 느껴지지 않습니다.
2) 리트라이: “한 번 더 시도하되, 똑똑하게”
리트라이는 일시적 실패를 흡수하는 도구이지 만능 키가 아닙니다. 대상은 깔끔히 한정하세요. 408/429/5xx, 네트워크 에러, 그리고 멱등 요청만 재시도합니다. 횟수는 2~3회가 적당하고, 전체 요청 타임아웃은 서비스 SLO 안에서 끝나야 합니다.
금지 사항도 분명합니다. 4xx 전반 무지성 리트라이, 긴 고정 지연, 무제한 재시도는 증폭과 폭주를 부릅니다. 리트라이는 반드시 지수 백오프 + 지터와 함께 움직여야 합니다.
3) 백오프: “간격을 띄워 동시 재폭주 차단”
여러 클라이언트가 동시에 재시도하면 그 자체가 또 하나의 폭주가 됩니다. 그래서 지수 백오프로 시도 간격을 늘리고, 여기에 풀 지터(full jitter) 를 섞어 재시도 타이밍을 분산합니다.
예를 들어, 1차 200ms, 2차 400ms, 3차 800ms에 최대 2초 cap을 두고, 각 단계 대기는 0~현재 지연 사이 임의값으로 정합니다. 이렇게 하면 평균 지연은 유지하면서도 군집 재폭주를 깔끔히 꺾을 수 있습니다.
4) 아이템포턴시(Idempotency): “여러 번 보내도 한 번만 처리”
쓰기·결제·주문처럼 부작용 있는 작업은 같은 요청이 다시 와도 딱 한 번만 처리되어야 합니다. 그래서 Idempotency-Key(본문 해시+타임스탬프 등)를 요청에 포함하고, 서버는 키별 결과/상태를 저장합니다.
이러면 타임아웃이나 네트워크 분할로 클라이언트가 재시도해도 안전하고, 중복 과금·이중 재고 차감 같은 사고를 원천 봉쇄할 수 있습니다. 단, 키의 보관 기간(TTL) 과 저장소 GC 정책을 함께 운영해야 합니다.
5) 네 가지의 정석 조합: 실패를 부드럽게 흡수하는 흐름
흐름은 단순합니다. 쓰기 요청이면 먼저 아이템포턴시 키를 확인해 중복이면 즉시 이전 결과를 돌려줍니다. 다음으로 서킷 상태를 봐서 Open이면 폴백/큐잉/캐시 값을 반환하고, Half-Open이면 소수만 통과시킵니다. 다운스트림 호출이 실패하면 지수 백오프+풀 지터로 2~3회 리트라이하고, 끝내 실패하면 브레이커에 실패를 기록한 뒤 사용자 친화 메시지와 함께 종료합니다. 성공하면 아이템포턴시 키에 결과를 저장해 이후 재시도도 안전하게 만듭니다.
6) 어떤 오류를 리트라이할까? (결정표)
- 리트라이 허용: 408, 429(헤더의 Retry-After 준수), 502/503/504, TCP 재설정, 일시적 DNS 실패
- 리트라이 금지: 400/401/403/404/422 등 클라이언트 오류, 비멱등 작업(키 없이 재시도 금지)
7) 타임아웃과 예산(Budget): “무한 대기는 없다”
프런트 SLO가 2초라면, 게이트웨이·내부 서비스·외부 호출에 시간 예산을 나눠 잡아야 전체가 맞춰집니다. 개별 재시도 타임아웃과 요청 전체 타임아웃을 분리하고, 브레이커 기준은 평균이 아닌 분포(p95/오류율) 를 기반으로 삼으세요. “느리지만 언젠가 응답”은 가장 나쁜 응답입니다.
8) 폴백 전략: 실패를 사용자 경험으로 번역하기
읽기 요청은 stale-while-revalidate 값이나 최근 성공 응답으로 버틸 수 있습니다. 쓰기는 작업 큐잉으로 비동기 확정을 걸고, 사용자에겐 영수증/상태 조회 경로를 알려주세요. 알림류는 대체 채널(SMS/이메일)을 준비해 정보 최소본이라도 전달하면 체감 불편이 크게 줄어듭니다.
9) 분산 환경·다중 리전에서의 고려사항
아이템포턴시 키 저장소는 전역 일관성이 필요하거나, 최소한 요청을 특정 리전에 스티키하게 라우팅해야 합니다. 서킷 상태는 리전별로 독립 판단을 하되, 광역 장애에는 중앙 요약 신호로 빠르게 제어하세요. 관측은 오류율·지연·타임아웃·브레이커 전환 이벤트를 한 대시보드에서 함께 보이도록 하는 것이 운영의 반입니다.
10) 운영 체크리스트(바로 적용)
- 리트라이 대상을 408/429/5xx/네트워크로 제한
- 지수 백오프 + 풀 지터, 최대 2~3회
- 쓰기·결제 아이템포턴시 키 도입, TTL/GC 명시
- 서킷 브레이커 전이 기준·최소 유지 시간 설정
- 타임아웃 예산화(전체 vs 개별)
- 폴백 경로: 읽기=stale, 쓰기=큐잉
- 관측: p50/p95/p99, 재시도 횟수, 타임아웃, 브레이커 이벤트
- 장애 주입 테스트로 시나리오 검증
11) 흔한 실수와 해결책
- 무제한 리트라이 → 다운스트림 DDoS: 최대 횟수 + 전체 타임아웃을 강제
- 고정 백오프 → 동시 재폭주: 지수 + 지터로 교체
- 비멱등 요청 재시도 → 중복 처리: 아이템포턴시 도입, 불가하면 금지
- 과민 브레이커 → 플래핑: 히스테리시스·샘플 수·최소 유지 시간으로 안정화
- 폴백 부재 → 사용자 불만 급증: 최소한의 stale/요약/대체 채널 확보
12) FAQ
Q. 리트라이는 몇 번이 적당한가요?
A. 대부분의 서비스에서 2~3회가 체감 개선과 비용의 균형점입니다. 그 이상은 이득이 급감합니다.
Q. 내부 서비스 사이에도 브레이커가 필요한가요?
A. 예. 마이크로서비스 연쇄 장애를 막는 마지막 안전장치입니다. 내부일수록 기준을 더 엄격히 두세요.
Q. 아이템포턴시는 결과까지 저장해야 하나요?
A. 최선은 결과 저장이지만, 최소한 작업 상태(Started/Done) 만 저장해도 중복 실행을 대부분 막을 수 있습니다.
마무리
네트워크 안정성은 실패를 통제 가능한 단위로 분해하는 기술입니다. 서킷 브레이커로 비정상 구간을 격리하고, 리트라이는 지수 백오프+지터와 함께 멱등 요청에만 적용하세요. 아이템포턴시로 중복을 잠그고, 폴백으로 사용자 경험을 다듬으면, 에러율과 꼬리 지연은 동시에 내려갑니다. 작은 범위에서 시작해 지표로 확인하고, 점진적으로 전면 확대하세요. 운영이 즉시 편해질 것입니다.