반드시 튜닝해야 하는 대상은?

시간적 여유가 있고, 모니터링 툴이 있다면 전체 화면을 점검하는 것을 권장하지만, 시간 대비 효율을 따졌을 때 상당히 비효율적인 작업이 될 수도 있다.

로그인 및 초기 화면, 가장 많이 사용하는 화면을 위주로 성능을 분석하는 것이 가장 현명한 방법이다.

어떤 화면이 많이 쓰이는지 알고 싶다

웹 로그란?

아파치나 Nginx와 같은 모든 웹 서버에 공통적으로 제공되는 기능이 웹 로그이다.

서버에 어떤 사용자가 어떤 요청을 하였고, 결과는 어떠한지 파일에 한 줄씩 쌓아 준다.

웹 로그를 사용하면 그 동안 생각지도 못했덛ㄴ 문제점을 찾을 수도 있다. 상용 툴을 사용한다면 실시간으로 데이터를 처리하여 확인할 수도 있을 것이다.

튜닝의 절차는 그때그때 달라요

성능 튜닝 step by step

  1. 원인 파악
    • 원인 파악을 잘못하면 매우 많은 시간낭비가 발생할 것이다.
    • 어디가 병목인지 확실히 파악하여 원인을 찾아야 한다.
  2. 목표 설정
    • 필수는 아니지만 성능 개선뿐만 아니라, 향후 유지보수까지 모두 고려해서 목표를 설정하는 것이 좋다.
  3. 튜닝 실시
    • 가장 좋은 방법은 프로파일링 툴이나 APM과 같은 툴을 사용하는 것이고, 개선이 얼마나 되었는지를 확인하려면 JMH나 캘리퍼와 같은 성능 비교 도구를 사용하는 것도 큰 도움이 될 수 있다.
  4. 개선율 확인
  5. 결과 정리 및 반영

성능 튜닝의 비법

  • 하나만 보지 말아라.
  • 큰 놈을 없애라.
  • 깊게 알아야 한다.
  • 결과 공유는 선택이 아닌 필수!

하나만 보지 말아라

  • 병목의 대상 목록들

    대상 세부 대상
    서버 장비 CPU, Network, Disk, Memory 등
    서버 OS OS 커널, OS 설정, 수행중인 프로세스 등
    자바 애플리케이션 스레드 풀 설정, DB Connection Pool 설정, Cache 설정, GC 설정, Heap 크기 설정, 검증되지 않은 프레임워크의 버그, 개발된 애플리케이션 등
    웹 서버 프로세스 개수 설정, Connector 설정, 개발된 모듈의 버그 등
    네트워크 사용자의 네트워크의 종류, 사용자의 위치, L4 및 Switch 등 네트워크 장비, 방화벽 등
    클라이언트 클라이언트 장비의 CPU, Network, Disk, Memory, OS 등의 성능, 클라이언트 애플리케이션, 클라이언트 장비에서 수행 중인 프로세스 등
  • 성능상 이슈가 있을 때는 절대로 하나만 보면 안 된다. 전체를 봐야 한다.

큰 놈을 없애라

  • 잔챙이 백날 튜닝해 봤자 효과 없다.

  • 성능 튜닝을 하다 보면 중심이 되는 라이브러리에 문제가 발생하는 경우도 적지 않다. 이 경우 선택은 두 가지다.

    1. 해당 라이브러리를 튜닝하는 것
    2. 다른 것으로 교체하는 것
    • 시스템의 코어 부분이 개발 완료되었을 때 성능 테스트를 통해서 사용중인 라이브러리의 성능을 검증하자.

서버의 성능도 큰 영향을 미치지만, 해당 애플리케이션을 사용하는 사용자들에게는 클라이언트의 성능도 무시할 수 없다. 구글의 Chrome, 애플의 Safari와 같은 브라우저들은 훌륭한 웹 성능 프로파일링 도구를 제공한다.

깊게 알아야 한다

  • 개발 언어, OS, DB, N/W, 서버 등 대충 알면 대충 튜닝할 수밖에 없다. 하나라도 전문가가 되자.

