본문 바로가기
책을 읽겠습니다!/자바 성능 튜닝 이야기

XML과 JSON도 잘 쓰자! & 서버를 어떻게 세팅해야 할까?!

by Unagi_zoso 2023. 9. 12.

설정파일이나 DB 쿼리, 주고 받는 데이터들을 XML과 JSON으로 사용하는 경우가 많은데 잘 알고써야한다.

XML은 eXtensible Markup Language의 약자로 장점은 누구나 데이터의 구조를 정의하고 그 구조를 공유함으로 일관된 데이터 전송, 처리를 할 수 있다 그리고 이러한 특성 때문에 파싱을 해야한다. 구조에 맞게 처리를 한다는 것.

자바에서는 XML 파싱 위해 JAXP 제공

SAX는 순차적 방식으로 처리, DOM은 모든 XML 읽어 트리 만든 후 처리(메모리 부담이 커)

서버단에서 사용하기 적합한 건 SAX와 DOM.

SAX 파서

순차적으로 처리하는 이벤트 기반 모델. DOM보다 손이 많이 간다

DOM 파서

트리 형태의 데이터로 만든 후 가공한다.

결과는 SAX 파서 써라..

XML 파서가 문제가 된 사례

해당 사이트는 여러 지역에 WAS가 분리되어 있는데, 특정 지역의 WAS의 힙 덤프가 1분에 한 번 씩 발생.

  • 힙 덤프는 현재 JVM의 힙 메모리에서 점유하고 있는 객체에 대한 정보를 파일로 생성해 놓은 곳. OutOfMemoryError가 발생할 경우 자동하도록 하는 옵션도 있다. 현재 문제에선 OutOfMemoryError가 계속 났다는 의미같다.

해당 사이트 Finalizer가 메모리 70퍼를 잡아 먹고 잇었고 이는 GC를 위한 API. 이 사이트는 XML로 문서를 주고 받는다. was 내장 파서가 특수문자를 입력 받으면 무한루프를 돌아 OutOfMemoryError를 발생시킨다는 것이다. 그래서 검증된 아파치 그룹의 SAX 파서를 사용해야했다.

JSON과 파서들

JSON 데이터는 다음과 같은 두 가지의 구조를 기본으로 하고 있다.

  • name/value 형태의 쌍으로 collection 타입
  • 값의 순서가 있는 목록 타입

JSON도 많은 CPU와 메모리를 점유하며 응답 시간이 느리다. (요즘은 잘만 쓰는거 같은데)

자바 기반 JSON 파서들이 존재하는데 가장 많이 사용되는 JSON 파서로는 Jackson JSON, google-gson이 있다.

XML과 JSON을 비교하면 JSON이 우세하다. 데이터를 전송하기 위해 이 데이터를 Serialize, Deserialize할 경우가 있는데 JSON은 이 처리 성능이 좋지 않다. XML 파서보다 JSON 파서가 더 느린 경우가 대부분이다.. 그래서 데이터 전송 위한 기술들이 나오고 있다

데이터 전송을 빠르게 하는 라이브러리

요즘 자바 객체를 전송하는 방식 많다.

avro, protocol buffer, thrift 같은게 있는데 무거운 데이터를 보내야할 때 한 번 고려해 봐야겠다.

주기적으로 설정파일을 읽도록 한다면 그 빈도수를 줄이든가 해야한다. 파싱 속도 무시 못해. 단지 내부에서 사용하기 위해 이런 것들을 데이터 처리 기준으로 선정하면 메모리 및 CPU 사용량 손실을 염두해야한다.

# 서버 세팅 어떻게 하지

기본값으로 최대한의 성능을 낼 순 없다. DBMS, OS에 대한 세팅은 다루지 않는다. 개인적으로 공부해봐야지.

Data Source 개수, 스레드 개수, 메모리 등?

