brunch

You can make anything
by writing

C.S.Lewis

by 강진우 Sep 08. 2015

nginx upstream 성능 최적화

Linux Performance

Java, Ruby 등 다양한 언어를 이용해서 서비스를 개발할 때 보통 Framework를 많이 활용하게 됩니다. Play , RoR, Spring 등등 언어 별로 지원되는 다양한 Framework들이 있죠. 이런 Framework 들을 통해서 개발한 서비스를 사용자에게 제공하기 위해 보통 nginx의 upstream을 활용하는데요, 놓치기 쉬운 upstream 성능 최적화에 대해 간단하게  살펴보겠습니다.


nginx upstream이란?


proxy_pass 지시자를 통해 nginx가 받은 리퀘스트를 넘겨 줄 서버들을 정의하는 지시자가  upstream입니다. (http://nginx.org/en/docs/http/ngx_http_upstream_module.html)

실제로 로직을 구현하는 애플리케이션 서버는 nginx 뒷 단에 있으며 nginx가 앞 단의 리퀘스트를 받아서 넘겨주는 구조로 서비스하게 됩니다. 이번 글에서 예제로 든 애플리케이션 서버는 play framework  2.2.6입니다.

nginx upstream의 서비스 구조

언뜻 보기에는 뒷 단의 서버로 바로 전해져야 하는 리퀘스트를 앞 단에서 한 번 걸러서 주기 때문에 성능이 저하되는 구조로 보일 수 있지만, 앞 단의 nginx에서 제공하는 기능들이 워낙 막강하기 때문에 약간의 성능 저하는  감수할 수 있다고  생각합니다.

예를 들면, valid client를 체크하기 위해 HTTP header 값의 user-agent를 검사한다거나, referer 검사를 한다거나 하는 로직들을 애플리케이션 서버에서 직접 처리하려면 header를 추출하고, 비교하는 로직들을 직접 구현해야 하겠지만, nginx 같은 웹 서버를 앞에 둔다면 간단하게 설정 만으로 구현할 수 있기  때문입니다.

하지만 성능이  저하된다는 것은 부정할 수 없는 사실이죠. 그럼 어떻게 하면 성능 저하를  최소화할 수 있을까요?


upstream의 keepalive


nginx upstream 설정할 때에 놓칠 수 있는 부분 중에 하나가 바로 upstream의  keepalive입니다.

아래 예제는 nginx 홈페이지에서 제공하는 기본적인 upstream 설정을 입니다.

upstream backend {   
     server backend1.example.com:9000
}

이런 기본 설정을 하게 될 경우의 문제점은 뭘까요? 바로 nginx와 play를 연결하는 내부 통신에도  리퀘스트마다 세션을 만들고 TCP handshake가 일어난다는  점입니다.

nginx가 play로 리퀘스트를 넘기는 모습

위와 같이 클라이언트가 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 설정을 꼭 잊지 않고 하시기 바랍니다.


읽어 주셔서 감사합니다.

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari