우아한테크코스 테코톡

아이나의 서킷브레이커

https://youtu.be/lddAZUvOUrs?si=6Rh2JtJ9h4ErH-aM

외부 API를 연동하는 서비스는 생각보다 쉽게 불안정해질 수 있다. 평소에는 문제없이 동작하던 기능도, 의존 중인 외부 서비스가 잠시 느려지거나 오류를 내기 시작하는 순간부터 전체 시스템의 응답성이 급격히 나빠질 수 있다.

문제는 장애가 “외부 API 하나의 문제”로 끝나지 않는다는 점이다. 응답을 기다리던 내부 스레드가 묶이고, 커넥션 풀이 고갈되고, 사용자는 버튼을 반복 클릭하고, 실패하는 외부 서비스로 요청이 더 몰리면서 결국 부분 장애가 전체 장애로 확산된다.

이런 상황에서 중요한 것은 단순히 “재시도”하는 것이 아니라, 언제 끊어야 하는지 판단하고, 빠르게 실패시키며, 시스템 전체를 보호하는 것이다.

그 역할을 하는 대표적인 패턴이 바로 서킷 브레이커(Circuit Breaker) 다.

아이나의 서킷브레이커


1. 서킷 브레이커란 무엇인가

서킷 브레이커는 시스템의 회복 탄력성(resilience)내결함성(fault tolerance) 을 높이기 위한 패턴이다.

이 표현이 다소 추상적으로 들릴 수 있는데, 조금 더 실무적으로 풀어보면 다음과 같다.

  • 회복 탄력성: 장애가 발생한 뒤에도 다시 정상 상태로 돌아올 수 있는 능력
  • 내결함성: 일부 구성 요소가 실패해도 전체 시스템으로 장애가 번지지 않도록 버티는 능력

즉, 서킷 브레이커는 “실패를 없애는 패턴”이 아니라, 실패가 생겼을 때 그 영향 범위를 통제하는 패턴” 이다.

이름의 유래도 직관적이다. 전기 회로에서 과전류가 흐를 때 회로 차단기가 전류를 끊어 전체 손상을 막듯, 소프트웨어에서도 문제가 발생한 외부 의존성으로의 호출을 끊어 전체 시스템을 보호한다.


2. 외부 API 연동에서 왜 필요한가

외부 API를 호출하는 서비스는 본질적으로 내가 통제할 수 없는 요소에 의존한다. 그래서 정상 시나리오보다 실패 시나리오 설계가 더 중요하다.

예를 들어 온라인 쇼핑몰의 주문 흐름을 생각해보자.

  1. 사용자가 주문 버튼을 누른다.
  2. 주문 서비스가 결제 서비스에 요청한다.
  3. 결제 서비스가 외부 PG사 API를 호출한다.
  4. 그런데 PG사 서버가 응답하지 않는다.

이 시점부터 문제가 시작된다.

  • 사용자는 응답이 없으니 버튼을 다시 누른다.
  • 같은 요청이 반복 유입된다.
  • 외부 PG사에는 처리 못하는 요청이 계속 쌓인다.
  • 내부 서비스는 응답 대기 상태로 스레드와 커넥션을 점유한다.
  • 결국 내부 시스템까지 느려지거나 멈춘다.

이건 단순한 API 실패가 아니다. 의존성 하나의 장애가 전체 가용성을 무너뜨리는 구조적 문제다.

서킷 브레이커가 필요한 이유는 바로 여기에 있다.

서킷 브레이커는 장애가 감지되면 중간에서 호출을 차단하여:

  • 불필요한 외부 요청을 막고
  • 내부 자원을 보호하고
  • 사용자에게 빠르게 실패를 반환하고
  • 실패 중인 외부 서비스가 회복할 시간을 벌어준다

결국 핵심은 장애를 감추는 것이 아니라, 장애를 통제 가능한 형태로 바꾸는 것이다.


3. 서킷 브레이커의 상태와 동작 방식

서킷 브레이커는 보통 세 가지 상태를 가진다.

3.1 Closed

정상 상태다. 요청이 들어오면 외부 서비스로 호출을 보낸다. 이 과정에서 성공/실패 결과를 계속 기록한다.

즉, Closed 상태는 “문제가 없다고 보고 통과시키는 상태”다.


3.2 Open

장애가 일정 수준 이상이라고 판단되면 회로를 연다. 이 상태에서는 더 이상 외부 서비스로 요청을 보내지 않는다.

여기서 중요한 점은, Open 상태가 되었을 때 시스템이 멈추는 것이 아니라 의도적으로 더 이상 호출하지 않도록 차단한다는 점이다.

이 덕분에:

  • 실패하는 서비스에 트래픽이 더 쌓이지 않고
  • 내부 자원도 낭비되지 않으며
  • 사용자에게는 빠른 실패 응답이 가능해진다

