리트라이 전략과 서킷 브레이커와의 관계

반응형

리트라이 전략에 대해 2개의 포스트를 통해 학습을 진행하였습니다.

 

외부 API 실패는 어떻게 다뤄야 할까? Retry 전략 설계기

외부 API를 사용한다는 것은 내가 통제할 수 없는 환경에 의존한다는 의미입니다. 즉, 외부 API의 상태 변화는 언제든지 우리 서비스의 결과에 직접적인 영향을 줄 수 있습니다. 만약 외부 API 호출

b-programmer.tistory.com

 

Retry 전략 - 수치는 어떻게 지정해야 할까

Retry 전략을 코드로 직접 구현해 보니, 자연스럽게 몇 가지 숫자들이 눈에 들어오기 시작했습니다. max-attempts, backoff-millis 같은 값들입니다. 처음에는 AI의 도움을 받아 무난한 값으로 설정했지만,

b-programmer.tistory.com

리트라이 전략에 대해 간략하게 복습해 보면, 리트라이는 외부 API 호출 중 일시적 실패(transient failure)가 발생했을 때,
요청의 의미가 변하지 않는 경우에 한해 재시도하여 실패 확률을 낮추는 전략입니다. 또한, 발생한 예외를 재시도 가능한 실패(retriable failure)와 재시도 불가능한 실패(non-retriable failure)로 구분하여, 재시도 여부를 결정하는 것이 핵심 특징입니다.
리트라이 전략을 학습하기 이전 서킷 브레이커를 학습한 적이 있었죠.

 

외부 API 연동 - 서킷 브레이커 설정

배경PG사가 도입이 되었다. 원래 같으면 feign을 사용할지 restTemplate를 사용할지 결정만하고 그걸 사용했었던거 같다.하지만 그리 간단한 문제가 아니었다. 서킷 브레이커라고 들어는 봤나??사실

b-programmer.tistory.com

무려 5개월 전에 작성했었네요. 두 개의 공통점은 외부 API에서 예외가 어떻게 터지냐에 따라 결과가 달라진다는 점이었습니다. 어떻게 보면 비슷한데 또 어떻게 보면 완전히 역할이 다르다고 느껴집니다. 그렇다면 이 둘의 관계는 어떻게 될까요?
(이 글은 수치에 대한 내용은 작성되어 있지 않고 짧게 작성된 글입니다.)

다루는 대상은 동일하지만, 관심범위는 다르다.

일단 두 용어의 전제부터 정리해 보면,
리트라이는 외부 API 호출 실패가 일시적인 문제일 수 있다는 가정 하에,
재시도를 통해 성공 가능성을 보정하는 전략입니다.

반면 서킷 브레이커는 외부 API가 이미 장애 상태이거나 불안정하다는 가정 하에,
호출 자체를 차단하여 내부 시스템을 보호하기 위한 메커니즘입니다.

앞서 두 기법의 공통점은 외부 API 호출 과정에서 발생하는 예외의 성격에 따라 결과가 달라진다는 점이라고 설명했습니다.
이를 조금 더 구체적으로 살펴보면, 리트라이는 이번 요청을 다시 시도해 볼 가치가 있는지를 판단하고,
서킷 브레이커는 현재 이 외부 시스템을 계속 호출해도 되는 상태인지를 판단합니다.

즉, 두 기법은 동일한 실패 상황을 다루지만, 판단의 대상이 서로 다릅니다.

그렇다면, 둘은 상호보완적인 존재들인 건가? 아님 양자 택일을 해야 하는 존재들인가?

결론부터 말씀드리자면, 상호보완적인 관계입니다.

외부 API 실패를 다룬다는 점에서 대상은 같아 보이기 때문에 양자택일의 관계처럼 느껴질 수 있습니다.
하지만 두 전략이 다루는 실패의 범위는 서로 다릅니다.

리트라이는 개별 요청 단위에서 발생한 일시적인 실패에 대해 성공 가능성을 보정하기 위한 전략인 반면,
서킷 브레이커는 특정 외부 시스템의 장애 상태를 감지하고,
호출 자체를 제한함으로써 내부 서비스로 장애가 전파되는 것을 방지하기 위한 메커니즘입니다.

즉, 리트라이는 "이번 요청을 다시 시도할 것인가"에 대한 판단이고,
서킷 브레이커는 "지금 이 외부 시스템을 계속 호출해도 되는가"에 대한 판단입니다.

따라서 두 기법은 서로를 대체하는 관계가 아니라, 서로 다른 범위의 실패를 다루는 상호보완적인 안정성 전략이라고 볼 수 있습니다.

상호보완이라면, 두 개의 목적은 어떨까? 

리트라이 목적은 실패 확률 감소와 성공 가능성 보정에 있습니다.
서킷브레이커의 목적은 장애 전파를 차단하여 내 시스템을 보호하는것에 중점을 두고 있습니다. 궁극적으로 스레드와 커넥션 고갈을 방지가 되어지죠.

비유가 맞는 비유인지는 잘 모르겠지만, 서킷 브레이커는 외부 시스템이 이미 죽었을 수도 있다는 전제 위에서 동작합니다.
어떻게 보면 슈뢰딩거의 고양이와 비슷한 느낌이라고 볼 수도 있습니다.

다만 중요한 점은, 서킷 브레이커가 외부 시스템이 실제로 죽었는지, 살았는지를 알고 판단하는 것은 아니라는 점입니다.
서킷 브레이커 역시 외부 시스템의 상태를 직접 알 수는 없습니다.

결국 서킷 브레이커도 요청을 실제로 보내고, 그 결과를 관측한 뒤 판단합니다.
그리고 그 관측 결과에서 일정 시간 동안 예외가 반복적으로 발생한다고 판단되면,
외부 시스템이 신뢰할 수 없는 상태라고 간주하고 회로를 차단합니다.

이후 일정 시간이 지난 뒤, 일부 요청만 다시 허용하여 외부 시스템의 상태를 재확인하고,
정상 응답이 관측되면 다시 회로를 복구하는 메커니즘입니다.

그렇다면 어떻게 순서를 지정하면 좋을까?

하나의 외부 API 호출을 기준으로 살펴보겠습니다. 대략적으로 그리면 다음처럼 그릴 수 있습니다.
(이 그림은 재시도를 무조건?한다는 전제하에 그린 그림입니다.)

보통은 서킷 브레이커를 바깥에 두고, 리트라이를 안쪽에 두는 구조로 구성합니다.
이렇게 구성해야 재시도 과정에서 발생한 여러 번의 실패 결과를 하나의 호출 흐름으로 묶어 서킷 브레이커가 관측할 수 있기 때문입니다.

마무리

리트라이를 학습하면서 이전에 서킷 브레이커를 학습했던적이 있었습니다. 하지만 이 둘에 대해 차이점을 정확히 모르고 있었죠.
그래서 한번 정리해보면 좋을거 같아 작성하게 되었습니다. 정리하고 보니 둘은 상호보완적으로 동작을 하는 관계로 여겨지네요. 

반응형

댓글

Designed by JB FACTORY