병목 존재 시 진단하기 가장 좋은 방법은 성능 테스트를 통해서 미리 파악하는 것. 무조건 애플리케이션 위주로 병목을 찾기 보단, 문제가 될만한 세팅값이 없느 지 진단하는 것이 가장 효율적.

영향을 줄만한 세팅이다.

  • 웹 서버 세팅
  • WAS 서버 세팅
  • DB 서버 세팅
  • 장비 세팅

아파치 웹 서버 설정

WAS를 웹 서버로 사용하면 안된다. 웹 서버는 반드시 WAS 앞에 두어야한다. 이는 웹에서 사용하는 애플리케이션을 위한 서버지 웹 서버가 아니다. 정적인 부분은 웹 서버에서 처리되어야한다. 그렇지 않으면 WAS 서버에서 이미지, CSS, 자바 스크립트, HTML을 처리하여야 한다. WAS 서버 스레드가 점유 당한다. 반드시 상용 웹 서버나 아파치 웹 서버를 WAS 앞 단에 두고 운영하자. 웹 서버 하나에 WAS가 두 개 이상 연동되는 경우 등을 고려하면웹 서버를 앞 단에 두는 것이 좋다. (이건 무슨 말이지?!)

아파치 웹 서버에서 영향을 줄 수 있는 요소..

아파치 웹 서버는 MPM이라는 것을 사용한다. Multi-Processing Module의 약자로 여러 개의 프로세싱 모듈 기반의 서비스를 제공한다.
아파치 웹 서버의 설정을 바꾸는 방법은 conf 디렉터리에 있는 httpd.conf 파일을 수정하는 것이다.

중간에 보면

ThreadsPerChild 250
MaxRequestsPerChild 0

ThreadsPerChild는 웹 서버가 사용한ㄴ 스레드의 개수를 지정한다. 위와 같이 지정하면, 아파치 프로세스 하나당 250개의 스레드가 만들어진다. 이 숫자가 커야 서버가 더 많은 사용자의 요청을 받을 수 있다.

MaxRequsetsPerChild는 최대 요청 개수를 지정하는 부분이다. 0이면 그 수에 제한을 두지 않겠다는 의미. 만약 이 값을 10으로 두면, 그 이상의 처리는 하지 않는다. 가급적 기본값 0을 두고 사용하길 권장.

스레드와 관련된 내용을 보다 세밀히 지정하려면 httpd.conf 파일의 #으로 주석 처리되어 있는 "Include conf/extra/httpd-mpm.conf"를 찾아 주석해제한다. 이렇게 하면 세밀한 스레드 설정 정보를 httpd-mpm.conf를 통해서 지정할 수 있게 된다. 스레드 방식을 사용하기 위해서는 worker 부분을 수정해 주어야 한다.

<IFModule mpm_worker_module>
StartServers 2
MaxClients 150
MinSpareThreads 25
MaxSpareThreads 75
ThreadsPerChild 25
MaxRequestsPerChild 0

  • StartServers: 서버에 띄울 때 프로세스의 개수르 지정한다. 보통 이야기하는 child 프로세스의 개수를 이야기한다.
  • MaxClients: 최대 처리 가능한 클라이언트의 수를 지정한다.
  • MinSpareThreads: 최소 여유 스레드 수를 지정한다.
  • MaxSpareThreads: 최대 여유 스레드 수를 지정한다.
  • ThreadsPerChild: 프로세스당 스레드 수를 지정한다.
  • MaxReqestsPerChild: 앞서 이야기한 MaxrequestsPerChild와 같은 의미

여기에서 프로세스 수는 2개이고, 프로세스 당 스레드 수가 25이므로 기본적으로 50개의 요청을 처리할 수 있다. 또한 최대 여유 스레드는 75개 이므로, 최대 사용 가능한 클라이언트 수는 150이 된다. 150명 이상의 요청은 서버 리소스에 여유가 있어도 처리 안한다.

