Circuit Breaker 패턴 NGINX Plus로 구현하기
Martin Fowler가 대중화시킨 용어인 Circuit Breaker 패턴은 Microservices 아키텍트 사이에서 연쇄적인 서비스 장애를 방지하는 애플리케이션 설계 패턴으로 널리 사용되고 있습니다. Circuit Breaker 패턴의 개념은 애플리케이션 서비스와 애플리케이션 간에 흐르는 트래픽을 모니터링하여 장애를 방지하고, 장애가 발생할 경우 해당 장애가 애플리케이션에 미치는 영향을 최소화하는 것입니다.
Microservices 애플리케이션 설계는 애플리케이션이 작동하는 방식에 대대적인 변화를 가져왔습니다. Microservices 아키텍처에서 ‘애플리케이션’은 이제 작업을 수행하고 기능을 제공하기 위해 서로 의존하는 서비스들의 집합입니다. 복잡한 애플리케이션에서는 서비스 그래프가 상당히 깊고 다양한 서비스 간에 여러 상호 의존성이 있을 수 있습니다.
예를 들어, 사용자 서비스는 서비스에서 제공하는 데이터에 의존하는 다른 많은 서비스에 필수적일 수 있습니다. 이 시나리오에서 사용자 서비스에 장애가 발생하면 애플리케이션 전체에 연쇄적으로 장애가 발생할 수 있습니다.
Microservices의 경우 Circuit Breaker 패턴은 상향식 탄력성을 제공하는 데 특히 유용합니다. 올바르게 구현하면 서비스를 사용할 수 없는 경우에도 서비스 연속성을 제공하여 연쇄적인 장애를 방지할 수 있습니다. Circuit Breaker 패턴은 넷플릭스가 애플리케이션 설계 철학의 핵심 요소로 채택한 것으로 가장 잘 알려져 있습니다.
목차
1. 실패를 피하지 말고 포용하세요
2. Circuit Breaker 패턴으로 일관성 향상
3. Circuit Breaker 패턴으로 유연성 제공
4. NGINX Plus에서 Circuit Breaker 패턴 구현하기
5. 결론
1. 실패를 피하지 말고 포용하세요
현대 애플리케이션 설계의 핵심 원칙은 장애가 발생할 수 있다는 것입니다. 클라우드 호스팅 가상 머신에서 컨테이너, 애플리케이션 라이브러리, 동적 네트워킹에 이르기까지 현대 애플리케이션이 의존하는 계층화된 구조는 모든 애플리케이션에서 움직이는 부품이 군단처럼 많다는 것을 의미합니다. 애플리케이션의 하나 이상의 부분이 언젠가는 어떤 식으로든 실패할 것이라는 가정이 필요합니다. 장애를 예상하고 그 영향을 완화하기 위한 메커니즘을 구축하면 애플리케이션의 탄력성을 높이는 데 큰 도움이 됩니다.
Circuit Breaker 패턴의 가장 중요한 목표 중 하나는 장애를 애초에 방지하는 것입니다. 메모리 부족과 같은 일부 유형의 오류 조건의 경우, 장애가 임박했음을 인식하고 이를 방지하기 위한 조치를 취할 수 있습니다. 이러한 조치는 일반적으로 서비스가 상태가 좋지 않다는 신호를 보내면 Circuit Breaker가 요청 수를 제한하거나 완전히 경로를 재설정하여 서비스가 복구할 기회를 주는 방식으로 이루어집니다. 서비스가 복구된 후에는 Circuit Breaker가 서비스에 대한 요청을 서서히 증가시켜 서비스에 즉시 과부하가 걸리거나 다시 비정상 상태가 되지 않도록 방지하는 것이 중요합니다.
NGINX Microservices 참조 아키텍처에는 Resizer라는 서비스가 있습니다. Resizer는 큰 사진이 시스템에 업로드되면 압축을 풀고, 회전을 수정하고, 축소한 다음 다시 축소하여 수정된 원본 이미지와 크기가 조정된 두 이미지를 객체 저장소에 저장합니다. 이러한 프로세스의 특성으로 인해 Resizer는 애플리케이션에서 가장 프로세서와 메모리를 많이 사용하는 부분이 됩니다.
많은 이미지의 크기를 동시에 조정하는 경우 Resizer의 메모리가 부족하여 일부 시나리오에서는 완전히 실패할 수 있습니다. 문제를 방지하기 위해 크기 조정기 서비스 인스턴스와 이 인스턴스에 이미지를 공급하는 업로더 서비스 사이에 Circuit Breaker를 배치했습니다. 업로더는 Resizer 인스턴스의 상태를 정기적으로 Query합니다. 이 Query는 Resizer가 사용 가능한 메모리의 80% 이상을 사용했는지를 평가하고 다른 Health Check 항목과 함께 업로더에게 상태 정보를 응답하도록 트리거합니다.
Resizer 인스턴스가 정상적이지 않다고 표시되면 업로더는 그림 1과 같이 요청을 다른 인스턴스로 라우팅하지만 해당 Resizer 인스턴스가 복구되었는지 계속 확인합니다. Resizer 인스턴스가 다시 정상이라고 표시되면 Load Balancing Pool에 다시 배치되고 업로더는 트래픽을 인스턴스의 최대 용량까지 천천히 증가시킵니다. 이 설계는 Resizer 인스턴스가 완전히 실패하는 것을 방지하고, 작업이 시작되었지만 완료되지 않는 것을 방지하고, 프로세스가 실패했을 사용자를 과도하게 기다리는 것을 방지하고, 시스템이 Resizer로 전송된 요청 Stream을 가장 효과적으로 처리할 수 있도록 도와줍니다.

