Linux Performance
Java, Ruby 등 다양한 언어를 이용해서 서비스를 개발할 때 보통 Framework를 많이 활용하게 됩니다. Play , RoR, Spring 등등 언어 별로 지원되는 다양한 Framework들이 있죠. 이런 Framework 들을 통해서 개발한 서비스를 사용자에게 제공하기 위해 보통 nginx의 upstream을 활용하는데요, 놓치기 쉬운 upstream 성능 최적화에 대해 간단하게 살펴보겠습니다.
proxy_pass 지시자를 통해 nginx가 받은 리퀘스트를 넘겨 줄 서버들을 정의하는 지시자가 upstream입니다. (http://nginx.org/en/docs/http/ngx_http_upstream_module.html)
실제로 로직을 구현하는 애플리케이션 서버는 nginx 뒷 단에 있으며 nginx가 앞 단의 리퀘스트를 받아서 넘겨주는 구조로 서비스하게 됩니다. 이번 글에서 예제로 든 애플리케이션 서버는 play framework 2.2.6입니다.
언뜻 보기에는 뒷 단의 서버로 바로 전해져야 하는 리퀘스트를 앞 단에서 한 번 걸러서 주기 때문에 성능이 저하되는 구조로 보일 수 있지만, 앞 단의 nginx에서 제공하는 기능들이 워낙 막강하기 때문에 약간의 성능 저하는 감수할 수 있다고 생각합니다.
예를 들면, valid client를 체크하기 위해 HTTP header 값의 user-agent를 검사한다거나, referer 검사를 한다거나 하는 로직들을 애플리케이션 서버에서 직접 처리하려면 header를 추출하고, 비교하는 로직들을 직접 구현해야 하겠지만, nginx 같은 웹 서버를 앞에 둔다면 간단하게 설정 만으로 구현할 수 있기 때문입니다.
하지만 성능이 저하된다는 것은 부정할 수 없는 사실이죠. 그럼 어떻게 하면 성능 저하를 최소화할 수 있을까요?
nginx upstream 설정할 때에 놓칠 수 있는 부분 중에 하나가 바로 upstream의 keepalive입니다.
아래 예제는 nginx 홈페이지에서 제공하는 기본적인 upstream 설정을 입니다.
upstream backend {
server backend1.example.com:9000
}
이런 기본 설정을 하게 될 경우의 문제점은 뭘까요? 바로 nginx와 play를 연결하는 내부 통신에도 리퀘스트마다 세션을 만들고 TCP handshake가 일어난다는 점입니다.
위와 같이 클라이언트가 3번의 GET 요청을 날렸다고 가정합니다. nginx는 그 리퀘스트들을 받고 바로 play로 넘기게 되는데요, 이 때 play로 날리는 요청도 외부에서 들어오는 요청과 똑같은 TCP 통신이기 때문에 TCP handshake를 하게 되고, keepalive가 켜져 있지 않기 때문에 한 번의 요청만 처리된 후 소켓이 끊어집니다.
이 것은 두 가지 문제점을 일으킬 수 있는데요, 첫째, keepalive를 통해 소켓이 유지되지 않기 때문에 다수의 Time-Wait 소켓이 만들어진다는 점과 둘째, 불필요한 TCP handshake나 일어남으로써 응답 속도 지연을 일으킬 수 있다는 것입니다.
사실 Time-Wait 소켓이 만들어지는 것 자체는 성능에 큰 영향은 없겠지만, nginx 입장에서는 outgoing 트래픽이기 때문에 local port를 사용하게 되고 초당 리퀘스트가 높은 서버서는 local port의 고갈을 일으킬 수도 있습니다.
local port의 고갈은 tw_reuse 파라미터를 enable 해서 해결할 수는 있습니다.
그래서 upstream에도 아래와 같이 keepalive 지시자를 줌으로써 nginx와 play 간에 불필요한 통신을 최소화할 수 있게 됩니다.
proxy_http_version 1.1; #proxy 통신 시 HTTP/1.1로 통신함을 명시
proxy_set_header Connection "";
upstream backend {
server backend1.example.com:9000
keepalive 100; #keepalive로 유지시키는 최대 커넥션 개수
}
그렇다면 upstream keepalive를 적용하면 성능 차이가 어느 정도 날까요? nginx로 인해 발생하는 성능 저하도 함께 테스트하기 위해 Play 웹 서버와의 비교 테스트도 진행했습니다.
Java 코드는 1부터 100까지 계산한 후 값을 찍도록 간단하게 로직을 구현한 후 아래와 같이 성능 테스트를 간단하게 해 봤습니다.
* 환경 : nginx 1.9.4, play 2.2.6
* 커맨드 : ab -n 10000 -c 1
* 각 경우마다 5번씩 테스트를 돌렸으며, 각 테스트마다 90초의 시간텀을 주고 진행
테스트 결과는 아래와 같습니다.
결과에서도 볼 수 있듯이 Play 만을 사용하는 것보다 성능 저하가 일어나긴 하지만 upstream keepalive를 적용하지 않았을 때 보다는 더 좋은 성능을 보이는 것을 볼 수 있습니다.
이번 글에서는 play framework를 통해서 테스트를 해 봤지만, 그 외의 다른 웹 서버들도 비슷한 결과가 나올 것이라고 생각합니다.
nginx를 앞 단에 놓고 proxy_pass와 upstream으로 서비스를 구성한다면 keepalive 설정을 꼭 잊지 않고 하시기 바랍니다.
읽어 주셔서 감사합니다.