본문 바로가기

엔지니어링(TA, AA, SA)/성능과 튜닝

[엔지니어링] 성능 엔지니어링에 대한 접근 방법

출처: http://bcho.tistory.com/787 (Performance Tuning)

 

성능 개선, Performance Tuning, 용량 산정과 같은 튜닝 관련 용어들은 모든 개발자나 엔지니어에게 흥미가는 주제입니다. 그만큼 소프트웨어에서 고성을 내는 시스템은 만들기도 힘들뿐더러, 고성능 시스템이란 즉 잘 설계되고 구현된 소프트웨어를 뜻하는 것이니 관심을 가지는 것이 당연하지 않을까 싶습니다.

 

엔터프라이즈 시스템에 장애 해결, 장애 회피 설계, 성능 개선, 고성능 시스템 설계 및 구현에 관련 중 장애 해결과 성능 개선 작업은 하고 나면 뿌듯하여도, 특정한 기술이 필요하다기 보다는 문제를 정의하고 접근하는 능력과 끝까지 목표를 달성할 때까지 지루한 작업을 반복적으로 할 수 있는 인내심을 필요로 하는 작업입니다.

 

Performance Engineering의 전반적인 접근 방법과 용량 산정방법 그리고 자바 기반 서버 애플리케이션에 대한 성능 튜닝 및 병목 발견 방법에 대해서 설명하겠습니다.

 

문제의 정의 → Break Down → Isolation → Narrow down → Bottleneck 발견 → 해결 (BINB)

 

더보기

 

Performance Engineering의 정의와 범위

 

Performance Engineering은 시스템의 목표 성능 (응답 시간과 동시 접속자수)을 정의하고, 이를 달성하기 위해서, 시스템의 구조를 반복적으로 개선하는 작업을 이야기 합니다.

 

좁게 생각하며, 코드 상의 병목을 잡고, 시스템 설정(Configuration)을 바꿔서 성능을 올리는 튜닝으로 생각할 수 있지만, 성능 목표의 정의에서 부터, 최적의 성능을 내기위한 디자인 및 구현과 같은 개발 초기의 설계 부분과 개발후의 운영단계 모니터링까지의 전과정을 포함합니다.

 

 

Performance Engineering은 언제 해야 하는가?

 

Performance Engineering은 전체 소프트웨어 개발 과정에 걸쳐서 아래와 같이 4단계에 걸쳐서 일어납니다. 개발 모델은 전형적인 Water fall model입니다. 개발 프로세스 챕터에서도 설명하였지만, 스크럼같은 애자일 방법론을 사용하더라도 큰 범위에서 개발 사이클은 Waterfall 모델과 크게 다르지 않게됩니다. (각 단계별을 SPRINT 단위로 수행하는 것)

 

 

 

 

1. 분석 단계

초기 요구 사항 분석 및 시스템 기획 단계에서는 성능에 대한 목표를 정의해야합니다.

목표 응답시간은 어떻게 되는지, 시스템을 사용할 총 사용자수와 동시에 시스템을 사용하는 동시 접속자 수가 어떻게 되는지와 같은 성능 목표를 정의합니다. 또한 고려해야하는 사항중의 하나는 성능 모델입니다. 시스템에 부하가 어떤 패턴으로 들어오는지 정의할 필요가 있습니다.

 

예를 들어 일반적인 웹컨텐츠 사이트의 경우 사용자가 들어와서 페이지 컨텐츠를 1~3분 내에 읽고 다른 페이지로 이동하다가, 20 여분 후에는 로그아웃하거나 다른 사이트로 이동합니다. 즉 한 사용자의 체류 시간은 20분정도 되며, 총 평균 20 페이지를 보는 트랜잭션을 발생시키고 나간다고 할 수 있습니다. 한글로 만든 사이트이고, 육아나 주부를 대상으로 한 사이트라고 가정하면, 시스템의 부하는 한국 시간으로 아이들이 학교나 유치원을 간 후인 10시~11시와, 저녁시간대인 10시~12시 사이에 몰린다고 가정할 수 있다.

 

다른 예로 게임 시스템을 예로 들어보겠습니다. 주로 초등학생을 타겟으로 한 게임이라면, 방과후 시간인 3시~5시 대에 부하가 가장 몰릴 것이며, 게임의 종류에 따라 다르겠지만, 스타크래프트와 같은 게임의 경우 한번 플레이에 40분 정도가 소요되고, 한 사용자가 하루에 두번정도 게임을 한다 가정 아래, 사용자당 체류 시간은 2시간, 게임 횟수는 2회/일, 그리고 주요 부하는 3시~5시 오후대라는 성능 모델을 만들 수 있습니다.

 

초기 성능 정의는 서비스의 종류(웹, 게임, 기업 시스템, 쇼핑, 뱅킹 등)나, 서비스를 사용하는 사용자층, 그리고 서비스를 사용하는 지역, 즉 전세계를 서비스하는 시스템이라면 시스템의 부하는 365일, 24시간 거의 다 걸린다고 봐야합니다. 그러나 한국만을 대상으로 서비스하는 한국어로 된 사이트의 경우, 새벽 기간에는 일반적으로 로드가 없는 것과 같은 한국의 시간대에 영향을 받을 뿐만 아니라 명절, 휴일, 휴가와 같은 한국이라는 국가 특성에 따라 시스템의 부하가 영향을 받습니다.

 

2. 디자인 단계

다음으로는 디자인 단계에서는 목표 성능과 용량을 달성할 수 있는 규모의 시스템으로 설계를 진행합니다. 성능 관점에서 시스템 디자인은 항상 Peak Time(최대 성능)에 맞춰서 디자인이 됩니다. 최대 성능을 기반으로 전체 시스템이 받아낼 수 있는 용량과 응답 시간을 고려해야 합니다.

 

특히 성능과 용량을 애플리케이션 디자인 뿐만 아니라 Technology selection에도 많은 영향을 받습니다. 어떤 하드웨어를 사용할 것인지, 어떤 미들웨어나 프레임웍을 사용할 것인지에 따라 용량과 성능의 차이가 많이 발생하기 때문에, 디자인 단계에서부터 성능과 용량을 감안해서 시스템을 설계해야합니다.

 

하드웨어 관점에서는 예전에는 성능 모델을 산정한 후에, Peak Tiem 기준(최대 성능 요구)으로 시스템을 설계하고, 하드웨어를 구매하였으나, 근래에는 클라우드를 이용하여 필요시에만 하드웨어를 탄력적으로 사용하는 Auto Scale Out 모델을 많이 사용합니다.

 

기업 내부의 업무처럼(예를 들어 이메일), 부하가 일정하고 예측이 가능한 경우에는 Fixed된 사이즈의 하드웨어를 사용하도록 설계하고, 출시 이벤트 행사 사이트와 같이 부하가 갑자기 몰리는 시스템의 경우 클라우드를 고려해보는 것도 권장할 만합니다.

 

또한 빠른 응답 시간이 필요한 경우 SSD 디스크를 사용하거나, RAID 구성도 5보다는 1+0 등을 고려하는 등, 성능 모델에 따라서 적절한 하드웨어 선정과 구성 설계가 필요합니다.

 

미들웨어나 프레임워크 관점에서도 정의된 성능 모델에 따라 적절한 제품군과 설계 구조를 채택해야 합니다. 100,000 사용자 정도의 시스템 규모에서는 RDBMS를 사용해도 성능이나 용량상에 문제가 없습니다. 그러나 50,000,000 사용자 정도를 지원해야 하는 시스템의 경우 그냥 RDBMS를 사용할 수 없습니다. 샤딩이나 NoSQL과 같은 다른 차원의 접근이 필요합니다.

 

또한 빠른 응답 시간을 요구하는 경우 Redis나 Memcahed와 같은 Cache 솔루션을 적극적으로 활용하거나, 미들웨어 부분에서는 Tomcat과 같은 일반적인 Web Application Server보다는 Netty나 Vertex와 같은 고성능 미들웨어를 고려해볼 수 있습니다.

 

