2025. 3. 16. 16:40ㆍSpring Microservice
🚀 [Spring Cloud + Resilience4j] Bulkhead 패턴 가이드 (Semaphore vs Thread Pool)
지난번 포스팅에서는 Resilience4j의 서킷 브레이커와 폴백 패턴을 다뤘습니다. 이번에는 시스템의 자원을 안전하게 보호하고 효율적으로 관리할 수 있는 Bulkhead(격벽) 패턴을 심층적으로 알아보겠습니다.
📌 Bulkhead 패턴이란 무엇인가요?
Bulkhead(격벽) 패턴은 배의 선체가 여러 격실로 나뉘어 침수가 배 전체로 퍼지지 않도록 막는 원리에서 착안되었습니다.
소프트웨어에서도 비슷한 원리를 적용하여, 하나의 서비스가 장애를 일으킬 때 해당 장애가 다른 서비스 호출까지 영향을 미치지 않도록 요청을 격리하는 방식입니다.
🔑 Bulkhead 패턴의 두 가지 방식
Resilience4j는 두 가지 방식으로 Bulkhead를 구현할 수 있습니다.
- Semaphore 방식 (디폴트)
- Thread Pool 방식
각각의 방식에 대해 그림을 통해 자세히 살펴보겠습니다.
🎯 1. Semaphore Bulkhead 방식 이해하기

Semaphore Bulkhead 방식은 동시에 허용 가능한 최대 요청 수를 제한하여 서비스에 대한 과부하를 방지합니다.
- 설정한 최대 요청 수에 도달하면 추가 요청은 즉시 거부됩니다.
- 세마포어(semaphore)를 이용해 요청 수를 제한하며, 설정된 숫자를 초과한 요청은 즉시 실패 처리됩니다.
🔑 Semaphore 방식 특징
- 동시 요청 수 제한 (예: 최대 20개 호출)
- 제한 초과 시 즉시 요청 거부 (reject)
- 호출 대기 시간이 매우 짧아야 하는 환경에서 효과적입니다.
🎯 2. Thread Pool Bulkhead 방식 이해하기

