TechFeedTechFeed
Backend

외부 API 회로 차단기 도입 케이스 2026 | 타임아웃 폭주·스레드 고갈·격리

외부 본인인증 API 지연이 결제·장바구니까지 연쇄 장애로 번진 팀의 포스트모템 케이스다. 타임아웃·재시도 핫픽스, 워커 분리, 회로 차단기(Circuit Breaker) Closed·Open·Half-Open 임계값, 폴백·메트릭·기능 플래그 롤아웃 전후 숫자를 정리한다. 백엔드·API·개발자·Sentry·구조화 로깅·Rate limiting 연계 가이드.

by

화요일 11:14, 결제 확인 API의 p99 응답이 180ms에서 12초로 뛰었다. 원인은 우리 서버가 아니라 파트너 본인인증 API였다. 그런데 팀 전체 장애 티켓은 결제·장바구니·알림까지 네 개 서비스에 동시에 열렸다.


외부 의존성 하나가 느려졌을 뿐인데, 내부 스레드 풀이 전부 그 호출에 묶였고 나머지 요청은 줄 서서 타임아웃으로 죽었다. 이 글은 B2B 결제·인증 연동 팀이 재시도만 늘리다가 장애를 키운 뒤, 회로 차단기(Circuit Breaker)로 의존성을 격리하기까지의 문제·시도·결과를 시간순으로 복원한 케이스다. 기업명과 일부 수치는 익명 처리했다.


※ 2026년 상반기 포스트모템·런북 초안을 바탕으로 재구성. 수치·시각은 교육용으로 단순화.


무엇이 깨졌나 | 11:14~12:40 타임라인

서비스는 Node.js API 게이트웨이 뒤에서 결제 확인, 본인인증 프록시, 알림 발송을 묶었다. 국내 카드·간편결제와 본인인증 벤더 HTTP API 한 곳에 의존했고, 타임아웃은 기본 30초, 재시도는 실패 시 즉시 2회였다.


시각사건체감 영향
11:14벤더 p99 지연 급증 (내부 메트릭)결제 확인만 느려짐
11:18게이트웨이 동시 연결 상한 80% 돌파장바구니 조회도 대기
11:22재시도 폭주 (요청당 최대 3회)벤더 QPS 3배, 우리 스레드 고갈
11:315xx 비율 42%슬랙 온콜 호출
11:55인스턴스 스케일 아웃 2배대기 요청만 늘고 회복 없음
12:40벤더 측 복구 + 트래픽 수동 차단약 86분 부분 장애

장애 구간 동안 결제 성공률은 정상의 38% 수준으로 떨어졌다. 본인인증과 무관한 읽기 API까지 함께 느려진 점이 포스트모템 1순위 질문이었다.


외부 API 지연이 내부 게이트웨이 스레드를 점유하는 장애 흐름 개념도
외부 지연이 내부 대기열로 전이되는 구조 (개념 다이어그램)

원인 분해 | 벤더 장애가 아니라 전파 구조

벤더는 그날 일부 리전에서 지연을 인정했다. 다만 팀이 겪은 전면 체감 저하는 전파 설계 쪽 비중이 컸다.


  • 긴 타임아웃 — 30초 대기는 사용자 체감보다 스레드 점유에 치명적이다. 요청 하나당 최대 90초(재시도 포함) 점유 가능.
  • 무조건 재시도 — 5xx·타임아웃에 즉시 재시도하면 실패 트래픽을 스스로 3배로 만든다. Rate limit 글에서 다루는 백프레셔와 같은 방향이다, 외부 호출에도 그대로 적용된다. 관련: 프로덕션 API Rate Limiting 설계.
  • 공유 워커 풀 — 인증 프록시와 장바구니 조회가 같은 프로세스·같은 이벤트 루프 부하 한도에 묶여 있었다. 격리 큐가 없었다.
  • 폴백 없음 — 벤더가 죽으면 즉시 5xx. 캐시된 최근 인증 상태나 "나중에 재확인" 플로우가 없었다.