이러한 성능이나 용량에 관련된 제품 선정이나 설계는 돌려 보지 않으면 사실 확신하기가 어렵습니다. 그래서 가능하면, Technology selection 후에, 간단한 프로토타입을 구현한후에 시나리오가 단순한 대규모의 성능 및 용량 테스를 해보는 PoC(Proof Of Concept)과 같은 작업을 이 단계에서 수행하는 것을 권장합니다.

 

3. 개발 단계

개발 단계는 개발프로세스 챕터에서 설명하였듯이, risk가 높은 부분과 아키텍쳐에 관련된 부분, 난이도가 높은 부분, 핵심 기능 등을 개발 초기의 스프린트에서 개발합니다.

 

초기 스프린트가 끝나고 릴리즈가 되서 성능 테스트가 가능한 QA나 스테이징 환경으로 시스템이 이전되면, Performance Engineering 역량을 이 단계에 집중하여, 시스템의 아키텍쳐와 모듈들이 성능 목표를 달성할 수 있는지 지속적으로 테스트하고 튜닝을 수행합니다.

 

초기 단계에 성능 목표의 달성 가능 여부가 판단되어야, 아키텍쳐 변경이 가능하고, 주요 성능 이슈들을 초반에 발견해야, 발견된 성능 문제들에 대해서는 같은 문제가 발생하지 않도록 디자인 가이드나 코딩 가이드를 개발자들에게 배포하여 성능에 대한 위험도를 줄일 수 있습니다.

 

4. 최종 테스트 단계

앞의 단계에서 성능과 용량을 고려해서 설계가 되었고, 개발 초기 단계에서 성능과 용량 부분 검증을 제대로 하였다면, 최종 테스트 단계에서는 개발된 최종 시스템에 대한 성능과 용량 부분의 측정과 미세 튜닝 (애플리케이션의 병목을 찾아서 부분적으로 수정하거나, 하드웨어나 미들웨어의 Configuration하는 수준)을 하는 정도로 마무리가 되어야 합니다.

 

이 과정에서는 실수로 잘못한 설정(misconfiguration)이나 잘못된 코딩으로 된 부분에 대해서 검증이 이루어지는데, 이 경우에는 보통 2배에서 크게는 10배까지의 성능향상이 이루어집니다. 이런 경우는 대부분 실수에 의한 것이고 성능이 터무니 없이 낮게 나오기 때문에 찾기가 쉽습니다.

 

예를 들어 로그 파일을 NFS와 같은 리모트 디스크에 쓴다던지, Intel 계열의 CPU에서 하이퍼쓰레딩을 ON을 안했다던지와 같이 실수에 의한 경우가 많습니다. 이런 오류성의 문제들이 해결되면 실제 미세 튜닝에 들어가게 되는데, JBM 튜닝이나 톰캣의 설정 튜닝, SQL 튜닝들이 이루어지는데, 이 미세 튜닝을 통해서는 비약적인 성능향상을 이루어지지 않는다. 보통 20% 내외 정도 성능이 올라간다고 보면 됩니다.

 

5. 운영 단계

마지막으로 시스템이 운영 단계로 넘어가게 되면, 테스트시에 발견되지 않은 성능적인 문제가 있을 수 있기 때문에, 모니터링 도구를 사용하여 지속적으로 성능을 모니터링하고, 성능상에 문제가 있는 부분을 지속적으로 수정해야 합니다. 웹서버의 access로그에서 응답 시간을 모니터링하거나, 제니퍼와 같은 전문적인 APM(Application Performance Monitoring)툴이나, Ganglia와 같은 시스템 모니터링 도구를 사용하면, 시스템의 성능 상태를 잘 알 수 있습니다.

 

더불어 용량 부분에 대해서도 운영단에서는 고민을 해야하는데, 일반적으로 PEAK Time의 시스템 용량 (CPU) 80% 정도에 다다르면, 시스템 용량 증설을 고려해야 합니다.

 