결과 공유는 선택이 아닌 필수!

  • 결과 정리를 할 때 꼭 포함되어야 하는 내용
    • 개요: 튜닝을 실시한 배경
    • 튜닝 환경: 튜닝을 실시하고 성능을 측정한 서버 및 툴에 대한 상세한 내용
    • 튜닝 결과: 튜닝 전과 후의 결과를 비교
    • 결론: 어느 부분을 어떻게 변경하는 것이 가장 큰 효과를 줄지, 튜닝 작업을 진행한 담당자의 의견 등을 포함
  • 튜닝 결과 정리 시 유의사항
    • 확실한 결과 위주로 포함하자. (기존 대비 개선률을 수치로 보여주자)
    • 개선효과가 가장 큰 것부터 나열하자.
    • 개발한 사람의 심기를 나쁘게 하지 말자.

애플리케이션에서 점검해야 할 대상들

패턴과 아키텍처는 잘 구성되어 있는가?

너무 많은 패턴을 사용하지 않았는가?

  • 너무 많은 패턴을 적용하면 유지보수성이 떨어지고, 문제가 발생했을 때 추적하기가 어려워진다.
  • 좋은 패턴이라고 무작정 적용하기보다는 꼭 필요한 패턴만을 사용해야 한다.

데이터를 리턴할 때 TO(혹은 VO) 패턴을 사용하였는가? 아니면 Collection 관련 클래스를 사용하였는가?

  • 데이터를 주고 받는 시간을 절약하기 위해서 일반적으로 TO 패턴을 사용한다. 그리고 때에 따라서는 Collection 관련 클래스를 사용하기도 한다.
  • 이러한 패턴을 적용하지 않았거나 관련 표준을 정하지 않고 개발을 할 경우 시스템의 응답 시간도 영향이 있겠지만, 유지 보수성이 떨어진다.
  • 게다가 HashMap으로 데이터를 주고 받으면, 소스를 완전히 뜯어 보지 않는 이상 개발자만 어떤 키과 값이 들어 있는지 알게 된다.

Service Locator 패턴은 적용이 되어 있는가?

  • 이 패턴을 사용하면 애플리케이션에서 필요한 대상을 찾는 룩업 작업을 할 때 소요되는 대기 시간을 줄일 수 있다.
  • 만약 서버의 CPu 사용량이 높지 않을 때 응답 속도가 느리다면, 서비스 로케이터를 적용해야 한느 부분이 있지 않은가 한 번쯤 확인을 해 봐야 한다.

기본적인 애플리케이션 코딩은 잘 되어 있는가?

명명 규칙은 잘 지켰는가?

  • 클래스 이름을 보고 어떤 일을 하는 클래스인지 바로 인식이 가능한지를 확인해야 한다.
  • 문제에 더 빨리 접근할 수 있도록 자바의 기본 명명 규칙을 따르자.

필요한 부분에 예외 처리는 되어 있는가?

  • 예외 처리를 제대로 하지 않으면 사용자는 아무런 응답을 받지 못하고, 운영하는 시스템을 더 이상 사용하지 않을 수도 있다.
  • 문제가 발생했을 때 원인을 밝히기 위해서 예외 처리는 필수다.

예외 화면은 지정되어 있는가?

  • 예외 화면에 대한 표준이 있는지 확인해 보아야 한다.
  • 만약 예외 화면을 구성하지 않고 지정하지도 않는다면, 사용자는 서버의 종류가 어떤 것인지 알게 될 것이다. 때에 따라서는 시스템에 어떤 클래스가 있는지도 확인할 수 있을 것이다.

예외 정보를 혹시 e.printStackTrace()로만 처리하고 있지 않은가?

  • e.printStackTrace() 메서드를 호출하면 서버에서 스택 정보를 취합하여야하기 때문에 서버에 많은 부하가 발생하게 된다.

