2024년 11월 블랙프라이데이 당일 새벽 1시, 중국 동부의 국경 간 전자상거래 화장품 3PL의 OMS가 API 타임아웃을 대량으로 뿜어내기 시작했다. 당직 엔지니어의 첫 번째 직감은 재시도 추가였다——5초 타임아웃 후 재전송, 최대 3회 시도. 새벽 3시가 되자 전체 체인이 붕괴했다: WMS는 주문 수신 확인을 받을 수 없었고, TMS는 운임 청구서 조회를 할 수 없었으며, 피킹 작업 상태는 40분간 미보고 상태로 멈춰 있었다. 창고는 수작업 정산으로 되돌아갔다. 이는 단발성 사고가 아니다. 창고 시스템 통합에서 반복되는 각본이다. 하나의 API 타임아웃이, 선의의 재시도에 의해 재시도 폭풍으로 굴러가는 것.
현상: 하나의 타임아웃이 체인 전체의 붕괴로 눈덩이처럼 불어나는 구조
그날 밤의 타임라인은 시사하는 바가 크다. 블랙프라이데이 자정 세일로 OMS 주문량이 폭증했고, WMS 주문 수신 API는 느려졌으며, 5초 타임아웃이 나타났다. WMS의 재시도 로직은 하드코딩되어 있었다: 타임아웃 시 즉시 재전송, 3회 시도. 그러나 하류의 TMS 운임 청구서 조회도 같은 피크 부하에 시달리고 있었고, WMS의 재시도 트래픽이 TMS에 대한 쿼리량을 몇 배로 부풀렸다. TMS는 무너졌고, 캐리어의 추적 API를 두들겨 속도 제한기(rate limiter)를 작동시켰다. 제한이 걸리자 콜백이 큐에 쌓였고, WMS 피킹 작업의 모든 상태 업데이트가 막혔다——한 링크의 타임아웃이 각 계층의 재시도에 의해 홉마다 증폭되어, 전체 체인이 쓰러질 때까지 이어졌다.
공개된 시스템 설계 문헌을 통해 이 증폭을 정량화할 수 있다. 호출 체인이 4홉(OMS → WMS → TMS → 캐리어 API)이고, 각 홉이 타임아웃 시 2회 재시도한다고 가정하자. 최악의 경우, 최하류 서비스는 정상 요청량의 3 × 3 × 3 = 27배에 직면한다. 200 QPS에서 건강하던 API가 갑자기 5,400 QPS를 맞닥뜨리는데, 캐리어의 속도 제한은 보통 피크의 2–3× 수준으로만 설정되어 있다. 이는 예시 계산이지만, 그날 밤 TMS가 가장 먼저 쓰러진 이유를 설명한다: "조금 많은" 트래픽이 아니라, 한 자릿수 차원의 압살이었다.
두 번째 사례도 같은 정도로 주목할 만하다. 한 3PL의 TMS는 타임아웃 30초·서킷 브레이커 없이 캐리어 추적 API를 호출하고 있었다. 성수기에는 요청이 30초 타임아웃에 공중에 매달렸고, 스레드 풀이 가득 찼으며, WMS 상태 콜백은 스레드를 전혀 얻지 못했다——40분간 큐에 머물렀다. 근본 원인은 사실 타임아웃이 아니었다. 타임아웃된 요청이 계속 스레드를 점유했다는 점이다. 긴 타임아웃은 짧은 것보다 하류에 더 큰 상처를 입힌다——많은 엔지니어가 반대로 믿고 있는 결론이다.
수치: 재시도 증폭은 보이는 것보다 무섭다
계산을 한 단계 더 밀어붙여 보자. 재시도 폭풍의 파괴력은 결합되는 두 배수에서 나온다: 깊이——홉이 하나 늘어날 때마다 증폭이 재시도 횟수만큼 곱해진다. 지속 시간——타임아웃이 길수록 매달린 요청이 리소스를 오래 점유하고, 하류의 회복 창은 짧아진다. AWS Architecture Blog의 2015년 분석 "Exponential Backoff And Jitter"에 따르면, 고정 리듬 재시도에는 또 다른 살인자가 숨어 있다: 동기화다. 모든 클라이언트가 같은 순간에 타임아웃되고, 같은 순간에 재시도하며, 파도처럼 공진한다——하류가 숨을 고르자마자 다음 파도가 온다.
따라서 그날 밤의 붕괴는 "트래픽이 너무 많아서"가 아니라 "재시도가 너무 동기화되어서"였다. OMS·WMS·TMS는 모두 같은 템플릿——타임아웃 5초, 즉시 재시도 3회——으로 완벽히 동기화되어 뛰었고, 마치 세 무리가 동시에 하나의 문으로 몰려드는 것 같았다. 이것이 많은 포스트모템이 "서버 증설"로 결론 내면서도 개선이 미미한 이유이기도 하다: 서버는 용량을 늘리지만, 폭풍이 먹는 것은 회복의 시간 창이다. 차원이 다르다.
귀인: 네 가지 재시도 메커니즘, 그리고 "백오프 + 지터 + 브레이커"만 살아남는 이유
일반적인 재시도 전략을 나란히 놓고 보면, 명확한 이야기가 보인다:
| 전략 | 메커니즘 | 증폭 | 치명적 결함 |
|---|---|---|---|
| 즉시 재시도 | 즉시 재전송, 최대 N회 | N배, 장애 지속 시간에 따라 증가 | 하류 회복 전에 두 번째 파도가 도착 |
| 고정 간격 재시도 | T초마다 재전송 | N배, 분산 제한적 | 모든 클라이언트가 동시에 재시도——공진 |
| 지수 백오프 | 대기가 2s → 4s → 8s로 증가 | 시간에 따라 감쇠, 회복 여유 제공 | 재시도 동기화는 고치지 못함 |
| 백오프 + 지터 | 백오프에 무작위 지터 추가 | 감쇠하고 비동기화 | 올바르게 구현하기가 약간 까다로움 |
| 서킷 브레이커 + 폴백 | 실패율 임계값 초과 시 개방, half-open 프로브 | 거의 0 | 설계된 폴백 경로 필요 |
백오프의 가치는 "점점 느려진다"는 것으로, 회복 시간을 번다. 지터의 가치는 동기화를 깨는 것——재시도 폭풍의 진짜 연료는 재시도 자체가 아니라, 모든 클라이언트가 같은 초에 재시도한다는 점이다. 서킷 브레이커는 Michael Nygard가 *Release It!*에서 소개한 패턴으로, 실패율 임계값을 넘으면 회로를 열어 요청 전송을 멈추고, half-open 프로브를 거쳐 트래픽을 다시 통과시킨다. WMS-캐리어 통합에서는 거의 필수다. 제한이 걸린 캐리어 API를 계속 두드리면 차단 목록에 오를 뿐이다.
과소평가되는 규칙이 하나 더 있다: 재시도할 가치가 있는 것은 멱등한 엔드포인트뿐이다. 주문 확정이나 재고 차감 같은 쓰기 작업을 멱등 키 없이 재시도하면, 이중 주문·이중 차감이 될 수 있다——타임아웃보다 훨씬 비싸다. 많은 WMS/OMS 통합 문서는 "3회 재시도"라고 아무렇지 않게 쓰면서, "주문이 두 번 들어가면 어떻게 되나?"에는 절대 답하지 않는다.
실전: 창고 시스템 통합을 위한 세 가지 규칙
첫째, 타임아웃을 계층화하라. 에지 쿼리는 3초, 핵심 주문 API는 10초, 무거운 배치 정산은 동기 HTTP를 완전히 버리고 비동기 큐로 간다. 긴 타임아웃은 관대한 것이 아니다——리소스를 오래 점유할 뿐이다.
둘째, 재시도 정책은 코드의 기본값이 아니라 통합 문서에 써라. 백오프 기준, 지터 범위, 최대 시도 횟수, 브레이커 임계값——통합 테스트에서 상류·하류와 맞춰야 할 네 숫자다. 그날 밤의 붕괴는, 세 계층이 하나의 템플릿 설정을 공유하고 공진을 아무도 고려하지 않았기 때문에 직접적으로 일어났다.
셋째, 하류를 스로틀하고 벌크헤드로 격리하라. TMS의 운임 청구서 조회와 WMS의 상태 콜백은 스레드 풀을 공유해서는 안 된다. 하나가 포화되어도 다른 하나를 죽여서는 안 된다. 외부 캐리어 API에 대한 클라이언트 측 스로틀링도 중요하다——그들의 속도 제한은 그들을 지키고, 당신의 것은 당신을 지킨다.
강조할 만한 반직관적 판단이 하나 더 있다: 타임아웃 후 "더 오래 기다렸다가 재시도"는 "빨리 실패하고 호출자가 큐에 넣게 하기"보다 종종 나쁘다. 스레드 풀은 동시 점유로 죽는 것이지, 재시도 횟수로 죽는 것이 아니다. 하나의 요청이 30초간 스레드를 점유하는 것은, 3초 타임아웃 + 2회 재시도보다 하류에 더 큰 상처를 입힌다. 짧은 타임아웃, 적은 재시도, 호출자에게 노출된 빠른 실패로 큐잉——겉보기엔 UX가 나빠 보이지만, 전체 체인에 가장 책임 있는 선택이다.
추세는 명확하다: 재시도는 "호출자의 책임"에서 "인프라의 역량"으로 이동하고 있다. 데드레터 큐, 아웃박스 패턴, 서비스 메시는 재시도 정책을 인프라 계층으로 밀어 넣고, 비즈니스 코드는 손으로 쓴 재시도 로직을 점점 쓰지 않게 된다. 이는 진보다——폭풍의 근본 원인 중 하나는 각 팀이 제각각 재시도를 쓰고, 리듬이 한 번도 맞지 않았다는 점이다. 비즈니스 코드에서 재시도 로직이 완전히 사라지는 날, 폭풍은 진정으로 길들여질 것이다. 그때까지는, WMS 통합 문서의 "재시도 전략" 섹션은 다시 쓸 가치가 있다——지금 당장.
Dr. Chen, 공급망 연구원 지음
영어판 번역본입니다. 내용이 다를 경우 영어판을 우선합니다.











