벌크헤드 패턴

시스템의 리소스를 격리해서 한 부분의 장애가 전체로 퍼지지 않게 하는 패턴입니다. 선박의 격벽(Bulkhead)에서 이름을 따왔습니다.

비유

[선박의 격벽]
┌─────┬─────┬─────┬─────┐
│ 침수 │  OK │  OK │  OK │  ← 한 구역 침수해도 전체 침몰 방지
└─────┴─────┴─────┴─────┘

[시스템의 벌크헤드]
┌─────┬─────┬─────┬─────┐
│ 장애 │  OK │  OK │  OK │  ← 한 서비스 장애가 전체 영향 안 줌
└─────┴─────┴─────┴─────┘

왜 필요한가?

[벌크헤드 없이]
주문 서비스 ────┐
결제 서비스 ────┼───▶ 공유 스레드 풀 (100개)
배송 서비스 ────┘
                          │
              결제 서비스 느려짐
                          │
              스레드 100개 모두 결제에 점유
                          │
              주문, 배송 서비스도 마비
[벌크헤드 적용]
주문 서비스 ────▶ 스레드 풀 A (30개)
결제 서비스 ────▶ 스레드 풀 B (40개) ← 장애 발생
배송 서비스 ────▶ 스레드 풀 C (30개)
                          │
              결제만 영향, 주문/배송 정상

격리 방식

방식설명장점단점
스레드 풀 격리서비스별 별도 스레드 풀완전 격리스레드 오버헤드
세마포어 격리동시 요청 수 제한가벼움타임아웃 제어 어려움
커넥션 풀 격리서비스별 별도 커넥션 풀DB 장애 격리리소스 증가

Resilience4j로 구현

세마포어 방식

BulkheadConfig config = BulkheadConfig.custom()
    .maxConcurrentCalls(10)           // 동시 요청 최대 10개
    .maxWaitDuration(Duration.ofMillis(500))  // 대기 시간
    .build();

Bulkhead bulkhead = Bulkhead.of("paymentService", config);

Supplier<String> supplier = Bulkhead
    .decorateSupplier(bulkhead, () -> paymentService.process());

스레드 풀 방식

ThreadPoolBulkheadConfig config = ThreadPoolBulkheadConfig.custom()
    .maxThreadPoolSize(10)      // 최대 스레드 수
    .coreThreadPoolSize(5)      // 기본 스레드 수
    .queueCapacity(20)          // 대기 큐 크기
    .build();

ThreadPoolBulkhead bulkhead = ThreadPoolBulkhead.of("paymentService", config);

Spring Boot 설정

resilience4j:
  bulkhead:
    instances:
      paymentService:
        maxConcurrentCalls: 10
        maxWaitDuration: 500ms
      orderService:
        maxConcurrentCalls: 20
        maxWaitDuration: 1s
@Service
public class PaymentService {

    @Bulkhead(name = "paymentService")
    public Payment process(PaymentRequest request) {
        return paymentGateway.charge(request);
    }
}

벌크헤드 vs 서킷브레이커

패턴목적동작
벌크헤드리소스 격리동시 요청 수 제한
서킷브레이커장애 감지/차단실패율 기반 차단
// 함께 사용하면 더 강력
@Bulkhead(name = "payment")
@CircuitBreaker(name = "payment")
public Payment process(PaymentRequest request) {
    return paymentGateway.charge(request);
}

실무 적용 예시

[마이크로서비스 환경]

API Gateway
    ├─▶ 주문 서비스 (스레드 30개)
    ├─▶ 결제 서비스 (스레드 20개)
    ├─▶ 재고 서비스 (스레드 20개)
    └─▶ 알림 서비스 (스레드 10개)

→ 결제 장애 시 다른 서비스 정상 운영

CSLOG에서 보기