System.gc() 메서드가 소스에 포함되어 있지 않은가?

  • 우리가 개발할 때는 GC가 '언제 되어야 할지'에 대해서 절대로 신경쓰면 안 된다.

System.exit() 메서드가 소스에 포함되어 있지 않은가?

  • 이 메서드가 수행되면 WAS의 프로세스가 죽는다.
  • 스레드가 죽는 것이 아니라 프로세스가 죽는 것이니 이 메서드가 소스에 포함되어 있다면 반드시 제거하자.

문자열을 계속 더하도록 코딩하지는 않았는가?

  • 몇십 페이지 정도 되는 쿼리를 더하기로 처리할 경우 서버는 GC를 하느라 바빠질 것이다.
  • JDK 5.0 이상을 사용하면 자동으로 StringBuilder 클래스로 변환을 해주기는 하지만 루프를 수행하면서 문자열을 더할 경우에는 컴파일러도 어쩔 수가 없다.

무한 루프가 작동할 만한 코드는 없는가?

static을 남발하지 않았는가?

  • 메모리를 아낀다고 static을 남발하다가는 시스템이 심각한 오류를 발생시킬 수 있다.

필요한 부분에 synchronized 블록을 사용하였는가?

  • 그냥 동시에 해당 메서드가 호출될 것 같아서 synchronized 블록을 사용한 것은 아닌지 확인해야 한다.
  • 필요 없는 부분에 synchronized 블록을 사용하게 될 경우 성능 저하를 발생시킬 수 있다.

IO가 계속 발생하도록 개발되어 있지 않은가?

  • 가장 많이 실수하는 부분이 설정 파일을 매번 파일에서 읽도록 개발하는 것이다.
  • 100명 이상의 사용자가 사용하는 시스템에서 해당 설정 파일을 매번 읽기 위해 IO를 발생시키면서 시스템의 아까운 자원을 낭비하지 말자.

필요 없는 로그는 다 제거했는가?

  • '로그 레벨이 DEBUG가 아니니 괜찮겠지.'하는 생각은 버려라.
  • 우리가 개발하는 시슽메은 필요한 데이터만 처리하기 위해서 메모리가 소비된다.
  • 프린트하지도 않을 로그 데이터를 문자열로 만들기 위해서 아까운 메모리를 사용하고, 그 메모리를 청소하기 위해서 GC를 해야 하는 불쌍한 JVM을 생각해보라.

디버그용 System.out.println은 다 제거했는가?


웹 관련 코딩은 잘 되어 있는가?

JSP의 include는 동적으로 했는가? 아니면 정적으로 했는가?

  • JSP를 동적으로 include하면 서버에도 부하를 줄 뿐만 아니라 응답 시간에도 영향을 준다.
  • 그렇다고 무조건 정적으로 include를 하면 변수를 공유하게 되어 예기치 못한 문제가 발생할 수 있으므로 유의해서 사용하자.

자바 빈즈는 너무 많이 사용하지 않았나?

  • 하나의 하ㅘ면에서 자바 빈즈를 숫비 개씩 사용하지 않는가?
  • 하나의 TO를 사용해서 처리할 수도 있는 데이터라면, 여러 개의 자바 빈즈를 사용해서 응답 시간에 영향을 주는 일이 없도록 하자.

태그 라이브러리는 적절하게 사용했나?

  • 많은 양의 데이터를 태그 라이브러리로 처리할 경우에는 성능에 많은 영향을 끼친다.

EJB는 적절하게 사용하였나?

  • EJB를 많이 사용한다고 해서 시스템이 안정화되는 것은 아니다.
  • 서버를 기동하는데 시간의 여유가 있다면, 서버의 메모리가 충분히 여유가 있다면 상관없다.
  • 하지만 EJB 하나하나는 일반 클래스보다 많은 메모리를 점유해야 하며, 서버를 기동할 때에도 많은 시간이 소요된다.
  • 따라서 반드시 필요한 경우에만 EJB를 사용하자.

이미지 서버를 사용할 수 있는 환경인가?

  • 웹 서버 이외에 정적인 컨텐츠만을 제공하는 서버를 사용하자.