그리고 업무의 특성에 맞게 미리미리 용량을 준비해놓는게 좋습니다. 예를 들어 대학의 수강 신청 시스템의 경우, 학기 시작하는 날에 부하가 폭주하기 때문에, 클라우드 기반일 경우 수강신청 전에 시스템 수를 미리 늘려놓는다던지, 클라우드가 아닌 경우, 수강 신청 기간을 앞뒤로 서버를 임대해서 용량을 늘려놓는 등의 대책을 미리 세워놓을 수 있습니다.

 

마지막으로, 운영단계에서 Performance Engineering 관점으로 챙겨야 하는 부분은 운영 로그의 수집입니다. 성능 및 용량 목표 설정은 매우 중요한 과정입니다. 특히 용량 목표의 경우에는 기존의 업무 시스템의 사용 패턴을 분석하는 것이 가장 효율적이기 때문에 운영 시스템의 로그를 수집하고 분석하여 운영 중인 업무 시스템의 성능 모델을 분석 및 보유 해놓는 것이 좋습니다.

 

 

시스템 용량 산정 (Capacity Planning)

 

성능에 관련된 용어와 함께 시스템의 목표 용량 산정 방법에 대해서 이야기해보겠습니다. 이용어는 이 글에서 정의하는 의미의 용어이며, 다른 성능 이론에서 언급되는 용어와 다를 수 있습니다.

 

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이라고 합니다.

 

Concurrent User(동시 사용자): 시스템을 현재 사용하고 있는 사용자를 정의합니다. 웹사이트를 사용하기 위해서, 현재 브라우저를 열어놓고 웹사이트를 보고 있는 것과 같이 현재 시스템을 사용하고 있는 사용자 수를 의미합니다.

 

위의 그림을 보면 5명의 사용자가 A~E가 있다고 가정했을때, 단위 시간 10분동안에 Transaction Time과 Think Time중에 있는 사용자는 A,B,C 총 3명으로 해당 시간 10분간의 Concurrent User는 3명이 됩니다.

 

Activce 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로 총 Actice User는 3명이 됩니다.

 

Transaction(트랜잭션): Transaction이란, 사용자로 부터의 요청을 다루는 단위를 정의합니다. 이 정의가 상당히 중요한데, 성능 모델링이나 성능 테스트시 이 Transaction의 정의에 따라서 시스템의 성능이 매우 다르게 정의됩니다.

예를 들어서 사용자가 웹페이지를 클릭했을때, 그 페이지에 대한 응답을 받는 것까지를 하나의 트랜잭션이라고 정의해보겠습니다. 이 때, 웹페이지에는 서버에서 생성된 HTML 이외에, 여기서 참고하는 리소스 즉, 이미지나 동영상, 자바 스크립트 들이 있을 수 있습니다. 이 경우 트랜잭션에 대한 응답 시간을 측정할 때, HTML 생성 이외에 이러한 리소스들을 로딩하는 것까지 하나의 트랜잭션으로 정의해야 하느냐를 고려해야 합니다. 리소스에 로딩을 트랜잭션의 범위로 넣게 되면 저체 시스템의 응답 시간은 떨어지게 됩니다. (리소스를 로딩할 때까지 기다려야 하니)

 

이러한 트랜잭션의 정의는 무엇을 판단 기준으로 할것인가에 따라 결정이 되는데, 예를 들어 리소스를 톰캣과 같은 WAS에서 처리하지 않고 앞단의 CDN이나 웹서버에서 처리할 경우 톰캣을 리소스에 대한 트랜잭션 요청을 받지 않기 때문에, 전체 시스템에서 비즈니스 로직에 대한 처리 성능을 측정하고자 할때는 리소스에 대한 로딩 시간을 계산하지 않고 트랜잭션을 정의합니다. 또한 리소스에 대한 로딩은 비즈니스 로직에 대한 처리에 비해서 부하가 상대적으로 매우 적고, 일반적으로 브라우져에 캐쉬되기 대문에 보통 서버의 성능 측정시 이러한 리소스 로딩에 대한 부하는 트랜잭션의 단위로 처리하지 않는 경우가 많습니다.

 

