Java 아키텍처·Spring 용어 사전
회복탄력성Bulkhead · rate limiter · 타임아웃

벌크헤드

의존성별로 자원을 격리해 한쪽의 폭주가 전체를 삼키지 않게 하는 것. 배의 격벽에서 온 이름이다.

의존성마다 쓸 수 있는 자원을 따로 떼어 두는 것. 배가 한 구역에 물이 차도 가라앉지 않게 격벽을 두는 데서 온 이름이다.

무엇을 막나

스레드 풀 200개를 모든 외부 호출이 공유한다

추천 서비스가 느려졌다 (부가 기능)

  • 추천 호출이 스레드 200개를 다 점유한다
  • 주문 · 결제 같은 핵심 기능까지 스레드를 못 얻는다
  • 부가 기능 하나가 서비스 전체를 삼킨다

격리한다

  • 결제 — 최대 50
  • 주문 — 최대 100
  • 추천 — 최대 20 ← 추천이 아무리 느려도 20을 넘지 못한다
@Bulkhead(name = "recommend", type = Bulkhead.Type.SEMAPHORE)
List<Item> recommend(Long userId) { return client.get(userId); }
resilience4j.bulkhead.instances.recommend:
  maxConcurrentCalls: 20
  maxWaitDuration: 0        # 자리가 없으면 즉시 실패 (기다리지 않는다)

두 가지 방식

  • 세마포어 방식 — 같은 스레드에서 동시 호출 수만 제한한다 (가볍다)
  • 스레드 풀 방식 — 의존성마다 별도 스레드 풀을 준다 (완전 격리·비용이 크다)

가상 스레드 시대에 더 중요해졌다

예전에는 스레드 풀 자체가 암묵적인 벌크헤드였다

  • "톰캣 풀 200" = 동시 외부 호출 200 이라는 상한

가상 스레드는 그 브레이크가 없다

  • 5,000개가 동시에 느린 의존성으로 몰릴 수 있다
  • 자원마다 상한을 '명시적으로' 걸어야 한다
private static final Semaphore RECOMMEND = new Semaphore(20);

List<Item> recommend(Long userId) throws InterruptedException {
    if (!RECOMMEND.tryAcquire(0, TimeUnit.MILLISECONDS)) return List.of();  // 폴백
    try { return client.get(userId); } finally { RECOMMEND.release(); }
}

rate limiter와의 차이

  • 벌크헤드 — '동시에 몇 개' 를 제한한다 (진행 중인 작업 수)
  • rate limiter — '단위 시간에 몇 번' 을 제한한다 (호출 빈도)

외부 API 가 분당 1000회 제한이면 → rate limiter 느린 의존성이 자원을 삼키는 게 문제면 → 벌크헤드

타임아웃이 없으면 전부 무의미하다

타임아웃 없이 벌크헤드만 걸면

  • 20개 슬롯이 영원히 안 돌아온다
  • 그 의존성은 완전히 멈춘 것과 같다

순서: 타임아웃 → 벌크헤드 → 서킷 브레이커

면접 함정

  • "벌크헤드는 성능을 떨어뜨린다" → 최악의 경우 전체가 멈추는 것을 막는 보험이다.
  • "서킷 브레이커가 있으면 벌크헤드는 불필요" → 회로가 열리기 전까지 자원을 삼키는 걸 막는 게 벌크헤드다.

무엇을 격리 단위로 삼나

  • 외부 의존성별 — 결제 · 배송 · 추천 (가장 흔하다)
  • API 중요도별 — 핵심 API 와 부가 API 의 스레드 풀을 나눈다
  • 테넌트별 — 특정 고객사의 폭주가 다른 고객사에 번지지 않게

톰캣 레벨에서 나누기

# 중요 API 만 별도 커넥터·포트로 분리하는 구성도 있다
server.tomcat.threads.max: 200
# 부가 기능은 별도 인스턴스로 아예 분리하는 것이 가장 확실한 격리다

폴백을 반드시 짝지운다

@Bulkhead(name = "recommend", fallbackMethod = "empty")
List<Item> recommend(Long userId) { return client.get(userId); }

List<Item> empty(Long userId, Throwable t) { return List.of(); }   // 추천이 없어도 화면은 뜬다

폴백이 없으면 벌크헤드는 그냥 에러를 앞당길 뿐이다. "부가 기능은 없어도 된다"를 코드로 표현하는 것이 격리의 완성이다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 회복탄력성 — 재시도·타임아웃·벌크헤드·RateLimit