사용 중인 프레임워크는 검증되었는가?


DB 관련 코딩은 잘 되어 있는가?

적절한 JDBC 드라이버를 사용하는가?

DB Connection, Statement, ResultSet은 잘 닫았는가?

  • 반드시 finally 구문을 사용해서 Connection, Statement, ResultSet을 명시적으로 닫자.

DB Connection Pool은 잘 사용하고 있는가?

  • 프로젝트 규모가 크면 클수록 표준을 따르지 않을 확률도 커진다.
  • 관련되는 표준을 반드시 정하고, DB Connection Pool은 반드시 사용해야 DB의 리소스를 보다 효율적으로 사용할 수 있다.

자동 커밋 모드에 대한 고려는 하였는가?

  • 기본 커밋 모드는 자동 커밋으로 되어 있다.
  • 하지만 조회성 프로그램도 자동 커밋 여부를 지정하게 되면, 약간의 응답시간 저하가 발생하게 된다.
  • 반드시 필요하지 않은 경우에는 자동 커밋을 하도록 하자.

ResultSet.last() 메서드를 사용하였는가?

  • 데이터의 건수가 많을수록 응답 시간이 느려진다.
  • 쿼리를 두 번 수행하더라도 전체 건수를 가져오기 위한 last() 메서드의 사용은 자제하자.

PreparedStatements를 사용하였는가?

  • Statement를 사용하면 매번 쿼리를 수행할 때마다 SQL 쿼리를 컴파일하게 된다. 이 작업은 DB에 많은 부하를 준다.
  • 그러므로 쿼리 문장이 계속 동적으로 변경되어야 하는 경우를 제외한 대부분의 경우에는 PreparedStatements를 사용하자.

서버의 설정은 잘 되어 있는가?

자바 VM 관련 옵션들은 제대로 설정되어 있는가?

  • 클래스 패스는 순차적으로 인식된다. 여러 JAR 파일 중 만약 같은 패키지명에 같은 클래스가 존재할 경우에는 앞에 명시한 클래스 패스에 우선권이 있다.

메모리는 몇 MB로 설정해 놓았는가?

  • 설정하지 않으면 서버는 기본 64MB로 시작한다.

GC 설정은 어떻게 되어 있는가?

  • 혹시 -client로 설정되어 있는지 확인해 보자.
  • GC의 방식에 따라서 서버의 성능이 엄청난 차이가 발생할 수 있다.
  • 가능하면 성능 테스트를 통해서 시스템과 서버에 맞는 GC 옵션을 설정하자.

서버가 운영 모드인지 개발 모드인지 확인하였는가?

  • 서버가 개발 모드라면, WAS는 주기적으로 변경된 클래스가 있는지 확인할 것이다. 이 작업은 서버에 많은 부하를 주게 된다.
  • 서버가 주기적으로 느려진다면 이 부분을 확인해보자.

WAS의 인스턴스가 몇 개 기동되고 있는가?

  • WAS에서 하나의 인스턴스당 적어도 한 개는 있어야 제대로 된 운영이 가능하다.
  • CPU는 4개인데, 인스턴스가 30개 정도 있는 것은 아닌지 확인하자.
  • WAS의 CPU 개수가 적을 겨우, 때에 따라서 인스턴스 개수를 증가시키면 서버의 처리량이 증가될 수 있다. 이 인스턴스의 개수에 대해서는 정답이 없다.
  • 그러므로, 서버 및 애플리케이션의 상황에 맞게 성능 테스트를 통해서 시스템의 적절한 인스턴스 개수를 도출해야 한다.

JSP Precompile 옵션은 지정해 놓았는가?

  • 서버를 기동할 때 JSP를 미리 컴파일하도록 해 놓으면 사용자는 JSP가 수정이 되었는지 여부와 상관없이 일정한 응답 속도를 느낄 것이다.
  • 하지만 서버가 기동될 때 JSP를 컴파일하기 위한 시간도 많이 소요된다.