Redis 캐시 스탬피드 사례와 닮은 점이 있다. 단일 의존성이 정상이 아닐 때 재시도·재조회가 오히려 부하를 키운다. 참고: Redis 캐시 장애 대응 72시간.


포스트모템 한 줄: "외부 장애를 막을 수는 없었다. 내부 전체가 같이 쓰러진 것은 막을 수 있었다."

시도 1 | 타임아웃 단축과 재시도 제거

당일 오후 핫픽스는 단순했다. 타임아웃 30초→3초, 재시도 2회→0회. 배포 후 p99는 즉시 내려갔고, 스레드 점유도 줄었다.


다만 부작용이 바로 나왔다. 벤더가 간헐적으로 2~3초 경계에서 응답하는 정상 구간에서도 실패율이 올라갔다. 고객 상담 티켓에 "본인인증 실패가 늘었다"가 쌓였다. 숫자만 보면 장애 시간은 줄었지만, 성공해야 할 요청까지 잘라낸 상태였다.


팀은 다음 날 타임아웃을 5초로 다시 올리고, 재시도는 멱등 GET에만 1회·지터 백오프로 제한했다. 이 단계는 응급 지혈이지 구조 해결이 아니었다.


시도 2 | 인스턴스 늘리기와 큐 분리

스케일 아웃은 장애 중에도 시도됐다. 인스턴스를 2배로 올리면 대기 요청을 더 받을 수 있을 거라는 가정이다. 결과적으로 벤더로 가는 동시 호출만 늘었고, 복구 시점은 거의 변하지 않았다.


사후 조치로 인증 프록시를 별도 워커 프로세스로 분리했다. 게이트웨이는 인증 호출을 짧은 내부 RPC로 넘기고, 인증 워커만 벤더 HTTP를 담당한다. 장바구니·상품 조회는 인증 워커가 포화돼도 직접 영향이 줄었다.


커넥션·풀 고갈 계열 장애와 겹치는 교훈이다. 공유 자원 상한이 없으면 한 경로가 전체를 먹는다. 유사 사례: PostgreSQL 커넥션 풀 고갈 케이스.


인증 전용 워커와 게이트웨이 분리 아키텍처 스케치
공유 풀 분리 후 장애 전파 범위가 줄어든 구조

시도 3 | 회로 차단기 상태 머신 도입

3주차에 회로 차단기를 붙였다. 라이브러리는 런타임에 맞춰 opossum(Node) 계열을 썼고, 개념은 마틴 파울러의 Circuit Breaker 패턴과 동일하다. 상태는 세 가지다.


상태동작전환 조건(이 팀 값)
Closed정상 호출최근 60초 실패율 < 50%
Open즉시 실패(호출 안 함)실패율 ≥ 50% 또는 연속 실패 20
Half-Open소수 프로브만 통과Open 후 30초 경과

Open 상태에서는 벤더를 때리지 않고 로컬 폴백을 탄다. 최근 성공 인증 토큰이 캐시에 있으면 짧은 유예 창을 주고, 없으면 사용자에게 "잠시 후 재시도"와 대체 채널(고객센터 확인 코드)을 안내한다. 실패를 숨기지 않고, 대기 자원만 지키면서 실패를 빠르게 확정하는 것이 목표였다.


