Chapter 4. 성능 테스트 패턴 및 안티패턴
4.1 성능 테스트 유형
- 성능 테스트를 진행하는 경우 가장 많은 실수는 구체적인 내용을 정하지 않는 것이다.
- 테스트로 확인하고 싶은 정량적 질문과 그 테스트가 해당 애플리케이션 입장에서 중요한지 생각해야 한다.
- 지연 테스트: 종단 트랜잭션에 걸리는 시간은?
- 처리율 테스트: 현재 시스템이 처리 가능한 동시 트랜잭션 개수는?
- 부하 테스트: 특정 부하를 감당할 수 있는가?
- 스트레스 테스트: 이 시스템의 한계점은 어디인가?
- 내구성 테스트: 시스템을 장시간 실행할 경우 성능 이상 증상이 나타나는가?
- 용량 계획 테스트: 리소스를 추가한만큼 시스템이 확장되는가?
- 저하 테스트: 시스템이 부분적으로 실패할 경우 어떤 일이 벌어지나?
4.1.1 지연 테스트
- 고객이 트랜잭션(또는 페이지 로딩)을 얼마나 오래 기다려야 하는지 측정
- 지연을 튜닝하는 목적은 유저 경험을 개선하거나 서비스 수준 협약서(service-level agreement, SLA) 조항을 이행하는 것
- 그러나 단순 평균값은 애플리케이션의 요청 응답성을 측정하는 지표로 별로 소용이 없기에 주의해야 한다.
4.1.2 처리율 테스트
- 어떤 측면에서 처리율은 지연과 동등한 개념
- 지연 테스트를 수행할 때는 처리율도 반드시 명시(그리고 제어)해아 한다. (반대도 마찬가지)
- 지연 분포가 갑자기 변하는 시점, 한계점이 바로 ‘최대 처리율’이라고 할 수 있다.
- 스트레스 테스트의 목표는 이 지점과 그 시점의 부하 수준을 포착하는 것
- 반면 처리율 테스트는 시스템 성능이 급락하기 직전, 최대 처리율 수치를 측정하는 것이 목표이다.
4.1.3 부하 테스트
- ‘시스템이 이 정도 부하는 견딜 수 있을까’에 대한 답을 구하는 과정
- ex) 신규 고객을 유치하기 전 예상되는 트래픽 확인
4.1.4 스트레스 테스트
- 시스템 여력이 어느 정도인지 알아보는 수단
- 특정 처리율을 기준으로 시간이 갈 수록 동시 트랜잭션은 증가하고 시스템 성능이 저하될텐데 측정값이 나빠지기 시작하기 직전 값이 바로 최대 처리율이다.
4.1.5 내구 테스트
- 메무리 누수, 캐시 오염, 메모리 단편화 등은 한참 시간이 지나서 드러나는 문제점이다.
- 위 종류의 문제는 내구 테스트로 감지한다.
- 내구 테스트는 빠른 응답을 요구하는 (지연이 낮은) 시스템에서 많이 한다.
- 풀 GC가 일으키는 STW 시간도 허용하지 않음
4.1.6 용량 계획 테스트
- 스트레스 테스트와 비슷하지만 용량 게획 테스트는 업그레이드한 시스템이 버틸 수 있는 부하를 미리 보는 것이다.
- 이벤트나 위협 요소에 대응하는 것이 아닌 예정된 계획의 일부분으로 실행하는 경우가 많다.
4.1.7 저하 테스트
- 부분 실패 테스트라고도 한다.
- 저하 테스트는 부하를 가하는 도중, 서브시스템이 능력을 상실하는 시점에 벌어지는 일들을 확인한다.
- 저하 테스트에선 트랜잭션 지연 분포와 처리율을 눈여겨 봐야 한다.
- 카오스 멍키(Chaos Monkey)
- 복원성 있는 아키텍처에서는 한 컴포넌트가 잘못돼도 다른 컴포넌트까지 연쇄적으로 무너지지 않아야 한다.
4.2 기본 베스트 프랙티스
- 성능 튜닝 시 주안점을 두어야 할 세 가지 기본 원칙
- 나의 관심사가 무엇인지 식별하고 측정 방법 고민
- 최적화하기 용이한 부분이 아니라 중요한 부분을 최적화
- 중요한 관심사를 먼저 다룬다.
4.2.1 하향식 성능
- 자바 애플리케이션을 대규모 벤치마킹하는 일이 작은 코드 섹션별로 정확한 수치를 얻는 것보다 쉽다.
하향식 성능 - 전체 애플리케이션의 성능 양상부터 먼저 알아보는 접근 방식
- 하향식 접근 방식으로 성과 극대화 하는 방법
- 무엇을 측정하고 최적화해야 하는지 명확히 이해
- 성능 활동을 전체 소프트웨어 개발 주기에서 어떻게 병행해야 하는지 명확히 이해
4.2.2 테스트 환경 구축
- 테스트 환경은 가급적 모든 면에서 운영 환경과 똑같이 복제해야 한다.
- CPU 수, OS, 자바 버전, 웹 서버, DB, 로드 밸런서 등
- 편의상 기존 QA 환경을 성능 테스트 환경으로 재활용할 수도 있다.
- 소규모 환경이나 일회성 테스트라면 괜찮겠지만 관리 오버헤드, 스케줄링, 로지스틱스(물류) 문제가 생길 가능성도 존재
- 성능 테스트 인프라 구축엔 비용이 들지만 테스트 환경 구축 비용을 아끼려다 서비스 중단으로 더 큰 대가를 치를 수도 있다.
4.2.3 성능 요건 식별
- 성능 비기능 요건(NonFunctional Requirement, NFR)
- 코드 관점 뿐만이 아닌 시스템을 전체적으로 바라봤을 때 고객과 경영진에게 중요한 측정값
- 최적화하려는 핵심 지표
- 명확한 목표
- 95% 백분위 트랜잭션 시간을 100밀리초 줄인다
- 하드웨어 처리율 5배 높일 수 있게 시스템을 개선한다
- 평균 응답시간을 30% 줄인다.
- 모호한 목표
- 일반 고객을 서비스하는 리소스 비용을 50% 줄인다.
- 애플리케이션 클러스터 성능이 50% 덜어져도 시스템 응답 목표를 25% 이내로 유지한다.
- 고객 이탈률을 25밀리초 지연당 25% 낮춘다.
4.2.4 자바에 특정한 이슈
- JVM은 메모리 영역의 동적 튜닝 등 특유의 동적 자기 관리 기능이 있어 성능 엔지니어가 주의 깊게 봐야할 부분들이 있다.
- JIT 컴파일러의 경우 어떤 메서드를 JIT 컴파일해서 최적화한 기계어로 변환할지 분석한다.
- JIT 컴파일 대상이 아닌 메서드는 다음 둘 중 하나다.
- 자주 실행되는 메서드가 아니다.
- 메서드가 너무 크고 복잡해서 컴파일 분석을 할 수 없다.
- JVM 성능 활동 첫 단추는 어떤 메서드가 컴파일 중인지 로그를 남겨 살피는 것
- 핵심 코드의 중요 메서드가 잘 컴파일되고 있는지
4.2.5 SDLC 일부로 성능 테스트 수행하기
- 수준 높은 팀일수록 성능 테스트를 전체 SDLC의 일부로 수행하며 특히 성능 회귀 테스트를 상시 수행하는 편이다.
- Software Development LifeCycle
4.3 성능 안티패턴 개요
- 성능 튜닝은 항상 목표 지향형 프로세스로 접근해야 한다.
- 신규 서비스 오픈 시 예기치 않은 사고가 발생하는 경우
- 성능 테스트를 진행하지 않았거나
- 온갖 추정만 했거나
- 개발 이슈의 대부분 기술적인 측면보다 의사소통 문제 같은 인적 요소가 원인일 때가 많다.
- “왜 개발자는 잘못된 기술 선택을 밥 먹듯이 하나”라는 블로그 게시글에서 그 원인을 다섯 가지로 분류한다.
4.3.1 지루함
- 개발자는 자기 역할에 지루함을 느끼고 도전적인 일을 할 때가 있다.
- 지루함에서 시작된 도적은 프로젝트에 여러 해약을 끼칠 수 있다.
- 잘 구현된 라이브러리를 사용하지 않고 직접 구현
- 알려지지 않은 기술로 컴포넌트를 제작
- 맞지도 않은 유스케이스에 억지로 기술을 욱여 넣기
4.3.2 이력서 부풀리기
- 본인의 이력서를 과대 포장할 구실을 찾는 개발자도 존재한다.
- 이런 사고로 프로젝트를 불필요한 방향으로 끌고 가는 선택의 발단이 될 수도 있다.
4.3.3 또래 압박
- 기술 결정 시 충분한 논의 없이 진행하면 잘못된 결과가 나오기 쉽다.
- ex) 선배 앞에서 실수 안 하려는 신입 개발자
- ex) 특정 주제를 잘 모른다는 것을 두려워하는 개발자
- 제대로 사정을 따지지 않고 섣불리 중요한 결정을 내려버릴 수도 있다.
4.3.4 이해 부족
- 지금 사용하는 툴도 잘 모르는데 무턱대고 새로운 툴로 문제를 해결하려는 개발자가 있다.
- 새로 나온 멋진 기술도 좋지만 기술 복잡도를 높이는 것과 현재 툴로도 할 수 있는 것 사이의 균형을 잘 맞추어야 한다.
- 이해가 부족한 상태에서 새로운 기술을 복잡하게 사용하게 되면 운영 단계에서 회복 불가능한 중단 사태를 맞이할 수도 있다.
4.3.5 오해와 있지도 않은 문제
- 문제 자체를 제대로 이해하지 못한 채 오로지 기술을 이용해서 문제를 해결하려는 개발자가 있다.
- 성능 수치를 측정도 안 해보고 성공을 장담할 수는 없다.
- 안티 패턴을 예방하려면 팀원 모두 기술 이슈를 활발히 공유하는 분위기를 적극 장려해야 한다.
4.4 성능 안티패턴 카탈로그
4.4.1 화려함에 사로잡히다.
- 증상
- 최신의 멋진 기술을 튜닝 타깃으로 정함
- 신기술을 공부하는 것이 재밌다.
- 현실
- 애플리케이션을 측정하고 튜닝하려 하지 않고 어떻게든 해보려고 한다.
- 신기술을 제대로 알지 못한 채 지레짐작한다.
- 신기술에 관한 예제는 보통 작은 규모의 전형적인 데이터셋을 다루기에 기업 규모로의 확장하는 데 베스트 프랙티스라는 일언반구 말도 없다.
- 처방
- 측정을 해보고 진짜 성능 병목을 찾기
- 새 컴포넌트는 전후로 충분한 로그 남기기
- 베스트 프랙티스 및 단순화한 데모 참조
- 팀원이 새 기술을 이해하도록 독려하고 팀 차원의 베스트 프랙티스 수준을 정하기
4.4.2 단순함에 사로잡히다.
- 증상
- 객관적인 아픈 부위를 들추지 않는다.
- 무작정 시스템의 제일 간단한 부분만 파고든다.
- 원 개발자가 작업한 부분을 다른 개발자가 이해하지 못한다.
- 현실
- 원 개발자는 자신이 맡은 파트를 어떻게 튜닝해야 할지 안다.
- 다양한 컴포넌트에 대한 공유를 하지 않고 짝 프로그래밍을 안 한 결과, 독보적 전문가만 양산된다.
- 처방
- 측정을 해보고 진짜 성능 병목 찾기
- 본인이 익숙하지 않은 컴포넌트에 문제가 생기면 전문가에게 도움을 청하기
- 개발자가 전체 시스템 컴포넌트를 고루 이해하도록 독려
4.4.3 성능 튜닝 도사
- 증상
- 현실
- 특정 이슈를 해결한 방법이나 자신이 알고 있는 것을 남과 공유 안하는 초인을 지향하는 팀원은 매우 반생산적
- 처방
- 측정을 해보고 진짜 성능 병목 찾기
- 새로운 팀내 전문가가 다른 팀원들과 지식을 공유할 수 있도록 하기
4.4.4 민간 튜닝
- 증상
- 웹사이트 서칭 중에 찾은 ‘마법’의 설정 매개변수를 발견하고 적용
- 현실
- 전후 맥락이나 기초를 모르면 그 팁이 어떤 영향을 미칠지 모른다.
- 어떤 시스템에선 통해도 다른 시스템에서도 통할지 장담할 수 없다.
- 처방
- 시스템의 중요한 부분에 영향을 미치는 기술은 확실히 파악하고 검증된 것만 사용하기
- 매개변수를 UAT에서 시험해보고 철저히 검증하고 효율을 프로파일링하기
- 다른 팀원들과 설정 문제를 리뷰하고 토의하기
4.4.5 안되면 조상 탓
- 증상
- 이슈와는 상관 없는 특정 컴포넌트를 문제 삼는다.
- 현실
- 충분한 분석 없이 성급한 결론을 내린다.
- 평소 의심했던 컴포넌트를 용의자로 지목
- 비난의 대상은 평소 코드 베이스나 라이브러리를 잘 파악하지 않은 것들
- 처방
- 정상적으로 분석하기
- 분석 결과를 모든 이해 관계자와 의논
4.4.6 숲을 못 보고 나무만 보다
- 증상
- 전체적인 변경 영향도를 파악하지 않은 채 애플리케이션의 국소적인 부분만 프로파일링한다.
- JVM 스위치를 바꿔보며 성능을 조금이라도 올려보려고 시도
- 마이크로벤치마킹 기법
- 현실
- 변경 영향도를 완전히 이해한 사람이 아무도 없다.
- 마이크로벤치마킹으로 인한 전체 시스템 영향도를 파악하지 않는다.
- 운영계를 그대로 모사한 UAT 환경 없이 최적의 효용성을 판단하기는 어렵다.
- 부하가 높을 때만 도움 되고 평소에 외려 성능을 떨어뜨리는 최적화는 의미가 없다.
- 처방
- 스의치를 변경하기 전 다음 절차를 따른다.
- 운영계 성능 지표 측정
- UAT에서 한 번에 한 스위치씩 변경
- 스트레스 받는 지점이 UAT와 운영계가 동일한지 확인
- 운영계에서 일반적인 부하를 나타내는 테스트 데이터 확보
- UAT에서 스위치를 변경하며 테스트
- UAT에서 다시 테스트
- 추론한 내용을 다른 사람에게 재검토 요청
- 결론을 다른 사람과 공유
4.4.7 내 데스크톱이 UAT
- 증상
- UAT 환경이 운영계 환경과 전혀 다른 경우가 많다.
- 운영계 서버가 고성능이라면 UAT 환경을 갖추는 것이 비용이 많이 든다.
- 현실
- UAT 환경이 운영계와 달라서 서비스 중단 사태가 벌어지면 장비 몇 대 추가하는 비용보다 더 비싼 대가를 치르게 된다.
- 처방
- 서비스 중단 비용과 고객 이탈로 인한 기회비용 따져보기
- 운영 환경과 동일한 UAT 환경 구입
4.4.8 운영 데이터처럼 만들기는 어려워
- 증상
- UAT에서 데이터셋을 단순화하여 테스트하는 경우가 많다.
- 현실
- UAT에서 정확한 결과를 얻으려면 운영계 데이터와 최대한 맞추어야 한다.
- 처방
- 데이터 도메인 전문가에게 컨설팅을 받아 운영 데이터를 (필요시 뒤섞거나 난독화해서) UAT로 이전하는 프로세스에 시간과 노력 투자하기
- 엄청난 트랜잭션이 예상되는 서비스는 출시 전 철저히 준비하기
4.5 인지 편향과 성능 테스트
- 안티패턴 대부분 하나 이상의 인지 평향으로 인해 비롯된 것들이다.
- ex) 소프트웨어가 아닌 인프라가 문제라는 등의 편향된 결론
- 하지만 모든 문제의 원인은 소프트웨어
4.5.1 환원주의
- 환원주의: 시스템은 작은 조각으로 나누어 구성 파트를 이해하면 전체 시스템도 다 이해할 수 있다는 분석주의적 사고방식
- 하지만 복잡한 시스템은 전체로 바라봐야 문제의 원인을 찾을 수 있다.
4.5.2 확증 편향
- 확증 편향은 성능 문제를 초래하거나 애플리케이션을 주관적으로 보게 한다.
- 확증 편향은 보통 강력한 동기가 부여되거나 감정 요소가 개입되기 때문에 거스르기 어렵다.
4.5.3 전운의 그림자(행동 편향)
- 시스템이 예상대로 동작하지 않는 상황 또는 중단된 시간 중에 발현되는 편향
- 원인
- 영향도 분석과 논의 없이 인프라 변경
- 시스템이 의존하는 라이브러리 변경
- 연중 가장 업무가 빠듯한 날에 처음 보는 버그나 경합 조건 발생
- 시스템 중산 시나리오를 충분히 테스트해보고 로그도 남기면서 체계적으로 문제에 접근하는 태도를 길러야 한다.
4.5.4 위험 편향
- 사람은 위험을 피하고 변화를 거부하는 본성이 있다.
- 뭔가 바꿨더니 잘못됐던 경험 때문에 변화를 꾀하기 어려워진다.
- 단위 테스트 세트와 운영계 회귀 테스트 체계만 확실히 갖추면 리스크를 상당히 줄일 수 있다.
- 위험 편향은 애플리케이션에 문제가 생겼을 때 제대로 학습하고 적절한 조치를 하지 못한 까닭에 더 고착화된다.
4.5.5 엘스버그 역설
- 엘스버그 역설: 인간이 확률을 이해하는 데 얼마나 서투른지 잘 보여주는 사례
- ‘알려지지 않은 미지의 것’ 보다 ‘알려진 기지의 것’을 추구하는 인간 본연의 욕구에 관한 역설