그림 1. Active Health Check를 통해 건강하지 않은 Microservices 인스턴스로의 호출 방지
2. Circuit Breaker 패턴으로 일관성 향상
NGINX 수준에서 Circuit Breaker를 구현하면 얻을 수 있는 이점 중 하나는 Microservices 애플리케이션 전반에서 Circuit Breaker를 관리하기 위한 범용적이고 일관되며 매우 유연한 계층을 생성한다는 점입니다. 이러한 범용성과 일관성은 각 언어별 Circuit Breaker 라이브러리의 미묘한 차이와 불일치를 관리하고 구축할 필요가 없음을 의미합니다.
각 서비스의 코드에서 대부분의 Circuit Breaker 기능을 제외하고 대신 NGINX Plus에서 구현하면 많은 이점을 얻을 수 있습니다.
- 예를 들어 Java로 작성된 서비스의 Circuit Breaker는 PHP로 작성된 서비스의 Circuit Breaker와 동일하며, 필요에 따라 Circuit Breaker 자체를 다른 언어로 작성할 수 있습니다.
- 각 서비스에서 사용하는 여러 언어와 지원 라이브러리에서 Circuit Breaker 기능을 다시 구현할 필요가 없습니다.
- 따라서 Circuit Breaker 코드를 포함할 필요가 없는 각 서비스가 간소화되어 실행 속도가 빨라지고 작성, 디버그, 실행 및 유지 관리가 더 쉬워집니다.
- 각 서비스에 대한 지원 코드가 간소화되어 사용되는 라이브러리 및 시스템의 조합은 서비스의 핵심 기능만 반영할 수 있습니다.
- Circuit Breaker 코드가 간소화되어 한 곳에만 존재하는 기존 코드를 현지 상황에 맞게 조정할 필요 없이 필수 요소로만 간소화할 수 있습니다.
- Circuit Breaker 코드는 캐싱과 같은 NGINX Plus 기능을 활용하여 훨씬 더 강력하게 만들 수 있습니다.
- NGINX Plus 수준의 Circuit Breaker 코드를 미세 조정한 다음 On-Premise, 다양한 클라우드 플랫폼, 혼합 환경과 같은 다른 애플리케이션과 배포 플랫폼에서 재사용할 수 있습니다.
그러나 Circuit Breaker는 NGINX Plus만으로는 구현할 수 없다는 점에 유의해야 합니다. 진정한 Circuit Breaker를 구현하려면 서비스가 지정된 URI(일반적으로 /health
)에서 Introspective, Active Health Check을 제공해야 합니다. Health Check는 해당 특정 서비스의 요구 사항에 적합해야 합니다.
Health Check을 개발할 때는 서비스의 장애 프로필과 데이터베이스 연결 실패, 메모리 부족 상태, 디스크 공간 부족, CPU 과부하 등 장애를 일으킬 수 있는 조건의 종류를 이해해야 합니다. 이러한 조건은 Health Check 프로세스에서 평가되어 정상 또는 비정상이라는 Binary 상태를 제공합니다.
3. Circuit Breaker 패턴으로 유연성 제공
여기에 설명된 대로 NGINX Level에서 Circuit Breaker 패턴을 구현하는 경우, 서비스 인스턴스가 정상적이지 않다고 전달하는 상황을 처리하는 것은 NGINX Plus 의 몫입니다. 여기에는 여러 가지 옵션이 있습니다.
첫 번째 옵션은 요청을 다른 정상 인스턴스로 리다이렉션하고 정상적이지 않은 인스턴스가 복구되는지 확인하기 위해 계속 Query하는 것입니다. 두 번째 옵션은 서비스를 요청하는 클라이언트에 캐시된 응답을 제공하여 서비스를 사용할 수 없는 경우에도 안정성을 유지하는 것입니다. 이 솔루션은 콘텐츠 서비스와 같이 읽기 지향적인 서비스에 적합합니다.
또 다른 옵션은 대체 데이터 소스를 제공하는 것입니다. 예를 들어, 프로필 데이터를 사용하여 사용자에게 타겟팅 광고를 게재하는 개인화된 광고 서버를 보유한 고객이 있습니다. 개인화된 광고 서버가 다운되면 사용자 요청은 모든 사용자에게 적합한 일반 광고 세트를 제공하는 백업 서버로 리다이렉션됩니다. 이 대체 데이터 소스 접근 방식은 매우 강력할 수 있습니다.
마지막으로, 서비스의 장애 프로필을 명확하게 파악하고 있다면 차단기에 속도 제한을 추가하여 장애를 완화할 수 있습니다. 서비스에서 처리할 수 있는 속도로만 요청이 허용됩니다. 이렇게 하면 Circuit Breaker 내에 Buffer가 생성되어 트래픽 급증을 흡수할 수 있습니다.
속도 제한은 사이트 전체의 총 트래픽 사용량을 잘 파악할 수 있는 제한된 수의 Load Balancer를 통해 애플리케이션 트래픽이 라우팅되는 Router Mesh 모델
과 같은 중앙 집중식 Load Balancing 시나리오에서 특히 강력할 수 있습니다.
4. NGINX Plus에서 Circuit Breaker 패턴 구현하기
위에서 설명한 것처럼 Circuit Breaker 패턴은 비정상적인 서비스로의 트래픽을 줄이거나 요청을 해당 서비스에서 멀리 라우팅하여 장애가 발생하기 전에 예방할 수 있습니다. 이를 위해서는 각 서비스에 대한 Introspective Health 모니터에 연결된 Active Health Check가 필요합니다. 안타깝게도 Passive Health Check는 장애 발생 여부만 확인하므로 예방 조치를 취하기에는 이미 너무 늦어버리기 때문에 효과가 없습니다. 이러한 이유로 NGINX Open Source는 Circuit Breaker 패턴을 구현할 수 없으며 Passive Health Check만 지원합니다.
그러나 NGINX Plus는 상태 문제를 확인하고 대응할 수 있는 다양한 옵션을 갖춘 강력한 Active Health Check 시스템을 갖추고 있습니다. Microservices 참조 아키텍처에 대한 일부 서비스 유형의 구현을 살펴보면 Circuit Breaker를 구현하기 위한 옵션과 사용 사례에 대한 좋은 예를 확인할 수 있습니다.
Resizer에 연결되는 업로더 서비스부터 시작해 보겠습니다. 업로더는 이미지를 객체 저장소에 넣은 다음 Resizer에 이미지를 열어 수정하고 크기를 조정하도록 지시합니다. Resizer는 말 그대로 실행 중인 호스트를 종료시킬 수 있으므로 업로더는 Resizer의 상태를 모니터링하고 과부하를 피해야 합니다.
가장 먼저 할 일은 Resizer Health Check를 위해 특별히 location 블록을 생성하는 것입니다. 이 블록은 내부 location이므로 서버의 표준 URL(http://example.com/health-check-resizer
)로 요청하여 액세스할 수 없습니다. 대신 Health Check 정보를 위한 Placeholder 역할을 합니다. health_check
지시문은 3초마다 /health
URI로 Health Check를 전송하고 conditions
이라는 match
블록에 정의된 테스트를 사용하여 서비스 인스턴스의 Health Check합니다. 서비스 인스턴스가 한 번이라도 검사를 놓치면 정상적이지 않은 것으로 표시됩니다. proxy_*
지시문은 지정된 HTTP 헤더가 null로 설정된 상태에서 TLS 1.2 over HTTP 1.1을 사용하여 resizer
upstream 그룹에 Health Check을 보냅니다.
location /health-check-resizer {
internal;
health_check uri=/health match=conditions fails=1 interval=3s;
proxy_pass https://resizer;
proxy_ssl_session_reuse on;
proxy_ssl_protocols TLSv1.2;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Accept-Encoding "";
}
다음 단계는 condition match
블록을 생성하여 정상 및 비정상 상태를 나타내는 응답을 지정하는 것입니다. 첫 번째 확인은 응답 상태 코드입니다. 응답 상태 코드가 200에서 399 범위 내에 있으면 테스트가 다음 평가 문으로 진행됩니다. 두 번째 확인은 콘텐츠 유형이 애플리케이션/json인지 확인합니다. 마지막으로 세 번째 확인은 교착 상태, 디스크 및 메모리 지표의 값에 대한 정규식 일치 여부입니다. 모두 정상이면 서비스가 정상으로 판단됩니다.
match conditions {
status 200-399;
header Content-Type ~ "application/json";
body ~ '{
"deadlocks":{"healthy":true},
"Disk":{"healthy":true},
"Memory":{"healthy":true}
}';
}
upstream 블록의 Resizer 서비스에 대한 server
지시문에 slow_start 매개변수를 설정하면, Resizer 인스턴스가 정상적이지 않은 상태에서 처음 돌아올 때 트래픽 흐름을 조절하도록 NGINX Plus에 지시할 수 있습니다. 정상 서비스로 전송되는 요청과 동일한 수의 요청으로 서비스를 압박하는 대신, 복구 중인 서비스로의 트래픽은 slow_start 매개변수로 지정된 기간(이 경우 30초) 동안 정상 속도로 천천히 증가합니다. Slow Start는 서비스가 정상 기능으로 돌아올 가능성을 높이는 동시에 정상 기능으로 돌아가지 않을 경우의 영향을 줄여줍니다.
upstream resizer {
server resizer slow_start=30s;
zone backend 64k;
least_time last_byte;
keepalive 300;
}
요청 제한은 서비스에 대한 요청의 흐름을 관리하고 조정합니다. 애플리케이션의 장애 프로필을 충분히 이해하여 특정 시간에 처리할 수 있는 요청의 수를 알고 있다면 요청 제한을 구현하는 것이 프로세스에 큰 도움이 될 수 있습니다. 그러나 이 기능은 NGINX Plus 가 서비스에 전달되는 총 연결 수를 완전히 인식하고 있는 경우에만 작동합니다. 따라서 Fabric 모델에서와 같이 서비스 자체와 함께 컨테이너에서 실행되는 NGINX Plus 인스턴스 또는 클러스터의 모든 트래픽을 관리하는 중앙 집중식 Load Balancer에서 요청 제한 Circuit Breaker를 구현하는 것이 가장 유용합니다.
다음 구성 코드 Snippet은 컨테이너의 Resizer 서비스 인스턴스에 적용될 요청에 대한 속도 제한을 정의합니다. limit_req_zone 지시문은 초당 100개의 요청으로 속도 제한을 정의합니다. $server_addr
변수가 Key로 사용되므로 Resizer 컨테이너로 들어오는 모든 요청이 제한에 따라 계산됩니다. Zone의 이름은 moderateReqs
이며 요청 수를 유지하는 기간은 1분입니다. limit_req 지시문을 사용하면 NGINX Plus가 최대 150개의 요청 Burst를 버퍼링할 수 있습니다. 이 숫자를 초과하면 클라이언트는 limit_req_status 지시문에 지정된 대로 503 오류 코드를 수신하여 서비스를 사용할 수 없음을 나타냅니다.
http {
# Moderated delivery
limit_req_zone $server_addr zone=moderateReqs:1m rate=100r/s;
# ...
server {
# ...
limit_req zone=moderateReqs burst=150;
limit_req_status 503;
# ...
}
}
NGINX Plus 내에서 Circuit Breaker를 실행하면 얻을 수 있는 또 다른 강력한 이점은 캐싱을 통합하고 캐시된 데이터를 중앙에서 유지 관리하여 시스템 전체에서 사용할 수 있다는 점입니다. 이는 Backend에서 읽는 데이터가 자주 변경되지 않는 콘텐츠 서버와 같은 읽기 중심 서비스에 특히 유용합니다.
proxy_cache_path /app/cache levels=1:2 keys_zone=oauth_cache:10m max_size=10m inactive=15s use_temp_path=off;
upstream user-manager {
server user-manager;
zone backend 64k;
least_time last_byte;
keepalive 300;
}
server {
listen 443 ssl;
location /v1/users {
proxy_pass http://user-manager;
proxy_cache oauth_cache;
proxy_cache_valid 200 30s;
proxy_cache_use_stale error timeout invalid_header updating
http_500 http_502 http_503 http_504;
}
}
그림 2에서 볼 수 있듯이 데이터를 캐싱하면 많은 고객 데이터 요청이 Microservices 인스턴스에 도달하지 않으므로 이전에 수신되지 않은 요청을 위한 용량을 확보할 수 있습니다.