3.3 Half-Open

Open 상태가 일정 시간 유지된 뒤, 시스템은 외부 서비스가 회복되었는지 확인할 필요가 있다. 그때 일부 요청만 제한적으로 다시 흘려보내는 상태가 Half-Open이다.

이 상태에서 성공률이 회복되면 다시 Closed로 돌아가고, 여전히 실패율이 높으면 다시 Open으로 전환된다.

즉, 서킷 브레이커는 단순히 “막는 장치”가 아니라 차단 → 관찰 → 제한적 재개 → 복구 판단이라는 흐름을 가진다.


4. Retry만으로는 부족한 이유

외부 API 장애 대응을 처음 설계할 때 많은 팀이 가장 먼저 떠올리는 방법은 Retry다. 일시적인 네트워크 문제라면 Retry는 분명 유효하다.

하지만 모든 실패에 Retry를 걸면 오히려 상황을 악화시킬 수 있다.

왜냐하면 Retry는 기본적으로 이런 전제를 깔고 있기 때문이다.

“다시 시도하면 성공할 수도 있다.”

그런데 외부 서비스가 이미 명확히 망가진 상태라면?

  • 첫 요청 실패
  • 재시도
  • 또 실패
  • 다시 재시도
  • 끝까지 실패

이 과정은 사용자 입장에서 응답 시간만 길어지고, 외부 서비스 입장에서는 회복도 못 했는데 요청만 더 받게 된다. 내부 서비스는 그동안 대기 자원을 계속 소비한다.

즉, Retry는 일시적 실패에는 효과적이지만, 지속적 실패 앞에서는 문제를 더 키울 수 있다.

그래서 실무에서는 보통 이렇게 생각해야 한다.

  • 재시도가 의미 있는 실패인가?
  • 아니면 지금은 빨리 포기하고 fallback으로 가야 하는 실패인가?

이 판단을 자동화하는 데 서킷 브레이커가 강하다.


5. 프로젝트 적용 사례: 외부 LLM API 보호

영상 속 프로젝트 사례에서는 약속 장소 추천 서비스가 소개된다. 이 서비스는 추천 지역을 고르는 핵심 기능이 외부 LLM API에 의존하고 있었고, 이 때문에 장애 대응이 중요했다.

초기에는 @Retry@Recover 로 대응하려 했지만 한계가 있었다.

  • 실패할 것을 알면서도 재시도하게 되고
  • 응답 지연이 길어지며
  • 실패 중인 외부 API에 부하를 더 준다

그래서 단순 재시도 대신, 예외의 성격을 구분해서 서로 다른 서킷 브레이커 전략을 적용했다는 점이 인상적이다.

5.1 예외를 하나로 보지 않았다

프로젝트에서는 외부 API의 예외를 두 가지로 나눴다.

1) 원인이 명확하고 재시도가 의미 없는 실패

예: 요청 제한 초과(429)

이 경우는 다시 보내도 바로 성공하기 어렵다. 오히려 추가 요청이 더 해롭다.

그래서 브레이커 A 는 이런 상황에서 빠르게 열리고, 즉시 fallback을 호출하도록 설계했다.

2) 일시적일 수 있어 관찰이 필요한 실패

예: 서버 내부 오류(500)

이 경우는 순간적인 장애일 수 있으므로 무조건 한 번에 차단하지 않고, 실패율을 보다가 임계치를 넘으면 차단하도록 했다.

그래서 브레이커 B 는 실패율을 모니터링하다가 일정 수준을 넘으면 열리고, fallback으로 전환한다.

이 설계가 중요한 이유는, 실패를 단순히 “성공/실패” 이분법으로 보지 않고 실패의 의미와 재시도 가치까지 반영했기 때문이다.

5.2 실제 효과

이 구조를 적용한 결과, 기존 Retry + Recover만 사용했을 때보다 응답 지연 시간을 약 3초 줄였다고 한다.

3초는 숫자 자체보다 의미가 크다. 사용자 체감에서는 “멈춘 것 같은 서비스”와 “실패했지만 빠르게 반응하는 서비스”를 가르는 차이이기 때문이다.


6. 서킷 브레이커를 도입할지 판단하는 기준

모든 외부 API 연동에 서킷 브레이커가 필요한 것은 아니다. 이 점은 오히려 실무에서 더 중요하다.

다음과 같은 경우라면 굳이 도입하지 않아도 된다.

  • 외부 API가 매우 안정적이다
  • 실패했을 때 단순 에러 반환만으로 충분하다
  • 응답 시간보다 구현 단순성이 더 중요하다

