본문 바로가기

생각

서킷브레이커의 사용설명서는 없다 with.연동 라이브러리들

Discovering circuit breakers was the day I first encountered error handling. On a weekend afternoon, errors occurred consecutively across projects within the team. The initial point of error was a service heavily integrated with external systems. Errors originating from this service cascaded to affect other services, spreading like wildfire.

circuitBreakers 를 처음 알게 된 건, 에러 대응을 한 날이었습니다. 주말 오후, 팀 내 프로젝트 간에 연달아 에러가 발생했습니다. 첫 에러 발화 지점은 외부 시스템 연동이 잦은 서비스였습니다. 해당 서비스에서 시작한 에러는 번지듯 다른 서비스에 영향을 끼쳤습니다. 

 



서킷브레이커를 적용해야하는 경우의 시나리오입니다. 이런 상황에 대비하기 위해 우리팀도 서킷브레이커를 적용하였습니다. 이때 우리는 대략적인 설정값을 가져갔습니다. 이 점이 조금 찝찝했습니다. 해당 애플리케이션에 대한 이해가 없는 채로, 객관적인 수치나 근거 없이 느낌적인 느낌대로 "이만치면 되겠지" 같은 헐거운 방식이었기 때문입니다. 그러면 어떤 방식이어야 좀 덜 찝찝했을지 생각해보았습니다.

 

제가 해당 포스팅에서 짚고 싶은 바는 다음과 같습니다.

  • 서킷 브레이커의 모듈들은 어떤 역할을 하는건지
  • 서킷 브레이커와 연동할 수 있는 라이브러리는 어떤 것들이 있는지. 각각 어떤 역할을 하는건지.
  • 서킷 브레이커 설정값에서 best practice 라는 것이 있을 수 있는지. 어떻게 얻을 수 있는지.
  • 하나의 애플리케이션에 대한 이해와 통찰은 어떻게 얻을 수 있는지.


우선, circuitBreakers 는 MSA 기반의 시스템에서 사용되는 에러 전파 제어 장치입니다. MSA 에서 장애가 전파되지않도록 하는 원칙이 있는데, 그 때 유용하게 사용될 수 있는 도구가 서킷브레이커입니다. 이 외, 다른 원칙은 여기서 확인해보실 수 있습니다. MSA 12 Factors Application

 

 

사실 제가 서킷 브레이커라는 용어를 처음 접한 건 주식 시장에서였습니다..

 

 

주식 시장에서의 서킷 브레이커는 어떤 종목에서 비정상적인 급등 또는 급락시 거래 정지가 발효되는 안전 제동 장치로서 작동합니다. 개발 시스템에서의 서킷 브레이커도 비슷한 역할을 하는 것으로 보입니다. 여기서 제가 중점을 둔 건 무엇을 안전하게 지키고 싶은가하는 그 대상이었습니다. MSA 12 Factors Application 을 토대로 이해해보면, 외부 서비스의 장애 상황으로부터 내부 서비스를 보호하는 개념이었습니다. 그러니까, 비정상적일 수 있는 외부 상황에 대비하여 내부 애플리케이션의 저항력을 높이기위해 서킷브레이커를 활용해볼 수 있다는 말입니다. 

 

 

먼저, 서킷 브레이커의 각 모듈이 어떤 역할을 하는지 짧게 짚어보겠습니다. 

 

위 그림은 아래의 다양한 모듈에서의 설정을 조합하여 기술해두었습니다.

 

 

Circuit Breaker 모듈
Circuit Breaker 패턴을 구현한 모듈.

  • 요청 중 지연된 응답이 몇 퍼센트 이상일 때 서킷의 상태를 OPEN 으로 전이할지 설정한다.
  • 서킷 상태가 OPEN 일 때, `CallNotPermittedException` 과 함께 요청 거절한다.
  • 서킷 상태가 OPEN 이 되면 설정한 시간 이후에 HALF_OPEN 으로 바뀌고 설정한 수 만큼 요청을 허용해 서버가 사용 가능 상태인지 확인한다.
  • 서킷 상태를 전이시키는 방식에는 아래 두 가지가 있음.
    Count Based sliding window N번의 요청 중 M번 실패했을 때, 실패율(Failure Rate)이 설정값(FailureRateThreshold) 보다 클 경우 서킷 상태를 OPEN 으로 전이시키는 방식
    Time Based sliding window N번의 요청 중 응답 시간이 일정 시간을 초과하는 퍼센트(Slow Call Rate)가 설정값(slowCallRateThreshold) 보다 클 경우 서킷 상태를 OPEN 으로 전이시키는 방식

 

