마이크로서비스(Microservices)를 기반으로 운영하다보면 간혹 인기가 많은 특정 서비스에 요청이 집중되는 현상이 나타난다. 보통 어카운트(Account) 또는 프로파일(Profile) 서비스에 부하가 많이 몰린다. 하지만 대부분 성능 검증(Load Test, Stress Test, Aging Test)을 통해서 서버(Server) 한 대당 어느 정도를 처리할 수 있을 지 미리 가늠할 수 있고, 그 결과를 기준삼아 스케일 아웃(Scale-out) 정책을 펼쳐서 몰려드는 요청을 처리할 수 있다. 대부분은 이러한 방식으로 처리가 가능하지만, 특수한 예외상황도 존재한다. 이를테면 인증 서비스(Authentication, i.g., sign-in/log-in)에서 문제가 발생했을 경우가 있다. 사용자들이 갑작스럽게 몰려들었을 수도 있고, 비정상적인 형태로 공격해 들어올 수도 있다. 어쨌든 인증 서비스가 멈췄고 더 이상 응답을 줄 수 없는 상황이 될 것이다. 이러한 상황을 가정했을 때 우리는 당연히 인증 서비스를 필요로하는 다른 마이크로서비스 외에는 정상기능이 동작해야 한다고 생각할 것이다. 그러나 실제로 그렇지 않은 경우가 많다.
그 이유는 몇몇 마이크로서비스들이 인증 서비스에게 요청 재시도를 하면서 계속 기다리게 되고 마이크로서비스들끼리 서로서로 엮여있는 경우 서비스 전체가 느려지거나 동작이 불가능한 상황이 되기 때문이다. 그럼에도 불구하고 각 마이크로서비스들이 재시도를 하는 이유는 일시적인 네트워크 지연 등을 가정해서 요청을 다시 보내기 때문인데, 재시도 할 때마다 최대 대기 시간동안 기다리게 되고(Timeout), 이 시간이 쌓이면서 다른 요청들은 처리하지 못하 채 계속 미뤄두게 된다. 그러면서 서비스 전체가 느려지게 된다. 전화를 걸었는데 상대방이 전화를 못받는 경우를 상상해보자. 상대방이 전화를 받을 때까지 계속 전화를 시도하면 상대방에게는 부재중 통화만 쌓일 것이다. 한 번 못받았다면 상대방은 전화를 쉽게 못받는 상황일 가능성이 높기 때문이다. 일시적인 통신회선의 문제였거나 수신자가 잠깐 수신음을 놓쳤을 경우도 있을 수 있겠지만 대부분 바로 다시 걸었을 때 전화를 받지 못한다. 아마도 계속해서 전화를 걸수록 온갖 걱정으로 전화 건 사람의 마음만 더 애태울 것이다. 그런데 만약 상대방이 '지금 회의중입니다' 라는 문자메시지를 보내왔다고 생각해보자. 그렇다면 오히려 안심이 된다. 불안한 마음으로 계속 전화를 걸 필요없이 시간 간격을 더 길게두고 다시 전화를 하면 된다. 아니면 상대방이 다시 전화해 주길 기다릴 수도 있다. 1
마이크로서비스에서의 서킷브레이커(Circuit Breaker)는 앞에서 이야기한 상황을 해결하기 위한 설계 기법을 말한다. 우리가 흔히 알고 있는 차단기에서 의미를 차용한 것으로 보인다. 우리가 이미 알고 있듯이 차단기는 배선에 허용된 용량보다 많은 양의 전류가 흐르지 않도록 전기를 차단하는 기능을 한다. 이유는 전선에 과부하가 걸릴경우 화재가 발생할 수 있기 때문이다. 비슷하게 특정 마이크로서비스가 수용할 수 있는 수준의 요청을 넘어서는 경우 중간에서 요청을 차단하는 기능이 필요하다. 특정 서비스에 요청이 쇄도하여 마비될 경우 더 이상 요청을 받지 않도록 하는 것이 서비스 전체를 위해서 더 유리하기 때문이다. 요청을 보내는 입장에서는 응답없음이 네트워크 지연때문인지 상대방이 대답할 수 없는 상황인지 판단하기 어렵기 때문에 계속해서 재시도를 할 수 밖에 없는데, 받는 쪽에서 '부재중입니다' 라고 알려준다면 생각보다 쉽게 문제가 해결될 수 있기 떄문이다.
주식 시장에서도 비슷한 응용사례가 있다고 한다. 어떤 특정 주식의 가격이 급격하게 오르거나 내려가면 거래를 중지시킨다고 한다. 사람의 심리가 자신이 가진 주식의 가격이 매우 단시간에 떨어지거나 올라갈 경우 마음이 조급해진다. 그래서 손해를 줄이거나 이익을 극대화하기 위해 급하게 팔게 되는데 그러면 해당 주식의 가격은 더 떨어지기거나 더 급격하게 오르기 때문이다. 그래서 이성적으로 판단할 시간을 벌고 투기세력의 영향을 막기위해서 주식 가격의 변동폭이 크면 거래를 중지시키는 서킷브레이커가 동작한다고 한다.
마이크로서비스로 운영하는 과제에서 서비스가 기능하지 않는 문제가 있었다. 개발자들은 카산드라(Cassandra)가 문제였다고 지적했지만 카산드라는 지극히 정상이었다. 그랬더니 이번에는 클라이언트 어플리케이션(Client Application)에서 비상식적으로 요청해서 그렇다고 말을 바꾸었다. 그러나 내가 보기에 외부요인 보다는 내부 요인이 크게 작용한 것 같았다. 마이크로서비스들끼리 서로 의존성을 갖도록 개발했기 때문이다. 그러다가 부하가 조금 늘어나자 가장 취약한 서비스에서 타임아웃이 발생했고 그 서비스에 요청을 보낸 다른 서비스들이 연속적으로 대기하는 상태가 되었다. 그리고 연결이 끊어지자 자동으로 요청을 다시 보내고 기다리다가 재시도를 하는 악순환이 반복되었다. 2 결국 뒤이어 밀려 들어오는 실제 사용자의 요청들은 처리하지 못하고 서비스 전체가 무너졌다. Sumo Logic으로 애플리케이션 로그(Application Log)를 분석해봤는데, 문제가 발생한 시간부터 약 100만 건의 오류가 나왔고, 거기에서 찾은 UUID(Universal Unique IDentifier)를 가지고 샘플링 검색해보니 15분 동안 같은 UUID로 300만건의 타임아웃이 발생했다. 이 때 타임아웃이 발생한 호출들은 우리 내부의 마이크로서비스들끼리 일어났다. 당시 클라이언트 접속은 대략 100만 건이었다. 대충 계산해 보면, 1분 동안 내부 서비스들끼리 2000만 번 호출을 시도했고 그 만큼 타임아웃 오류를 받았다. 3
- 마이크로서비스도 이렇게 전화번호를 남겨 놓듯이 큐(Queue)에 트랜잭션 아이디(Transaction Id.)와 API 엔드포인트(Endpoint)를 남겨두면 비동기적으로 처리할 수 있다. 이 부분에 대한 내용은 다음에 나중에 따로 이야기하면 좋겠다. [본문으로]
- 이 부분에 대해서 무려 6개월 전부터 문제점이라고 지적했었으나 아직 일어나지 않은 일에는 관심이 없는 것 처럼 보였다. [본문으로]
- 개인적으로 이러한 현상을 리퀘스트 하울링이라고 불렀다. 마이크와 스피커 사이의 거리가 가까워서 스피커로 나온 소리가 마이크 들어가게되어 순식간에 자기중폭이 되는 현상인 하울링과 비슷하다고 생각했기 때문이었다. [본문으로]
'Sorry Architecture' 카테고리의 다른 글
Immutable Infrastructure (0) | 2019.02.21 |
---|---|
CI/CD Pipeline (0) | 2019.02.21 |
Microservices, and Hangul(한글) (0) | 2019.02.21 |
PhoenixServer (0) | 2019.02.20 |
Netflix Frigga (0) | 2019.02.18 |