이렇게 설정 되어있을 때 사용자가 늘거나 WAS가 멈추면 어떤 상황이 펼쳐지냐. 어떤 서비스가 초당 150명의 요청을 받고 있다고 가정해보자. 이 상황에서 웹 서버에서 150명의 요청을 받고, 그 요청을 전달받은 WAS도 마찬가지로 150명의 요청을 받는다. ㄱ런데, 자바는 GC를 할 때 JVM 자체가 멈춘다. 만약 이 GC가 2초 걸리면 어떻게 될까? 아파치 웹 서버에 총 300명의 요청이 기다리게 될 것이다. 그런데 GC를 하는 동안 WAS가 멈추기 때문에 새로운 연결을 할 수 없다. 이 경우 Tomcat에서는 AJP Connector라는 웹 서버와 WAS 사이의 커넥터에 설정한 backlog 라는 값의 영향을 바든다. 만약 이 값을 설정하지 않으며녀 기본값은 100이다. 즉, WAS가 응답하지 않을 때 100개의 요청까지 큐에 담아둔다는 말이다. 따라서, 이 100개를 넘는 요청들은 503(Service Unabailable)이라는 HTTP 헤더 코드 값을 리턴 받게 된다. 이러한 값 받지 않으려면 다음과 같은 조치 취함

  • 서버를 늘린다. 가장 편한 방법. 금전적인 여유가 있을 때
  • 서버를 튜닝한다. 서비스가 응답이 안 되는 원인을 찾고 튜닝한다. 그 원인 찾는데 몇 시간, 몇 달이 소요될 수 있다.
  • GC를 튜닝한다. 만약 GC가 오래 소요되어 응답이 안될 경우 GC 튜닝을 한다.
  • 각종 옵션 값을 튜닝한다. 가장 간단할 수도 있다. 잘못 설정하면 훨 낭패고. 웹 서버 및 WAS 전문가나 엔지니어와 상담하자.

서버 환경이 좋다면 httpd.conf 같은 파일에 최대한 자원 잘 사용하도록 설정하자.

웹 서버의 Keep Alive

모든 웹 서버를 설정할 때 또 한 가지 중요한 값이 있다. 바로 KeepAlive 설정이다. 아파치 웹 서버의 경우, httpd.conf 파일에 다음의 설정이 없으면간단하게 추가하자 KeepAlive On

웹 서버와 웹 브라우저가 연결이 되었을 때 KeepAlive 기능이 켜져 있지 않으면 매번 HTTP 연결을 맺었다 끊었다 하는 작업을 반복한다. 초기 화면이 매우 간단한 구글과 가은 사이트는 해당 사이트에 연결할 때 KeepAlive가 적용되지 않더라도 그리 느리지 않을 것이다. 하지만 네이버나 다음과 같이 초기 화면에서 엄청나게 많은 이미지와 CSS 등의 파일을 받아야하는 사이트에 KeepAlive 옵션이 적용되어 있지 않다면, 초기 화면을 띄우는 데 몇 분씩 소욜될지도 모른다. 즉, 이미지와 같은 모든 개체들도 서버에 매번 접속을 해야하는 상황이 발생한다. 하지만 KeepAlive 기능이 켜져있으면 두 개 정도의 연결을 열어서 끊지 않고, 연결을 계속 재사용한다. 이렇게 되면 연결을 하기 위한 대기시간이 짧아지기 때문에 사용자가 느끼는 응답 속도도 엄청나게 빨라진다.

  • 참고 사용자의 접근이 많은 사이트에서는 이미지나 CSS와 같이 정적인 파일들을 일반 웹 서버에서 처리하지 않고, CDN(Content Delivery Network)라는 서비스를 사용한다. 즉, 별도의 URL에서 해당 컨텐츠들을 내려받도록 설정하고, 동적인 컨텐츠들은 WAS 에서 처리하도록 해 놓으면 Web_WAS 서버의 부담도 줄어든다.

KeepAlive 설정을 할 때 반드시 같이 해야하는 설정이 있다. 바로 KeepAliveTimeout 설정이다. 초 단위로 KeepAlive가 끊기는 시간을 설정한다. 연결이 끝난 이후 다음 연결이 될 때까지 얼마나 기다릴지.

