백엔드 면접 용어 사전
시스템 설계bulkhead

벌크헤드

자원 풀을 격리해 한 곳의 고갈이 전체로 번지지 않게 하는 패턴.

자원을 여러 칸으로 격리해 한 칸이 침수돼도 전체가 가라앉지 않게 하는 패턴. 배의 격벽(bulkhead)에서 온 이름이다.

무엇을 막는가

공유 스레드 풀 (200개)
├─ 결제 API 호출 (느려짐: 30초)  → 스레드 190개 점유
├─ 상품 조회                     → 스레드를 못 받음 ✗
└─ 로그인                        → 스레드를 못 받음 ✗

결제 하나가 느려져서 전혀 무관한 로그인까지 막힌다

자원을 공유하는 한, 한 곳의 문제가 전체로 번진다. 서킷 브레이커가 "호출을 끊는" 방식이라면, 벌크헤드는 "자원을 나누는" 방식이다.

격리 후

  • 결제 풀 — (50개) → 다 막혀도 50개만 손실
  • 조회 풀 — (100개) → 정상 동작 ✅
  • 로그인 풀 (50개) — → 정상 동작 ✅

결제 기능은 실패하지만 나머지는 살아 있다. 이것이 부분 장애다.

두 가지 구현

① 스레드 풀 격리

ExecutorService paymentPool = Executors.newFixedThreadPool(50);
ExecutorService searchPool  = Executors.newFixedThreadPool(100);

완전히 분리되어 확실하지만, 스레드 수가 늘어 컨텍스트 스위칭 비용이 는다. 호출이 별도 스레드에서 일어나므로 ThreadLocal(보안 컨텍스트, MDC)이 전파되지 않는 문제도 있다.

② 세마포어 격리

Semaphore paymentLimit = new Semaphore(50);

같은 스레드에서 실행하되 동시 진입 수만 제한한다. 가볍고 ThreadLocal도 유지된다. 대신 호출이 블로킹되면 호출한 스레드가 그대로 묶인다 — 타임아웃이 반드시 필요하다.

실무 기준 — 외부 시스템 호출처럼 오래 걸릴 수 있으면 스레드 풀 격리, 내부 처리라면 세마포어 격리.

어느 층위에서 나눌 수 있나

층위
스레드 풀기능별로 분리
커넥션 풀조회용/쓰기용 DataSource 분리
인스턴스배치 전용 서버를 따로
셀 기반 아키텍처고객군을 완전히 분리된 셀로 (한 셀 장애가 그 고객만)

커넥션 풀 분리가 실무에서 특히 효과적이다. 무거운 통계 쿼리가 커넥션을 다 쓰면 일반 조회까지 막히는데, 풀을 나누면 격리된다.

크기를 어떻게 정하나

너무 작으면 → 정상 상황에서도 대기 발생 (스스로 병목이 됨)

  • 너무 크면 — → 격리 효과가 없다 (다 함께 죽는 것과 같음)

기준: 해당 기능의 피크 동시 요청 수 + 여유

  • 전체 합이 시스템 총 자원을 넘지 않게

중요 — 각 칸의 합이 전체 자원보다 크면 격리가 무의미하다. "결제 100 + 조회 100 + 로그인 100 = 300, 실제 스레드는 200"이면 결국 서로 잠식한다.

서킷 브레이커·타임아웃과의 조합

세 패턴은 층이 다르다.

  • 타임아웃 — : 개별 호출이 무한정 기다리지 않게 (가장 기본)
  • 벌크헤드 — : 자원을 나눠 전파 자체를 물리적으로 차단
  • 서킷 브레이커 — : 고장난 대상 호출을 아예 생략

타임아웃 없는 벌크헤드는 반쪽짜리다 — 격리된 칸이 결국 다 차 버린다. 셋을 함께 쓰는 것이 표준 구성이다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 시스템 설계·분산 — CAP·복제·샤딩·Saga·서킷브레이커