반대로 아래 조건이라면 도입을 적극 검토할 만하다.

  • 외부 API가 간헐적으로 실패한다
  • 응답 시간이 사용자 경험에 직접 영향을 준다
  • 실패 시 fallback 또는 대체 응답이 필요하다
  • 외부 장애가 내부 시스템 자원 고갈로 이어질 수 있다
  • 서비스 가용성을 높이는 것이 중요한 목표다

즉, 기준은 기술 유행이 아니라 의존성의 불안정성 + 장애 비용 + 사용자 체감 영향도다.


7. 도입 시 반드시 고민해야 할 설계 포인트

서킷 브레이커를 붙인다고 끝나는 것이 아니다. 오히려 “어떻게 붙일 것인가”가 핵심이다.

7.1 적용 범위

어디 단위에 브레이커를 둘 것인가?

  • 외부 서비스 전체 단위
  • API 엔드포인트 단위
  • 특정 예외 유형 단위
  • 인스턴스/호스트 단위

범위가 너무 넓으면 일부 장애를 전체 장애처럼 다루게 되고, 너무 좁으면 관리가 복잡해진다.


7.2 임계치 설정

무엇을 기준으로 Open할 것인가?

  • 실패율
  • 연속 실패 횟수
  • 타임아웃 비율
  • 특정 예외 타입 빈도

여기서 중요한 건 숫자 자체보다 서비스 특성과 호출 패턴에 맞는 기준이다. 트래픽이 적은 시스템과 많은 시스템은 같은 기준을 쓰면 안 된다.


7.3 회복 대기 시간

Open 후 얼마 뒤 Half-Open으로 갈 것인가?

너무 짧으면 아직 회복되지 않은 시스템을 계속 자극하고, 너무 길면 이미 회복된 서비스를 불필요하게 오래 막는다.

즉, 회복 시간도 고정값이라기보다 서비스 특성에 맞는 운영 파라미터다.


7.4 Fallback 전략

실패했을 때 무엇을 반환할 것인가?

  • 기본 응답
  • 캐시된 데이터
  • 일부 기능 축소 응답
  • 사용자 친화적 에러 메시지
  • 비동기 재시도 안내

서킷 브레이커의 진짜 품질은 Open 자체보다 Open 이후 사용자 경험을 어떻게 설계했느냐에서 드러난다.


8. 서킷 브레이커의 한계와 분산 시스템에서의 딜레마

서킷 브레이커는 강력하지만 만능은 아니다. 특히 분산 시스템에서는 더 조심해야 한다.

대표적인 문제는 부분 실패를 전체 실패로 잘못 해석할 수 있다는 점이다.

예를 들어 동일한 외부 시스템이 여러 인스턴스로 구성되어 있고, 그중 특정 인스턴스 하나에만 트래픽이 몰려 오류가 집중된 상황을 생각해보자.

이때 전역 브레이커가 전체 시스템이 죽었다고 판단해버리면 정상 인스턴스까지 모두 차단해버릴 수 있다.

반대로 전체가 살아있다고 보면 실패 중인 특정 인스턴스로 들어가는 요청은 계속 깨질 수 있다.

이게 분산 시스템에서의 서킷 브레이커 딜레마다. 그래서 이런 경우에는 단일 전역 브레이커보다 더 세밀한 단위의 로컬 브레이커가 필요할 수 있다.

즉, 분산 환경에서는 “브레이커를 둘 것인가”보다 “브레이커의 관찰 범위와 의사결정 범위를 어디까지로 잡을 것인가” 가 더 중요하다.


9. 정리

서킷 브레이커는 외부 API 장애를 없애는 기술이 아니다. 대신 장애가 생겼을 때 그 영향이 전체 시스템으로 번지지 않도록 막아주는 보호 장치다.

핵심 가치는 분명하다.

  • 연쇄 실패를 막아 시스템과 자원을 보호한다
  • 사용자에게 빠른 실패 응답을 제공한다
  • 실패 중인 외부 서비스의 부하를 줄인다
  • 회복 여부를 자동으로 감지해 가용성을 높인다

실무적으로 보면, 서킷 브레이커는 “외부 API를 쓰는가?”보다 “외부 API 장애가 우리 서비스에 얼마나 큰 비용을 발생시키는가?” 에 대한 답으로 결정해야 한다.

그리고 가장 중요한 점은 이것이다.

실패를 견디는 시스템은, 실패를 막는 시스템이 아니라 실패를 빠르게 인지하고, 멈추고, 우회하고, 회복하는 시스템이다.

외부 의존성이 많아질수록 안정성은 구현 코드보다 보호 장치의 설계 수준에서 갈린다. 그런 의미에서 서킷 브레이커는 단순 패턴이 아니라, 운영 가능한 시스템을 만들기 위한 기본 사고방식에 가깝다.



© 2020. All rights reserved.

SIKSIK