BulkHead 모듈

동시 실행의 수를 제어하기 위한 모듈.

  • BulkHead 패턴을 위해 2가지 구현을 제공
    세마포어방식의 SemaphoreBulkhead
    고정된 Thread pool 과 Bounded queue 를 사용하는 FixedThreadPoolBulkHead

RateLimiter 모듈

서비스 호출의 속도를 제어하여 초당 허용된 호출 횟수를 넘지 않도록 하는 기능 제공


TimeLimiter 모듈
서비스 호출에 대한 최대 실행 시간을 설정하여 지정된 시간 내에 호출이 완료되지 않으면 예외를 발생시키는 기능 제공

Retry 모듈
일시적인 장애나 네트워크 문제로 인한 호출 실패에 대응하여 지정된 횟수만큼 재시도하는 기능 제공

 


사실 이 정도는 다른 블로그와 공식 문서에도 많이 나와있는 정보입니다. 설정값들의 의미에 대해서는 이 포스팅의 관심사가 아니라서 구글링으로 찾아보시는 것을 추천드립니다. 

 

더 세세한 사항은 해당 포스팅 을 참고하시면 됩니다.



서킷 브레이커 모듈의 이해를 바탕으로 제가 구현했던 서킷 브레이커를 복기해보겠습니다. 우리는 spring cloud에서 쓰이는 서킷 브레이커 라이브러리인 resilience4j 를 활용했습니다. 구글링와 공식 문서를 참고하여 다음과 같은 설정으로 가져갔습니다. 

 

resilience4j > circuitbreaker > configs 는 예시를 위한 설정값입니다.

 

 

 

circuitbreaker 키워드를 검색해보면 3군데에 존재합니다. HTTP 통신에 사용되는 `openfeign`, 스프링부트 기반의 애플리케이션 모니터링인 `actuator` 설정, 마지막으로 서킷브레이커 내부 설정 할 수 있는 `resilience4j` 입니다. 저는 이 라이브러리들 간의 연관성이 궁금해졌습니다. 도대체 서킷 브레이커는 이 라이브러리들과 어떤 관계를 맺고 있는걸까요. 구글링과 chatGPT 의 도움을 받아 다음과 같은 그림을 그릴 수 있었습니다. 

 

서킷브레이커와 연동할 수 있는 통합 라이브러리들

 

 

그림 내부에서 더 긴밀하게 연결되는 관계가 있을 수 있지만, 제가 파악한 건 이 정도입니다. 그림을 그려보니 각 라이브러리들이 하는 역할도 대충 파악이 되고, 서로 어떤 연관을 맺고 있는지도 구조가 잡혔습니다. 다시 application.yaml 파일로 돌아가서보면 설정 값에 왜 circuitbreaker 키워드가 존재하는지 알 수 있습니다. 

 

`resilience4j` 는 Spring Cloud 반열의 서킷 브레이커 구현 라이브러리입니다. 서비스 간 통신시 사용되는 `openfeign` 과 통합하여 서비스 간 통신시 장애 상황이 발생하면 그 전파를 막을 수 있습니다. 이 모든 설정과 각 라이브러리의 상태를 `actuator`가 게시합니다. `actuator` 는 Spring Boot에서 자체적으로 제공해주는 애플리케이션을 모니터링하고 관리하는 기능입니다. 내부적으로는 메트릭 파사드인 Micrometer에 대한 종속성 관리 및 자동 구성을 제공합니다. 데이터독, 그라파나와 같은 메트릭 시각화 역할을 `actuator` 가 미약하게나마 맡고 있다고 이해했습니다. 

