높은 가용성을 위한 tomcat 튜닝, 그리고 fail fast 시스템#
문제 상황#
netflix는 수많은 mid tier 시스템으로 구성되어 있는데, 이 서비스들중에 하나가 갑자기 어느순간 트래픽이 몰리면서, cpu가 올라가고 서버가 응답하지 않는 현상이 발생했다.
일반적인 상황에서는 cpu도 널널하고, 게다가 서비스 자체가 cpu 집약적이지도 않음에도 불구하고 이런 현상이 발생한 것이다.
일반적인 상황에서는 cpu도 널널하고, 게다가 서비스 자체가 cpu 집약적이지도 않음에도 불구하고 이런 현상이 발생한 것이다.
왜 그런걸까?#
테스트를 해보니 일단 apache와 tomcat의 설정이 적절하지 않았다.
트래픽이 올라가는 시점에서 다수의 apache worker들과 tomcat thread들이 busy하게 되고,
이러한 스레드들의 context swtiching 비용으로 사실상 의미 있는 일을 하지 않는 상황에서도 cpu가 높게 올라가는 이슈가 발생했다.
트래픽이 올라가는 시점에서 다수의 apache worker들과 tomcat thread들이 busy하게 되고,
이러한 스레드들의 context swtiching 비용으로 사실상 의미 있는 일을 하지 않는 상황에서도 cpu가 높게 올라가는 이슈가 발생했다.
해결은 어떻게?#
일단은 apache - tomcat 으로 구성된 시스템을 tomcat 단일 시스템으로 변경했다.
아무래도 apache를 제거하면 configuration 설정이 상대적으로 심플해질 수 있으니. (tomcat만 관리하면 되니까)
그렇다면 왜 tomcat thread는 그렇게 바쁘게 동작하게 된 것일까? 이것을 알려면 tomcat의 스레드 처리 모델을 알아야 한다.
아무래도 apache를 제거하면 configuration 설정이 상대적으로 심플해질 수 있으니. (tomcat만 관리하면 되니까)
그렇다면 왜 tomcat thread는 그렇게 바쁘게 동작하게 된 것일까? 이것을 알려면 tomcat의 스레드 처리 모델을 알아야 한다.
Tomcat 스레딩 모델의 이해#
high level에서의 처리 프로세스는 아래 그림을 참조한다. (외부 이미지 발췌)
여기서 acceptCount값은 OS단에서 클라이언트의 TCP 커넥션 갯수를 queueing 할 수 있는 갯수를 지정하는 것이다.
이것을 크게 잡았을 경우엔 트래픽이 몰릴 경우, OS상의 커넥션 큐를 금방 채우고 tomcat의 worker 스레드를 더 busy하게 만들며 더 몰릴 경우 busy한 worker 스레드를 증가하게 만들어 CPU를 고갈시켜 버린다.
따라서 우리는 request가 대기하지 않도록 만드는것이 가장 중요한 해결책이라 생각했으며, 최대 가용량에 도달했을 경우 재빨리 503 에러를 주면서 빠른 실패(fail fast)를 하도록 만들었다.
이것을 크게 잡았을 경우엔 트래픽이 몰릴 경우, OS상의 커넥션 큐를 금방 채우고 tomcat의 worker 스레드를 더 busy하게 만들며 더 몰릴 경우 busy한 worker 스레드를 증가하게 만들어 CPU를 고갈시켜 버린다.
따라서 우리는 request가 대기하지 않도록 만드는것이 가장 중요한 해결책이라 생각했으며, 최대 가용량에 도달했을 경우 재빨리 503 에러를 주면서 빠른 실패(fail fast)를 하도록 만들었다.
fail fast는 어떻게 했나?#
먼저 peak load에서의 몇개의 스레드가 busy한가에 대한 예상치를 결정해야 한다. (자세한 예제는 생략할께요. 궁금하면 본문 참조 바랍니다.)
아무튼 우리는 경험상으로 peak load에서의 가용 가능한 스레드 갯수를 정했으며, 버퍼는 약 3배로 잡았는데 잘 동작했다.
먼저 현재 active한 동시 요청 갯수를 메모리 상에 저장하고, 예상한 스레드 갯수에 근접하면 503 에러를 리턴했다.
아무튼 우리는 경험상으로 peak load에서의 가용 가능한 스레드 갯수를 정했으며, 버퍼는 약 3배로 잡았는데 잘 동작했다.
먼저 현재 active한 동시 요청 갯수를 메모리 상에 저장하고, 예상한 스레드 갯수에 근접하면 503 에러를 리턴했다.
'acceptCount'에 대한 고찰#
이 값이 너무 작을 경우는 OS단에서 queueing해주는 커넥션수가 얼마 없어서 connection timeout 에러가 클라이언트에 리턴된다. 이럴 경우 실제 가용량에 턱도 없이 모자르게 사용될 것이다.
이 값이 너무 클 경우엔 위처럼 cpu가 고갈되는 문제도 있으며, 클라이언트가 응답을 못받고 대기상태에 빠지며, timeout이 발생하는 케이스가 늘어날 것이다.
따라서 적절한 값을 찾으려면 10부터 천천히 올려가면서 테스트 해보고 connection timeout이 발생하지 않는 지점을 찾아내는 것이 좋다.
이 값이 너무 클 경우엔 위처럼 cpu가 고갈되는 문제도 있으며, 클라이언트가 응답을 못받고 대기상태에 빠지며, timeout이 발생하는 케이스가 늘어날 것이다.
따라서 적절한 값을 찾으려면 10부터 천천히 올려가면서 테스트 해보고 connection timeout이 발생하지 않는 지점을 찾아내는 것이 좋다.
그외에 고려사항#
- keep alive에 대한 고려
(자세한 내용은 생략할께요. 궁금하면 본문 참조, 결국 worker thread가 사용하지도 않는 connection을 들고 있는 불필요한 비용이 증가하기에 껐다는 내용입니다.) - 본문의 그래프를 보면 apache-tomcat 구조에서 얼마나 throughput이 낮았는지를 알 수 있음.
- apache-tomcat을 모두 튜닝하며 해답을 찾을 수 있겠지만 본인들은 심플하게 만드는 것으로 결정하여 tomcat만 사용했다.
정리하며 나의 생각#
netflix에서는 "acceptCount"의 config 설정으로 인해 스레드만 많아지고 이에 따라 cpu 사용량이 올라가면서 실제 요청은 제대로 수행하지 못하는 현상이 발생했습니다.
이것을 해결하기 위해 apache를 제거하여 좀 더 구성을 심플하게 만든 뒤 좀 더 적절한 "acceptCount"값을 찾는 노력을 했고, 응답은 주지 못하고 요청만 대기하는 상태가 빠지게 되는 현상을 해결하기 위해 적절한 값(fail fast를 위한 동시 요청 스레드 갯수)을 찾아내고 503 error를 내어 재빨리 실패 처리를 하는 총명함을 보여주었습니다.
사실 failt fast가 정말 서비스 입장에서 옳은가? 라고 하면 다른 의견을 가진 사람도 있으리라 생각됩니다.
하지만 응답을 주지도 못하고 대기만 하는 상태가 클라이언트나 서버 모두 크리티컬 할 수 있다고 생각되고, 전체 장애를 부분의 장애로 막을 수 있다는 장점은 누구나 인정하지 않을까 싶기도 하네요.
이것을 해결하기 위해 apache를 제거하여 좀 더 구성을 심플하게 만든 뒤 좀 더 적절한 "acceptCount"값을 찾는 노력을 했고, 응답은 주지 못하고 요청만 대기하는 상태가 빠지게 되는 현상을 해결하기 위해 적절한 값(fail fast를 위한 동시 요청 스레드 갯수)을 찾아내고 503 error를 내어 재빨리 실패 처리를 하는 총명함을 보여주었습니다.
사실 failt fast가 정말 서비스 입장에서 옳은가? 라고 하면 다른 의견을 가진 사람도 있으리라 생각됩니다.
하지만 응답을 주지도 못하고 대기만 하는 상태가 클라이언트나 서버 모두 크리티컬 할 수 있다고 생각되고, 전체 장애를 부분의 장애로 막을 수 있다는 장점은 누구나 인정하지 않을까 싶기도 하네요.
댓글
댓글 쓰기