DB Connection Pool 개수와 스레드 개수는 적절한가?

  • 스레드 개수가 DB Connection Pool의 수보다 절대로 적어서는 안 된다.
  • 스레드의 개수는 DB Connection Pool의 개수보다 보통 10개 정도 더 많이 설정한다.
  • 만약 DB Connection Pool의 설정을 서버 기본 설정으로 해 놓고 운영을 한다면, 서버의 리소스도 별로 사용하지 않고 10명 이상의 동시 사용자를 처리하지 못할 것이다.
  • 대부분 WAS의 DB Connection Pool의 초기 값은 10~20개이다.

세션 타임아웃 시간은 적절한가?

  • 세션을 더 이상 사용하지 않을 때 세션 정보는 삭제되어야 한다.
  • 만약 세션 타임아웃 시간을 하루 이상의 큰 값으로 지정하고, 해당 서버를 재시작하지 않으면 해당 서버는 메모리 릭이 발생하여 더 이상 사용할 수 없는 상황이 될 것이다.

검색 서버가 있다면, 검색 서버에 대한 설정 및 성능 테스트를 하였는가?

  • 일반적으로 검색 서버에서 주기적으로 데이터를 모으기 위해서 서버의 리소스를 많이 사용하게 된다.
  • 만약 검색 서버의 세팅을 점검하지 않고, 선능 테스트를 하지 않는다면, 예기치 못한 부분에서 시스템의 리소스를 점유하고 있을지도 모른다.

모니터링은 어떻게 하고 있는가?

웹 로그를 남기고 있는가?

  • 애플리케이션으 ㅣ사용량과 자주 사용하는 애플리케이션을 분석하기 위해서, 추후 용량 산정의 기초 자료가 되는 웹 로그는 반드시 남겨서 관리하자.

verbosegc 옵션은 남기고 있는가?

  • GC가 어떤 형태로 발생하는지를 확인하고자 한다면, 반드시 verbosegc 옵션을 사용하여 로그를 남기자.
  • 메모리의 크기를 지정하기 위해서나, GC 옵션을 변경하기 위해서라도 verbosegc 옵션은 매우 유용하게 활용될 것이다.
  • 하지만, GC 튜닝을 하지 않을 시스템에 verbosegc 옵션을 남기는 것은 리소스 낭비다.

각종 로그 파일에 대한 규칙은 있는가?

  • 일별로 로그를 쌓도록 옵션을 지정하면, 특정 이슈가 발생한 날짜의 데이터를 분석하기에도 좋고 백업을 하기에도 편리하다.

서버의 시스템 사용률은 로그로 남기고 있는가?

  • WAS나 DB가 얼마나 사용을 하고 있는지 로그를 남겨야 한다.
  • 대부분의 사이트에는 SMS나 APM을 설치하여 서버를 모니터링하고 있겠지만, 그렇지 않은 사이트에서는 서버의 사용량을 유닉스의 vmstat나 sar 명령어를 사용해서 남겨야 시간대별 서버 사용량을 구할 수 있다.
  • 그리고 나중에 서버를 증설할 때에도 이 데이터는 매우 유용하게 사용된다.

모니터링 툴은 사용 중인가?

  • WAS를 모니터링하기 위한 툴은 설치되었는가? 모니터링 툴이 없다면, 장님이 코끼리를 만지는 것과 비슷하다.
  • 툴을 구매할 여력이 안되고 서버가 많은 부하를 받고 있지 않는다면, JMX 기술을 사용하여 서버를 모니터링하는 것도 좋은 방법이다. (JConsole도 있음)

모니터링 툴에 대한 설정은 적절하게 되어 있는가?

  • 모든 메서드에 대해서 프로파일링을 하도록 지정을 사면 대부분의 툴이 성능에 많은 영향을 주게 된다.
  • 그러므로 꼭 필요한 내용만 모니터링이 되도록 설정을 잘 맞추어야 제대로 사용할 수 있다.