KeepAliveTimeout 15

만약 사용자가 너무 많아 접속이 잘 안 될 경우, 이 설정을 5초 저도로 짧게 주는 것도 서버의 리소스를 보다 효율적으로 사용할 수 있는 방법이다. 추가로 서비스의 상황에 따라 KeepAlive 옵셔을 껐을 때 더 좋은 성능이 나오게 되는 경우가 존재한다. 상황에 맞게 사용하자.

DB Connection Pool 및 스레드 개수 설정

WAS에서 설정해야 하는 값들은 너무나 많다. 그 중 가장 성능에 많은 영향을 주는 DB Connection Pool과 스레드 개수가 있다.
이 두 항목의 개수는 메모리와관련이 있다. 많ㅇ 사용할 수록 메모리를 많이 점유하게 된다. 그렇다고 적게 지정하면, 서버에서는 많은 요청을 처리하지 못하고 대기할 수 밖에 없다.

대부분의 WAS에서는 DB Connection Pool의 개수를 최소치, 중가치, 최대치 드ㅡㅇ으로 자세하게 지정할 수 있다. 최소치는 말 그대로 서버가 기동될 때 연결을 수행하는 개수이다. 개발자용 PC는 이 값이 높을 필요가 없다. 최소 개수가 많으면 많을 수록 서버 기동ㅇ하는 시간이 오래 걸린다. 개발자가 디버그를 하기 우해 여러 번 재기동을 할 때는 좋지 않다. 하지만 운영 중에는 최소 및 최대 값을 동일하게 하는 것이 좋다. 사용자 수가 갑자기 증가하면 Pool의 개수도 증가되어야 하고, ㅈㅇ가할 때 대기 시간이 발생할 확류이 크기 때문이다. DB 서버의 리소스가 부족하다면 치소값을 적게 해 놓는 것도 한 방법이 될 수 있다.

ㄷ부분 WAS에서 두 설정 값의 기본 개수가 10

20개 정도다. 따라서 기본값으로 서비스를 오픈하면 서버가 원하는 요청량을 처리하지 못하게 된다. DB Connetion Pool은 보통 40

50개로 지정하며, 스레드 개수는 이보다 10개 정도 더 지정한다. 이렇게 지정하는 이유는, 스레드 개수가 DB Connection Pool의 개수보다 적으면 적은 수만큼의 연결은 필요없기 때문이다. 스레드의 수가 DB 연결 개수보다 많아야 하는 이유는 뭘까. 모든 애플리케이션이나 화면이 DB에 접속하는 것은 아니다. 또한 관리자 콘솔을 사용하여 서버에 접속할 수도 있기 때문에, 그만큼 여유분을 갖도록 지저하는 것이 보통이다. 가장 적합한 DB Connection Pool과 스레드 수는 몇일까. 물론 사왕에 마다 다르다. 서버의 상황에 맞게 값을 지정해야한다. 가장 좋은 방법은 성능 테스트를 통해 적절한 값을 구하는 것.

DB Connection Pool의 개수를 기준으로 적절한 수치를 찾는 방법을 생각해보자. DB Connection Pool을 40개로잡아 놓았다 가저하자. 40개 전부를 사용하면서 DB의 CPU 사용량이 100%에 도달했ㄷ마ㅕㄴ, DB의 CPU를 점유하는 쿼리를 찾아 튜닝을 수행해야한다. 인덱스가 없거나 테이블을 풀 스캔하는 쿼리가있는 것은 아닌지 쿼리의 플랜을 떠서 확인해야한다. 40개를 전부 사용한다고해서 DB Connection Pool 개수를 80~200개로 늘리면, 모든 DB와의 연결을 전부 사용하고 응답 시간은 엄청나게 느려질 뿐이다.