TPS(Transaction Per Second): 초당 처리할 수 있는 트랜잭션의 양을 정의합니다. 주로 서버의 성능평가 기준이 됩니다. Active 사용자가 순간 Transaction을 처리한다고 하면, 이를 목표 응답시간(Response Time)으로 나눈 값이 목표 TPS가 됩니다. 예를 들어, Active User가 50명이고, 개당 Response Time이 2초라고 하면, 이 시스템의 TPS는 25 TPS가 됩니다.

 

HPS(Hit Per Second): 시스템이 처리할 수 있는 모든 웹 request의 초당 처리량입니다. TPS가 비즈니스 트랜잭션에 대한 처리 시간만을 정의한다면, HPS는 리소스(이미지, 자바스크립트)에 대한 request 처리량을 포함하기 때문에, TPS에 비해서 10~20배 정도 높게 나옵니다.

 

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)라고 하겠습니다. 이것이 바로 성능 목표가 됩니다.

 

다음으로 성능 모델을 정의해야 하는데, 해당 시스템의 주요 사용자 시나리오가 여러개 있을때, 각 시나리오별의 사용 비중을 정의해야 합니다. 예를 들어 사진을 저장하는 클라우드 서비스 시나리오가 있다고 하면, 이 서비스의 주요 사용자 시나리오는 (1. 로그인 -> 2. 사진 리스트 -> 3. 사진 업로드 -> 4. 사진 보기 -> 5. 사진 다운로드 -> 6. 로그 아웃) 등이 됩니다. 이 중에서 한 사용자가 실행하는 비율을 따져야 합니다. 즉 사용자가 로그인 한후, 리스트 보기를 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) 관리하는 것을 권장합니다.

 

예전에는 부하 테스트가 사내에서 사내에 있는 시스템을 대상으로 했었기 때문에 큰 문제가 없었습니다. 그러나 근래 들어서 클라우드 컴퓨팅을 사용하는 사례가 늘어남에 따라, 서비스 시스템이 회사 밖에 즉, 클라우드에 있는 경우가 많아졌습니다.

 

상용 부하 테스트툴의 경우에는 부하 발생기의 위치와 툴 사용자에 대해서 제약을 두는 경우가 있는데, 툴을 구매했다 하더라도, 부하 테스터의 controller(부하 발생기 제외)는 반드시 사내에 있어야 하며, 사용자 역시 그 회사의 내부 직원으로만 한정하는 경우가 있습니다. 예를 들어, 내가 부하 테스트 도구를 서울에 있는 회사에서 구매하여, 이툴을 Amazon 클라우드 미국에 설치하고 부하 테스트를 미국지사 직원을 통해서 진행하는 것이 불가능 합니다.

 

이 경우 부하 테스트 툴의 Controller는 한국 서울 사무소에 설치하고, 부하 생성기만 Amazon에 설치한 후 한국 서울 사무소 직원을 통해서만 사용해야 합니다.

 

간혹 부하 테스트 툴의 판매 영업 사원이 이러한 사실을 제대로 통보하지 않아서, 툴을 잘 쓰다가 갑자기 영업 사원이 변경되거나, 부하 테스트 툴의 이전을 요청하였을때, 갑자기 벤더로부터 추가 라이센스 구매 요청을 받을 수 있으니, 구매 전에 반드시 구매 조건에 사용 시나리오와 Controller의 위치, 사용 주체 및 테스트 대상 시스템들에 대해서 명시적으로 기재하고 구매계약을 추진하는 것이 좋습니다.

 

 

테스트 및 모니터링

 

부하 테스트 준비가 되었으면, 부하 테스트를 진행하고 진행중에 주요 성능 Factor에 대해서 지속적으로 모니터링 및 기록을 하여야 합니다. 주로 모니터링해야하는 Factor들은 다음과 같습니다.

 

1. 애플리케이션 관점

가장 기본적으로 애플리케이션 즉 시스템의 성능을 측정해야 합니다. 주요 모니터링 Factor는 다음과 같습니다.

 

