- Published on
Optimizing Java Chap4
- Authors
- Name
- ywj9811
들어가며
이번에는 다양한 종류의 성능 테스트를 확인하며 유형별 베스트 프랙티스를 확인하도록 한다.
그리고 반대로 좋지 않는 영향을 끼치는 안티패턴을 확인하고 이러한 안티패턴에 대한 처방법을 확인한다.
성능 테스트 유형
지연 테스트 (Latency test)
지연 테스트 가장 일반적인 성능 테스트로 ‘고객이 트랜잭션(혹은 페이지 로딩)을 얼마나 오래 기다려야 할지’와 같은 내용을 측정한다.
하지만 이 테스트를 통해 다른 종류의 테스트로 밝히려는 정량적 대답의 필요성을 흐릿하게 인식하지 말자.
처리율 테스트
처리율 테스트는 그 다음으로 일반적인 성능 테스트이다. 어떻게 보면 처리율과 지연을 동등한 개념으로 볼 수 있기도 하다.
예를 들면 지연 테스트를 수행할 때에는 진행중인 동시 트랜잭션을 반드시 명시해야 하며 동시에 처리율 역시 지연을 모니터링 하면서 테스트를 한다.
지연 분포가 갑자기 변하는 시점, 이때가 최대 처리율이기 때문이다.
부하 테스트
부하 테스트는 처리율 테스트와는 조금 다른데, ‘시스템이 이정도 부하는 견딜 수 있을까?’ 에 대한 대답을 구하는 과정이다.
스트레스 테스트
스트레스 테스트는 시스템의 여력이 어느 정도 되는지 알아보는 수단이다.
보통 일정한 수준의 트랜잭션, 즉 특정 처리율을 시스템에 계속 걸어놓고 시스템의 성능이 저하되는 순간을 찾는 것이다.
내구 테스트
내구 테스트는 시간이 지나고 드러나는 문제점을 찾는 테스트로 예를 들면 메모리 누수, 캐시 오염, 메모리 단편화와 같이 한참 시간이 지나고 나서 나타나는 문제점을 찾는 테스트이다.
이는 평균 혹은 그 이상의 사용률로 시스템에 일정 부하를 계속 주며 모니터링 하다가 갑자기 리소스가 고갈나거나 시스템이 꺼지는 시점을 확인하는 것이다.
이 테스트는 빠른 응답을 요구하는 시스템에서 많이 진행한다.
용량 계획 테스트
용량 계획 테스트는 스트레스 테스트와 비슷하지만, 명확한 차이점이 있다.
스트레스 테스트는 얼마나 버틸 수 있는지 알아보는 것이었다면 용량 계획 테스트는 업그레이드한 시스템이 어느 정도 부하를 감당할 수 있을지 미리 내다보는 것이다.
저하 테스트
여기서 살펴볼 저하 테스트는 복원 테스트로 기본적으로 평상시 운영 환경과 동등한 수준의 부하를 시스템에 가하던 도중, 특정 컴포넌트 혹은 전체 서브 시스템이 갑자기 능력을 상실하는 시점에 벌어지는 일을 확인하는 것이다.
이때는 트랜잭션 지연 분포와 처리율을 눈여겨 확인하는 것이 좋다.
베스트 프랙티스 (Best practice)
성능 튜닝 시 주안점을 두어야 할 부분은 다음 세가지 기본 원칙에 따른다.
- 나의 관심사가 무엇인지 식별하고 그 측정 방법을 고민한다.
- 최적화하기에 용이한 부분이 아니라, 중요한 부분을 최적화 한다.
- 중요한 관심사를 먼저 다룬다.
하향식 성능
자바 성능을 다루는 엔지니어가 흔히 간과하는 사실은 자바 어플리케이션을 대규모 벤치마킹 하는 일이 작은 코드 섹션별로 정확하게 수치를 얻는 것보다 쉽다는 사실이다.
이를 위해서는 먼저 테스트팀이 테스트 환경을 구축한 다음, 무엇을 측정하고 무엇을 최적화 할지, 이 과정을 전체 개발 주기에서 어떻게 병행해야 할지 모든 팀원이 명확하게 이해하도록 하자.
테스트 환경 구축
테스트 환경은 가급적 모든 면에서 운영 환경과 똑같이 복제하도록 하자.
(서버 뿐만 아닌 웹 서버, DB, 로드 밸런서, 방화벽 등등)
물론 과거와 다르게 최근에는 클라우드를 이용하기 때문에 같은 개수의 하드웨어를 구축하는 간단한 방식과 다르게 복잡해 졌지만 사용하지 않을 때는 테스트 환경을 꺼둘 수 있다는 장점도 있기에 동일한 환경을 구축하는 것은 중요하다.
성능 요건 식별
성능을 평가하는 지표는 코드 관점에서만 생각해서도 안되고 시스템을 전체적으로 바라보며 고객과 경영진에게 중요한 측정값을 고려해야 한다.
이러한 핵심 지표를 성능 비기능 요건이라 한다.
따라서 이를 위해서는 이해관계자들이 모여 측정 대상과 목표를 논의하는 자리가 필요하다.
자바의 특정 이슈
JVM에는 성능 엔지니어가 잘 이해하고 주의 깊게 살펴야 할 복잡 미묘한 부분들이 있다.
JIT컴파일의 경우 매우 중요하기 때문에 어떤 메소드가 컴파일 중인지 로그를 남겨 살펴보고 핵심 코드 경로상의 중요 메소드가 잘 컴파일 되고 있는지 확인해야 한다.
SDLC 일부로 성능 테스트 수행하기
SDLC(소프트웨어 개발 생명주기) 의 일부로 테스트를 수행하는 것으로 개발팀, 인프라팀이 서로 조율하며 특정 시점에서 몇 버전 코드를 성능 테스트 환경에 배포할지 조정하여 소프트웨어 개발 생명주기에 포함시키는 것이다.
성능 안티패턴 개요
‘캐리 플리첼’이 블로그에 작성한 “왜 개발자는 잘못된 기술 선택을 밥 먹듯이 하나” 이라는 게시글에서 분류된 원인 다섯가지를 살펴보자.
지루함
개발자는 대부분 자기 역할에 지루함을 느끼는데 그렇기 때문에 뭔가 새롭고 도전적인 일을 찾아 궁리를 하기도 한다.
예를 들어 Collections.sort()
한 줄로 정렬할 수 있는 것을 직접 정렬 알고리즘을 구현하기도 하는 등 지루함을 느낀다.
이력서 부풀리기
개발자들이 자신의 몸값을 올리기 위해 프로젝트를 불필요한 방향으로 끌고가는 경우가 있다.
이는 기존의 개발자가 좋은 회사로 떠나고 나면 오랫동안 시스템에 지대한 영향을 끼칠 수 있다.
또래 압박
팀원들이 기술을 결정할 때 관심사를 분명히 밝히지 않고 서로 충분한 논의 없이 진행하다 보면 겪을 수 있는 문제이다.
혹은 경쟁심이 불타오르는 팀 분위기 속에서 개발이 광속으로 진행되는 듯 보이고자 제대로 사정도 따져보지 않고 중요한 결정을 해버리는 경우도 이에 속한다.
이해 부족
사용하는 툴의 기능도 온전히 알지 못하면서 무턱대고 새로운 툴을 이용하여 문제를 해결하려는 개발자가 있기도 하다.
새로운 멋진 기술이 나오고 소문이 퍼지게 되면 눈이 가기 때문이다.
이때 현재의 툴과 새로운 툴 사이의 균형을 잘 맞추도록 하자.
오해와 있지도 않은 문제
문제 자체를 제대로 이해도 못한 채 오로지 기술을 이용해서 문제를 해결하려는 문제이다.
성능 지표를 수집/분석해야만 비로소 문제의 본질을 정확히 이해할 수 있다.
이러한 안티패턴을 예방하기 위해서는 팀원 모두 참여해서 기술 이슈를 활발히 공유하는 분위기를 적극 장려하는 것이 좋다.
성능 안티패턴 카탈로그
이번에는 갖가지 안티패턴을 유형별로 알아보자.
화려함에 사로잡히다.
증상
최신의 멋진 기술을 튜닝 타깃으로 정하는 것이다.
레거시 코드를 파헤치느니 신기술의 작동 원리를 연구하는게 더 재밌기도 하고 최신 기술이다 보니 관리도 쉽다보니 최신의 멋진 기술을 찾아 헤매는 것이다.
현실
어플리케이션을 측정하고 튜닝하지 않고 그냥 어떻게든 되겠지 하고 생각하는데 다른 문제가 불거지기 일쑤다.
처방
측정하고 로그를 남기고 베스트 프랙티스도 참조하고 팀원들과 기술 이해 및 베스트 프랙티스 수준을 정하자.
단순함에 사로잡히다.
증상
전체적으로 어플리케이션을 프로파일링하여 객관적으로 확인하지 않고 간단한 부분만 파고든다.
현실
원개발자는 해당 부분을 어떻게 튜닝할지 안다. 즉, pair programming을 안한 결과이다.
처방
마찬가지로 측정을 하고 익숙하지 않은 경우 전문가에게 도움을 요청하자.
그리고 개발자가 전체 시스템 컴포넌트를 이해하도록 독려 하자.
성능 튜닝 도사
증상
경영진은 전문가를 수소문해 채용해온다.
그리고 엄청난 튜닝 스킬로 사내 모든 성능 이슈를 해결하라 한다.
현실
명망 높은 마법사나 초인이 하는 일은 드레스 코드에 도전하는 일이 고작이다.
처방
마찬가지로 측정을 하도록 하자.
그리고 성능 이슈를 해결하기에 실력이 부족한 개발자가 소외감을 느끼지 않도록 채용된 팀내 전문가가 다른 팀원들과 지식을 공유하며 팀워크를 유지할 수 있도록 하자.
민간 튜닝
증상
인터넷에서 성능이 향상된다고 했으니 우리 서버에 적용해서 해결해보자.
현실
어떤 시스템에서는 통했을지 모르지만 다른 시스템에서는 통하지 않을지도 모를 뿐더러 심지어는 오히려 안좋아질 수도 있다.
처방
시스템의 가장 중요한 부분에 직접 영향을 미치는 기술은 확실히 파악하고 충분히 검증된 것들만 사용하며 UAT(유저 인수 테스트) 단계에서 시험하도록 하자.
안되면 조상 탓
증상
이슈와는 상관도 없는 특정 컴포넌트를 문제 삼는다.
현실
충분한 분석도 안 해보고 성급한 결론을 내리는데, 평소 의심했던 범인을 유일한 용의자로 지목한다.
처방
성급한 결론을 내리지 말고 정상적인 분석을 하자.
숲을 못 보고 나무만 본다.
증상
전체적인 변경 영향도를 완전히 파악하지 않은 채 일단 그냥 변경을 해버리거나 국소적인 부분만 프로파일링 한다.
현실
변경 영향도를 완전히 이해한 사람이 아무도 없고 완전한 프로파일링하지 않았다.
이러한 경우 운영계를 그대로 묘사한 UAT(유저 인수 테스트)가 아닌 경우 효용성을 판단하기 어려우며 부하가 높을 때만 도움이 되고 평소에는 오히려 성능이 떨어지는 경우가 있을 수 있다.
처방
운영계에서 스위치를 변경하기 전 다음 절차를 따르라.
- 운영계 성능 지표를 측정한다.
- UAT에서 한번에 스위치 하나씩 변경한다.
- 스트레스를 받는 지점이 UAT와 운영계가 동일한지 확인한다.
- 운영계에서 일반적인 부하를 나타내는 테스트 데이터를 확보한다.
- UAT에서 스위치를 변경하며 테스트한다.
- UAT에서 다시 테스트한다.
- 다른 사람에게 재검토를 요청한다.
- 다른 사람과 결론을 공유한다.
내 데스크톱이 UAT
증상
개발자 장비보다 코어 수가 훨씬 많고 대용량 RAM에 비싼 I/O장비를 탑재한 고성능 운영계 장비의 차이를 이해하지 못하면 문제가 발생할 수 있다.
현실
UAT 환경이 운영계와 달라서 서비스가 중단되는 사태가 벌어지면 장비 몇 대를 더 추가하는 비용보다 훨씬 더 비싼 대가를 치르게 된다.
처방
UAT환경을 맞추기 위한 기회 비용을 잘 따져서 UAT 환경을 구매하도록 한다.
운영 데이터처럼 만들기는 어려워
증상
데이터라이트 라고도 하는 이 안티패턴은 운영계와 유사한 데이터를 나타내고자 할 때 빠지는 함정으로 UAT에서 데이터셋을 단순화 하여 테스트 하는 경우 쓸모 있는 결과를 얻기 힘들어진다.
현실
UAT에서 정확한 결과를 얻기 위해서는 운영계 데이터와 최대한 맞춰야 한다.
처방
데이터 도메인 전문가에게 컨설팅을 받고 엄청난 트랜잭션이 예상되는 서비스는 출시 전 철저한 준비를 하자.
인지 편향과 성능 테스트
우리는 사람이다 보니 무의식중에 어떤 가정을 내리게 된다.
이점을 명시하도록 하자.
모든 문제의 원인은 소프트웨어다. 개발자가 가장 잘 알고 있고 직접 영향력을 행사할 수 있는 시스템 파트는 바로 소프트웨어니까.
환원주의
시스템을 아주 작은 조각으로 나누어 각 파트를 이해하면 전체 시스템을 다 이해할 수 있을 것이라는 분석주의적 사고방식이다.
하지만 복잡한 시스템은 그렇지 않기 때문에 단순히 구성 파트를 합한 것보다 시스템을 전체로서 바라봐야 문제를 찾을 수 있다.
확증 편향
확증 편향은 성능 면에서 중대한 문제를 초래하거나 어플리케이션을 주관적으로 바라보게 한다.
만약 테스트 세트를 부실하게 선택하거나 테스트 결과를 통계적으로 건전하게 분석하지 않으면 확증 편향의 포로가 되기 쉽다.
또한 확증 편향은 보통 강력한 동기가 부여되거나 감정 요소가 개입되기 때문에 거스르기 쉽지 않기에 조심하자.
(팀원 한 사람이 어떤 사실을 입증하려고 애쓰는 경우)
전운의 그림자 (행동 편향)
시스템이 예상대로 작동하지 않는 상황 혹은 아예 시스템이 중단된 경우 발생하는 편향이다.
이럴 경우 시스템 중단을 해결하기 위해 뭐라도 해야 한다는 막연한 느낌으로 서두르기만 하다가 절망에 빠질 수 있다.
시스템이 가동을 멈춘 상황에서 긴장을 풀고 여유를 가지고 침착하게 대응하기란 어렵기 때문에 미리 충분한 테스트와 체계적인 로그로 예방하도록 하자.
위험 편향
인간은 위험을 피하고자 변화를 거부한다.
과거에 건드렸다가 잘못된 경험이 있기에 해당 위험을 감수하지 않으려 한다.
이러한 편향은 단위 테스트 세트와 운영계 회귀 테스트 체계만 확실히 갖춘다면 리스크를 상당히 줄일 수 있다.
마치며
성능 결과를 평가할 때는 데이터를 적절한 방법으로 처리하고 비과학적인, 주관적인 사고에 빠지지 않도록 조심하도록 하자.
추가 공부
웹 어플리케이션 성능 테스트를 위한 툴로는 자바 오픈 소스 Apache Bench, APache JMeter, 네이버에서 Grinder를 이용한 nGrinder과 Gatling 등등이 있다.
이중에서 JMeter를 살펴보도록 하자.
JMeter
Apache에서 만든 자바로 만들어진 웹 어플리케이션 성능 테스트 오픈 소스이다.
JMeter를 이용해서 할 수 있는 테스트는 아래와 같다.
- 웹 - HTTP, HTTPS: JMeter를 사용하여 웹 애플리케이션의 성능을 테스트할 수 있습니다. HTTP와 HTTPS 프로토콜을 사용하는 웹 사이트나 웹 서비스의 부하를 시뮬레이션하고 성능을 측정할 수 있습니다.
- SOAP / REST 웹 서비스: JMeter는 SOAP (Simple Object Access Protocol) 및 REST (Representational State Transfer) 웹 서비스의 성능을 테스트하는 데 사용될 수 있습니다. SOAP 및 REST 요청을 생성하고 웹 서비스의 응답 시간 및 성능을 분석할 수 있습니다.
- FTP: File Transfer Protocol (FTP)를 사용하여 파일 전송 서비스의 성능을 테스트할 수 있습니다. JMeter는 FTP 클라이언트를 시뮬레이션하여 파일 업로드 및 다운로드 등의 작업을 수행합니다.
- JDBC: JDBC (Java Database Connectivity)를 사용하여 데이터베이스와의 연결을 테스트할 수 있습니다. JMeter를 사용하여 데이터베이스 쿼리의 응답 시간 및 성능을 확인할 수 있습니다.
- LDAP: LDAP (Lightweight Directory Access Protocol) 서버의 성능을 테스트할 수 있습니다. JMeter를 사용하여 LDAP 서버와의 연결과 요청에 대한 응답 시간을 측정할 수 있습니다.
- JMS - Message-oriented middleware (MOM): JMeter는 JMS (Java Message Service)를 지원하여 메시지 중개형 미들웨어 시스템의 성능을 테스트할 수 있습니다. JMS 메시지 브로커와의 통신을 시뮬레이션하여 메시지 큐 시스템의 부하를 확인할 수 있습니다.
- Mail - SMTP (S), POP3 (S), and IMAP(S): JMeter를 사용하여 이메일 서버의 성능을 테스트할 수 있습니다. SMTP(Simple Mail Transfer Protocol), POP3 (Post Office Protocol 3), 및 IMAP (Internet Message Access Protocol) 서버와의 통신을 시뮬레이션합니다.
- Native commands or shell scripts: JMeter는 시스템 명령어나 쉘 스크립트를 실행하여 시스템 자원 또는 프로세스의 성능을 테스트할 수 있습니다.
- TCP: JMeter를 사용하여 TCP 프로토콜 기반의 서비스를 테스트할 수 있습니다. TCP 서버와의 연결을 시뮬레이션하고 요청 및 응답을 측정할 수 있습니다.
- Java Objects: JMeter는 Java 객체와의 상호작용을 지원하며, Java 클래스를 사용하여 사용자 정의 테스트 요소를 만들고 성능을 측정할 수 있습니다.
설치 및 실행
https://jmeter.apache.org/download_jmeter.cgi
여기서 설치할 수 있으며 알아서 받아서 설치하자.
이후에 terminal에서 압축을 푼 폴더 아래에 bin에서 jmeter를 실행시키면 된다.
실행시 위와 같은 화면이 뜬다.
테스트
테스트는 원래 서버와 테스트를 돌리는 서버가 달라야 한다.
JMeter를 돌리는 서버와 웹 어플리케이션 서버가 같으면 같은 메모리를 사용하기 때문에 정확한 값을 측정할 수 없다.
JMeter 테스트 용어
- Thread Group : 테스트에 사용될 쓰레드 개수, 쓰레드 1개당 사용자 1명
- Sampler : 사용자의 액션 (예: 로그인, 게시물 작성, 게시물 조회 등)
- Listener : 응답을 받아 리포팅, 검증, 그래프 등 다양한 처리
- Configuration : Sampler 또는 Listener가 사용할 설정 값 (쿠키, JDBC 커넥션 등)
- Assertion : 응답 확인 방법 (응답 코드, 본문 내용 비교 등)
Thread Group
테스트 수행을 위한 유저 수를 설정한다.
그룹의 이름을 정해주고
만약 Sample에서 Error가 발생하면 어떻게 진행할 것인지 설정해준다.
(현재 Continue)
- Number of Threads : 쓰레드 개수
- Ramp-up period : 쓰레드 개수를 만드는데 소요되는 시간
- Loop Count : infinite | n 으로 값을 설정할 수 있으며 설정된 값에 따라 Number of Threads X Ramp-up period 만큼 요청을 다시 보낸다.
Sampler
User가 어떤 행동을 할 것인지 정의한다.
이렇게 http://api.dacheserver.com의 /listen/top 에 get으로 요청을 보낸다고 설정한다.
Listener
Sampler가 받아오는 응답을 바탕으로 Graph, Reporting 과 같은 것들을 만들어 준다.
여기서 여러가지 보고서를 만들 수 있다.
여기서
View Result in Table라는 보고서가 모든 결과를 표로 보여주는 것으로 많이 사용된다.
Table 열 데이터
Sample
그냥 ID번호다.
이 번호를 보고 몇번째로 보는 부하인지 알 수 있다.
Start Time
보내기 시작한 시간으로 ms단위까지 나온다.
Thread Name
쓰레드 그룹 이름
Label
보낸 request의 이름
Sample Time(ms)
Load Time, Elapsed Time, Response Time이랑 같은 뜻이다. 요청 시작 시점부터 응답 종료 시점까지의 시간을 의미
Status
말그대로 응답상태를 확인할 수 있다.
Bytes
응답 데이터 바이트
Sent Byte
요청 데이터 바이트
Latency
지연 속도를 의미하는데 요청 시작 시점부터 응답 시작 시점까지의 시간을 의미
Connect Time(ms)
TCP Handshake를 이용해 연결하는 시간(그냥 TCP연결시간)
Table footer 데이터
No of Samples
No는 아니다라는 뜻이 아니라 number라는 뜻, 처리중인 데이터 수를 의미
Latest Sample
가장 마지막 Sample Time
Average
Sample Time 평균
Deviation
Sample Time의 표준편차
1000개의 샘플 요청을 보냈을 때는 아래와 같이 모두 성공하며 평균 420ms가 나왔지만
5000개의 샘플 요청을 보내니 3000개를 넘어가며 실패하더니 멈춰버렸다.
이외에도 굉장히 다양한 보고서 및 그래프가 있으니 다양하게 활용해 볼 수 있을 듯 하다.
참고