티스토리 뷰

728x90
반응형

포스팅은 다양한 성능 저하 현상을 분석하고 해결책을 제시해 보도록 하겠습니다.


성능 저하를 일으키는 요소는 매우 다양합니다. 직접 겪어 본 Case, application, JVM, 앞으로 발생 될 가능성이 있는 Case, DB, WAS, WEB, Network, OS 등등 워낙 많은 분야에서 성능을 저하시키는 요소가 있고 이를 해결하기 위해 각 분야의 전문 엔지니어가 존재합니다. 아래 내용들은 직접 본인이 직접 겪어 본 내용을 포함하여 다양한 성능 저하요소를 포함하고 있습니다.

먼저 Slow down in JVM 현상에 대한 내용입니다.

WAS의 성능에 큰 영향을 주는 것 중의 하나가 JVM입니다.

JVM의 튜닝 여부에 따라서 WAS상에서 작동하는 Ap의 성능을 크게는 20~30% 까지 향상시킬 수 있는데, 우리가 지금 살펴 보고 있는 slow down과 hangup 을 일으키는 직접적인 요인이 되는 것은 JVM의 Full GC입니다.

간단하게 JVM의 메모리 구조를 검토하고 넘어가 보도록 하겠습니다.

JVM은 크게 New영역과 Old영역, 그리고 Perm영역 3가지로 분류 됩니다.
Perm 영역
- Perm 영역은 Class나 Method들이 로딩되는 영역이고 성능상의 영향을 거의 미치지 않습니다.
- 우리가 주목해야 할 부분은 객체의 생성과 저장에 관련되는 New와 Old 영역인데, 모든 객체는 생성이 되자 마자 New 영역에 저장되고, 시간이 지남에 따라 이 객체들은 Old 영역으로 이동됩니다.
New 영역
- New 영역을 Clear하는 과정을 Minor GC라하고, Old 영역을 Clear하는 과정은 Major GC또는 Full GC라 하는데, 성능상의 문제는 이 Full 영역에서 발생합니다.
- Minor GC의 경우는 1초 이내에 아주 고속으로 이뤄지는 작업이기 때문에, 신경을 쓸 필요가 없지만, Full GC의 경우에는 시간이 매우 오래 걸립니다.
- Full GC가 발생할 동안은 Application이 순간적으로 멈춰 버리기 때문에 시스템이 순간적으로 Hangup 으로 보이거나 또는 Full GC가 끝나면서 갑자기 request가 몰려버리는 현상 때문에 종종 System의 장애를 발생시키는 경우가 있습니다.
Old 영역
- Full GC는 통상 1회에 3~5초 정도가 적절하고, 보통 하루에 JVM Instance당 5회 이내가 적절하다고 여겨집니다.
- Full GC가 자주 일어나는 것이 문제가 될 경우에는 JVM의 Heap영역을 늘려주면 천천히 일어나지만 반대로 Full GC에 소요되는 시간이 증가합니다.
- 개당 Full GC 시간이 오래 걸릴 경우에는 JVM의 Heap 영역을 줄여주면 빨리 Full GC가 끝나지만 반대로 Full GC가 자주 일어난다는 단점이 있습니다.
그래서 이 부분에 대한 적절한 Tuning이 필요합니다.

대부분의 Full GC로 인한 문제는 JVM자체나 WAS의 문제이기 보다는 그 위에서 구성된 Application이 잘못 구성되어 메모리를 과도하게 사용하거나 오래 점유하는 경우가 있습니다. 예를 들어 대용량 DBMS Query의 결과를 WAS상의 메모리에 보관하거나 , 또는 Session에 대량의 데이터를 넣는 것들이 대표적인 예가 될 수가 있습니다.

다음으로 Slow down analysis in DBMS에 대한 부분입니다.

Application이 느려지는 원인중의 많은 부분을 차지 하고 있는 것은 DBMS의 성능 문제가 있는 경우가 많습니다.

흔히들 DBMS Tuning을 받았더니 성능이 많이 향상되었다고 하는 경우가 많은데, 그건 그만큼 DB 설계를 제대로 하지 못했다는 이야기가 됩니다.
DBMS 자체 Tuning에 대한 것은 이 문서와는 논외이기 때문에 제외하기로 하고, DBMS에 전송되는 각각의 SQL문장의 실행 시간을 Trace할 수 있는 것만으로도 문제가 되는 SQL을 찾아 수정함으로써 많은 성능 향상을 기대할 수 있습니다.

세번째로 Slow down analysis in Webserver & network에 대한 부분입니다.

WAS와 DBMS 앞단에는 WebServer와 Network 이 있기 때문에 이 Layer에서 문제가 되면 속도저하를 가지고 올 수 있습니다.

경험상 대부분의 slow down이나 hangup은 이부분에서는 거의 일어나지 않지만 성능상에 종종 영향을 주는 Factor가 있습니다.

먼저 WebServer 와Client간의 KeepAlive에 대한 부분입니다.

