한 놈이 죽어도 나머지는 산다: 서킷 브레이커
하나의 서비스 장애가 전체 시스템으로 번지는 연쇄 장애를 막기 위해 서킷 브레이커(Circuit Breaker) 패턴을 적용하는 법을 배웁니다.
마이크로서비스를 분리하고 나면 이제 각 서비스는 네트워크를 통해 서로 API를 호출하게 됩니다. 문제는 네트워크는 절대 믿을 수 없다는 점입니다. 만약 A 서비스가 B 서비스를 호출했는데, B 서비스가 멈춰버렸다면 어떻게 될까요? A 서비스도 응답을 기다리다 결국 뻗어버리고 맙니다. 이번 시간에는 이러한 연쇄 장애를 막아주는 강력한 방패, 서킷 브레이커(Circuit Breaker) 패턴에 대해 배웁니다.
연쇄 장애(Cascading Failure)의 공포
마이크로서비스 환경에서 가장 두려운 시나리오는 바로 연쇄 장애입니다. 도미노가 무너지듯 하나의 작은 서비스 장애가 전체 시스템의 마비로 이어지는 현상입니다.
예를 들어 사용자가 상품을 주문하는 상황을 상상해 봅시다. 주문 서비스는 정상 작동 중인데, 주문 내역에 포인트를 적립해주는 '포인트 서비스'가 예상치 못한 버그로 응답을 주지 않고 무한정 대기(Hang) 상태에 빠졌습니다.
스레드 고갈(Thread Exhaustion)의 늪
주문 서비스는 포인트 서비스의 응답을 기다리기 위해 스레드(Thread)를 하나 붙잡고 있습니다. 그런데 사용자들의 주문이 계속 들어오면서, 주문 서비스의 모든 스레드가 포인트 서비스의 응답을 기다리는 데 낭비되고 맙니다.
결국 주문 서비스마저 더 이상 새로운 요청을 처리할 스레드가 남아있지 않게 되어 뻗어버립니다. 포인트 적립이 안 되는 것을 넘어서 아예 주문조차 할 수 없는 최악의 상황이 발생한 것입니다. 단일 서버(Monolith)였다면 함수 호출 한 번으로 끝났을 일이, 네트워크 분리로 인해 치명적인 약점이 되었습니다.
연쇄 장애의 발생 과정
포인트 서비스 지연 발생
→포인트 적립 API 응답이 오지 않음
주문 서비스의 스레드 대기
→응답을 기다리며 주문 서비스의 스레드(자원)가 계속 쌓임
주문 서비스 스레드 풀 고갈
→새로운 주문 요청을 받을 수 없어 주문 서비스 다운
전체 시스템 마비
→사용자는 주문 실패 에러를 보게 됨
두꺼비집을 내려라: 서킷 브레이커의 등장
이러한 재앙을 막기 위해 등장한 개념이 바로 서킷 브레이커(Circuit Breaker) 입니다. 우리 집의 '두꺼비집(누전 차단기)'과 완전히 똑같은 원리입니다. 집에 과전류가 흐르면 두꺼비집이 퓨즈를 끊어 전기를 차단함으로써 화재를 막아냅니다.
소프트웨어의 서킷 브레이커 역시 다른 서비스로의 호출을 모니터링하다가, 에러율이 높아지거나 응답 지연이 심해지면 호출을 아예 차단해버립니다.
서킷 브레이커의 3가지 상태
서킷 브레이커는 내부적으로 상태(State)를 가지고 움직입니다.
- Closed (닫힘): 모든 것이 정상인 상태입니다. 전기가 흐르듯 API 호출이 정상적으로 통과합니다.
- Open (열림): 에러가 발생하여 차단된 상태입니다. 이때 호출을 시도하면 포인트 서비스로 요청을 아예 보내지 않고, 서킷 브레이커가 즉시 에러(혹은 기본값)를 반환합니다. 덕분에 주문 서비스는 스레드를 낭비하며 기다릴 필요가 없습니다.
- Half-Open (반열림): 일정 시간이 지나면 서킷 브레이커가 '이제 포인트 서비스가 살아났을까?' 하고 조심스럽게 몇 개의 요청만 흘려보냅니다. 이때 성공하면 다시 Closed로 돌아가고, 실패하면 다시 Open 상태를 유지합니다.
서킷 브레이커 상태 전환 규칙
- 1정상 상태(Closed)에서 설정된 임계치(예: 최근 10번 중 5번 실패)를 넘으면 Open 상태로 전환된다.
- 2Open 상태에서는 대상 서비스를 호출하지 않고 즉시 빠른 실패(Fail-Fast) 처리한다.
- 3설정된 대기 시간(예: 30초)이 지나면 Half-Open 상태가 되어 일부 트래픽만 허용해 복구 여부를 테스트한다.
실패를 우아하게 처리하기 (Fallback)
서킷 브레이커가 열려서(Open) 통신이 차단되었다고 해서 사용자에게 무작정 "에러가 발생했습니다"라고 보여줄 필요는 없습니다. 똑똑한 시스템은 차선책, 즉 폴백(Fallback) 을 준비합니다.
포인트 서비스가 응답하지 않아 서킷 브레이커가 작동했다면, 주문 서비스는 다음과 같이 대응할 수 있습니다.
"일단 주문은 정상적으로 처리하고, 포인트 적립은 '나중에 적립 예정'이라는 상태로 메시지 큐에 저장해두자. 나중에 포인트 서비스가 살아나면 그때 적립하면 돼."
이렇게 하면 사용자는 포인트 서비스가 죽어 있다는 사실조차 눈치채지 못하고 원활하게 상품을 주문할 수 있습니다. 이것이 바로 마이크로서비스가 추구하는 진정한 장애 격리(Fault Tolerance) 입니다.
바이브코딩 환경의 서킷 브레이커 개념 예시 (JavaScript)
// 서킷 브레이커가 적용된 가상의 포인트 서비스 호출 로직
async function checkout(orderData) {
try {
// 서킷 브레이커를 통해 포인트 적립 API 호출 시도
await circuitBreaker.call(
() => fetch("http://point-service/api/add", { method: "POST", body: orderData })
);
console.log("주문 및 포인트 적립 완료!");
} catch (error) {
if (error.type === "CircuitBreakerOpen") {
// 포인트 서비스가 아파서 서킷 브레이커가 차단한 상태 (Fallback 실행)
console.log("포인트 서비스 지연. 주문은 완료하되 포인트는 지연 적립 큐에 보관합니다.");
await saveToDelayQueue(orderData);
} else {
console.error("기타 에러 발생");
}
}
}개념 퀴즈
퀴즈 답을 맞춰야 학습 완료가 됩니다.서킷 브레이커(Circuit Breaker)가 'Open(열림)' 상태일 때 시스템은 어떻게 동작하나요?
3줄 요약
- 1마이크로서비스 환경에서는 네트워크 통신 지연으로 인해 하나의 장애가 전체 시스템 마비(연쇄 장애)를 일으킬 수 있습니다.
- 2서킷 브레이커 패턴은 에러율이 높은 서비스로의 요청을 일시 차단(Open)하여 연쇄 장애를 방지합니다.
- 3장애가 발생했을 때 차선책을 실행하는 폴백(Fallback) 전략을 통해 사용자 경험의 훼손을 최소화할 수 있습니다.