Spring Cloud 반열의 `resilience4j`대신 circuitbreaker 라이브러리를 직접 사용할 수 있지만 그런 경우 Spring Cloud 기반의 `openfeign`, `actuator`와의 통합성이 떨어집니다. 어찌됐든 여기까지 이해하고 나면 yaml 파일의 config 자체를 이해하는 데에는 큰 어려움이 없었습니다. 제가 서킷브레이커 도입시 고민했던 부분은 어느 설정을 어떻게 가져가느냐하는 부분이었습니다. 목록은 다음과 같습니다. 

 

 

설정 값에서의 고민 사항들  

1. 이슈 발생 횟수(Count-based)를 기준으로 해야할까, 이슈 발생 지속 시간(Time-based)으로 해야할까?
2. 요청이 실패했을 경우 재시도 정책을 어떻게 가져가야하지? 모든 요청 실패에 대해 재시도를 시도하는 것이 맞을까?
3. 요청할 때마다 결과값이 달라지는 request 의 경우, 그러니까 멱등성이 보장되지 않는 호출에서의 재시도는 안될텐데?
4. 장애 전파를 막기위해 커스텀하게 임계값을 설정하는 건데 모든 시스템에 일괄된 서킷 브레이커 적용이 맞나?
5. 모든 시스템에 적용하기 적합한 임계치 계산을 어떻게 할 수 있지? best practice 가 있을 수 있을까?
    - 어떤 성격을 띄는 서비스에 대한 호출인가 결제 관련? 시청 권한 관련?
    - 에러의 발생빈도는 얼마나 높지?
    - 영향이 얼마나 크지? 그 크기에 대한 계측을 할 수 있나?
    - 평균 에러 지속 시간이 어느정도 되더라? 길면 길수록 재시도의 의미는 낮아질텐데

 

 

 

먼저 서킷 브레이커 적용 대상 애플리케이션과 호출하는 애플리케이션의 관계를 이해하는 데에서 다시 시작해보았습니다.

서킷 브레이커 적용 대상과 내/외부 서비스들 간의 관계

 

 

 

왼쪽 초록색으로 나열된 애플리케이션들은 모두 우리팀에서 관리하고 있습니다. 그리고 맨 오른편에는 우리가 컨트롤할 수 없는 외부 애플리케이션이 있습니다. 그 사이에서 프록시 역할을 하는 애플리케이션이 서킷 브레이커 적용 대상이었습니다. 해당 애플리케이션은 외부 애플리케이션과 자사 애플리케이션간의 의존성을 낮추기 위해 탄생했습니다. 

외부 애플리케이션은 비교적 장애가 많이 발생하는 곳입니다. 이슈가 많이 발생하는 API는 회원 정보 조회와 회원이 가입한 서비스 목록 조회였고, 평균 에러지속 기간은 15~17분이었습니다. 지면상 모든 구체 정보를 다 기재할 수 없지만, 파악했던 데이터 기준은 다음과 같았습니다. 

 

 

데이터 독(APM) 에서 파악할 기준 정보들

  • 장애가 많이 발생하는 API, TOP 5 
  • 이로 인해 발생하는 Error
  • 평균 에러 지속 시간
  • requests(요청 건수)
  • hits X latency (total time)
  • P95 latency
    "P95 latency"는 성능 측정에서 사용되는 지표 중 하나로, 특히 지연 시간과 관련이 있습니다. 여기서 "P95"는 "percentile 95"를 나타내며, 이는 주어진 데이터 집합에서 하위 95%의 값을 의미합니다. 다시 말해, 주어진 데이터에서 가장 긴 5%의 지연 시간을 제외한 대다수의 경우에서 발생하는 지연 시간을 나타냅니다. 예를 들어, 웹 서비스의 P95 latency가 100밀리초라면, 대부분의 요청은 100밀리초 이하의 지연 시간을 갖지만, 일부 요청은 더 오래 걸릴 수 있습니다. 