Response Time: Request별 응답 시간 / TPS(Throughput per second): 초당 요청(Request) 처리량

 

이 Factor들이 궁극적으로 성능에 대한 최종 목표 값이 되기 때문에, 가장 중요한 성능 Factor가 되며, 부하 생성 도구를 통해서 손쉽게 측정할 수 있습니다.

 

2. 미들웨어 관점

미들웨어는 애플리케이션이 동작하기 위한 기본 솔루션입니다. Apache와 같은 웹서버나 Tomcat과 같은 Web Application Server, 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를 모니터링하면 특히 느리게 수행되는 쿼리들을 잡아서 튜닝할 수 있습니다.

 

Slow Query를 찾았으면, EXPLAIN 명령어를 이용하여 query의 수행 내용을 분석한후 index등의 튜닝을 수행할 수 있습니다.

 

3. 인프라 관점 : CPU, Memory, Network IO, Disk IO

다음으로 하드웨어 인프라에 대한 부분을 지속적으로 모니터링해줘야 하는데, 이는 하드웨어가 해당 성능을 내기 위해서 용량이 충분한지 그리고 하드웨어 구간에서 병목이 생기지는 않는지, 생긴다면 어느 구간에서 생기는지를 모니터링하여, 해당 병목 구간에 대한 문제를 해결하기 위함입니다.

 

인프라에 대한 모니터링은 Ganglia나 Cacti와 같은 전문화된 인프라 모니터링 도구를 사용하거나 top이나 glance, sar와 같은 기본적인 Unix/Linu 커맨드를 사용해서도 모니터링이 가능합니다. (부하 테스트 중에 top 등을 띄어놓고 모니터링을 하는 것이 좋습니다. Load Runner와 같은 상용 도구의 경우에는 부하 테스트를 자체에서 테스트 대상 시스템에 대한 하드웨어 사용률을 함께 모니터링 할 수 있게 제공해줍니다.)

 

CPU: 일반적으로 CPU는 대부분 잘 모니터링합니다. 목표 성능을 달성할 시에는 보통 70~80% 정도의 CPU를 사용하는 것이 좋고, 20~30%의 여유는 항상 가지고 가는 것이 좋습니다. 이유는 70~80% 정도의 CPU가 사용된 후에, 하드웨어를 물리적으로 늘리는 시간에 대한 여유 시간을 가지기 위함입니다. 하드웨어는 특성상 주문을 한다고해도, 바로 그 시간에 증설을 할 수 있는 것이 아니고, CPU가 100%가 되는 순간에는 이미 애플리케이션이 CPU 부족으로 제대로 작동을 하지 못하는 경우가 많기 때문에, 항상 여유를 남겨 놓고 성능 목표를 정의하는 것이 좋습니다. 그래서 성능 목표를 잡을 때는 'CPU 70%시, 500TPS, 응답시간 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 성능이 매우 급격하게 떨어집니다. 그래서 시스템에서 swapping이 발생하면 시스템의 성능이 장애 수준으로 매우 급격하게 떨어집니다.

 

부하 테스트 중이나, 운영중에 swapping이 발생하게 되면 전체 메모리 사용량을 줄이도록 튜닝하거나 반대로 물리 메모리를 늘리는 증설 과정이 필요합니다.

 

Disk IO: Disk IO는 파일 시스템에 파일을 저장하는 시나리오나, Log를 저장하는 모듈 그리고 데이터 베이스와 같이 뒷단에 파일 시스템을 필요로 하는 모듈에서 많이 발생합니다. Ganglia와 같은 도구를 사용하면, IOPS(Input Out per Second - 초당 read/write등의 IO 발생 횟수)를 통해서 모니터링할 수 있고, 또는 iostat나 sar와 같은 명령어를 사용하면 iowait를 통해서 디스크 IO의 pending이 발생할 경우 디스크 병목이 있는지 없는지를 확인할 수 있습니다. 또는 Process당 Disk IO는 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 Trnaslator), 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 Robin등을 사용하는 방법을 고려하는 것이 좋습니다.

 

 