WebBrowser와 WebServer간에는 KeepAlive 설정을 하는것이 좋은데. 그 이유는 WebBrowser에서 하나의 HTML 페이지를 로딩하기 위해서는 Image와 CSS등의 여러 파일 등을 로딩하는데, KeepAlive 설정이 없으면 각각의 파일을 로딩하는것에 각각의 Connection을 open, request, download, close를 합니다.
알고 있겠지만 Socket을 open하고 close 하는 데에는 많은 자원이 소요됩니다.


그래서 한번 연결해 놓은 Connection을 계속 이용해서 HTTP data를 주고 받는 설정이 KeepAlive입니다.
이 KeepAlive 설정은 웹을 이용한 서비스 제공에서 많은 성능 변화를 주기 때문에 특별한 이유가 없는 한 KeepAlive 설정을 유지해야 합니다.
설정 방법은 각 WebServer의 매뉴얼을 참고하기 바랍니다.

 

다음으로 WebServer와 WAS간의 KeepAlive에 대한 부분입니다.

WebServer와 WAS간에는 WebServer에서 받은 request를 forward하기 위해서 WebServer Side에 WAS와 통신을 하기 위한 plug-in 이라는 모듈을 설치하게 됩니다. 이 역시 WebServer와 Client간의 통신과의 같은 원리로 KeepAlive를 설정하게 되는데, 이 역시 성능에 영향을 줄 수 있는 부분이기 때문에 가급적이면 설정하기를 권장합니다.
※ WAS에서 Webserver와의 KeepAlive설정은 Default는 KeepAlive가 True로 설정되어 있습니다.

다음으로 OS에서 Kernel Parameter 설정에 대한 부분입니다.

OS의 TCP/IP Parameter와, Thread와 Process등의 Kernel Parameter 설정이 운영에 있어서 영향을 미치는 경우가 있습니다. 이 Parameter들은 Tuning하기가 쉽지 않기 때문에, WAS또는 OS Vendor에서 제공하는 문서를 통해서 Tuning해야 합니다.

 

네번째로 Common mistake in developing J2EE Application에 대한 내용입니다.
지금까지 간단하게나마 J2EE Application의 병목구간을 분석하는 부분에 대해서 알아보았습니다.

대부분의 병목은 Application에서 발생하는 경우가 많은데, 이런 병목을 유발하는 Application에 자주 발생하는 개발상의 실수를 정리해보도록 하겠습니다.

먼저 Java Programming에 의한 병목 유발 현상입니다.

synchronized block은 위에서도 설명 했듯이 synchronized 메소드는 lock contention과 deadlock 등의 문제를 유발할 수 있습니다. 꼭 필요한 경우에는 사용을 해야 하지만, 이점을 고려해서 Coding 해야 합니다.
String 연산의 불분명한 사용은 이미 많은 개발자들이 알고 있는 내용이겠지만 String 연산 특히 "+" 연산은 CPU를 매우 많이 소모하게 되고 종종 slow down의 직접적인 원인이 되는 경우가 매우 많습니다. String 연산이 필요한 경우에는 String 보다는 가급적 StringBuffer를 사용해야 합니다.
Socket & file handling은 FD (File Descriptor)를 사용하게 되는데, 이는 유한적인 자원이기 때문에 사용 후에 반드시 close명령을 이용해서 반환해야 합니다. 종종 close를 하지 않아서, FD가 모자라게 되는 경우가 많습니다.

다음으로 Servlet/JSP Programming에 의한 병목 유발 현상입니다.

Jsp 에서는 JSP의 출력 내용을 저장하는 buffer 사이즈를 지정할 수 있습니다.
이 buffer size는 출력 내용을 buffering했다가 출력하는데, 만약에 쓰고자 하는 내용이 Buffer size보다 클 경우에는 여러번에 걸쳐서 socket write가 일어나기 때문에 performance에 영향을 줄 수 있으므로 가능하다면 buffersize를 화면에 뿌리는 내용의 크기를 예측해서 지정해주는 것이 바람직합니다.
반대로 너무 큰 버퍼를 지정해버리면 메모리가 불필요하게 낭비될 수 있기 때문에 이점을 주의해야 합니다.
참고로 jsp page buffer size는 지정해 주지 않는 경우 default로 8K로 지정됩니다.

 

member variable은 Servlet/JSP는 기본적으로 Multi Thread로 동작하기 때문에, Servlet과 JSP 내에서 선언된 멤버 변수들은 각 Thread간에 공유가 됩니다. 그래서 이 변수들을 read/write할 경우에는 synchronized method로 구성해야 하는데, 이 synchronized는 속도 저하를 유발할 수 있기 때문에, member 변수로는 read만 하는 객체를 사용하는 게 좋습니다.
특히 Servlet이나 JSP에서 DataBase Connection을 멤버 변수로 선언하여 Thread간 공유하는 예가 있는데, 이는 별로좋지 않은 프로그래밍 방법이고, 이런 형태의 패턴은 Servlet이 단 하나만 실행되거나 하는 것과 같은 제약된 조건 아래에서만 사용해야 합니다.

 