서버가 갑자기 코어 덤프를 발생시키지 않는가?

  • 서버에서 코어 덤프를 발생시키는 경우의 수는 굉장히 많다. 만약 서버가 지속적으로 코어 덤프를 발생시킨다면 적어도 다음의 내용을 점검해 보기 바란다.
    1. 10,000건 이상 조회하는 것이 있나 확인해야 한다. 한꺼번에 많은 양의 데이터를 처리하려면 많은 메모리가 필요한데, 이 때 코어덤프가 발생할 수 있다.
    2. 메모리 릭이 있는지 확인해야 한다. 메모리를 점유하고 해제하지 않는 로직이 있으면 서버를 매일 재기동해도 메모리는 점차적으로 부족해진다. 서버가 기동된 후 힙 덤프를 받아 놓은 뒤, 운영 중과 운영 후에 각각 덤프를 받아서 메모리 사용량의 추이를 확인해야 한다.

응답 시간이 너무 느리지 않은가?

  • 응답 시간이 느려지는 이유는 상상을 초월할 정도로 많다. 그러므로 이 문제를 해결하기 위해서는 상황을 정확히 판단해야 한다.
  • WAS단 이후에서 느린것인지, 그 이전 단계인 네트워크나 웹 서버에서 느린지 확인해야 한다.
  • 만약 WAS에서 느리다면, 정확히 어느 부분에서 느린지 프로파일링을 해서 확인해 봐야 한다.

부록 C. Cache의 활용

캐시는 은탄환이 아니다 !!

아무 곳에나 무분별하게 캐시를 쓴다고 해서 성능이 무조건 좋아지는 것은 아니다.

캐시를 시스템 상으로 분류하면,

  • CPU L1 Cache
  • CPU L2 Cache
  • Disk Cache

이 캐시들은 OS 및 하드웨어에서 제공하는 캐시를 말한다. 즉, 시스템의 성능을 높이는 데 사용되는 것들이다.

지금부터 이야기할 캐시는 이 레벨의 캐시가 아니다.

서비스를 개발하는 개발자들의 기준으로 보는 캐시는 다음과 같이 아주 간단하게 분류할 수도 있다.

구분 장점 단점
로컬 캐시 같은 JVM내 혹은 같은 장비에서 데이터를 가져오므로 성능이 좋다. 하나의 장비 내에 있는 데이터만 동기화가 이루어진다.
글로벌 캐시 데이터 동기화가 실시간으로 이루어지기 때문에 모든 사용자가 동일한 데이터를 가질 수 있다. 성능이 로컬 캐시에 비해 좋지 않다.

캐시의 선택

만약 NoSQL 기반의 캐시를 사용할 때는 반드시 BMT(Benchmark Test)를 통해서 현재 개발하려는 시스템의 상황에 가장 잘 맞는 캐시를 선정해야만 한다.

모든 캐시가 분산 환경을 제공하는 것은 아니다.

기본적으로 캐시에서 제공되는 기능 외에도 성능은 매우 큰 캐시 선정 요건 중에 하나이다.

  • 실제 데이터가 얼마나 들어갈지를 확인해 보고, 그만큼 데이터를 넣어두고 성능을 측정해 보는 저검작업을 수행하는 것은 매우 중요하다.
  • '절대로' 캐시를 만든 사람들이 제공하는 수치를 믿지 말자.

캐시는 날아가도 상관 없는 데이터를 처리하는데 사용하는 것이 좋다.

  • 1분에 몇 번 로그인하는지 확인하고자 할 때
  • 카페 등에서 최근에 쓴 글이 어떤 글인지 확인하고자 할 때
  • CPU 사용량등과 같이 수집되는 데이터가 10만개 중 한두개 없어져도 전혀 문제 안되는 작업을 할 때

매우 자주 사용되면서 캐시에 없으면 DB에서 가져오는 되는 것들에 적용하면 큰 효과를 볼 수 있다.

성능 개선 효과가 있고, 데이터가 날아간다고 뭐라고 할 사람이 아무도 없는 경우에 사용하는 것이 좋다.

+ 따끈한 최근 게시물