그림 2. 캐싱은 일반적으로 Microservices 인스턴스에 대한 호출을 방지하여 성능 속도를 높이는 데 사용되지만, 전체 서비스 장애에 대비하여 서비스 연속성을 제공하는 역할도 합니다.
그러나 사용자 관리자 서비스처럼 데이터가 변경될 수 있는 서비스에서는 캐시를 신중하게 관리해야 합니다. 그렇지 않으면 사용자가 프로필을 변경했지만 데이터가 캐시되어 있기 때문에 일부 컨텍스트에서 이전 데이터를 보게 되는 시나리오가 발생할 수 있습니다. 합리적인 Timeout을 설정하고 궁극적으로 일관성을 유지하면서 고가용성 원칙을 수용하면 이 문제를 해결할 수 있습니다.
위의 코드 조각에서 서비스가 가장 일반적인 네 가지 500 시리즈 오류 코드 중 하나로 응답하는 경우처럼 서비스를 완전히 사용할 수 없는 경우에도 캐시된 데이터를 계속 제공할 수 있다는 것이 NGINX 캐시의 좋은 기능 중 하나입니다.
서버가 다운되더라도 클라이언트에 응답할 수 있는 유일한 옵션은 캐싱이 아닙니다. Circuit Breaker 패턴이 유연성을 제공한다는 글에서 언급했듯이, 고객 중 한 명은 개인화된 광고 서버가 다운될 경우를 대비해 탄력적인 솔루션이 필요했지만 캐시된 응답은 좋은 해결책이 아니었습니다. 대신 개인화된 서버가 다시 온라인 상태가 될 때까지 일반 광고 서버에서 일반 광고를 제공하길 원했습니다. 이는 server 지시문에 backup 매개변수를 사용하여 쉽게 달성할 수 있습니다. 다음 코드 Snippet은 개인 광고 서버
도메인에 대해 정의된 모든 서버를 사용할 수 없는 경우 일반 광고 서버
도메인에 대해 정의된 서버가 대신 사용되도록 지정합니다.
upstream personal-ad-server {
server personal-ad-server;
server generic-ad-server backup;
zone backend 64k;
least_time last_byte;
keepalive 300;
}
마지막으로, NGINX가 서비스의 응답 코드를 평가하여 개별적으로 처리하도록 할 수 있습니다. 다음 코드 Snippet에서 서비스가 503 오류를 반환하는 경우 NGINX Plus는 요청을 대체 서비스로 보냅니다. 예를 들어 Resizer에 이 기능이 있는데 로컬 인스턴스에 과부하가 걸리거나 작동이 중지되는 경우 요청은 다른 Resizer 인스턴스로 전송됩니다.
location / {
error_page 503 = @fallback;
}
location @fallback {
proxy_pass http://alternative-backend;
}
5. 결론
Circuit Breaker 패턴은 Microservices 애플리케이션에 탄력성과 제어 기능을 제공하는 강력한 도구입니다. NGINX Plus는 Circuit Breaker를 사용자 환경에 구현할 수 있는 다양한 기능과 옵션을 제공합니다. Circuit Breaker 패턴 구현의 핵심은 보호하려는 서비스의 장애 프로필을 파악한 다음 가능한 경우 장애를 가장 잘 방지하고 장애가 발생했을 때 그 영향을 가장 잘 완화할 수 있는 옵션을 선택하는 것입니다.
NGINX Plus를 직접 사용해 보거나 테스트해 보려면 지금 30일 무료 평가판을 신청하거나 사용 사례에 대해 최신 소식을 빠르게 전달받고 싶으시면 아래 뉴스레터를 구독하세요.
댓글을 달려면 로그인해야 합니다.