개선(Tuning)

 

병목을 찾았으면, 해당 병목 문제를 해결 및 반영해야 합니다. 튜닝은 병목 구간이 발생하는 부분에 대한 전문적인 지식을 필요하지만, 기본적인 접근 방법은 거의 같다고 보면 됩니다.

 

1. 문제의 정의: 성능 개선의 가장 기본은 문제 자체를 제대로 정의하는 것입니다. "그냥 느려요"가 아니라 "성능 목표가 350TPS에 1초내의 응답 시간인데, 현재 60TPS에 5초의 응답 시간에 WAS의 CPU 점유율이 100%입니다" 와 같이 명확해야 하며, 문제점이 재현 가능해야 합니다.

특히 재현 가능성은 매우 중요한 점인데, 테스트 환경이 잘못되었거나, 외부적 요인 예를 들어 부하 테스트 당시 네트워크 회선이 다른 테스트로 인하여 대역폭이 충분히 나오지 않았거나 했을 경우 결과가 그대마다 다르게 나올 수 있습니다.

즉 문제 자체를 명확하게 정의할 필요가 있습니다.

 

2. Break down: 다음으로는 문제가 발생하는 부분이 어떤 부분인지를 판단해야 합니다. 시스템은 앞단의 로드밸런서나 미들웨어, 데이터 베이스와 같은 여러 구간에서 발생합니다. 그렇기 때문에 성능 저하의 원인이 정확하게 어느 부분인지를 인지하려면, 먼저 성능 시나리오가 어떤 어떤 컴포넌트를 거치는지를 명확하게 할 필요가 있습니다. 이 과정을 break down이라고 합니다. 이 과정을 통해서 전체 성능 구간중, 어느 구간이 문제를 발생 하는지를 정의합니다.

 

3. Isolate: 다음으로는 다른 요인들을 막기 위해서, 문제가 되는 구간을 다른 요인으로부터 분리(고립)시킵니다. 물론 완벽한 분리는 어렵습니다. 애플리케이션이 동작하기 위해서는 데이터 베이스가 필수적으로 필요합니다. 이 경우에는 데이터베이스를 분리할 수 없습니다. 그러나 예를 들어 시나리오 자체가 로그인 시나리오이고 Single Sign On을 통해서 로그인 하는 시나리오라서 SSO 시스템과 연동이 되어 있다면, SSO 연동을 빼고 다른 mock up을 넣어서 SSO와의 연결성을 끊고 테스트를 하는 것이 좋습니다. 이렇게 문제에 대한 다른 요인과의 연관성을 최대한 제거하는 작업이 isolation입니다.

 

4. Narrow down: 문제를 isolation을 시켰으면, 근본적인 문제를 찾기 위해서 문제의 원인을 추적합니다. Profiling을 하거나, 코드에 디버그 정보를 걸어서 문제의 원인을 분석하는 과정을 narrow down이라고 합니다. 특히나 이 narrow down 과정은 분석을 위한 여러가지 기법이나 도구들을 사용해야 하고, 현상에 대한 이해를 하기 위해서는 해당 솔루션이나 기술 분야에 대한 전문성은 필수적으로 필요합니다.

 

5. Bottleneck 발견: Narrow down을 해서 문제의 원인을 계속 파헤쳐 나가면 병목의 원인이 되는 근본적인 문제가 판별이 됩니다.

 

6. 해결: 일단 병목의 원인을 찾으면 해결을 해야하는데, 찾았다고 모두 해결이 되는 것은 아닙니다. 데이터 베이스 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을 위해 필요한 것들.

 

1) 부하 테스트기: 가장 기초적으로 필요한 것은 부하 발생 도구입니다. HP Load Runner와 같은 상용 도구에서부터, nGrinder와 같은 오픈 소스 기반의 대규모 부하 발생 도구를 사용할 수도 있고, SOAP UI같은 micro benchmark 테스트 툴을 이용해서 소규모 (50 사용자 정도)를 발생시키거나 필요에 따라서는 간단하게 Python등의 스크립트 언어로 부하를 발생시킬 수도 있습니다.

 

