[분산시스템] 서킷 브레이커 패턴: Resilience4j 설정, fallback 설계, 연쇄 장애 차단
![[분산시스템] 서킷 브레이커 패턴: Resilience4j 설정, fallback 설계, 연쇄 장애 차단](https://blog.kakaocdn.net/dna/dKGqaS/dJMcadIW2rK/AAAAAAAAAAAAAAAAAAAAAMiZ1Lu6ZHQkgqmkqE9SBnUuMEwPxJtQpeUwKt4gljBF/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1788188399&allow_ip=&allow_referer=&signature=fJ57Uae7EeGKMNWq8gdlffAOn7I%3D)


[분산시스템] 서킷 브레이커 패턴: Resilience4j 설정, fallback 설계, 연쇄 장애 차단
외부 결제사가 느려졌을 뿐인데 내 서비스 전체가 죽는 일이 있어요. 결제와 무관한 상품 조회까지요. 장애가 호출 그래프를 타고 번지는 연쇄 장애인데, 그걸 끊는 차단기가 서킷 브레이커예요. 이름 그대로 두꺼비집이에요 — 누전이 감지되면 회로를 내려서 집 전체가 타는 걸 막는 거죠.
연쇄 장애가 번지는 구조에서 시작해, 서킷의 3상태(CLOSED·OPEN·HALF_OPEN), Resilience4j 실전 설정, 그리고 도입의 진짜 본체인 fallback 설계까지 짚어요.
01. 연쇄 장애, 남의 장애가 내 장애가 되는 길

경로는 늘 같아요. 의존 서비스가 느려짐 → 내 호출 스레드가 타임아웃까지 묶임 → 그런 스레드가 쌓여 풀 고갈 → 무관한 요청까지 처리 불가. OSIV의 커넥션 고갈이나 풀 말림과 정확히 같은 역학이에요 — 자원이 "일"이 아니라 "대기"에 소모되는 거죠.
여기서 통찰 하나 — 상대가 이미 죽었다면, 계속 부르는 것 자체가 자해예요. 어차피 실패할 호출에 스레드와 시간을 태우고 회복 중인 상대에게 부하까지 더해요. "빨리 포기하고, 잠시 안 부르기"가 양쪽 모두를 살려요. 그걸 자동화한 게 서킷 브레이커예요.
02. 3상태, 차단·탐색·복구

- CLOSED(정상) — 호출을 통과시키며 최근 호출들의 실패율을 집계해요.
- OPEN(차단) — 실패율이 임계(예: 50%)를 넘으면 회로를 열어요. 이후 호출은 상대를 부르지도 않고 즉시 실패해요. 내 스레드는 0ms에 풀려나고 상대는 부하 없이 회복할 시간을 벌어요.
- HALF_OPEN(탐색) — 대기 시간이 지난 뒤 기본 설정에선 자동 전이가 아니라, 그 다음 호출이 들어올 때 HALF_OPEN으로 바뀌며 소수의 시험 호출만 통과시켜요(자동 전이는 옵션). 성공하면 CLOSED 복귀, 실패하면 다시 OPEN. 복구를 자동으로 감지하는 장치예요.
중요한 디테일 — 느린 호출도 실패로 칠 수 있어요. 진짜 무서운 건 에러보다 "30초 걸리는 성공"이거든요(스레드를 제일 오래 묶으니까). Resilience4j는 slow call 비율로도 회로를 열 수 있어요.
03. Resilience4j 실전 설정
resilience4j:
circuitbreaker:
instances:
paymentApi:
sliding-window-size: 50 # 최근 50회 기준으로 집계
minimum-number-of-calls: 20 # 최소 20회는 모여야 판단 (오픈 오판 방지)
failure-rate-threshold: 50 # 실패율 50% 넘으면 OPEN
slow-call-duration-threshold: 3s # 3초 넘으면 "느린 호출"
slow-call-rate-threshold: 80 # 느린 호출 80% 넘어도 OPEN
wait-duration-in-open-state: 10s # 10초 차단 후 HALF_OPEN
permitted-number-of-calls-in-half-open-state: 5
@CircuitBreaker(name = "paymentApi", fallbackMethod = "payFallback")
public PaymentResult pay(PaymentRequest req) {
return paymentClient.approve(req);
}
private PaymentResult payFallback(PaymentRequest req, Throwable t) {
return PaymentResult.unavailable("결제가 지연되고 있어요. 잠시 후 다시 시도해주세요.");
}
주의 — @CircuitBreaker도 프록시 기반이에요. 자기호출에서 조용히 무시되는 그 규칙이 여기도 적용돼요. 그리고 의존성별로 인스턴스를 분리해요. 결제사와 추천 API가 한 서킷을 쓰면, 추천 장애가 결제까지 차단하는 황당한 일이 생겨요. 이름(name)이 곧 격리 단위예요.
04. 진짜 일은 fallback 설계예요

서킷이 열렸을 때 사용자에게 뭘 줄 거냐 — 이게 도입의 본체예요. 라이브러리 설정은 30분이면 끝나지만 fallback은 기능마다 비즈니스 판단이 필요해요.
- 대체 값 — 개인화 추천이 죽으면 인기 상품 목록을, 환율 조회가 죽으면 최근 캐시값을 줘요. 사용자는 차이를 거의 못 느껴요.
- 기능 축소(graceful degradation) — 리뷰 서비스가 죽으면 리뷰 영역만 숨기고 상품 페이지는 정상 서빙해요. "전부 아니면 전무"를 거부하는 거예요.
- 빠른 실패 안내 — 결제처럼 대체가 불가능한 기능은, 30초 멈춤 끝의 타임아웃보다 즉시 "잠시 후 재시도해주세요"가 압도적으로 나은 경험이에요.
기능 목록을 놓고 "이 의존성이 죽으면?"을 하나씩 답해보는 것 — 그 표가 곧 fallback 설계서예요. 답이 안 나오는 기능(결제)은 그 자체로 "이 의존성은 이중화가 필요하다"는 발견이고요.
05. 운영에서 챙길 것
- 상태 전이 알림 — 서킷이 OPEN 되는 순간이 곧 장애 감지예요. 상태 변화 이벤트를 모니터링·알림에 연결하면 의존성 장애를 사용자보다 먼저 알아요.
- 타임아웃과 한 세트 — 서킷은 "실패를 집계"하는 장치라, 호출 자체의 타임아웃이 없으면 느린 호출이 실패로 안 잡혀요. 타임아웃(TimeLimiter)을 짧게 거는 게 선행이에요 — 이건 다음 편 주제예요.
- 벌크헤드 — 의존성별로 스레드·동시 호출 수를 격리하는 패턴이에요. 서킷이 "시간축 격리"라면 벌크헤드는 "자원축 격리" — 같이 쓰면 연쇄 차단이 더 단단해져요.
06. 자주 만나는 문제
서킷이 너무 자주 열려요 (오탐)
minimum-number-of-calls가 작아 새벽 저트래픽에 한두 건 실패로 열리는 경우가 단골이에요. 최소 호출 수를 올리고 비즈니스 예외(잔액 부족 등)는 실패 집계에서 제외(ignore-exceptions)해요 — 그건 상대의 장애가 아니니까요.
서킷이 열렸는데 아무도 몰랐어요
fallback이 너무 매끄러워서 조용히 degrade만 된 거예요. 상태 전이 알림과 fallback 발동률 메트릭을 걸어요.
붙였는데 동작을 안 해요
자기호출(프록시)이거나 인스턴스 이름 불일치예요. actuator의 circuitbreakers 엔드포인트로 상태를 직접 확인해요.
이게 죽으면 뭘 보여줄까
서킷 브레이커는 "죽은 상대를 계속 부르는 자해"를 끊는 자동 차단기예요. 실패율과 느린 호출 비율로 OPEN되어 즉시 실패로 내 자원을 지키고 상대에게 회복 시간을 줘요. HALF_OPEN으로 복구를 탐지해요. 그런데 설정 자체는 30분이면 끝나고 진짜 무게는 의존성별 격리와 fallback 설계에 실려요.
그래서 도입의 본체는 라이브러리가 아니라 이 질문이에요 — "이 의존성이 죽으면 사용자에게 뭘 보여줄까?" 기능 목록을 놓고 하나씩 답해보면 그 표가 곧 fallback 설계서가 돼요.
서킷의 짝꿍인 타임아웃과 재시도 전략(재시도 폭풍, 백오프+지터)을 시리즈 마지막인 다음 글에서 다뤄요.
출처: Resilience4j — CircuitBreaker · Martin Fowler — CircuitBreaker
'백엔드 > 분산 & 운영' 카테고리의 다른 글
| [분산시스템] 타임아웃과 재시도 전략: 재시도 폭풍, 지수 백오프와 지터, 타임아웃 예산 (0) | 2026.08.12 |
|---|---|
| [분산시스템] 사가(Saga) 패턴: 보상 트랜잭션, 코레오그래피 vs 오케스트레이션 (0) | 2026.08.10 |
| [분산시스템] 아웃박스 패턴: DB 커밋과 Kafka 발행을 원자적으로 묶는 법 (0) | 2026.08.07 |
| [분산시스템] 멱등 API 설계: Idempotency-Key로 중복 결제 막기 (0) | 2026.08.06 |
📚 같이 보면 좋은
"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 일정액의 수수료를 제공받습니다."