성능 엔지니어링 대한 접근 방법 (Performance tuning)
성능 엔지니어링에 대한 접근 방법
조대협
성능 개선, Performance Tuning, 용량 선정 과 같은 튜닝 관련 용어들은 모든 개발자나 엔지니어에게 모두 흥미가 가는 주제일 것이다. 그 만큼 소프트웨어에서 고성능을 내는 시스템은 만들기도 힘들뿐더러, 고성능 시스템이란 즉 잘 설계되고 구현된 소프트웨어를 뜻하는 것이니 관심을 가지는 것이 당연하지 않을까 싶다.
필자의 경우, 엔터프라이즈 시스템에 대한 약 6년간 장애 해결, 장애 회피 설계, 성능 개선, 고성능 시스템 설계 및 구현에 관련된 일을 해왔다. 특히 장애 해결과 성능 개선 작업은 하고 나면 뿌듯하기는 하지만, 특정한 기술이 필요하기 보다는 문제를 정의하고 접근하는 능력과 끝까지 목표를 달성할 때까지 지루한 작업을 반복적으로 할 수 있는 인내심을 필요로 하는 작업이다
이번 챕터에서는 Performance Engineering의 전반적인 접근 방법과, 용량 산정 방법 그리고 자바 기반의 서버 애플리케이션에 대한 성능 튜닝 및 병목 발견 방법에 대해서 설명하고자 한다.
Performance Engineering 의 정의와 범위
Performance Engineering은 시스템의 목표 성능 (응답 시간과 동시 접속자수)을 정의 하고, 이를 달성하기 위해서, 시스템의 구조를 반복적으로 개선하는 작업을 이야기 한다.
좁게 생각하면, 코드상의 병목을 잡고, 시스템의 설정(Configuration)을 바꿔서 성능을 올리는 튜닝으로 생각할 수 있지만, 성능 목표의 정의에서 부터, 최적의 성능을 내기 위한 디자인 및 구현과 같은 개발 초기의 설계 부분와 개발후의 운영단계에서 모니터링 까지 전과정을 포함한다.
Performance Engineering은 언제 해야 하는가?
Performance Engineering은 전체 소프트웨어 개발 과정에 걸쳐서 크게 아래와 같이 4단계에 걸쳐서 일어난다.
위의 개발 모델은 전형적인 Water fall model이다. 개발프로세스 챕터에서도 설명하였지만, 스크럼과 같은 애자일 방법론을 사용하더라도 큰 범위에서 개발 사이클은 Waterfall 모델과 크게 다르지 않게 된다. (각 단계별을 SPRINT 단위로 수행한다.)
① 분석 단계
초기 요구 사항 분석 및 시스템 기획 단계에서는 성능에 대한 목표를 정해야 한다.
목표 응답시간은 어떻게 되는지, 시스템을 사용할 총 사용자수와 동시에 시스템을 사용하는 동시접속자 수가 어떻게 되는지와 같은 성능 목표를 정의한다.
또한 고려해야 하는 사항중의 하나는 성능 모델이다. 시스템에 부하가 어떤 패턴으로 들어오는지를 정의할 필요가 있다.
예를 들어 일반적인 웹컨텐츠 사이트의 경우 사용자가 들어와서 페이지 컨텐츠를 1~3분 내에 읽고 다른 페이지로 이동하다가, 20 여분 후에는 로그아웃하거나 다른 사이트로 이동한다. 즉 한 사용자의 체류 시간은 20분정도되며, 총 평균 20 페이지를 보는 트렌젝션을 발생 시키고 나간다고 할 수 있다. 한글로 만든 사이트이고, 육아나 주부를 대상으로 한 사이트라고 가정하면, 시스템의 부하는 한국 시간으로 아이들이 학교나 유치원을 간후인 10시~11시와, 저녁시간대인 10시~12시 사이에 몰린다고 가정할 수 있다.
다른 예로 게임 시스템을 예로 들어보자, 주로 초등학생을 타켓으로 한 게임이라면, 방과후 시간인 3시~5시 대에 부하가 가장 몰릴 것이며, 게임의 종류에 따라 다르겠지만, 스타크래프트와 같은 게임의 경우 한번 플레이에 40분 정도 소요가 되고, 한 사용자가 하루에 두번정도 게임을 한다 가정 아래, 사용자당 체류 시간은 2시간, 게임 횟수는 2회/일. 그리고 주요 부하는 3~5시 오후대 라는 성능 모델을 만들 수 있다.
초기 성능 정의는 서비스의 종류(웹,게임,기업 시스템,쇼핑,뱅킹등) 나, 서비스를 사용하는 사용자층, 그리고 서비스를 사용하는 지역. 즉 전세계를 서비스하는 시스템이라면 시스템의 부하는 365일,24시간 거의 다 걸린다고 봐야 한다. 그러나 한국만을 대상으로 서비스 하는 한국어로 된 사이트인 경우, 새벽 시간에는 일반적으로 로드가 없는 것과 같은 한국의 시간대에 영향을 받을 뿐만 아리나 명절,휴일,휴가와 같은 한국이라는 국가 특성에 따라 시스템의 부하가 영향을 받는다.
② 디자인 단계
다음으로는 디자인 단계에서는 목표 성능과 용량을 달성할 수 있는 규모의 시스템으로 설계를 진행한다.
성능 관점에서 시스템 디자인은 항상 Peak Time (최대 성능)에 맞춰서 디자인이 된다. 최대 성능을 기반으로 전체 시스템이 받아낼 수 있는 용량과 응답 시간을 고려해야 한다.
특히 성능과 용량은 애플리케이션 디자인 뿐만 아니라 Technology selection에도 많은 영향을 받는다. 어떤 하드웨어를 사용할 것인지, 어떤 미들웨어나 프레임웍을 사용할 것인지이에 따라 용량과 성능의 차이가 많이 발생하기 때문에, 디자인 단계에서 부터 성능과 용량을 감안해서 시스템을 설계해야 한다.
하드웨어 관점에서는 예전에는 성능 모델을 산정한 후에, Peak Time 기준 (최대 성능 요구)으로 시스템을 설계하고, 하드웨어를 구매 하였으나, 근래에는 클라우드를 이용하여 필요시에만 하드웨어를 탄력적으로 사용하는 Auto Scale Out 모델을 많이 사용한다.
기업 내부의 업무 처럼 (예를 들어 이메일), 부하가 일정하고 예측이 가능한 경우에는 Fixed 된 사이즈의 하드웨어를 사용하도록 설계 하고, 출시 이벤트 행사 사이트와 같이 부하가 갑자기 몰리는 시스템의 경우 클라우드를 고려해보는 것도 권장할만 하다.
또한 빠른 응답 시간이 필요할 경우 SSD 디스크를 사용하거나, RAID 구성도 5보다는 1+0 등을 고려하는 등, 성능 모델에 따라서 적절한 하드웨어 선정과 구성 설계가 필요하다.
미들웨어나 프레임웍 관점에서도 정의된 성능 모델에 따라 적절한 제품군과 설계 구조를 채택해야 한다. 100,000 사용자 정도의 시스템 규모에서는 RDBMS 를 사용해도 성능이나 용량상에 문제가 없다. 그러나 50,000,000 사용자 정도를 지원해야 하는 시스템의 경우 그냥 RDBMS 를 사용할 수 없다. Sharding이나, NoSQL과 같은 다른 차원의 접근이 필요하다.
또한 빠른 응답 시간을 요구하는 경우 Redis나 Memcached와 같은 Cache 솔루션을 적극적으로 활용하거나, 미들웨어 부분에서는 Tomcat과 같은 일반적은 Web Application Server 보다는 Netty나 Vertex와 같은 고성능 미들웨어를 고려해볼 수 있다.
이러한 성능이나 용량에 관련된 제품 선정이나 설계는 돌려 보지 않으면 사실 확신을 가지기 어렵다. 그래서 가능하면, Technology selection 후에, 간단한 프로토타입을 구현한후에 시나리오가 단순한 대규모의 성능 및 용량 테스트를 해보는 PoC (Proof Of Concept)과 같은 작업을 이 단계에서 수행하는 것을 권장한다.
③ 개발단계
개발 단계는 개발프로세스 챕터에서 설명하였듯이, risk 가 높은 부분과 아키텍쳐에 관련되는 부분, 난이도가 높은 부분, 핵심 기능등을 개발 초기의 스프린트에서 개발한다.
초기 스프린트가 끝나고 릴리즈가 되서 성능 테스트가 가능한 QA나 스테이징 환경으로 시스템이 이전되면, Performance Engineering 역량을 이 단계에 집중하여, 시스템의 아키텍쳐와 모듈들이 성능 목표를 달성할 수 있는지 지속적으로 테스트하고 튜닝을 수행한다.
초기 단계에 성능 목표의 달성 가능 여부가 판단되어야, 아키텍쳐 변경이 가능하고, 주요 성능 이슈들을 초반에 발견해야, 발견된 성능 문제들에 대해서는 같은 문제가 발생하지 않도록 디자인 가이드나 코딩 가이드를 개발자들에게 배포하여 성능에 대한 위험도를 줄일 수 있다.
④ 최종 테스트 단계
앞의 단계에서 성능과 용량을 고려해서 설계가 되었고, 개발 초기 단계에서 성능과 용량 부분을 검증을 제대로 하였다면, 최종 테스트 단계에서는 개발된 최종 시스템에 대한 성능과 용량 부분의 측정과 미세 튜닝 (애플리케이션의 병목을 찾아서 부분적으로 수정하거나, 하드웨어나 미들웨어의 Configuration 하는 수준)을 하는 정도로 마무리가 되어야 한다.
이 과정에서는 실수로 잘못한 설정(configuration) 이나 잘못된 코딩으로 된 부분에 대해서 검증이 이뤄지는데, 이 경우에는 보통 2배에서 크게는 10배까지의 성능 향상이 이루어진다. 이런 경우는 대부분 실수에 의한 것이고 성능이 터무니 없이 낮게 나오기 때문에 찾기가 쉽다.
예를 들어 로그 파일을 NFS와 같은 리모트 디스크에 쓴다던지, Intel 계열의 CPU에서 하이퍼쓰레딩을 ON을 안했다던지와 같이 실수에 의한 경우가 많다.
이런 오류성의 문제들이 해결되면 실제 미세 튜닝에 들어가게 되는데, JVM 튜닝이나 톰캣의 설정 튜닝, SQL 튜닝들이 이루어지는데, 이 미세 튜닝을 통해서는 비약적인 성능향상은 이루어나지 않는다. 보통 20% 내외 정도 성능이 올라간다고 보면 된다.
⑤ 운영 단계
마지막으로 시스템이 운영 단계로 넘어가게 되면, 테스트시에 발견되지 않은 성능적인 문제가 있을 수 있기 때문에, 모니터링 도구를 사용하여 지속적으로 성능을 모니터링 하고, 성능상에 문제가 있는 부분을 지속적으로 수정해야 한다. 웹서버의 access로그에서 응답 시간을 모니터링 하거나, 제니퍼(http://www.jennifersoft.com)과 같은 전문적인 APM (Application Performance Monitoring)툴이나, Ganglia와 같은 시스템 모니터링 도구를 사용하면, 시스템의 성능 상태를 잘 알 수 있다.
더불어 용량 부분에 대해서도 운영단에서는 고민을 해야 하는데, 일반적으로 PEAK Time의 시스템 용량이 (CPU) 80% 정도에 다다르면, 시스템 용량 증설을 고려해야 한다.
그리고 업무에 특성에 맞게 미리미리 용량을 준비해놓는게 좋다. 예를 들어 대학의 수강 신청 시스템의 경우, 학기 시작하는 날에 부하가 폭주하기 때문에, 클라우드 기반일 경우 수강신청 전에 시스템 수를 미리 늘려놓는다던지, 클라우드가 아닌 경우, 수강 신청 기간을 앞뒤로 서버를 임대해서 용량을 늘려놓는 등의 대책을 미리 세워놓을 수 있다.
마지막으로, 운영 단계에서 Performance Engineering 관점으로 챙겨야 하는 부분은 운영 로그의 수집이다. 성능 및 용량 목표 설정은 매우 중요한 과정이다. 특히 용량 목표의 경우에는 기존의 업무 시스템의 사용 패턴을 분석 하는 것이 가장 효율적이기 때문에 운영 시스템의 로그를 수집하고 분석하여 운영 중인 업무 시스템의 성능 모델을 분석 및 보유 해놓는 것이 좋다.
시스템 용량 산정 (Capacity Planning)
더 자세한 설명에 들어가기 앞서서, 성능에 관련된 용어와 함께 시스템의 목표 용량 산정 방법에 대해서 이야기 해보도록 하자.이 용어는 이 글에서 정의하는 의미의 용어이며, 다른 성능 이론에서 언급되는 용어와 다소 다를 수 있다.
l Response Time (응답 시간) : 사용자가 서버에 요청을 한 시간에서 부터, 응답을 받을 때 까지의 모든 시간을 포함한다. 이 응답시간은 내부적으로 다음과 같이 조금 더 세분하게 분리된다.
Network Time (또는 Latency time). 서버에 요청을 했을때, Request를 보내고 받을 때 소요되는 네트워크 시간을 의미한다.
Transaction Time : 서버에서 실제 트렉젝션이 처리되는 시간을 의미 한다.
Think Time : 사용자가 요청에 대해서 응답을 받은 후에, 웹페이지를 보거나 화면을 보는 등의 작업을 하는 시간의 의미한다.
예를 들어 보면 한국의 사용자가 미국이 페이스북을 사용한다고 했을때, 사용자가 웹 브라우져에서 클릭을 하면, 요청이 서버로 도달할때 까지 걸리는 시간 Network time (Request), 서버가 요청을 받아서 처리를 하고, 응답을 하는 시간 (Transaction Time), 그리고 그 응답이 사용자의 브라우져 까지 도착하는 시간이 Network time (Response) 이다. 이 전체 시간을 합친 것이 Response Time이 된다.
응답을 받은 후에는 사용자가 페이스북 내용을 보는데 소요 되는 시간이 Think Time이 된다.
Think Time 까지 포함하여 다음 요청이 발생하기 까지의 전체 시간을 Request Interval 이라고 한다.
l Concurrent User (동시 사용자) : 시스템을 현재 사용하고 있는 사용자를 정의한다. 웹사이트를 사용하기 위해서, 현재 브라우져를 열어놓고 웹사이트를 보고 있는 것과 같이 현재 시스템을 사용하고 있는 사용자 수를 의미 한다.
위의 그림을 보자, 5명의 사용자 A~E가 있다고 가정했을 때, 단위 시간 10분동안에 Transaction Time과 Think Time중에 있는 사용자는 A,B,C 총 3명으로 해다 시간 10분간의 Concurrent User는 3명이 된다.
l Active User (액티브 사용자) : 현재 시스템에 트렌젝션을 실행하여 부하를 주고 있는 사용자를 정의한다.
기존에는 Concurrent User와 Active User간의 차이가 없었다. 이 개념은 웹이 생기면서 구체화된 개념인데, 웹 사이트를 사용하기 위해서 컴퓨터 앞에 앉아 있는다고 하더라도, 웹 페이지가 로딩 되는 순간에만 서버는 부하를 받고, 페이지가 웹 브라우져로딩 된 후에는 부하를 받지 않고 사용자는 로딩된 페이지를 보는데 시간이 발생한다. 이 시간동안에는 서버는 부하를 받지 않는다. 즉 시스템을 사용하기 위해서 웹 사이트를 열어 놓고 있는다 하더라도 지속적으로 서버에 부하를 주는 것이 아니기 때문에 Concurrent User와 Active User 의 개념 차이가 발생한다.
Active User는 클릭을 발생시켜서 그 시간 당시에 서버에 트렌젝션을 발생 시키는 사용자를 의미한다.
Active User의 수는 서버에서 순간 실행되고 있는 Thread 수 (쓰레딩 기반의 자바 서버의 경우) 나 Process의 수와 같다. 이 Active User의 수는 실제로 서버가 동시에 처리할 수 있는 트렌젝션의 양을 판단할 수 있는 기준이 되기 때문에 매우 중요한 성능 Factor가 된다.
위의 그림을 보자, 위의 그림에서 특정 순간에 있는 사용자는 총 5 명으로 Concurrent User 는 5명이지만, Transaction Time 구간중의 있는 사용자는 A,B,C 로, 총 Active User는 3명이 된다.
l Transaction (트렌젝션) : Transaction이란, 사용자로 부터의 요청을 다루는 단위를 정의 한다. 이 정의가 상당히 중요한데, 성능 모델링이나 성능 테스트 시 이 Transaction의 정의에 따라서 시스템의 성능이 매우 다르게 정의 된다.
예를 들어서 사용자가 웹 페이지를 클릭했을때, 그 페이지에 대한 응답을 받는 것 까지를 하나의 트렌젝션이라고 정의 하자.
이 때, 웹페이지에는 서버에서 생생된 HTML 이외에, 여기서 참고 하는 리소스 즉, 이미지나 동영상, 자바 스크립트들이 들어있을 수 있다. 이 경우 트렌젝션에 대한 응답 시간을 측정할때, HTML 생성 이외에 이러한 리소스들을 로딩 하는 것 까지 하나의 트렌젝션으로 정의 해야 하느냐를 고려해야 한다.리소스에 로딩을 트렌젝션의 범위로 넣게 되면 전체 시스템의 응답 시간은 떨어지게 된다. (리소스를 로딩할 때 까지 기다려야 하니).
이러한 트렌젝션의 정의는 무엇을 판단 기준으로 할것인가에 따라 결정이 되는데, 예를 들어 리소스를 톰캣과 같은 WAS에서 처리하지 않고 앞단의 CDN이나 웹서버에서 처리할 경우 톰캣은 리소스에 대한 트렌젝션 요청을 받지 않기 때문에, 전체 시스템에서 비지니스 로직에 대한 처리 성능을 측정하고자 할 때는 리소스에 대한 로딩 시간을 계산하지 않고 트렌젝션을 정의 한다. 또한 리소스에 대한 로딩은 비지니스 로직에 대한 처리에 비해서 부하가 상대적으로 매우 적고, 일반적으로 브라우져에 캐쉬되기 때문에 보통 서버의 성능 측정시 이러한 리소스 로딩에 대한 부하는 트렌젝션의 단위로 처리하지 않는 경우가 많다.
l TPS(Transaction Per Second) : 초당 처리할 수 있는 트렌젝션의 양을 정의 한다. 주로 서버의 성능 평가 기준이 된다.
Active 사용자가 순간 Transaction을 처리한다고 하면, 이를 목표 응답시간 (Response Time)으로 나눈 값이 목표 TPS가 된다. 예를 들어,
Active User가 50 명이고, 개당 Response Time이 2초 라고 하면, 이 시스템의 TPS는 25 TPS가
된다.
※ Network time이 미세하다고 판단하여,
Network time을 0으로 가정하여 계산
l HPS(Hit Per Second) : 시스템이 처리할 수 있는 모든 웹 request의 초당 처리량이다. TPS가 비지니스 트렌젝션에 대한 처리 시간만을 정의 한다면, HPS는 리소스 (이미지, 자바스크립트)에 대한 request 처리량을 포함하기 때문에, TPS에 비해서 10~20 배 정도 높게 나온다.
l Peak Time(피크 타임) : 서버가 순간적으로 가장 부하를 많이 받는 순간을 정의 한다. 보통 서버의 용량 산정이나 성능 설계는 이 시간의 부하량을 기준으로 한다
일반적인 업무 시스템의 경우, 출근 9시~9시30분 사이가 가장 부하가 높다. 이 때 Peak (최고 정점)을 찍는 순간의 동시 사용자 수와 기준 응답 시간을 목표로 성능 목표를 정의 하는 것이 일반적이다.
위의 개념을 정리해서 공식화 해보자.
① TPS = (Active User) / (Average Response Time) – F1
② TPS = (Concurrent User) / (Request Interval) – F2
③ Active User = TPS * (Average Response Time) – F3
④ Active User = (Concurrent User) * (Average Response Time) / (Request Interval) – F4
⑤ Active User = (Concurrent User) * (Average Response Time) / [ (Average Response Time) + (Average Think Time) ] – F5
예를 들어 Concurrent User가 300명이고, 목표 응답시간이 3초 이내이며, Think Time이 15초 인 시스템의 경우, F5 공식에 따라서 Active User는 300*3/(3+15) = 50 이 되며, 시스템의 Thread 또는 적정 Process 양은 50개가 된다. 목표 TPS는 약 16.6 TPS가 된다.
위의 공식은 어디까지나 이론적인 공식이다. Network Latency 값은 가변적이며, Think Time 또한 유동적이다. 그러나 용량 산정에는 어느 정도의 산정 기준이 필요하기 때문에, 이 공식을 사용하면 대략적인 시스템에 대한 요구 용량을 예측할 수 있다.
Performance Engineering 의 절차
그러면 어떤 절차로 성능과 용량을 측정하고 개선하는 절차에 대해서 알아보도록 하자.
성능 목표와 모델의 정의
먼저 주요 업무 패턴이나, 튜닝의 대상이 되는 시나리오에 대한 개별 성능 목표를 정의 한다. 예를 들어 전체 성능 목표가 1,000 동시 사용자에 대해서 응답 시간 1초내의 시스템이 전체 성능 목표라고 가정하고, 전체 성능 목표를 대략 1,000 TPS (Transaction Per Second)라고 하자. 이것이 바로 성능 목표가 된다.
다음으로 성능 모델을 정의 해야 하는데, 해당 시스템의 주요 사용자 시나리오가 여러개 있을 때, 각 시나리오별의 사용 비중을 정의 해야 한다.
예를 들어 사진을 저장하는 클라우드 서비스 시나리오가 있다고 하면, 이 서비스의 주요 사용자 시나리오는
① 로그인
② 사진 리스트
③ 사진 업로드
④ 사진 보기
⑤ 사진 다운로드
⑥ 로드 아웃
등이 된다. 이 중에서 한 사용자가 실행하는 비율을 따져야 한다. 즉 사용자가 로그인 한후, 리스트 보기를 10번, 업로드를 2번, 보기를 5번, 그리고 다운로드를 1번 한후에 로그 아웃 한다고 하자. 그러면 비율은 다음과 같이 된다. (전체 트렌젝션 횟수 1+10+2+5+1+1 = 20회)
성능 모델 :로그인의 비율 5%, 리스트 보기 50%, 업로드 10%, 보기 25%, 로그아웃 5%
이 비율을 기준으로 복합 시나리오 (전체 시나리오를 함께 돌리는) 부하테스트를 수행하였을때, 1000 TPS가 나와야 하고, 각 개별 시나리오에 대해서 최소한, 로그인의 경우 1000 TPS의 5%인 50 TPS, 리스트 보기는 500 TPS를 상회 해야 한다.
부하 생성
성능 모델이 정의 되었으면, 이 모델에 따라서 부하를 생성해야 한다.
부하 생성 도구는 여러가지가 있다. 대표적인 오픈 소스 도구로는
가장 간단하게 쓸 수 있는 도구로는 Apache AB 라는 명령어 기반의 도구가 있으며, 복잡한 스크립트를 지원할 수 있는 도구로는 grinder나 apache JMeter 등이 있으며, NHN에서 grinder를 enhancement해서 만든 (GUI가 지원되는) nGrinder라는 도구가 있다.
근래에는 국내에서는 nGrinder라는 도구가 많이 사용되고 있다.
성능 모델이 단순하고, 테스트 시나리오가 간단할 경우에는 Apache ab 등으로도 가능하지만, 스크립트가 복잡해지는 경우에는 nGrinder와 같은 도구가 유리 하다.
또한 부하 생성에 사용되는 스크립트는 복잡도가 생각보다 높고, 향후 regression(회귀) 테스트에도 재 사용되기 때문에, 반드시 형상 관리 시스템을 통해서 (VCS) 관리 하는 것을 권장한다.
※ 자세한 부하 테스트에 대한 방법은 “4장 테스트의 시스템 테스트 “ 부분을 참고하기 바란다.
※ 클라우드 컴퓨팅과 부하 테스트 툴 라이센스 모델에 대해서
예전에는 부하 테스트가 사내에서 사내에 있는 시스템을 대상으로 했었기 때문에 큰 문제가 없었다. 그러나 근래 들어서 클라우드 컴퓨팅을 사용하는 사례가 늘어남에 따라, 서비스 시스템이 회사 밖에 즉, 클라우드에 있는 경우가 많아 졌다. 상용 부하 테스트툴의 경우에는 부하 발생기의 위치와 툴 사용자에 대해서 제약을 두는 경우가 있는데, 툴을 구매했다 하더라도, 부하 테스터의 controller (부하 발생기 제외)는 반드시 사내에 있어야 하며, 사용자 역시 그 회사의 내부 직원으로만 한정하는 경우가 있다. 예를 들어, 내가 부하 테스트 도구를 서울에 있는 회사에서 구매하여, 이 툴을 Amazon 클라우드 미국에 설치하고 부하 테스트를 미국 지사 직원을 통해서 진행하는 것이 불가능 하다. 이 경우 부하 테스트 툴의 Controller는 한국 서울 사무소에 설치하고, 부하 생성기만 Amazon에 설치한후 한국 서울 사무소 직원을 통해서만 사용해야 한다.
간혹 (이럴리는 없어야 하겠지만) 부하 테스트 툴의 판매 회사 영업 사원이 이러한 사실을 제대로 통보하지 않아서, 툴을 잘 쓰다가 갑자기 영업 사원이 변경되거나, 부하 테스트 툴의 이전을 요청 하였을때, 갑자기 벤더로 부터, 추가 라이센스 구매 요청을 받을 수 있으니, 구매 전에 반드시 구매 조건에 사용 시나리오와 Controller 위치, 사용 주체 및 테스트 대상 시스테들에 대해서 명시적으로 기재 하고 구매 계약을 추진 하는 것이 좋다.
|
테스트 및 모니터링
부하 테스트 준비가 되었으면, 부하 테스트를 진행하고 진행중에 주요 성능 Factor에 대해서 지속적으로 모니터링 및 기록을 하여야 한다. 주로 모니터링해야하는 Factor들은 다음과 같다.
① 애플리케이션 관점
가장 기본적으로 애플리케이션 즉 시스템의 성능을 측정 해야 한다. 주요 모니터링 Factor는 다음과 같다.
Response Time : Request 별 응답 시간
TPS (Throughput per second) : 초당 요청(Request) 처리량
이 Factor들이 궁극적으로 성능에 대한 최종 목표 값이 되기 때문에, 가장 중요한 성능 Factor가 되며, 부하 생성 도구를 통해서 손쉽게 측정할 수 있다.
② 미들웨어 관점
미들웨어는 애플리케이션이 동작하기 위한 기본적인 솔루션이다.. Apache와 같은 웹서버나 Tomcat과 같은 Web Application 서버 , RabbitMQ와 같은 Message Queue, MySQL과 같은 데이타 베이스 등이 이에 해당한다.
각 성능 시나리오별로, 거쳐 가는 모든 미들웨어들을 모니터링해야 하는데, 이를 위해서는 각 솔루션에 대한 개별적인 깊은 이해가 필요하다.
웹서버의 경우 거의 성능 문제가 되는 부분은 없다. 성능 문제가 발생하는 부분은 대부분 Network outbound io (bandwidth)쪽이 되는 경우가 많다. 웹서버가 설치된 하드웨어의 network out bound bandwidth를 모니터링 하는 것이 유용하다.
대부분의 성능 문제는 실제 애플리케이션 로직이 수행되는 Tomcat과 같은 application server와 데이타 베이스단에서 많이 발생하는데, application server의 경우에는 Thread의 수와 Queue의 길이가 1차 모니터링 대상이 된다.
서버가 용량을 초과 하게 되면, Idle Thread수가 떨어지게 되고, Idle Thread가 0이 되면 request message가 앞단의 queue에 저장되게 된다. 그래서 이 두 개를 모니터링 하면 시스템이 병목 상태인지 아닌지를 판단할 수 있다. 이 값들은 JMX (Java Management Extension) API를 이용하여 모니터링 하면 된다.
DB의 경우에는 slow query를 모니터링하면 특히 느리게 수행되는 쿼리들을 잡아서 튜닝할 수 있다. MySQL 5.6의 경우 slow query는 http://dev.mysql.com/doc/refman/5.6/en/slow-query-log.html
를 사용하면 쉽게 잡아낼 수 있다.
Slow query를 찾았으면, EXPLAIN 명령어를 이용하여 query의 수행 내용을 분석한후 Index등의 튜닝을 수행할 수 있다.
http://dev.mysql.com/doc/refman/5.0/en/using-explain.html
③ 인프라 관점 : CPU, Memory, Network IO, Disk IO
다음으로 하드웨어 인프라에 대한 부분을 지속적으로 모니터링해줘야 하는데, 이는 하드웨어가 해당 성능을 내기 위해서 용량이 충분한지 그리고 하드웨어 구간에서 병목이 생기지는 않는지, 생긴다면 어느 구간에서 생기는지를 모니터링하여, 해당 병목 구간에 대한 문제 해결을 하기 위함이다.
인프라에 대한 모니터링은 Ganglia나 Cacti와 같은 전문화된 인프라 모니터링 도구를 사용하거나 top이나 glance, sar와 같은 기본적인 Unix/Linux 커맨드를 사용해서도 모니터링이 가능하다. (부하 테스트주에 top 등을 띄워놓고 모니터링을 하는 것이 좋다. Load Runner와 같은 상용 도구의 경우에는 부하 테스트 툴 자체에서 테스트 대상 시스템에 대한 하드웨어 사용률을 함께 모니터링할 수 있게 제공해준다.)
CPU : 일반적으로 CPU는 대부분 잘 모니터링 한다. 목표 성능을 달성할 시에는 보통 70~80% 정도의 CPU 를 사용하는 것이 좋고, 20~30%의 여유는 항상 가지고 가는 것이 좋다 이유는, 70~80% 정도의 CPU가 사용된 후에, 하드웨어를 물리적으로 늘리는 시간에 대한 여유 시간을 가지기 위함이다. 하드웨어는 특성상 주문을한다고 해도, 바로 그 시간에 증설을 할 수 있는 것이 아니고, CPU 가 100%가 되는 순간에는 이미 애플리케이션이 CPU 부족으로 제대로 작동을 하지 못하는 경우가 많기 때문에, 항상 여유를 남겨 놓고 성능 목표를 정의 하는 것이 좋다. 그래서 성능 목표를 잡을 때는 “CPU 70%시, 500 TPS, 응답시간 1.5초 내외” 식으로 하드웨어에 대한 사용률을 포함하는 것을 권장한다.
Memory : 다음으로는 Memory 부분이다. Peak Time시에 Memory가 얼마나 사용되느냐가 중요한데, Java Application의 경우 특성상, 전체 JVM 프로세스가 사용할 메모리량을 미리 정해놓기 때문에, 부하 테스트 중에도 메모리 사용량 자체는 크게 변화하지 않는다. 다만 자주 놓치는 점이 swapping status 인데, Unix/Linux는 시스템의 특성상 물리 메모리 이상의 메모리를 제공하기 위해서 virtual memory 라는 개념을 사용하고 swapping space라는 디스크 공간에 자주 사용하지 않는 메모리의 내용을 dump해서 저장한 후 다시 사용할때 memory에 loading 하는 방식을 사용한다. 그런데 이 메모리의 내용을 디스크에 저장 및 로드 하는 과정 (swapping이라고 함)이 실제 disk io를 발생 시키기 때문에, 실제 메모리 access 성능이 매우 급격하게 떨어진다. 그래서 시스템에서 system에서 swapping이 발생하면 시스템의 성능이 장애 수준으로 매우 급격하게 떨어진다.
부하 테스트 중이나, 운영 중에 swapping이 발생하게 되면 전체 메모리 사용량을 줄이도록 튜닝을 하거나, 반대로 물리 메모리를 늘리는 증설 과정이 필요하다.
Disk IO : Disk IO는 파일 시스템에 파일을 저장하는 시나리오나, Log를 저장하는 모듈 그리고 데이타 베이스와 같이 뒷단에 파일 시스템을 필요로 하는 모듈에서 많이 발생을 한다. Ganglia와 같은 도구를 사용하면, IOPS (Input Out per Second - 초당 read/write등의 IO 발생 횟수)를 통해서 모니터링할 수 있고, 또는 iostat나 sar와 같은 명령어를 이용하면 iowait 를 통해서 디스크 IO의 pending이 발생할 경우 디스크 병목이 있는지 없는지를 확인할 수 있다.
Figure 1. iostat
또는 Process당 Disk IO는 iotop과 같은 툴을 사용하면 조금 더 상세한 정보를 얻을 수 있다.
Figure 2. iotop
Disk IO에 대한 Bottleneck은 여러가지 해결 방법이 있다. 먼저 하드웨어 인프라 ㅈ체에서 접근 하는 방식은, 디스크 자체를 SSD로 변경하거나, 버퍼가 크거나 RPM이 높은 디스크로 변경하는 방식, 인터페이스를 SATA에서 SAS나 SSD와 같은 높은 IO를 제공하는 디스크 인터페이스로 변경, Disk Controller는 iSCSI에서 FC/HBA와 같은 광케이블 기반의 고속 컨트롤러를 사용하는 방식 또는 RAID 구성을 Stripping 방식으로 변경해서 IO를 여러 디스크로 분산 시키는 방식 등이 있으며, 애플리케이션 차원에서는 데이타 베이스 앞에 memcache와 같은 캐슁을 사용하거나, 로깅의 경우에는 중간에 message queue를 써서 로그를 다른 서버에서 쓰도록 하여 IO를 분산하거나 또는 Back write와 같은 방식으로 로그 메세지가 발생할때 마다 disk에 writing 하는 것이 아니라 20개 30개씩 한꺼번에 디스크로 flushing 하는 방식등을 이용할 수 있다.
또는 조금더 높은 아키텍쳐 레벨로는 디스크 IO가 많이 발생하는 로직의 경우 동기 처리에서 message queue를 사용하는 비동기 방식으로 시스템의 설계를 변경하는 방법을 고민할 수 있다. 예를 들어 사진을 올려서 변환하는 서비스의 경우 파일을 업로드 하는 시나리오와 변경하는 모듈을 물리적으로 분리하여, 파일 업로드가 끝나면, 사용자에게 동기 방식으로 바로 응답을 줘서 응답 시간을 빠르게 하고, 업로드된 파일은 뒷단에서 비동기 프로세스를 통해서 변환 과정을 다 끝낸 후에 사용자에게 변환이 끝나면 알려주는 방법을 사용할 수 있다.
Network IO: Network IO는 특히 고용량의 파일이나 이미지 전송에서 병목이 많이 발생하며, Reverse Proxy, NAT (Network address Translator), Router, Load Balancer 등에서 많이 발생한다. 여러가지 지점과 장비에 대해서 모니터링 해야 하기 때문에, 일반적인 unix/linux command 를 사용하는 방법보다는 Cacti나 Ganglia와 같은 RRD 툴이나 OpenNMS와 같은 NMS (Network Management System)을 사용하는게 좋다.
그래프를 보면서 추이를 지켜 보는 것이 중요한데, 부하를 넣으면 일정 수준이 되어도, 시스템들의 CPU나 메모리, Disk등의 기타 자원들은 넉넉한데, Network Input/Output이 일정 수준 이상으로 올라가지 않는 경우가 있다. 이 경우는 네트워크 구간의 병목일 가능성이 높다.
특히 소프트웨어 기반의 Load Balancer나, 소프트웨어 기반의 NAT 장비에서 많이 발생하는데, 이미지와 같은 정적 컨텐츠는 가급적이면 CDN이나 분리된 Web Server를 이용해서 서비스 하도록 하는 것이 좋다. 클라우드의 경우에는 특히나 소프트웨어 기반의 NAT나 Load Balancer를 사용해서 문제가 되는 경우가 많은데, NAT의 경우에는 여러개의 NAT를 사용해서 로드를 분산하도록 하고, Load Balancer의 경우에도 충분히 큰 용량을 사용하거나 2개 이상의 Load Balancer를 배포한 후 DNS Round Robine등을 사용하는 방법을 고려 하는 것이 좋다.
개선 (Tuning)
병목을 찾았으면, 해당 병목 문제를 해결 및 반영해야 한다.
튜닝은 병목 구간이 발생하는 부분에 대한 전문적인 지식을 필요로 하지만, 기본적인 접근 방법은 거의 같다고 보면 된다.
① 문제의 정의 : 성능 개선의 가장 기본은 문제 자체를 제대로 정의 하는 것이다. “그냥 느려요”가 아니라, “성능 목표가 350TPS에 1초내의 응답 시간인데, 현재 60 TPS에 5초의 응답 시간에 WAS의 CPU 점유율이 100% 입니다.”와 같이 명확해야 하며, 문제점이 재현 가능해야 한다.
특히 재현 가능성은 매우 중요한 점인데, 테스트 환경이 잘못되었거나, 외부적 요인 예를 들어 부하 테스트 당시 네트워크 회선이 다른 테스트로 인하여 대역폭이 충분히 나오지 않았거나 했을 경우 결과가 그 때마다 다르게 나올 수 있다.
즉 문제 자체를 명확하게 정의할 필요가 있다.
② Break down : 다음으로는 문제가 발생하는 부분이 어떤 부분인지를 판단해야 한다. 시스템은 앞단의 로드밸런서나 미들웨어, 데이타 베이스와 같은 여러 구간에서 발생을 한다. 그렇기 때문에, 성능 저하의 원인이 정확하게 어느 부분인지를 인지하려면, 먼저 성능 시나리오가 어떤 어떤 컴포넌트를 거치는지를 명확하게 할 필요가 있다. 이 과정을 break down이라고 한다. 이 과정을 통해서 전체 성능 구간중, 어느 구간이 문제를 발생 하는지를 정의한다.
③ Isolate : 다음으로는 다른 요인들을 막기 위해서, 문제가 되는 구간을 다른 요인으로 부터 분리 (고립) 시킨다. 물론 완벽한 분리는 어렵다. 애플리케이션이 동작하기 위해서는 데이타 베이스가 필수적으로 필요하다. 이 경우에는 데이타 베이스를 분리할 수 는 없다. 그러나 예를 들어 시나리오 자체가 로그인 시나리오이고 Single Sign On을 통해서 로그인 하는 시나리오라서 SSO 시스템과 연동이 되어 있다면, SSO 연동을 빼고 다른 mock up을 넣어서 SSO와의 연결성을 끊고 테스트를 하는 것이 좋다.
이렇게 문제에 대한 다른 요인과의 연관성을 최대한 제거 하는 작업이 isolation이다.
④ Narrow down : 문제를 isolation을 시켰으면, 근본적인 문제를 찾기 위해서 문제의 원인을 파 내려간다. Profiling을 하거나, 코드에 디버그 정보를 걸어서 문제의 원인을 분석하는 과정을 narrow down이라고 한다. 특히나 이 narrow down 과정은 분석을 위한 여러가지 기법이나 도구들을 사용해야 하고, 현상에 대한 이해를 하기 위해서는 해당 솔루션이나 기술 분야에 대한 전문성은 필수적으로 필요하다.
⑤ Bottleneck 발견 : Narrow down을 해서 문제의 원인을 계속 파해쳐 나가면 병목의 원인이 되는 근본적인 문제가 판별이 된다.
⑥ 해결 : 일단 병목의 원인을 찾으면 해결을 해야 하는데, 찾았다고 모두 해결이 되는건 아니다. 데이타 베이스 index를 걸지 않아서 index를 걸어주면 되는 간단한 문제도 있을 수 있지만, 근본적인 솔루션 특성이나 설계상의 오류로 인해서 문제가 발생하는 경우도 있다. 하드웨어를 늘려서 해결하는 방법도 있지만, 비지니스 시나리오 자체를 바꾸거나 UX 관점에서 해결 하는 방법도 고려할 수 있다. 예를 들어 로그인 화면이 넘어가는데 시간이 많이 걸린다고 했을때, 이 문제가 근본적으로 솔루션의 특성이라면 애플리케이션이나 솔루션 수정으로는 해결이 불가능하다. 이런 경우에는 모래 시계 아이콘이나 progress bar등을 넣어서 UX 관점에서 사용자로 하여금 체감되는 응답 시간에 대해서 느리지 않고 몬가 진행이 되고 있다고 보여주는 형태로 접근을 해서 문제를 해결할 수 도 있다.
간단한 예를 하나 들어보자. Drupal 이라는 웹 CMS 기반의 웹사이트가 있다고 하자. 성능 테스트를 수행하였는데, CPU 점유율이 지나치게 높게 나오고 응답 시간이 느리게 나왔다. 이것이 문제의 정의이다.
성능의 문제점을 찾아내기 위해서, 성능 테스트 시나리오를 검토하였다 성능 테스트 시나리오는 1) 로그인 페이지 로딩, 2) id,password를 post로 전송 3) 초기 화면으로 redirect됨 4) 로그 아웃 4가지 과정을 거치고 있었다. 1,2,3,4 과정의 응답시간을 각각 체크해서 보니, 2) 과정에서 성능의 대부분을 차지 하고 있음을 찾아 내었다. 전체적으로 성능이 안나오는 것을 인지한 후, 문제를 여러 구간으로 나누어서 접근 하는 것이 Break down이다.
2) 과정을 분석하기 위해서 성능 테스트를 다시 진행한다. 다른 시나리오가 영향을 주는 것을 방지하기 위해서, 1,3,4 시나리오를 제외 하고, 2 시나리오만 가지고 성능 테스트를 진행한다. 이렇게 문제점을 다른 변수로 부터 분리하여 고립 시키는 것을 isolation이라고 한다.
다음으로 Xhprof 라는 프로파일링 툴을 사용하여 로직중 어느 부분이 가장 성능 문제가 발생하는 지를 profiling 하였다. 대 부분의 성능 저하가 SQL 문장 수행에서 발생함을 찾아내었다. 이렇게 하나의 포인트를 깊게 들어 가면서 범위를 좁혀가는 것을 narrow down이라고 한다.
SQL 수행이 문제가 있음을 정의하고(문제의 정의), 어떤 SQL 문장이 수행되는지(Break down) 각각을 정의한후, 가장 수행 시간이 긴 SQL 문장을 찾아서 원인을 분석하였더니(narrow down) index 가 걸려 있지 않음을 찾아내었다.
해당 테이블에 index를 적용하고, 성능 테스트를 다시 수행하여 성능 목표치를 달성하였음을 해결하였다.
가상의 시나리오지만 성능 튜닝의 접근 방법은 대부분 유사 하다. 관건은 문제를 어떻게 잘 정의하고, 문제가 어떤 요소로 구성이 되어 있으며 각각이 어떤 구조로 동작을 하고 있는지 잘 파고 들어갈 수 있는 문제에 대한 접근 능력과, 점점 솔루션의 아랫부분(low level)로 들어갈 수 있는 전문성이 필요하다.
반복
튜닝이 끝났으면 다시 “테스트 및 모니터링” 항목으로 돌아가서 성능 목표에 도달할때까지 위의 작업을 계속해서 반복해서 수행한다.
Performance Engineering을 위해 필요한 것들
그러면 성능 엔지니어링을 하기 위해서 필요한 것들은 무엇이 있을까? 먼저 도구 적인 측면부터 살펴보자.
① 부하 테스트기 : 가장 기초적으로 필요한 것은 부하 발생 도구 이다. HP Load Runner와 같은 상용 도구에서 부터, nGrinder와 같은 오픈 소스 기반의 대규모 부하 발생 도구를 사용할 수 도 있고, SOAP UI같은 micro benchmark 테스트 툴을 이용해서 소규모 (50 사용자 정도)를 발생 시키거나 필요에 따라서는 간단하게 Python등의 스크립트 언어로 부하를 발생시킬 수 도 있다.
② 모니터링 도구 : 다음으로는 모니터링 도구이다. 어느 구간이 문제가 있는지 현상이 어떤지를 파악하려면 여러 형태의 모니터링 도구들이 필요하다.
③ 프로파일링 도구 : 그리고, 문제되는 부분을 발견했을때, 그 문제에 대한 근본적인 원인을 찾기 위해서 프로파일링을 할 수 있는 도구들이 필요하다.
우리가 일반적으로 이야기 하는 프로파일링 도구들은 IDE와 같은 개발툴에서 debug 용도로 사용은 가능하지만, 대부분 대규모 부하 환경에서는 사용이 불가능한 경우가 많다.그래서 그런 경우에는 해당 시스템의 상태에 대한 스냅샷을 추출 할 수 있는 dump 도구들을 많이 사용하는데, unix process의 경우에는 ptrace를 통해서 system call을 모니터링 하거나, pmap을 이용하여 메모리 snapshot등을 추출할 수 도 있고, 자바의 경우에는 thread dump를 추출해서 병목 당시 애플리케이션이 무슨 동작을 하고 있었는지를 찾아낼 수 있다.
다음이 이 글에서 정말 언급하고 싶은 내용인데, 앞에서 도구를 언급했다면 다음은 엔지니어로써의 역량이나 지식적인 부분이다.
④ 역량 : 당연한 것이겠지만, 기술적인 역량은 필수적이다. netstat를 통해서 TCP 소켓이 FIN_WAIT가 발생하였는데, 이 FIN_WAIT가 의미하는 것이 무엇인지 모르면 아무리 모니터링을 잘해도 소용이 없다. 기본적인 엔지니어로써의 컴퓨터와 프로그래밍, OS등에 대한 넓은 이해는 필수적이다.
⑤ 하드웨어 인프라, 미들웨어 , 애플리케이션에 대한 지식 : 다음은 사용하는 특정 솔루션에 대한 전문적인 지식이다. 톰캣의 내부 구조가 어떻게 되어 있으며, JVM의 동작원리가 어떻게 되는지와 같은 특정 지식인데, 사실 이러한 지식은 오랜 경험이나 습득할 시간이 없으면 가지기가 어렵다. 이런 경우는 해당 솔루션 제품 엔지니어를 통해서 지원을 받는 방법도 고려해볼만 하다.
⑥ 그리고 경험 : 성능 엔지니어링에 대한 경험인데, 대략 시스템의 상태마 봐도 어느 부분이 의심이 되는지 경험이 많은 엔지니어는 쉽게 접근을 한다. 성능 문제는 넓어보이기는 하지만, 결국 발생되는 패턴이 거의 일정하다. 그리고 특정 솔루션에 대한 지식이 없다하더라도, 문제에 대한 접근 하는 방법이나 모니터링 방법, 툴등은 사용법이 다르다 하더라도 그 의미하는 방법은 거의 비슷하기 때문에, 다른 기술로 구현되어 있는 시스템이라고 하더라도, 경험이 있는 엔지니어는 문제를 접근해서 풀어나가는 방식이 매우 익숙하다.
⑦ 마지막으로 인내심 : 그리고 마지막으로 강조하고 싶은 점이 인내심인데, 사실 성능 엔지니어링은 상당히 지루한 작업이다. 반복적인 테스트와 모니터링 및 분석을 거쳐야 하고, 해당 솔루션에 대한 전문적인 지식이 없을 경우에는 보통 제품 문제라고 치부하고 하드웨어 업그레이드로 가는 경우가 많은데, 어짜피 솔루션이라고 해도 소스코드로 만들어진 프로그램이다. 디컴파일을 하건, 덤프를 추출하건, 꾸준히 보고, 오픈 소스의 경우 소스코드를 참고해서 로직을 따라가다 보변, 풀어낼 수 있는 문제가 대부분이다. 결국은 시간과 인내심의 싸움인데, 꾸준하게 인내심을 가지고 문제를 접근하고 풀어나가는 것을 반복하면 문제는 풀린다.