AI VIDEO BRIEFING

서킷 브레이커와 벌크헤드 패턴: 분산 시스템에서 장애 전파를 막고 폭발 반경을 가두는 회복탄력성 설계

분산 시스템에서 느려진 서비스 하나가 전체 API를 멈추게 하는 이유와, 서킷 브레이커·벌크헤드 두 패턴으로 장애의 폭발 반경을 가두는 방법을 정리했습니다.

느린 서비스 하나가 전체 시스템을 멈출 때: 서킷 브레이커와 벌크헤드로 장애를 가두는 법 영상 대표 이미지

핵심 메시지

  • 분산 시스템의 회복탄력성은 오류 처리나 재시도가 아니라 '장애를 어디서 멈출 것인가'라는 격리의 문제다.
  • 서비스가 죽지 않고 단지 느려지기만 해도, 대기 중인 스레드를 모두 소진시켜 멀쩡한 서비스까지 함께 마비시킬 수 있다.
  • 서킷 브레이커는 집의 두꺼비집처럼 동작해, 실패가 임계치를 넘으면 회로를 열어 즉시 실패시키고 스레드를 보호한다.
  • 벌크헤드는 의존성마다 자원(스레드 풀)을 분리해, 한 서비스의 장애가 자기 몫 이상을 잡아먹지 못하게 폭발 반경을 가둔다.
  • 두 패턴은 서로 다른 문제를 풀며, 함께 쓸 때 시스템이 한꺼번에 붕괴하지 않고 점진적으로 성능을 낮추며 버틴다.

쉽게 이해하기

정상적으로 돌아가던 분산 시스템에서 서비스 하나가 느려지는 상황을 가정해 보자. 데이터베이스 과부하나 하위 의존성의 지연 때문일 수 있다. 중요한 건 그 서비스가 죽은 게 아니라 단지 느릴 뿐이라는 점이다. 게이트웨이가 100개의 스레드를 가지고 있다면, 느린 서비스로 향하는 요청 하나하나가 스레드를 붙잡고 응답을 기다린다. 50개, 80개, 90개가 묶이고 나면, 정상적인 다른 서비스에 쓸 스레드조차 남지 않는다. 느린 의존성 하나가 장애도 아닌 '지연'만으로 전체 API를 끌어내린 것이다.

이때 개발자들이 가장 먼저 떠올리는 해법은 재시도다. 지수 백오프까지 곁들여 점잖게 다시 요청을 보낸다. 하지만 이미 허덕이는 서비스에 재시도를 거는 것은 익사 직전의 대상에게 부하를 더 얹는 셈이다. 회복을 돕는 게 아니라 붕괴를 앞당긴다. 영상은 회복탄력성이란 오류 처리나 재시도가 아니라 '장애가 어디서 멈추고, 시스템 자원을 얼마나 잡아먹도록 허용할 것인가'를 결정하는 장애 격리의 문제라고 짚는다.

첫 번째 패턴인 서킷 브레이커는 집의 두꺼비집과 똑같이 작동한다. 전류가 정상일 때 회로는 닫혀(closed) 있고 요청이 통과한다. 오류나 타임아웃이 급증하면 회로가 열려(open) 호출 자체를 시도하지 않고 즉시 실패한다. 차단기가 없으면 요청마다 30초짜리 타임아웃을 기다리며 스레드를 낭비하지만, 회로가 열려 있으면 밀리초 단위로 실패하고 스레드는 자유로워진다. 상태는 세 가지다. 닫힘(정상), 열림(빠른 실패), 그리고 쿨다운 후의 반열림(half-open)이다. 반열림 상태에서는 시험용 요청을 딱 하나만 흘려보내 성공하면 닫힘으로 복귀하고, 실패하면 다시 열림으로 돌아가는 '회복 탐침' 역할을 한다.

