Bulkhead Pattern
Bulkhead Pattern
Bulkhead(Partition, 격벽)란 항공기나 선박에서 내부의 구간을 나누는 칸막이 역할을 하는 요소를 말한다. 이는 구획별로 침수 피해를 입어도 다른 구획은 피해를 입지않도록 막아주는 역할을 한다.
서버에서 데이터의 흐름을 살펴보면 물의 흐름과 비슷한 점이 많다. 따라서 파이프의 서킷 브레이커 같은 용어를 가져와서 사용하기도 한다. Bulkhead도 마찬가지로 장애의 전파를 침수 피해와 비교해서 만들어진 용어이다. 선박이 손상되더라도 한 구역만 침수되고 전체가 가라앉지 않도록 하는 구조이다.
벌크헤드 패턴(Bulkhead Pattern)은 소프트웨어 아키텍처에서 시스템의 안정성과 회복력을 높이기 위해 사용되는 설계 패턴이다. 이는 시스템을 여러 독립적인 구획(컴포넌트 또는 리소스 풀)로 분리하여, 한 부분에서 장애가 발생해도 다른 부분에 영향을 미치지 않게 하는 설계 방식이다. 주요 아이디어는 다음과 같다.
- 시스템을 격리된 여러 부분으로 나눈다.
- 각 부분(벌크헤드)은 자원(스레드, 연결, 큐 등)을 독립적으로 관리한다.
- 한 부분의 장애나 과부하가 전체 시스템으로 확산되지 않게 한다.
문제 시나리오
클라우드 기반 애플리케이션에는 여러 서비스가 포함될 수 있으며, 각 서비스에는 하나 이상의 소비자가 있다. 서비스의 과도한 부하나 장애는 해당 서비스의 모든 소비자에게 영향을 미친다.
또한, 소비자는 각 요청에 리소스를 사용하여 여러 서비스에 동시에 요청을 보낼 수 있다. 소비자가 잘못 구성되었거나 응답하지 않는 서비스에 요청을 보내면 클라이언트 요청에 사용되는 리소스가 적시에 해제되지 않을 수 있다. 서비스에 대한 요청이 계속되면 해당 리소스가 고갈될 수 있다. 예를 들어, 클라이언트의 연결 풀이 고갈될 수 있다. 이 시점에서 소비자가 다른 서비스에 요청하는 것도 영향을 받으며, 결국 소비자는 원래 응답하지 않는 서비스뿐만 아니라 다른 서비스에도 더 이상 요청을 보낼 수 없게 된다.
동일한 리소스 고갈 문제는 여러 소비자가 있는 서비스에도 영향을 미친다. 한 클라이언트에서 발생한 많은 요청은 서비스의 가용 리소스를 고갈시킬 수 있다. 다른 소비자는 더 이상 서비스를 사용할 수 없게 되어 연쇄적인 장애 효과를 초래한다.
해결책
소비자 부하 및 가용성 요구 사항에 따라 서비스 인스턴스를 여러 그룹으로 분할한다. 이러한 설계는 장애를 격리하는 데 도움이 되며, 장애 발생 시에도 일부 소비자의 서비스 기능을 유지할 수 있도록 한다.
소비자는 리소스를 분할하여 한 서비스를 호출하는 데 사용되는 리소스가 다른 서비스를 호출하는 데 사용되는 리소스에 영향을 미치지 않도록 할 수도 있다. 예를 들어, 여러 서비스를 호출하는 소비자에게 각 서비스마다 연결 풀을 할당할 수 있다. 서비스에 장애가 발생하더라도 해당 서비스에 할당된 연결 풀에만 영향을 미치므로 소비자는 다른 서비스를 계속 사용할 수 있다.
이 패턴의 장점은 다음과 같다.
- 소비자와 서비스를 연쇄적인 장애로부터 격리한다. -> 소비자나 서비스에 영향을 미치는 문제를 별도의 격벽으로 격리하여 전체 솔루션의 장애를 방지할 수 있다.
- 서비스 장애 발생 시 일부 기능을 보존할 수 있다. -> 애플리케이션의 다른 서비스와 기능은 계속 작동.
- 애플리케이션 소비에 따라 다양한 서비스 품질을 제공하는 서비스를 배포할 수 있다. -> 높은 우선순위의 소비자 풀을 구성하여 높은 우선순위의 서비스를 사용할 수 있음.
다음 다이어그램은 개별 서비스를 호출하는 커넥션 풀을 중심으로 구성된 벌크헤드를 보여준다. 서비스 A에 장애가 발생하거나 다른 문제가 발생하면 연결 풀이 격리되므로 서비스 A에 할당된 스레드 풀을 사용하는 워크로드만 영향을 받는다. 서비스 B와 C를 사용하는 워크로드는 영향을 받지 않으며 중단 없이 작업을 계속 수행할 수 있다.
다음 다이어그램은 여러 클라이언트가 단일 서비스를 호출하는 모습을 보여준다. 각 클라이언트에는 별도의 서비스 인스턴스가 할당된다. 클라이언트 1은 너무 많은 요청을 보내 인스턴스에 과부하가 걸렸다. 각 서비스 인스턴스가 서로 분리되어 있기 때문에 다른 클라이언트는 계속 호출할 수 있다.
구현 예시
- 스레드 풀 분리
- 결제 서비스, 알림 서비스, 주문 서비스가 각각 독립된 스레드 풀을 가진다면,
- 결제 서비스가 느려져도 알림 서비스 스레드를 점유하지 않아 전체가 멈추지 않음.
- DB 연결 풀 분리
- 주요 데이터베이스와 보조 데이터베이스를 별도 연결 풀로 운영해 한쪽 장애가 다른 쪽에 영향을 주지 않게 함.
- 마이크로서비스 구조
- 각 마이크로서비스가 자체 자원과 장애 대응 전략을 가지고 있어서 한 서비스의 장애가 전체 시스템을 멈추지 않게 함.
특징
장점
- 장애 격리 및 시스템 안정성 향상
- 특정 기능의 과부하로 인한 전체 장애 방지
- 다른 회복 패턴(예: Circuit Breaker, Retry)과 함께 사용 시 복원력 극대화
단점
- 구성과 관리가 복잡해짐
- 자원 분리로 인한 리소스 낭비 가능성
- 잘못된 구획 설정 시 오히려 성능 저하
이 패턴을 언제 사용해야 할까?
이 패턴을 사용하면 다음을 수행할 수 있다.
- 특히 애플리케이션이 서비스 중 하나가 응답하지 않을 때에도 일정 수준의 기능을 제공할 수 있는 경우 백엔드 서비스 집합을 사용하는 데 사용되는 리소스를 격리한다.
- 중요한 소비자와 표준 소비자를 분리한다.
- 연쇄적인 오류로부터 애플리케이션을 보호한다.
다음과 같은 경우 이 패턴이 적합하지 않을 수 있다.
- 프로젝트에서 리소스를 덜 효율적으로 사용하는 것이 허용되지 않는 경우
- 추가된 복잡성은 필요하지 않은 경우