대충 이 정도의 기준을 가지고 데이터독에서 수치를 뽑아보았습니다. 그리고 해당 수치로 서킷 브레이커의 임계치를 잡아가기 시작했습니다. 평균 에러 지속 시간이 15~17분이라면 재시도의 의미가 크게 없다고 판단되어 재시도 모듈은 설정에서 제외시켰습니다. CLOSE 상태에서 OPEN 상태로 전이될 때는 지정해놓은 에러 건 수로 판단할 수 있도록 기준을 세웠습니다. OPEN 상태에서 HALF_OPEN 으로 전이될 수 있는 조건에 대해서는 짧지 않은 텀을 두었지만, call을 수용하는 횟수는 적게 두어 HALF_OPEN 이 될 수 있는 상태인지 지속적으로 체크할 수 있도록 설정을 잡았습니다. 반대로 HALF_OPEN 에서 OPEN 으로 전이되는 조건에 대해서는 아주 짧고 적은 최소한으로의 임계치만 설정했습니다. 서킷 브레이커가 열린 상태였으니 장애 상황 종료로 판단하는 기준을 보수적으로 잡는 것이 안전하다고 판단했기 때문입니다. 

장애로 인해 서킷브레이커가 열렸을 때, 우리 서비스는 어떻게 대처할 수 있을까에 대한 것도 고민 대상이었습니다. openFeign 을 이용한 fallBack 메소드에서 redis 를 조회하여 최근 값을 return 하는 것도 방법 중 하나였습니다. 다른 서비스들을 찾아보니 중요성이 좀 덜 한 뷰잉 페이지 같은 경우는 직전 데이터로 return 하는 방식으로 대처하는 케이스도 있었습니다.

네이버 메인페이지 서킷브레이커 적용기

여기서 드는 의문이 "서킷브레이커 혹은 fallBack 메소드 처리에서 best practice 라는 것이 있을 수 있는가"였습니다. default 설정 값이라도 해두는 것이 좋겠지만, 해당 애플리케이션이 어떤 성격을 띄고 있느냐에따라서 가져갈 수 있는 정책적인 부분에 더 가깝다는 생각이 들었습니다. 그래서 저는 "서킷브레이커 세팅의 사용설명서는 없다"로 결론지었습니다. 핵심 요약본처럼 "이렇게 세팅하면 만사형통!"한 수치 같은 것은 있을 수 없고, 적용 대상 서비스에 대한 이해가 먼저여야했습니다. 그렇다면 다음 질문은 이런 거였습니다. "어떻게 서비스의 특성을 정확히 이해할 수 있을까?" 제가 해당 포스팅에서 궁극적으로 공유하고자하는 고민입니다.

결국 이런 것을 판단할 데이터는 모니터링 시스템에 있고, 데이터독 또는 프로메테우스 (연동된 그라파나) 를 어느정도 다룰 줄 알아야할 것 같다는 생각이 들었습니다. 저는 데이터독 이용 시, 코드로 심어둔 로그를 보거나 APM 으로 플로우를 추적하는 것이 전부입니다. 더 많은 기능을 제공하고 있음에도 불구하고 외눈박이처럼 데이터독을 이용하는 것이 항상 갈증이었습니다. 저는 아마 이 과정을 통해 모니터링, 메트릭에 대한 전반적인 원리를 이해하고 싶었던 것 같습니다. 그래서 더 잘 모니터링하고 싶고, 더 정확한 지표를 보고 싶고, 더 쉽게 이슈를 파악하고 싶은 마음입니다. 

 

보통의 장애 대응 프로세스 입니다.


1. 에러가 발생하면 일단 달려가기 
2. 공유하기 
3. 영향 범위 파악하기
4. 빠르게 픽스하기
5. 원인 파악하기  
6. 사후 조치

저의 목표는 1번입니다. 제가 제일 못하는 부분입니다. 행동이 굼뜨고.. 이슈를 파악하는데 오래걸립니다. 1번을 더 잘하기 위해서 데이터독을 공부해보았습니다. 믿거나 말거나, 데이터 독이 data dog인 이유 초기 진압에 힘을 써서 더 큰 장애를 막는 일꾼이 됩시당.