2) 모니터링 도구: 다음으로는 모니터링 도구입니다. 어느 구간이 문제가 있는지 현상이 어떤지를 파악하려면 여러 형태의 모니터링 도구들이 필요합니다.

 

3) 프로파일링 도구: 그리고, 문제되는 부분을 발견했을때, 그 문제에 대한 근본적인 원인을 찾기 위해서 프로파일링을 할 수 있는 도구 들이 필요합니다. 우리가 일반적으로 이야기하는 프로파일링 도구들은 IDE와 같은 개발툴에서 debug 용도로 사용은 가능하지만, 대부분 대규모 부하 환경에서는 사용이 불가능한 경우가 많습니다. 그래서 그런 경우에는 해당 시스템의 상태에 대한 스냅샷을 추출할 수 있는 dump 도구들을 많이 사용하는데, unix process의 경우에는 ptrace를 통해서 systam call을 모니터링하거나, pmap을 이용하여 메모리 snapshot등을 추출할 수도 있고, 자바의 경우에는 thread dump를 추출해서 병목 당시 애플리케이션이 무슨 동작을 하고 있었는지를 찾아낼 수 있습니다.

 

앞에서 도구를 언급했다면 다음은 엔지니어로써의 역량이나 지식적인 부분입니다.

 

4) 역량: 당연한 것이겠지만, 기술적인 역량은 필수적입니다. netstat을 통해서 TCP 소켓이 FIN_WAIT가 발생하였는데, 이 FIN_WAIT가 의미하는 것이 무엇인지 모르면 아무리 모니터링을 잘해도 소용이 없습니다. 기본적인 엔지니어로써의 컴퓨터와 프로그래밍, OS 등에 대한 넓은 이해는 필수적입니다.

 

5) 하드웨어 인프라, 미들웨어, 애플리케이션에 대한 지식: 다음은 사용하는 특정 솔루션에 대한 전문적인 지식입니다. 톰캣의 내부 구조가 어떻게 되었으며, JVM의 동작원리가 어떻게 되는지와 같은 특정 지식인데, 사실 이러한 지식은 오랜 경험이나 습득할 시간이 없으면 가지기가 어렵습니다. 이런 경우는 해당 솔루션 제품 엔지니어를 통해서 지원을 받는 방법도 고려해볼만 합니다.

 

6) 경험: 성능 엔지니어링에 대한 경험인데, 대략 시스템의 상태만 봐도 어느 부분이 의심이 되는지 경험이 많은 엔지니어는 쉽게 접근을 합니다. 성능 문제는 넓어보이기는 하지만, 결국 발생되는 패턴이 거의 일정합니다. 그리고 특정 솔루션에 대한 지식이 없다고 하더라도, 문제에 대한 접근 방식이나 모니터링 방법, 툴 등은 사용법이 다르다 하더라도 의미하는 방법은 거의 비슷하기 때문에, 다른 기술로 구현되어 있는 시스템이라고 하더라도, 경험이 있는 엔지니어는 문제를 접근해서 풀어나가는 방식이 매우 익숙합니다.

 

7) 인내심: 사실 성능 엔지니어링은 상당히 지루한 작업입니다. 반복적인 테스트와 모니터링 및 분석을 거쳐야 하고, 해당 솔루션에 대한 전문적인 지식이 없을 경우에는 보통 제품 문제라고 치부하고 하드웨어 업그레이드로 가는 경우가 많은데, 어차피 솔루션이라고 해도 소스코드로 만들어진 프로그램입니다. 디컴파일을 하건, 덤프를 추출하건, 꾸준히 보고, 오픈소소의 경우 소스코드를 참고해서 로직을 따라가다 보면, 풀어낼 수 있는 문제가 대부분입니다. 결국은 시간과 인내심의 싸움인데, 꾸준하게 인내심을 가지고 문제를 접근하고 풀어나가는 것을 반복하면 문제는 풀립니다.