이번에 DB의 CPU 사용량은 50%도 되지 않는 상황에서 WAS의 CPU 사용량이 100%에 도달하고 있다 생각해보자. 그 때 사용하는 DB Connection Pool의 개수는 20개 정도 밖에 안된다. 이럴 땐 WAS의 애프리케이션을 튜닝ㅎ애야한다. 이미 튜닝된 상태라면 이 서버의 DB Connection Pool의 개수는 약간 여유를 두기 위ㅐ서 25~30개 정도로 지정하는 게 좋다.

서버를 늘리는건 맨 마지막에 하자. Connection Pool의 개수만큼 중요한 값이 있다. 바로 대기시간과 관련된 값이다.MyBatis, JPA같이 DB와 자바 프로그램을 매핑해주는 프레임워크에는 각종 설정 값이있는데, 이 값 중 대기 시간을 나타내는 wait time과 관련된 값들이 존재한다. DB Connection Pool의 개수를 넘어 섰을때 애플리케이션은 어디남는 커넥션 없는지 기다리는데 이 기다리는 시간이 바로대기 시간이다. MyBatis에서는 poolTimeToWait이라는 값으로 이대기 시간을 결정하며 기본 20초다. 다시 말해서 이 값을 그냥 놔 둘 경우 DB 연결을 못해 기다리는 사용자들이 적어도 20초는 대기해야한다. 대기 시간을 너무 짧게 주면 문제가 없을까. GC튜닝을 해본 바로 메모리를 1GB로 할당한 WAS에서 300 ms 이하의 Full GC 시간을 만들기는 매우 어렵다. 만약 DB ㅇㄴ결을 하려고 대기하는 순간 Full GC가 발생하면 그 순간에 대기하고 있는 모든 스레드는 DB와 연결을 못했다고 Timeout을 내뿜을 수 있다.

결론적으로 DB와 연동하는 프레임워크를 설정할 땐 Pool의 개수, Wait과 관련된 값이 들어간 값, 그리고 여기서 자세히 설명하지는 않았지만 Timeout 관련된 설정들을 설정하다보면 예기치 못하게 오류 페이지를 보낼 수 있따.

WAS 인스턴스 개수 설정

인스턴스를 너무 많이 생성ㅇ해도 CPU 코어수가 그에 따라주지 않으니 서로 CPU를 차지하기 위한 경합이 벌어지고 인스턴스의 수에 비해 성능이 좋지 못한 결과가 나온다. 보통 한 두개의 CPU 당 하나의 인스턴스를 지정하는 것이 좋다 한다. 근데 장비 하나 당 인스턴스 개수는 성능테스트를 통해서 구하는게 젤 바람직하다한다. 인스턴스 수 늘려도 별차이없다면 적정한 수로 타협해라. 코드는 늘 수정되는데 인스턴스 수 많을 수록 배포하기 어렵고, 모니터링 장애 상황 시 문제 찾고 해결하기 힘들다.

WAS 장비에 4GB의 여유 메모리가 있다고 해서 하나의 인스턴스에 4GB 다 주지마라 Full GC 시 시간 더 걸린다. 가급적 512MB, 2GB에서 지정(꽤 예전에 쓰인 책인데 지금도 유효한 수치일지 모르겠다.)

단독 인스턴스 사용은 피하라. 두 개 이상 으로 클러스터링 지정하여 사용자의 세션 정보를 공유하도록 하는 것이 좋다.

스레드나 DB Connection Pool의 개수를 100개 이상으로 증가시켜서 성능이 좋아진 경우, 그렇지 않은 경우 존재. 대부분 월등히 좋아지지는 않는다.

Session Timeout 시간 설정

WAS 종속 스펙은 아니고 서블릿 스펙에 정의된 표준 설정 값이다.

30

분 단위이며 이 동안 요청 없음 세션을 메모리에서 제거한다. 이 설정하지 않고, WAS에서 따로 설정한 바가 없나 세션 객체의 invalidate() 수행되지 않으면 세션은 삭제되지 않으므로 유의하자.

댓글