JSP에서 File upload control을 사용하는 경우가 많습니다. 이 control을 구현하는 과정에서 upload되는 파일 내용을 몽땅 메모리에 저장했다가 업로드가 끝나면 한꺼번에 file에 writing 하는 경우가 있는데, 이는 큰 사이즈의 파일을 업로드 할 때, 파일 사이즈만큼의 메모리 용량을 요구하기 때문에, 자칫하면 Out Of Memory 에러를 발생 시킬 수 있습니다. File upload는 buffer를 만들어서 읽고, 파일에 쓰는 작업을 병행하도록 해야 합니다.

다음으로 JDBC Programming에 의한 병목 유발 현상입니다.

JDBC Programming에서 가장 대표적으로 발생되는 문제가 Connection Leak입니다. Database Connection을 사용한 후에 close하지 않아서 생기는 문제인데, Exception이 발생하였을 때도 반드시 Connection을 close하도록 해줘야 합니다. 

 

SQL문장을 Query하고 나오는 resultset을 사용할 때, 모든 resultset의 결과를 Vector나 hashtable등을 이용해서 메모리에 저장해놓는 경우가 있습니다. 이런 경우에는 평소에는 문제가 없지만, SQL Query의 결과가 10만 건이 넘는 것과 같은 대용량일 때 이 모든 데이터를 메모리 상에 저장하려면 Out Of Memory가 나옵니다.
Query의 결과값을 처리할 때는 ResultSet을 직접 리턴 받아서 사용하는 것이 메모리 활용면에서 좀더 바람직합니다.

 

JDBC에서 Resultset이나 Statement 객체는 기본적으로 Connection을 close하게 되면 자동으로 닫히게 됩니다. 그러나 WAS나 Servlet Container의 경우에는 성능향상을 위해서 Connection Pooling 기법을 이용해서 Connection을 관리하기 때문에 Connection Pooling에서 Connection을 close하는 것은 실제로 close하는 것이 아니라 Pool에 반환하는 과정이기 때문에 해당 Connection에 연계되어 사용되고 있는 Statement나 ResultSet이 닫히지 않습니다.
Connection Pooling에서 Statement와 ResultSet을 사용후에 닫아주지 않으면 Oracle에서 too many open cursor와 같은 에러가 나오게 됩니다. (Statement는 DB의 Cursor와 mapping이 됩니다.)

다음으로 EJB Programming에 대한 병목 현상입니다.

EJB는 분명 강력하고 유용한 개발 기술임에는 틀림이 없다. 그러나 EJB의 장점과 용도를 모르고 사용하면 오히려 안쓰는것만 못한 경우가 많습니다. 각 EJB 모델 (Session Bean, Entity Bean)이 어떤 때 유용한지를 알고 사용하고, 정확한 Transaction Model등을 결정해서 사용해야 합니다.


위에서도 설명했듯이 EJB의 Home Interface를 lookup 해오는 과정은 객체의 Serialization/DeSerialization을 동반하기 때문에, 시스템 성능에 영향을 줄 수 있습니다. EJB Home을 한번 look up한 후에는 Hashtable등에 저장해서 반복해서 Remote Call(Serialization / DeSerialization)하는 것을 줄이는 게 좋습니다.

 

WAS Vendor 마다 WAS 운영 중에 EJB를 Deploy할 수 있는 HotDeploy 기능을 제공합니다. 그러나 이는 J2EE spec에 있는 구현이 아니라 각 vendor마다 개발의 편의성을 위해서 제공하는 기능이다. 운영 중에 EJB를 내렸다 올리는 것은 위험합니다. (Transaction이 수행 중이기 때문에) Hot Deploy 기능은 개발 중에만 사용하도록 권고합니다.

마지막으로 JVM Memory tuning에 대한 병목 현상입니다.

Application을 개발해 놓고, 운영환경으로 staging할 때 별도의 JVM 튜닝을 하지 않는 경우가 많습니다. 튜닝이 아니더라도 최소한의 메모리 사이즈와 HotSpot VM 모델 (server/client)은 설정해줘야지 어느 정도의 Application의 성능을 보장 받을 수 있습니다. 최소한 메모리 사이즈와 VM모델정도는 설정을 해주고 운영을 하도록 합니다.

결론입니다.
J2EE Application의 병목구간을 확인하기 위해서는 그 문제를 발견하고 툴과 경험을 이용해서 문제의 원인을 발견하고 제해야 합니다.
대부분의 WAS또는 User Application의 slow down이나 hang up은 Thread dump를 통한 분석을 통해서 대부분 발견 및 해결을 할 수 있습니다.
그 외에 부분 JVM이나 WebServer, Network 등에 대해서는 별도의 경험과 Log 분석 등을 알아내야 하고 DB에 의한 slowdown이나 hang up 현상은 DB 자체의 분석이 필요합니다.

본 포스팅을 통해 성능 저하 현상 발생 시 유연하게 대응 할 수 있는 엔지니어가 되었으면 합니다.

맙습니다.

728x90
반응형