설정에 대한 튜닝은 반드시 해야 합니다. 대체로 기본값으로 최대한의 성능을 낼 수 있는 것은 없습니다. 웹 개반 시스템도 정상적으로 작동하게 하려면 세팅이 대단히 중요합니다. 프로그램에 문제가 없는데 세팅값 하나 때문에 애플리케이션의 성능이 안 좋아지는 경우가 굉장히 많기 때문입니다. DB서버를 제외한 서버 세팅에 대해 알아보겠습니다.
세팅해야 하는 대상
개발하는 것만큼 중요한 것이 서버의 세팅입니다. 개발된 프로그램이 0.1초 걸린다고 해도 서버 세팅을 잘못하면 1초가 걸릴 수도 있고, 10초가 걸릴 수 있습니다. 이러한 문제를 진단하는 가장 좋은 방법은 성능 테스트를 통해서 병목 지점을 미리 파악하는 것입니다. 무조건 애플리케이션 위주로 병목을 찾는 것보다, 일단 문제가 될 만한 세팅값을 먼저 진단하는 것이 효율적입니다. 웹 기반의 시스템에서 성능에 영향을 줄만한 세팅을 나열해보면 다음과 같습니다.
1) 웹 서버 세팅
2) WAS 서버 세팅
3) DB 서버 세팅
4) 장비 세팅
각 서버별로 어떤 값의 세팅을 유념해야 하는지 자세히 알아보겠습니다. 앞에서도 이야기했지만, 우리의 중심 분석 대상은 애플리케이션이므로 db 서버 관련 세팅은 대상에서 제외합니다.
아파치 웹서버 세팅
웹 서버는 반드시 WAS 앞에 두어야 합니다. 웹 서버를 WAS 뒤에 둘 사람은 없지만, WAS를 웹서버로 사용하면 안됩니다. 왜냐하면 WAS는 Web Application Server이기 때문입니다. 웹에서 사용하는 애플리케이션 서버지 웹 서버가 아닙니다. 정적인 부분은 웹 서버에서 처리해야 합니다. 그렇지 않으면 WAS 서버에서 웹 서버의 역할까지 수행해야 합니다. 웹서버를 WAS 서버 앞에 두지 않으면 이미지, CSS, 자바 스크립트, HTML 등을 처리하느라 아까운 WAS 서버의 스레드를 점유하게 됩니다. 반드시 상용 웹서버나 아파치 웹서버를 WAS 앞 단에 두고 운영하는 것을 권장합니다.
상용 웹서버의 경우 어차피 설치와 설정을 벤더에서 해주기 때문에 상용 웹서버에서 설정하는 방법에 대해서는 다루지 않습니다. 대신 아파치 웹서버에서 성능에 영향을 줄 수 있는 세팅을 알아보겠습니다.
아파치 웹 서버는 MPM이라는 것을 사용합니다. MPM은 Multi-Processing Module의 약자로 여러 개의 프로세싱 모듈 기반의 서비스를 제공한다는 의미입니다. 가장 쉽게 아파치 웹 서버의 설정을 바꾸는 방법은, 설치 폴더 하단의 conf 디렉토리에 있는 httpd.conf 파일을 수정하는 것입니다. 이 파일의 중간 부분을 보면 다음과 같은 설정이 있습니다.
...
ThreadsPerChild 250
MaxRequestsPerChild 0
...
ThreadsPerChild는 웹 서버가 사용하는 스레드의 개수를 지정합니다. 위와 같이 지정하면, 아파치 프로세스 하나당 250개의 스레드가 만들어집니다. 만약 이 수치가 적게 지정되어 있다면, 이 수치를 늘려 주어야 합니다. 그래야 서버가 더 많은 사용자의 요청을 처리할 수 있게 됩니다.
MaxRequestsPerChild는 최대 요청 개수를 지정하는 부분입니다. 0이면 그 수에 제한을 두지 않겠다는 의미가 됩니다. 만약 이 값을 10으로 둔다면, 그 이상의 처리는 하지않게 됩니다. 가급적 기본값인 0으로 두고 사용하는 것이 좋습니다.
스레드와 관련된 내용을 보다 세밀하게 지정하려면 httpd.conf 파일에서 #으로 주석 처리되어 있는 "Include conf/extra/httpd-mpm.conf"를 찾아서 주석을 해제합니다. 이렇게 하면 세밀한 스레드 설정 정보를 httpd-mpm.conf를 통해서 지정할 수 있게 됩니다. 스레드 방식을 사용하기 위해서는 worker 부분을 수정해 주어야 합니다.
...
<IfModule mpm_worker_module>
StartServers 2
MaxClients 150
MinSpareThreads 25
MaxSpareThreads 75
ThreadsPerChild 25
MaxRequestsPerChild 0
</IfModule>
...
StartServers: 서버를 띄울 때 프로세스의 개수를 지정한다. 보통 이야기하는 child 프로세스의 개수를 이야기한다.
MaxClients: 최대 처리 가능한 클라이언트의 수를 지정한다.
MinSpareThreads: 최소 여유 스레드 수를 지정한다.
MaxSpareThreads: 최대 여유 스레드 수를 지정한다.
ThreadsPerChild: 프로세스당 스레드 수를 지정한다.
MaxRequestsPerChild: 앞서 이야기한 MaxRequestsPerChild와 같은 의미이다.
여기에서는 프로세스 수(StartServers)가 2개이고, 프로세스당 스레드 수(ThreadPerChild)가 25이므로 기본적으로 50개의 요청을 처리할 수 있습니다. 또한 최대 여유 스레드(MaxSpareThreads)가 75개이므로, 최대 사용 가능한 클라이언트 수(MaxClient)는 150이 됩니다. 그냥 서버에서 곱해서 최대 클라이언트 수를 지정하면 되는데, 왜 굳이 이렇게 MaxClient를 지정하도록 해 놓았는지는 잘 모르겠습니다. 하지만 여기서 중요한 점은 이 서버는 150명이 최대라는 점입니다. 150명 이상의 요청은 서버에 여유가 있어도 처리를 안하게 됩니다.
만약 서버가 매우 좋아서 더 많은 요청을 처리해야 할 필요가 있다면 httpd.conf 파일이나 httpd-mpm.conf 파일을 수정하여 서버가 최대한의 자원을 사용하도록 변경하는 것이 좋습니다.
모든 웹 서버를 설정할 때 또 한가지 중요한 값이 있습니다. 바로 KeepAlive 설정입니다. 아파치 웹 서버의 경우, httpd.conf 파일에 다음의 설정이 없으면 간단하게 한줄 추가하면 됩니다.
KeepAlive On
웹 서버와 웹 브라우저가 연결이 되었을 때 KeepAlive 기능이 켜져 있지 않으면, 매번 HTTP 연결을 맺었다 끊었다가 하는 작업을 반복합니다. 초기 화면이 매우 간단한 구글과 같은 사이트는 해당 사이트에 연결할 때 KeepAlive가 적용되지 않더라도 그리 느리지 않을 것입니다. 하지만 네이버나 야후와 같이 초기 화면에서 엄청나게 많은 이미지와 CSS, 자바 스크립트 등의 파일을 받아야 하는 사이트에서 KeepAlive 옵션이 적용되어 있지 않다면, 초기 화면을 띄우는데 몇 분씩 소요될지도 모릅니다. 즉, 이미지와 같은 모든 개체들도 서버에 매번 접속을 해야하는 상황이 발생합니다. 하지만 KeepAlive 기능이 켜져 있으면 두 개 정도의 연결을 열어서 끊지 않고, 연결을 계속 재사용합니다. 이렇게 되면 연결을 하기 위한 대기 시간이 짧아지기 때문에 사용자가 느끼는 응답 속도도 엄청나게 빨라집니다.
KeepAlive 설정을 할 때 반드시 같이 따라와야 하는 설정이 있습니다. 바로 KeepAlive Timeout 설정입니다. 이 설정은 초 단위로 KeepAlive가 끊기는 시간을 설정하기 위한 부분입니다. 마지막 연결이 끝난 이후에 다음 연결이 될 때까지 얼마나 기다릴지를 지정합니다. 설정은 다음과 같습니다.
KeepAliveTimeout 15
만약 사용자가 너무 많아 접속이 잘 안될 경우, 이 설정을 5초 정도로 짧게 주는 것도 서버의 리소스를 보다 효율적으로 사용할 수 있는 방법입니다. 더 많은 설정이 있을 수 있으나, 아파치 서버의 설정은 여기까지만 알아보겠습니다.
DB Connection Pool 및 스레드 개수 설정
WAS에서 설정해야 하는 값은 너무나 많습니다. 그 중 가장 성능에 많은 영향을 주는 DB Connection Pool과 스레드 개수에 대해서 알아보겠습니다. 이 두 개 개수는 메모리와 관련이 있습니다. 많이 사용할수록 메모리를 많이 점유하게 됩니다. 그렇다고 메모리를 위해서 두 개수를 적게 지정하면, 서버에서는 많은 요청을 못하고 대기할 수 밖에 없습니다.
대부분의 WAS에서는 DB Connection Pool의 개수를 최소치, 증가치, 최대치 등으로 자세하게 지정할 수 있습니다. 최소치는 말 그대로 서버가 기동될 때 연결을 수행하는 개수입니다. 개발자용 PC에서는 이 값이 높을 필요가 없으므로 이 값은 최소한으로 지정하는 것이 좋습니다. 최소 개수가 많으면 많을수록 서버 기동하는 시간이 오래 소요되므로, 개발자가 디버그를 하기 위해서 여러 번 재기동을 할 때에는 좋지 않습니다. 하지만 운영 중에는 최소 및 최대 값을 동일하게 하는 것이 좋습니다. 만약 사용자 수가 갑자기 증가하면 DB Connection Pool의 개수도 증가되어야 하고, 증가할 때 대기 시간이 발생할 확률이 크기 때문입니다. 만약 DB 서버의 리소스가 부족하다면 최솟값을 적게 해놓는 것도 한 방법이 될 수 있습니다.
대부분 WAS에서 두 설정 값의 기본 개수가 10~20개 정도입니다. 따라서 기본값으로 오픈하면 서버가 원하는 요청량을 처리하지 못하게 됩니다. DB Connection Pool은 보통 40~50개로 지정하며, 스레드 개수는 이보다 10개 정도 더 지정합니다. 이렇게 지정하는 이유는, 스레드 개수가 DB Connection Pool의 개수보다 적으면 적은 수만큼의 연결은 필요 없기 때문입니다. 예를 들어 스레드를 40개로 지정하고, DB Connection Pool 개수를 50개로 지정하면, 적어도 10의 연결은 전혀 사용을 하지 않게 됩니다. 쉽게 스레드는 입구이고 DB Connection Pool은 출구라고 생각하면 됩니다.
그럼 스레드의 개수가 DB 연결 개수보다 많아야 하는 이유는 뭘까요? 모든 애플리케이션이나 화면이 DB에 접속하는 것은 아닙니다. 또한 관리자 콘솔을 사용하여 서버에 접속할 수도 있기 때문에, 그만큼 여유분을 갖도록 지정하는 것이 보통입니다.
가장 적합한 DB Connection Pool과 스레드 개수는 몇 개일까요? 웹 서버의 세팅값도 그렇지만, 이 수치도 서버와 애플리케이션 상황에 따라 다릅니다. 완벽한 값은 없습니다. 서버 상황에 맞게 값을 지정해야 하는 것입니다. 가장 좋은 방법은 성능 테스트를 통해서 값을 구하는 것입니다.
DB Connection Pool의 개수를 기준으로 적절한 수치를 찾는 방법을 생각해 보겠습니다. DB Connection Pool을 40개로 잡아 놓았다고 가정하겠습니다. 40개 전부를 사용하면서 DB의 CPU 사용량이 100%에 도달했다면, 어떻게 해야 할까요? 이 경우에는 DB의 CPU를 점유하는 쿼리를 찾아서 튜닝을 수행해야 합니다. 다시 말하면, 인덱스가 없거나 테이블을 풀 스캔하는 쿼리가 있는 것은 아닌지 쿼리의 플랜을 떠서 확인해 봐야 합니다. 40개를 전부 사용한다고 해서 DB Connection Pool 개수를 80~200개로 늘리면, 모든 DB와의 연결을 전부 사용하고 응답 시간은 엄청나게 느려질 뿐입니다.
다른 예를 살펴보면, DB의 CPU 사용량은 50%도 되지 않는 상황에서 WAS의 CPU 사용량이 100%에 도달합니다. 그때 사용하는 DB Connection Pool의 개수는 20개 정도밖에 되지 않습니다. 이럴 때는 어떻게 해야 할까요? 이 경우에는 WAS의 애플리케이션을 튜닝해야 합니다. 하지만 이미 튜닝이 된 상태라면 이 서버의 DB Connection Pool의 개수는 약간의 여유를 두기 위해서 25~30개 정도로 지정하는 것이 좋습니다.
WAS 인스턴스 개수 설정
하나의 장비에 WAS의 인스턴스 개수를 몇 개로 해야 된다는 규칙은 어느 문서에도 존재하지 않습니다. 이 또한 절대값은 없다는 것입니다. 하지만 절대값이 없다고 해서 무한정 인스턴스를 늘리는 것이 답이 될 수는 없습니다.
당연한 이야기이지만, 서버의 인스턴스를 늘리면 늘릴수록 CPU가 처리해야하는 양은 많아집니다. 하나의 장비에 20개 이상의 인스턴스를 운영하는 경우도 있습니다. CPU의 개수도 4~8개 정도밖에 안되는 사이트였음에도, 성능이 잘 나오지 않는 경우였습니다. 여러개의 인스턴스에서 경합을 하면서 CPU를 차지하려고 하기 때문입니다. 아무리 메모리가 싸서 많이 꽂아 둔다 한들 서버의 처리량이 증가하지 않습니다. 어떤 애플리케이션이 어떤 CPU를 점유하는지 지정할 수 있으면 좀 달라지겠지만, 그렇지 않은 경우가 대부분입니다. 보통은 1~2개의 CPU당 하나의 인스턴스를 지정하는 것이 좋다고 말합니다.
만약 WAS 장비의 메모리가 4GB 있다면, 하나의 인스턴스에 4GB의 메모리를 지정하여 사용하는 것은 굉장히 좋지 않은 방법입니다. 왜냐하면 Full GC가 발생할 때마다 많은 시간이 소요될 확률이 커지기 때문이비다. 가급적이면 512MB~2GB 사이에서 메모리를 지정하는 것이 좋습니다. 이 경우 1GB로 메모리를 지정하여 2개의 인스턴스를 사용하는 것이 좋은 방법일 것입니다. 다른 애플리케이션이나 OS에서도 메모리를 사용하게 되므로 어느 정도 여유를 주는 것이 좋습니다.
단독 인스턴스를 구성하여 사용하는 것은 서버에 예기치 못한 상황이 발생했을 때 서비스가 불가능해지므로 되도록 피해야 합니다. 장비가 한 대여도, 두 개 이상의 인스턴스가 서로 클러스터링하도록 지정하여 사용자의 세션 정보를 공유하도록 하는 것이 좋습니다. 그리고 스레드나 DB Connection Pool의 개수를 100개 이상으로 설정하려고 할 때에는 보통 인스턴스를 두 개로 분리합니다. 경험상으로는 인스턴스 수를 증가시켜서 성능이 좋아진 경우도 있고 그렇지 않은 경우도 있었으나, 대부분 성능이 월등히 좋아지지는 않았습니다.
Session Timeout 시간 설정
특정 서버의 활성 세션 모니터링 화면에서는 시간이 지남에 따라 세션의 개수가 증가했다가 감소했다가 하는 것을 볼 수 있습니다. 하지만 비정상적인 세션 개수 모니터링 화면에서는 세션 수가 계속 증가하는 경우가 발생하기도 합니다. 보통 이런 경우는 원인은 세션 종료 시간에 있습니다. 정상적인 서버의 경우 세션 종료 시간을 4분으로 해놓지만 5,256,000분으로 세션 종료 시간을 설정해 놓은 경우일 수도 있습니다. 가장 간과하기 쉬운 것이 세션의 종료 시간 설정 부분입니다. 이 설정은 WAS에 종속적인 설정이 아닌 WEB-INF 폴더 하단의 web.xml 파일에 설정하는 서블릿 스펙에 정의된 표준 설정값입니다.
... <session-timeout>30</session-timeout> ...
이 설정값은 분 단위입니다. 설정되어 있는 분만큼 요청이 없으면 세션을 메모리에서 제거합니다. 이 설정을 하지 않은 상태에서, WAS에서 따로 설정한 바가 없거나 세션 객체의 invalidate() 메소드가 수행되지 않으면 세션은 삭제되지 않으므로 유의해야합니다.
GC 값 세팅 및 메모리 설정 방법
가장 기본적으로 해야 하는 설정은 최대 메모리 크기와 최소 메모리 크기를 지정하는 것입니다. 보통 장비는 아무런 설정을 하지 않을 경우 기본 64MB를 최대 메모리로 지정하도록 되어 있습니다.
어떤 사이트에서, 개발자가 자신의 PC에서 웹 로직 서버의 콘솔을 기동하면 OutOfMemoryError가 발생하는 경우가 있었습니다. 그래서 자바 옵션을 확인하여 봤더니 메모리 설정이 전혀 되어있지 않았습니다. 그렇게 사용하면 당연히 메모리가 부족해지므로 예외가 발생할 수 밖에 없습니다.
java ... -Xmx512m -Xms256m ...
Xmx는 최대 힙 메모리, Xms는 최소 힙 메모리를 지정합니다. 여기서 숫자 뒤에 m은 메가바이트를 의미합니다. 만약 킬로바이트를 지정하고 싶다면 K나 k를, 기가바이트를 지정하고 싶다면 G나 g를 숫자 뒤에 지정합니다.
GC 관련 옵션들은 굉장히 많은데, 다음은 성능과 관련된 옵션들에 대한 것입니다. 사이트에서 성능상 문제가 없을 경우 이 옵션을 지정할 일은 별로 없습니다. 그러나 일부 외국산 솔루션은 여기의 값을 지정해야 시스템이 정상적으로 작동되는 경우도 있습니다.
옵션 및 기본값 |
설명 |
-XX:+AggressiveOpts |
컴파일러 최적화를 켜는 옵션입니다. JDK6 버전부터는 기본으로 사용됩니다. |
-XX:CompieThreshold=10000 |
컴파일 전에 메소드의 수행과 분기되는 개수를 지정합니다. 클라이언트 버전의 기본값이 1,500입니다. |
-XX:LargePageSizeInBytes=4m |
자바 힙에서 사용하는 대형 페이지 크기를 지정합니다. |
-XX:MaxHeapFreeRatio=70 |
메모리의 감소를 피하기 위해서 GC를 수행한 이후의 최대 힙 영역의 퍼센트를 지정합니다. |
-XX:MaxNewSize=size |
Young 영역의 최대 크기를 바이트 단위로 지정합니다. |
-XX:MaxPermSize=64m |
Perm 영역의 크기를 지정합니다. |
-XX:MinHeapFreeRatio=40 |
메모리 확장을 피하기 위해서 GC를 수행한 이후의 최소 힙 영역의 퍼센트를 지정합니다. |
-XX:NewRatio=2 |
Young 영역과 Old 영역의 크기 비율을 지정합니다. |
-XX:NewSize=2.125m |
Young 영역의 기본 크기를 지정합니다. |
-XX:ReservedCodeCacheSize=32M |
예약된 코드의 캐시 크기를 지정합니다. |
-XX:SurviorRatio=8 |
Eden 영역과 Survivor 영역 크기의 비율을 지정합니다. |
-XX:TargetSurvivorRatio=50 |
GC를 수행한 이후에 Survivor 영역에서 사용하는 공간의 퍼센트를 지정합니다. |
-XX:ThreadStackSize=512 |
스레드의 스택 크기를 킬로바이트 단위로 지정합니다. 0으로 지정할 경우 기본 스택 크기를 의미합니다. |
-XX:+UseFastAccessorMethods |
기본 자료형의 값을 가져올 때 최적화 버전을 사용합니다. |
본인이 만든 시스템에 가장 적절한 설정값은 성능 테스트를 통해서 찾아야 합니다. 모든 수치에 절대값은 없기 때문입니다. 성능이 굉장히 중요한 시스템은 반드시 성능 테스트를 해야 합니다. 사용자가 지정되어 있는 내부 시스템도 성능 테스트를 하긴 해야 하지만, 외부사용자에게 서비스를 하는 시스템은 그 필요성이 더합니다. 실제 사용자의 수를 가늠하기 어렵기 때문입니다. 반드시 성능 테스트를 수행해서 가장 적절한 튜닝 값을 찾아내고, 해당 시스템이 견딜 수 있는 사용자 수를 알고 있어야 합니다.
'서버운영 (TA, ADMIN) > 미들웨어' 카테고리의 다른 글
[서버관리] SWAP 메모리 추가 할당 (1) | 2017.07.22 |
---|---|
[서버관리] 인바운드 트래픽과 아웃바운드 트래픽 (1) | 2017.07.20 |
[서버관리] 톰캣과 아파치 웹서버 연동 (0) | 2017.05.20 |
[서버관리] 단일호스트 서버의 부하 튜닝 (1) | 2017.05.15 |
[버전관리] Git 브랜치 학습 사이트 (0) | 2017.05.08 |