회로 차단기 래퍼 스케치 (Node.js + opossum 개념)
const CircuitBreaker = require('opossum') async function callVendorVerify(payload) { // 실제 벤더 HTTP — 타임아웃 5s, 재시도 없음(브레이커가 담당) return httpPost(VENDOR_URL, payload, { timeout: 5000 }) } const breaker = new CircuitBreaker(callVendorVerify, { timeout: 5000, errorThresholdPercentage: 50, resetTimeout: 30000, volumeThreshold: 20, // 표본이 너무 작을 때 오픈 방지 }) breaker.fallback(async () => { return { ok: false, code: 'VENDOR_OPEN', retryAfterSec: 30 } }) breaker.on('open', () => metrics.inc('vendor_cb_open')) breaker.on('halfOpen', () => metrics.inc('vendor_cb_half_open')) breaker.on('close', () => metrics.inc('vendor_cb_close')) module.exports.verify = (payload) => breaker.fire(payload)

운영에서 중요한 건 임계값 숫자보다 관측이다. Open/Half-Open/Close 카운터를 메트릭으로 남기고, Open이 5분 이상이면 온콜 알림을 따로 띄웠다. 런타임 에러 모니터링과 같이 보면 "우리 버그"와 "의존성 차단"을 분리하기 쉽다. 관련: Sentry 런타임 에러 모니터링 실전, 프로덕션 구조화 로깅 체크리스트.


결과 | 도입 전후 숫자 비교

같은 벤더 지연 재현 테스트(스테이징, 인위 지연 8초, 5분)와 이후 실제 소규모 벤더 장애 1건을 기준으로 비교했다. 절대값은 환경마다 다르니 방향만 참고하면 된다.


지표도입 전도입 후
벤더 지연 시 비인증 API p999~14초120~280ms
장애 구간 전체 5xx42%인증 경로 중심 11%
벤더 방향 QPS (장애 중)정상의 2.8배정상의 0.2배(Open)
복구 후 정상 트래픽 회복스케일 아웃 의존Half-Open 프로브 후 자동
온콜 소요(해당 유형)약 90분약 25분(확인·공지 중심)

회로 차단기가 "성공률을 마법처럼 올린" 것은 아니다. 인증 자체 성공률은 벤더 상태에 묶인다. 달라진 것은 실패의 형태다. 느린 실패·연쇄 대기가 아니라 빠른 실패·범위 한정으로 바뀌었다.


중간에 밟은 함정 4가지

1. volumeThreshold 없이 오픈 — 트래픽이 거의 없는 심야에 실패 2건으로 Open이 걸렸다. 최소 표본(이 팀은 20)을 넣기 전까지 오탐이 잦았다.


2. 모든 에러를 실패로 집계 — 4xx 중 클라이언트 입력 오류(잘못된 주민번호 형식 등)까지 실패율에 넣으면 정상 구간에서도 Open이 뜬다. 타임아웃·5xx·연결 오류만 실패로 세도록 필터했다.


3. 폴백을 성공처럼 로깅 — 초기에 폴백 응답을 200으로만 남기니 대시보드상 에러가 사라져 보였다. 별도 코드 VENDOR_OPEN과 메트릭을 남겨 제품·CS가 "차단 중"을 구분하게 했다.


4. 기능 플래그 없이 전량 배포 — 첫 배포에서 임계값이 빡빡해 정상 피크에도 Half-Open이 반복됐다. 이후 테넌트 10%→50%→100% 플래그로 켰다. 관련: 기능 플래그 롤아웃 체크리스트.


회로 차단기 Closed Open Half-Open 상태 전환 모니터 화면 이미지
상태 전환 메트릭을 알림에 연결한 뒤 오탐·미탐 조정이 쉬워졌다

언제 붙이고 언제 빼나

회로 차단기는 만능이 아니다. 이 팀이 사내 가이드로 남긴 기준은 다음과 같다.


  • 붙이기 좋은 경우 — 외부 HTTP/gRPC, 결제·인증·SMS·지도처럼 지연 분산이 큰 의존성, 공유 스레드·커넥션을 쓰는 동기 호출.
  • 신중히 — 내부 동시성 높은 핵심 경로. Open 시 비즈니스 의미가 불명확하면 제품 폴백을 먼저 설계.
  • 다른 수단이 나은 경우 — 순수 CPU 버그, 배포 회귀. 이때는 브레이커보다 롤백·킬스위치·카나리가 맞다.
  • 같이 써야 하는 것 — 타임아웃, 격리 풀/큐, 벌크헤드(자원 칸막이), 구조화 로그, 사용자 메시지.

배포 전 점검 목록과 겹치는 항목이 많다. 서비스 출시 전에 외부 호출 목록·타임아웃·폴백 유무를 표로 남기는 습관이 재발을 줄인다. 참고: 프로덕션 배포 체크리스트.


팀 런북에 남은 체크 순서

온콜이 벤더 지연을 의심할 때 보는 순서다. 문서 그대로 옮겨 두었다.


  1. 인증 경로 5xx·타임아웃과 비인증 경로 p99를 분리해서 본다. 비인증이 같이 죽으면 격리 실패.
  2. vendor_cb_open 메트릭이 올라갔는지 확인한다. Open이면 벤더 콘솔·상태 페이지를 보고, 호출을 억지로 늘리지 않는다.
  3. 스케일 아웃 전에 재시도 설정·타임아웃·동시 호출 상한을 확인한다. 인스턴스만 늘리면 악화될 수 있다.
  4. Half-Open 프로브 성공률이 회복되면 Close를 기다린다. 수동으로 브레이커를 닫을 때는 플래그로만.
  5. 장애 종료 후 실패 샘플 20건의 상태 코드·지연·테넌트를 로그에서 뽑아 포스트모템 초안을 남긴다.

고객 공지 문구도 템플릿화했다. "전체 장애"가 아니라 "본인인증 연동 지연, 주문 조회는 정상"처럼 범위를 적는다. 지원 티켓 분류 시간이 줄었다.


참고 자료


패턴 이름과 파라미터 의미는 위 문서를 기준으로 했고, 본문 수치·타임라인은 익명 팀 포스트모템 재구성이다.


자주 묻는 질문

회로 차단기와 타임아웃 중 무엇을 먼저 넣어야 하나요?

타임아웃이 먼저다. 상한 없는 대기가 있으면 브레이커 표본 자체가 왜곡된다. 타임아웃으로 실패를 확정한 뒤, 실패율로 Open을 결정하는 순서가 안전하다.


재시도를 완전히 없애야 하나요?

아니다. 멱등 읽기·순간 네트워크 끊김에는 1회 지터 재시도가 도움이 된다. 다만 Open 상태이거나 타임아웃이 이미 난 요청에 연쇄 재시도를 걸면 장애를 키운다. 재시도 예산을 브레이커 바깥이 아니라 정책으로 고정하는 편이 낫다.


Half-Open에서 프로브는 몇 개가 적당한가요?

이 팀은 동시에 3개, 성공 3연속이면 Close, 실패 1이면 다시 Open으로 돌렸다. 트래픽이 매우 낮은 서비스는 프로브 성공만으로 과신하기 쉬우니 volumeThreshold와 함께 본다.


마이크로서비스마다 브레이커를 다 붙여야 하나요?

외부·교차 팀 경계부터다. 같은 배포 단위 안의 로컬 함수 호출에 전부 붙이면 노이즈와 운영 비용만 는다. 의존성 목록을 그리고 지연·실패가 전파되는 모서리부터 단다.


폴백 응답을 캐시해도 되나요?

인증·결제처럼 정합성이 중요한 값은 TTL을 짧게 두고, "유예"임을 UI·로그에 명시하는 편이 안전하다. 오래된 성공을 장시간 성공처럼 보여 주면 감사·보안 이슈가 된다. 캐시는 스탬피드 방지용 락과 함께 설계하는 것이 좋다.


이 패턴이 Rate limiting과 뭐가 다른가요?

Rate limiting은 보통 들어오는 트래픽·나가는 호출 양을 제한한다. 회로 차단기는 실패율·지연 패턴을 보고 호출 자체를 잠시 끊는다. 둘 다 백프레셔 도구라 같이 쓰는 경우가 많고, 역할은 겹치되 트리거가 다르다.


회로 차단기Circuit Breaker외부 API타임아웃재시도백엔드장애포스트모템API개발자opossumResilience

함께 보면 좋은 문제 해결

EXPLORE / Backend

이어서 읽어보기

전체 토픽 둘러보기