그러나 서킷 브레이커에는 빈틈이 있다. 서비스가 실패하기 시작한 순간부터 회로가 실제로 열리기까지의 짧은 시간 동안에는 요청이 여전히 통과하고 스레드도 계속 묶인다. 실패 임계치가 5건인데 그 사이에 100건이 몰려들었다면, 차단기는 출혈을 멈췄을 뿐 초기 출혈 자체는 막지 못한다. 두 번째 패턴인 벌크헤드는 이 문제를 배의 격벽에서 빌려온 방식으로 푼다. 배에 구멍이 나도 가라앉지 않는 이유는 밀폐된 격벽이 선체를 여러 구획으로 나누기 때문이다. 한 구획에 물이 차도 나머지는 멀쩡하다.

벌크헤드 패턴은 자원에 같은 일을 한다. 하나의 공유 스레드 풀 대신 의존성별로 자원을 분리한다. 서비스 B에 20개, C에 20개, D에 20개를 할당하면 B가 느려져도 B는 자기 몫 20개만 소진할 뿐이고, C와 D는 각자의 격리된 풀로 계속 돌아간다. 할당량을 넘는 B로의 요청은 큐에 쌓이지도 않고 즉시 거부되어, 피해 범위가 정확히 한 구획에 갇힌다. 정리하면 서킷 브레이커는 '언제 호출을 멈출 것인가'를, 벌크헤드는 '장애가 시스템을 얼마나 소비하도록 허용할 것인가'를 결정한다. Resilience4j 같은 라이브러리는 두 가지를 모두 제공하며, 설정 자체는 단순하지만 그 뒤에 깔린 사고방식이 핵심이다. 회복탄력성은 장애를 막는 것이 아니라, 한 방의 불이 집 전체를 태우지 않도록 벽을 세우는 일이다.

주요 인사이트

  • 장애의 본질이 '다운'이 아니라 '지연'일 수 있다는 점이 중요하다. 죽지 않고 느리기만 한 서비스가 공유 자원을 잠식해 더 위험한 연쇄 붕괴를 부른다.
  • 재시도는 직관적이지만 위험할 수 있다. 부하가 걸린 서비스에 대한 재시도는 회복을 돕는 대신 붕괴를 가속한다.
  • 서킷 브레이커의 빠른 실패(fail fast)는 단순히 에러를 빨리 반환하는 게 아니라, 30초짜리 타임아웃에 묶일 스레드를 즉시 풀어주어 자원을 지키는 데 목적이 있다.
  • 서킷 브레이커와 벌크헤드는 대체재가 아니라 보완재다. 벌크헤드는 장애 시작 순간부터 폭발 반경을 가두고, 서킷 브레이커는 패턴이 분명해진 뒤 낭비를 멈춘다.
  • 회복탄력성 설계의 어려움은 라이브러리 설정이 아니라, '어디에 벽을 세울 것인가'를 결정하는 판단에 있다.

자주 묻는 질문

서비스가 다운된 것도 아닌데 왜 전체 시스템이 멈출 수 있나요?

느려진 서비스로 향한 요청들이 응답을 기다리며 스레드를 계속 붙잡기 때문입니다. 게이트웨이의 스레드가 모두 묶이면 정상적인 다른 서비스에 쓸 스레드조차 남지 않아, 지연만으로도 전체 API가 마비될 수 있습니다.

서킷 브레이커의 세 가지 상태는 무엇인가요?

닫힘(closed)은 정상 상태로 요청이 통과하고, 열림(open)은 실패가 임계치를 넘었을 때 호출을 시도하지 않고 즉시 실패하는 상태입니다. 쿨다운 후의 반열림(half-open)은 시험용 요청 하나만 보내 성공하면 닫힘으로, 실패하면 다시 열림으로 돌아갑니다.

벌크헤드는 서킷 브레이커와 무엇이 다른가요?

서킷 브레이커가 '언제 호출을 멈출지'를 결정한다면, 벌크헤드는 의존성마다 스레드 풀을 분리해 '장애가 시스템 자원을 얼마나 소비할 수 있는지'를 제한합니다. 한 서비스의 장애를 자기 구획 안에 가두어, 회로가 열리기 전의 초기 피해까지 차단합니다.

원문과 출처

이 글은 원본 영상의 자막을 바탕으로 한국어 독자를 위해 요약했습니다. 전체 맥락과 최신 정보는 원문에서 확인하세요.

YouTube 원본 영상 보기 ↗
#분산시스템#서킷브레이커#벌크헤드#회복탄력성#장애격리