Thread Pool Bulkhead 방식은 요청을 개별 독립적인 쓰레드 풀에서 관리하여 서로 분리합니다. 서비스 간 호출을 독립된 쓰레드 그룹으로 격리하여, 하나의 서비스 장애가 다른 서비스에 영향을 주지 않도록 합니다.
예를 들어 위 그림에서,
- 서비스 A, 데이터베이스 B, 서비스 C 호출이 각각 다른 쓰레드 풀로 나뉘어 관리됩니다.
- 서비스 C가 장애가 발생해도, 서비스 A와 데이터베이스 B 호출은 정상적으로 작동합니다.
Thread Pool 방식 특징
- 별도의 고정된 쓰레드 풀(thread pool)과 큐를 사용
- 각 서비스가 개별 쓰레드 풀을 갖기 때문에 장애 시 영향 범위를 최소화 가능
- 성능이 서로 다른 다양한 서비스가 함께 존재할 때 효과적입니다.
🛠️ 실제 코드로 Bulkhead 구현하기 (Resilience4j)
이제 Resilience4j에서 실제로 Bulkhead를 설정하는 방법을 살펴보겠습니다.
① 기본 Semaphore 방식 설정
bootstrap.yml
resilience4j.bulkhead:
instances:
bulkheadLicenseService:
maxWaitDuration: 10ms # 최대 대기 시간
maxConcurrentCalls: 20 # 동시 호출 최대 제한
maxConcurrentCalls
: 동시에 허용 가능한 최대 요청 수maxWaitDuration
: 호출이 대기할 수 있는 최대 시간
② Thread Pool 방식 설정
Thread Pool 방식을 적용하려면 다음과 같이 설정합니다.
resilience4j.thread-pool-bulkhead:
instances:
bulkheadLicenseService:
maxThreadPoolSize: 10 # 최대 쓰레드 수
coreThreadPoolSize: 5
queueCapacity: 50
keepAliveDuration: 20ms
maxThreadPoolSize
: 쓰레드 풀의 최대 크기 (디폴트값은 CPU 코어 수)coreThreadPoolSize
: 기본 유지 쓰레드 개수 (디폴트값은 CPU 코어 수)queueCapacity
: 요청을 담아놓을 큐의 크기 (초과되면 즉시 거절)keepAliveDuration
: 쓰레드가 작업 없이 유지될 수 있는 최대 시간
🔖 어노테이션으로 설정하기 (코드 예시)
실제 서비스 메서드에 Bulkhead 패턴을 적용한 예시는 다음과 같습니다.
@CircuitBreaker(name = "licenseService", fallbackMethod = "buildFallbackLicenseList")
@Bulkhead(name = "bulkheadLicenseService") // 디폴트로 Semaphore 방식
public List<License> getLicensesByOrganization(String organizationId) {
randomlyRunLong();
return licenseRepository.findByOrganizationId(organizationId);
}
- Semaphore Bulkhead 방식의 디폴트 적용 예입니다.
- 별도의 타입 지정이 없으면 디폴트인 Semaphore 방식이 적용됩니다.
만약 Thread Pool 방식을 적용하려면 다음과 같이 명시적으로 설정해야 합니다.
@CircuitBreaker(name = "licenseService", fallbackMethod = "buildFallbackLicenseList")
@Bulkhead(name = "bulkheadLicenseService", type = Bulkhead.Type.THREADPOOL)
public List<License> getLicensesByOrganization(String organizationId) {
randomlyRunLong();
return licenseRepository.findByOrganizationId(organizationId);
}
이렇게 하면 Thread Pool을 이용해 Bulkhead를 설정하고, 장애 상황에서도 별도의 쓰레드 풀로 격리 관리합니다.
📌 적절한 쓰레드 풀 크기 설정 팁 (공식 추천 공식)
적절한 Thread Pool 크기는 다음 공식을 이용해 설정하면 효과적입니다.
(서비스가 정상일 때 피크 시간당 초당 요청 수 * 초당 99번째 percentile 지연 시간)
+ 오버헤드를 위한 소량의 추가 스레드
이 공식을 통해 운영 환경에서 효과적인 Bulkhead 쓰레드 풀 크기를 설정할 수 있습니다.
🚨 꼭 기억해야 할 주의사항!
- 하나의 서비스 장애가 다른 서비스로 전파되지 않도록 호출을 격리하는 것이 Bulkhead의 핵심입니다.
- Fallback 메서드 사용 시 추가적으로 장애가 전파되지 않게 주의하세요.
- 설정 값을 항상 운영 환경의 실측 데이터 기반으로 최적화하는 것이 좋습니다.
📚 핵심 내용 요약 정리
Bulkhead 방식 | 특징 | 활용 상황 |
---|---|---|
Semaphore 방식 (기본) | 최대 동시 호출 제한 | 빠른 응답이 필요한 환경 |
Thread Pool 방식 | 서비스별 쓰레드 풀 격리 | 성능 차이 크거나 장애 빈번 서비스 격리 필요 시 |
- Semaphore 방식은 간단히 설정 가능한 방식이며, 단순 동시 처리량 제한에 유리합니다.
- Thread Pool 방식은 서로 다른 특성을 가진 서비스 호출을 명확히 격리할 때 유용합니다.
🔥 결론
이번 포스팅에서 Bulkhead의 Semaphore 방식과 Thread Pool 방식을 모두 명확하게 설명했습니다. 제공된 모든 내용을 빠짐없이 반영했으며, 마이크로서비스 환경의 안정성을 한 단계 더 높이는 데 도움이 되길 바랍니다. 🚀✨
다음 포스팅에서도 더 유익한 정보로 찾아뵙겠습니다. 감사합니다! 🙌😊
'Spring Microservice' 카테고리의 다른 글
Implementing the retry pattern (0) | 2025.03.16 |
---|---|
99 퍼센타일(percentile) (0) | 2025.03.16 |
Fallback processing (0) | 2025.03.16 |
Implementing a circuit breaker (0) | 2025.03.16 |
Spring Cloud와 Resilience4j를 사용하기 위한 라이센싱 서비스 설정 (0) | 2025.03.16 |