j2ee
Hot deploy for enterprise Java architecture without vendor specialized technology
Main-Frame시대에서 hot deployment는 당연한 기술적 접근이었다. 업무 수행중 버그가 발견되고 긴급히 patch해야하는 경우 cobol 소스를 바로 수정하여 운영계에 배포하면 운영중지 없지 즉시 반영되어 업무를 수행할 수 있었다. 그러나 Java에서는 이 Hot Deployment는 해결되어지지 않는 영원한 숙제처럼 여겨진다. Web Application Server Vendor들이 나름대로의 기술적 안을 가지고 Hot Deployment를 지원한다고 하지만 실제 Enterprise에서 이를 사용하는 경우는 거의 존재하지 않는다. 설령 된다고 할 지라도 여러 기술적 한계와 다른 특성들과 결합될 때 운영상에 많은 문제점들이 발견되고 또한 Vendor자체가 제한하는 기술적 요소 발생한다. 따라서 hot deployment는 Java영역에서는 풀리지 않는 신비? 또는 넘을 수 없는 장벽? 된다고 정말 되는거야? 라고 의아해 하는 기술이 되어 버렸다. 그러나 방법이 없는 것은 아니다. 운영중 무정지 상태로 긴급 반영 되어야 하는 업무 application을 JVM restart또는 Web Application Context Reload없이 지원할 수 있는 기술적 대안이 있다. Hot deployment의 대안이 될 수 있는 이 기술에 대한 방법과 기술적 근거, 아키텍처를 살펴 보도록 하겠다. About hot deployment
Idea, idea, idea
Technical Architecture
Benefit and limitations of the idea
Demo Q&A |
CORBA Technology and the Java Platform
J2EE
|
Tackling Misconceptions About Spring
Posted by Scott Delap on Aug 08, 2006 08:36 AM
Spring has transitioned from a bleeding edge project to widely used component of enterprise applications written in Java today. As with any popular project misconceptions start to arise. Steve Anglin recently blogged on oreillynet.com about 10 common misconceptions developers have about Spring, summarized here:
... the Spring Framework has been designed from day one to be non-invasive and modular. You can and should choose only what is needed for your particular project, but as requirements evolve, you can incrementally add (or remove) modules. The Spring team emphasizes keeping things as simple as possible ...2. Spring is overkill for simple applications. As mentioned in the previous point, the Spring Framework can help you to keep things as simple as possible. You can use only what you need initially yet adapt and extend your application as requirements become more clear.... 3. Spring does not scale to handle very large applications 4. Spring forces you to use Aspect-Oriented Programming (AOP) which is experimental. 5. Spring replaces Java Enterprise Edition (JEE). 6. Spring and EJB are mutually exclusive. 8. For a large application, Spring’s XML can become a maintenance nightmare. 9. Spring does everything with reflection, so it is slow. 10. Spring MVC is complex and not as simple as the rest of Spring. |
Spring WS에서 Why Contract fist?
Spring Webservice 를 공부하다 Object-Relational Impedance Mismatch 내용을 보게 되었다. 즉 RDBMS의 Entity가 Object화 될 때 생기는 문제가 반드시 존재하게 되고 이를 해결하기 위해서는 데이터 베이스 디자인과 개발자 사이에 매우 긴밀하고 완전한 기술적인 이해와 상호 협력이 필요하는 것이다. 따라서 이는 현실적으로 어려우므로 RDBMS를 객체지향중심으로 표현하기 위해서는 결국 임피던스 불일치가 있을 수 밖에 없다. 따라서 스프링은 이 문제와 더불어 구조적 언어인 xml, xsd와 같은곳에서 표현하게 되는 restrction과 같은 표현은 사실상 object에서는 구현이 불가하게 된다. 이 문제를 해결하기 위해서는 결국 WSDL contract를 먼저 수행하고 java를 implement하게 된다. 따라서 Spring이 구현한 것은 바로 Contract First를 사용한다고 하니 참 재미난 내용이다. http://static.springframework.org/spring-ws/sites/1.5/reference/html/why-contract-first.html |
Tips for improving Spring’s start up performance
Someone is blathering on again about how Spring sucks, and so startup performance came up in the conversation. CXF uses Spring under the covers by default (it’s optional, don’t worry). Initially there was some major slowness, but I’ve spent a lot of time profiling/improving CXF’s startup time. I thought I’d share what I learned so you can improve your startup time as well:
If all goes well, you should be able to get the Spring startup time pretty low. At this point its probably other things, like your ORM layer (*cough* Hibernate *cough*), that are slowing things down. Random tidbit: I was profiling the CXF startup the other day. About 33% of the time is in Introspector.getBeanInfo(), so its really not all Spring’s fault IMO :-). Maybe someone can hack the JDK to be faster there? |
Scaling Your Java EE Applications
If an application is useful, then the network of users will grow crazily fast at some point. As more and more mission-critical applications are now running on Java EE, many Java developers are caring about scalability issues. However, most of popular Web 2.0 sites are built with script languages, and there are a lot of voices to doubt the scalability of Java Applications. In this article, Wang Yu takes real world cases as examples to explain ways on how to scale Java applications based on his experiences on the laboratory projects, and at the same time, bring together practice, science, algorithms, frameworks, and experience on failed projects, to help readers on building high scalable Java applications. |
J2EE Clustering
Discuss this ArticlePrefaceMore and more mission-critical and large scale applications are now running on Java 2, Enterprise Edition (J2EE). Those mission-critical applications such as banking and billing ask for more high availability (HA), while those large scale systems such as Google and Yahoo ask for more scalability. The importance of high availability and scalability in today's increasingly inter-connected world can be proved by a well known incident: a 22-hour service outage of eBay in June 1999, caused an interruption of around 2.3 million auctions, and made a 9.2 percent drop in eBay's stock value. J2EE clustering is a popular technology to provide high available and scalable services with fault tolerance. But due to the lack of support from the J2EE specification, J2EE vendors implement clustering differently, which causes a lot of trouble for J2EE architects and developers. Following questions are common:
The best way to understand the limitations and considerations is to study their implementations and uncover the hood of J2EE clustering. Basic TerminologyIt makes sense to understand the different concepts and issues that underlie clustering technology before we discuss the different implementations. I hope this will not only give you the foundation necessary to understand various design issues and concepts in J2EE clustering products, but will also frame the various issues that differentiate clustering implementations and make them easier to understand as well. ScalabilityIn some large-scale systems, it is hard to predict the number and behavior of end users. Scalability refers to a system’s ability to support fast increasing numbers of users. The intuitive way to scale up the number of concurrent sessions handled by a server is to add resources (memory, CPU or hard disk) to it. Clustering is an alternative way to resolve the scalability issue. It allows a group of servers to share the heavy tasks, and operate as a single server logically. High AvailabilityThe single-server’s solution (add memory and CPU) to scalability is not a robust one because of its single point of failure. Those mission-critical applications such as banking and billing cannot tolerate service outage even for one single minute. It is required that those services are accessible with reasonable/predictable response times at any time. Clustering is a solution to achieve this kind of high availability by providing redundant servers in the cluster in case one server fails to provide service. Load balancingLoad balancing is one of the key technologies behind clustering, which is a way to obtain high availability and better performance by dispatching incoming requests to different servers. A load balancer can be anything from a simple Servlet or Plug-in (a Linux box using ipchains to do the work, for example), to expensive hardware with an SSL accelerator embedded in it. In addition to dispatching requests, a load balancer should perform some other important tasks such as “session stickiness” to have a user session live entirely on one server and “health check” (or “heartbeat”) to prevent dispatching requests to a failing server. Sometimes the load balancer will participant in the “Failover” process, which will be mentioned later. Fault ToleranceHighly available data is not necessarily strictly correct data. In a J2EE cluster, when a server instance fails, the service is still available, because new requests can be handled by other redundant server instances in the cluster. But the requests which are in processing in the failed server when the server is failing may not get the correct data, whereas a fault tolerant service always guarantees strictly correct behavior despite a certain number of faults. FailoverFailover is another key technology behind clustering to achieve fault tolerance. By choosing another node in the cluster, the process will continue when the original node fails. Failing over to another node can be coded explicitly or performed automatically by the underlying platform which transparently reroutes communication to another server. Idempotent methodsPronounced “i-dim-po-tent”, these are methods that can be called repeatedly with the same arguments and achieve the same results. These methods shouldn’t impact the state of the system and can be called repeatedly without worry of altering the system. For example, “getUsername()” method is an idempotent one, while “deleteFile()” method isn’t. Idempotency is an important concept when discussing HTTP Session failover and EJB failov |
Powerful EJB3.1
Guide to the EJB 3.1 Public Draft Now that the EJB 3.1 Public Draft is available, here's a list of its main ease-of-use improvements and features, along with their corresponding sections in the spec. This should make it easier to quickly find some of the most relevant material. While it's true that the spec is a bit, shall we say, on the "large" side, I think you'll find that most of the new content is concentrated in a relatively small number of pages. Happy reviewing :-)
|
How to find bottleneck in J2EE application
How to find bottleneck in J2EE application 자바스터디 네트워크 [www.javastudy.co.kr] 조대협 [bcho_N_O_SPAM@j2eestudy.co.kr]
일반적으로 J2ee application을 서비스 하기 위해서는 아래와 같은 구조를 가지게 된다.
먼저 용어를 정의하도록 하자.. 시스템이 느려지거나 멈추는 현상에 대해서 아래와 같이 용어를 정의하자 - Hang up : Server Instance는 실행되고 있느나, 아무런 응답이 없는 상황 (멈춤 상태) 이 Hangup과 slowdown현상은, 대부분이 그림 1에서 설명한 다섯가지 요소중 하나 이상의 병목으로 인해서 발생한다. 즉, 이 병목 구간을 발견하고, 그 구간을 제거하면 정상적으로 시스템을 운영할 수 있게 되는것이다.
2-1. WAS의 기본 구조 이 병목 구간을 발견하는 방법에 앞서서, 먼저 WAS 시스템의 기본적인 내부 구조를 이해할 필요가 있다.
Queue에 저장된 내용은 WAS에서 Request를 처리할 수 있는 Working Thread들이 있을때 각각의 Thread들이 Queue에서 Request를 하나씩 꺼내서 각각의 Thread들이 그 기능을 수행한다. 여기서 우리가 주의깊게 봐야하는것은 Thread pooling이라는 것인데. WAS에서는 일반적으로 업무의 성격마다 이 Thread Pool을 나누어서 만들어놓고 사용을 한다. 즉 예를 들어서 금융 시스템의 예금 시스템과 보험 시스템이 하나의 WAS에서 동작할때, 각각의 업무를 Thread Pool을 나누어서 분리하는 방식이다. 이 방식을 사용하면 업무의 부하에 따라서 각 Thread Pool의 Thread수를 조정할 수 있으며, 만약에 Thread가 모자르거나 deadlock등의 장애사항이 생기더라도 그것은 하나의 Thread Pool에만 국한되는 이야기이기 때문에, 다른 업무에 영향을 거의 주지 않는다. 2-2. Thread Dump를 통한 WAS의 병목 구간 분석 위에서 살펴봤듯이, WAS는 기본적으로 Thread 기반으로 작동하는 Application이다. 우리가 hang up이나 slow down은 대부분의 경우가 WAS에서 현상이 보이는 경우가 많다. (WAS에 원인이 있다는 소리가 아니라.. 다른 요인에 의해서라도 WAS의 처리 속도가 느려질 수 있다는 이야기다.) 먼저 이 WAS가 내부적으로 어떤 Application을 진행하고 있는지를 알아내면 병목 구간에 한층 쉽게 접근할 수가 있다. 1) What is thread dump? 이를 위해서 JVM에서는 Java Thread Dump라는 것을 제공한다. Thread dump를 추출하기 위해서는 Thread dump는 Application을 구성하고 있는 현재 구동중인 모든 Thread들의 각각의 상태를 출력해준다.
- Thread name - Thread ID - Thread Status - Program stack of thread 2) How to analysis thread dump 앞에서 Thread dump가 무엇이고, 어떤 정보를 가지고 있는지에 대해서 알아봤다. 그러면 이 Thread dump를 어떻게 분석을 하고, System의 Bottle neck을 찾아내는데 이용할지를 살펴보기로 하자. System이 Hangup이나 slowdown에 걸렸을때, 먼저 Thread dump를 추출해야 한다. 이때 한개가 아니라 3~5초 간격으로 5개 정도의 dump를 추출한다. 추출한 여러개의 dump를 연결하면 각 Thread가 시간별로 어떻게 변하고 있는지를 판별할 수 있다. 먼저 각각의 Thread 덤프를 비교해서 보면, 각각의 Thread는 적어도 1~2개의 덤프내에서 연속된 모습을 보여서는 안되는게 정상이다. 일반적으로Application은 내부적으로 매우 고속으로 처리되기 때문에, 하나의 Method에 1초이상 머물러 있는다는것은 거의 있을 수 없는 일이다. 예를 들어서 설명해보자 . 아래 <그림 3-3>의 프로그램을 보면 MY_THREAD_RUN()이라는 메소드에서 부터 MethodA()aMethodB()aMethodC() 를 차례로 호출하는 형태이다.
우리는 이 덤프만을 보고 methodC에서 무엇인가 잘못되었음을 유추할 수 있고, methodC의 소스코드를 분석함으로써 문제를 해결할 수 있다. 그렇다면 이제부터 Slow down과 Hang up현상을 유발하는 일반적인 유형과 그 Thread Dump의 모양에 대해서 알아보도록 하자. [CASE 1] lock contention 잘 알다싶이 Java 는 Multi Threading을 지원한다. 이 경우 공유영역을 보호하기 위해서 Synchronized method를 이용하는데, 우리가 흔히들 말하는 Locking이 이것이다. 예를 들어 Thread1이 Synchronized된 Method A의 Lock을 잡고 있는 경우, 다른 쓰레드들은 그 Method를 수행하기 위해서, 앞에서 Lock을 잡은 쓰레드가 그 Lock을 반환하기를 기다려야한다. <그림 3-4>
이처럼 여러개의 Thread가 하나의 Lock을 동시에 획득하려고 하는 상황을 Lock Contention 이라고 한다.
[CASE 1] 해결방안 이런 Lock Contention 상황은 Multi Threading환경에서는 어쩔 수 없이 발생하는 상황이기는 하지만, 이것이 문제가 되는 경우는 Synchronized Method의 실행 시간이 불필요하게 길거나, 불필요한 Synchronized문을 사용했을 때 발생한다. 그래서 이 문제를 해결하기 위해 불필요한 Sychronized Method의 사용을 자제하고, Synchronized block 안에서의 최적화된 Algorithm을 사용하는것이 필요하다. [CASE 2] dead lock 이 Locking으로 인해서 발생할 수 있는 또 다른 문제는 dead Lock 현상이다. 두개 이상의 쓰레드가 서로 Lock을 잡고 기다리는 “환형대기조건” 이 성립되었을때, 서로 Lock이 풀리지 않고 무한정 대기하는 현상을 이야기 한다. <그림 3-7>을 보면 Thread1은 MethodA를 수행하고 있는데, sychronized methodB를 호출하기전에 Thread2가 methodB가 끝나기를 기다리고 있다. 마찬가지로 Therad2는 Thread3가 수행하고 있는 methodC가 끝나기를 기다리고 있고, methodC에서는 Thread1에서 수행하고 있는 methodA가 끝나기를 기다리고 있다.
<그림 3-9>를 보자. IBM AIX의 Thread 덤프이다. 1)항목을 보면 현재 8번 쓰레드가 잡고 있는 Lock은 10번과 6번 쓰레드가 기다리고 있음을 알 수 있으며. Lock은 OracleConnection에 의해서 잡혀있음을 확인할 수 있다. (아래) <그림 3-9>의 2)번 항목을 보면 이 6번쓰레드는 OracleStatement에서 Lock을 잡고 있는데, 이 Lock을 8번 쓰레드가 기다리고 있다. (아래) 결과적으로 6번과 8번은 서로 Lock을 기다리고 있는 상황이 되어 Lock이 풀리지 않는 Dead Lock상황이 된다. [CASE 2] 해결 방안 Dead Lock의 해결 방안은 서로 Lock을 보고 있는것을 다른 Locking Object를 사용하거나 Lock의 방향 (Synchronized call의 방향)을 바꿔줌으로써 해결할 수 있다. User Application에서 발생한 경우에는 sychronized method의 호출 순서를 “환형대기조건”이 생기지 않도록 바꾸도록 하고. [CASE 3] wait for IO response 다음으로 많이 볼 수 있는 패턴은 각각의 Thread들이 IO Response를 기다리는데… IO작업에서 response가 느리게 와서 시스템 처리속도가 느려지는 경우가 있다.
<그림 3-11>은 각각의 Thread의 stack dump인데, 그 내용을 보면 ExecuteQuery를 수행한후에, Oracle로 부터 데이타를 read하기 위해 대기 하는것을 확인할 수 있다. 이런 현상은 주로 DB 사용시 대용량 Query(시간이 많이 걸리는)를 보냈거나, DB에 lock들에 의해서 발생하는 경우가 많으며, 그외에도 Socket이나 File을 사용하는 AP의 경우 IO 문제로 인해서 발생하는 경우가 많다. [CASE 3] 해결 방안 이 문제의 해결 방안은 Thread Dump에서 문제가 되는 부분을 발견한후에, 해당 소스코드나 시스템등에 접근하여 문제를 해결해야한다. [CASE 4] high CPU usage 시스템이 느려지거나 거의 멈추었을때, 우리가 초기에 해볼 수 있는 조치가 무엇인가 하면, 현재 시스템의 CPU 사용률을 체크해볼 필요가 있다. 해당 시스템의 CPU 사용률이 높은 경우, 문제가 되는경우가 종종 있는데. 이런 문제에서는 CPU를 많이 사용하는 모듈을 찾아내는것이 관건이다. 이를 위해서 먼저 top 이나 glance(HP UX)를 통해서 CPU를 많이 점유하고 있는 Process를 판독한다. 만약 WAS 이외의 다른 process가 CPU를 많이 사용하고 있다면, 그 Process의 CPU 과사용 원인을 해결해야 한다. CPU 사용률이외에도, Disk IO양이 많지는 않은지.. WAS의 JVM Process가 Swap out (DISK로 SWAP되는 현상) 이 없는지 살펴보도록 한다. JVM Process가 Swapping이 되면 실행속도가 엄청나게 느려지기 때문에, JVM Process는 Swap out 되어버리면 안된다. 일단 CPU를 과 사용하는 원인이 WAS Process임을 발견했으면 프로그램상에 어떤 Logic이 CPU를 과점유하는지를 찾아내야한다. 방식을 정리해보면 다음과 같다. 1)과, 2)에서 추출한 정보를 Mapping하여, 2)의 Thread Dump상에서 CPU를 과점유하는 Thread를 찾아내어 해당 Thread의 Stack을 분석하여 CPU 과점유하는 원인을 찾아낸다. 대부분 이런 요인을 분석해보면 다음과 같은 원인이 많다. 과도한 String 연산으로 인해서 CPU 사용률이 높아지는 경우 잘 알고 있다시피 String 연산은 CPU 를 아주 많이 사용한다. String과 Loop문(for,while등)의 사용은 CPU부하를 유발하는 경우가 많기 때문에 가급적이면 String Buffer를 사용하도록 하자. 과도한 RMI Cal
JNDI lookup JNDI lookup은 Server의 JNDI에 Binding된 Object를 읽어오는 과정이다. 이 과정은 위에서 설명한 RMI call로 수행이되는데. 특히 EJB를 호출하기 위해서 Home과 Remote Interface를 lookup하는 과장에서 종종 CPU를 과점유하는 형태를 관찰 할 수 있다. 그래서 JNDI lookup의 경우에는 EJB Home Interface를 Caller Side(JSP/Servlet 또는 poor Java client)등에서 Caching해놓고 사용하는 것을 권장한다. 단. 이경우에는 EJB의 redeploy기능을 제약받을 수 있다. 다음은 각 OS별로 CPU 사용률이 높은 Thread를 검출해내는 방법이다. ■ Solaris에서 CPU 사용률이 높은 Thread를 검출하는 방법
■ AIX Unix에서 CPU 사용률이 높은 Thread 검출해 내기
■ HP Unix에서 CPU 사용률이 높은 Thread 검출해내기
지금까지 Thread Dump를 이용하는 방법을 간단하게 살펴보았다. 이 방법을 이용하면 WAS와 그 위에서 작동하는 Appllication의 Slow down 이나 hangup의 원인을 대부분 분석해낼 수 있으나, Thread Dump는 어디까지나 분석을 위한 단순한 정보이다. Thread Dump의 내용이 Slow down이나 hang up의 원인이 될수도 있으나, 반대로 다른 원인이 존재하여 그 결과로 Thread Dump와 같은 Stack이 나올 수 있기 때문에, 여러 원인을 동시에 살펴보면서 분석할 수 있는 능력이 필요하다.
WAS의 성능에 큰 영향을 주는것중의 하나가 JVM이다. 간단하게 JVM의 메모리 구조를 검토하고 넘어가보도록 하자. 우리가 주목해야할 부분은 객체의 생성과 저장에 관련되는 New와 Old 영역인데, 모든 객체는 생성이 되자 마자 New 영역에 저장되고, 시간이 지남에 따라 이 객체들은 Old 영역으로 이동이 된다. New 영역을 Clear하는 과정을 Minor GC라하고, Old 영역을 Clear하는 과정은 Major GC또는 Full GC라 하는데, 성능상의 문제는 이 Full 영역에서 발생한다. Minor GC의 경우는 1초 이내에 아주 고속으로 이뤄지는 작업이기 때문에, 신경을 쓸 필요가 없지만, Full GC의 경우에는 시간이 매우 오래걸린다. 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에 대량의 데이타를 넣는것들이 대표적인 예가 될 수 가 있다. 좀더 자세한 튜닝 방법에 대해서는 http://www.j2eestudy.co.kr/lecture/lecture_read.jsp?db=lecture0401_1&table=j2ee&id=1를 참고하기 바란다.
Application이 느려지는 원인중의 많은 부분을 차지 하고 있는 것은 DBMS의 성능 문제가 있는 경우가 많다. 흔히들 DBMS Tuning을 받았더니 성능이 많이 향상되었다고 하는 경우가 많은데, 그건 그만큼 DB 설계를 제대로 하지 못했다는 이야기가 된다. DBMS 자체 Tuning에 대한 것은 이 문서와는 논외기 때문에 제외하기로 하고, DBMS에 전송되는 각각의 SQL문장의 실행 시간을 Trace할 수 있는 것만으로도 많은 성능 향상을 기대할 수 있는데, 간단하게 SQL 문장을 실행시간은 아래 방법들을 이용해서 Trace할 수 있다.
WAS와 DBMS 앞단에는 WebServer와 Network 이 있기 때문에 이 Layer에서 문제가 되면 속도저하를 가지고 올 수 있다. 필자의 경험상 대부분의 slow down이나 hangup은 이 부분에서는 거의 일어나지 않지만 성능상에 종종 영향을 주는 Factor가 있는데, WebServer 와Client간의 KeepAlive 특히 WebServer의 Keep Alive설정이 그것이다.
WebServer와 WAS간의 KeepAlive WebServer와 WAS간에는 WebServer에서 받은 request를 forward하기 위해서 WebServer Side에 WAS와 통신을 하기 위한 plug-in 이라는 모듈을 설치하게 된다. 이 역시 WebServer와 Client간의 통신과의 같은 원리로 KeepAlive를 설정하게 되는데, 이 역시 성능에 영향을 줄 수 있는 부분이기 때문에 가급적이면 설정하기를 권장한다.
OS에서 Kernel Parameter 설정 OS의 TCP/IP Parameter와, Thread와 Process등의 Kernel Parameter 설정이 운영에 있어서 영향을 미치는 경우가 있다. 이 Parameter들은 Tuning하기가 쉽지 않기 때문에, WAS또는 OS Vendor에서 제공하는 문서를 통해서 Tuning하기 바란다.
지금까지 간단하게나마 J2EE Application의 병목구간을 분석하는 부분에 대해서 알아보았다. 대부분의 병목은 Application에서 발생하는 경우가 많은데, 이런 병목을 유발하는 Application에 자주 발생하는 개발상의 실수를 정리해보도록 하자. 1) Java Programming sycnronized block String 연산 이미 많은 개발자들이 알고 있는 내용이겠지만 String 연산 특히 String연산중 “+” 연산은 CPU를 매우 많이 소모하게 되고 종종 slow down의 직접적인 원인이 되는 경우가 매우 많다. Socket & file handling Socket이나File Handling은 FD (File Descriptor)를 사용하게 되는데, 이는 유한적인 자원이기 때문에 사용후에 반드시 close명령을 이용해서 반환해야한다. 종종 close를 하지 않아서, FD가 모자르게 되는 경우가 많다. 2) Servlet/JSP Programming JSP Buffer Size Jsp 에서는 JSP의 출력 내용을 저장하는 buffer 사이즈를 지정할 수 있다. <% page buffer=”12kb” %> 이 buffer size는 출력 내용을 buffering했다가 출력하는데, 만약에 쓰고자하는 내용이 Buffer size 보다 클 경우에는 여러번에 걸쳐서 socket write가 일어나기 때문에 performance에 영향을 줄 수 있으므로 가능하다면 buffersize를 화면에 뿌리는 내용의 크기를 예측해서 지정해주는것이 바람직하다. 반대로 너무 큰 버퍼를 지정해버리면 메모리가 불필요하게 낭비 될 수 있기 때문에 이점을 주의하기 바란다. 참고로 jsp page buffer size는 지정해주지 않는경우 default로 8K로 지정된다. member variable Servlet/JSP는 기본적으로 Multi Thread로 동작하기 때문에, Servlet과 JSP 내에서 선언된 멤버 변수들은 각 Thread간에 공유가 된다. 특히 Servlet이나 JSP에서 Data Base Connection을 멤버 변수로 선언하여 Thread간 공유하는 예가 있는데, 이는 별로 좋지 않은 프로그래밍 방법이고, 이런 형태의 패턴은 Servlet이 단 하나만 실행되거나 하는것과 같은 제약된 조건 아래에서만 사용해야 한다. Out of Memory in file upload JSP에서 File upload control을 사용하는 경우가 많다. 이 control을 구현하는 과정에서 upload되는 파일 내용을 몽땅 메모리에 저장했다가 업로드가 끝나면 한꺼번에 file에 writing하는 경우가 있는데, 이는 큰 사이즈의 파일을 업로드할때, 파일 사이즈만큼의 메모리 용량을 요구하기 때문에, 자칫하면 Out Of Memory 에러를 발생 시킬 수 있다. 3) JDBC Programming Connection Leak JDBC Programming에서 가장 대표적으로 발생되는 문제가 Connection Leak이다. Database Connection을 사용한후에 close않아서 생기는 문제인데,Exception이 발생하였을때도 반드시 Connection을 close하도록 해줘야한다.
SQL문장을 Query하고 나오는 resultset을 사용할때, 모든 resultset의 결과를 Vector나 hashtable등을 이용해서 메모리에 저장해놓는 경우가 있다. 이런 경우에는 평소에는 문제가 없지만, SQL Query의 결과가 10만건이 넘는것과 같은 대용량일때 이 모든 데이타를 메모리 상에 저장할려면 Out Of Memory가 나온다. Close stmt & 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이 된다.) 4) EJB Programming When we use EJB? EJB는 분명 강력하고 유용한 개발 기술임에는 틀림이 없다. 그러나 EJB의 장점과 용도를 모르고 사용하면 오히려 안쓰는것만 못한 경우가 많다. Reduce JNDI look up 위에서도 설명했듯이 EJB의 Home Interface를 lookup 해오는 과정은 객체의 Serialization/DeSerialization을 동반하기 때문에, 시스템 성능에 영향을 줄 수 있다. EJB Home을 한번 look up한후에는 Hashtable등에 저장해서 반복해서 Remote Call(Serialization / DeSerialization)하는 것을 줄이는게 좋다. Do not use hot deploy in production mode WAS Vendor 마다 WAS 운영중에 EJB를 Deploy할 수 있는 HotDeploy 기능을 제공한다. 그러나 이는 J2ee spec에 있는 구현이 아니라 각 vendor마다 개발의 편의성을 위해서 제공하는 기능이다. 운영중에 EJB를 내렸다 올리는것은 위험하다. (Transaction이 수행중이기 때문에) Hot Deploy 기능은 개발중에만 사용하도록 하자. 5) JVM Memory tuning Basic 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에 의한 slow down이나 hang up현상은 DB 자체의 분석이 필요하다. |
Java theory and practice: Understanding JTS -- The magic behind the scenes
How J2EE containers hide the complexity of transaction management
Brian Goetz (brian@quiotix.com), Principal Consultant, Quiotix Corp
commit() or rollback() methods, nor do they register what they've done with the transaction monitor. So how do the actions performed by J2EE components become part of the transaction?
Transparent resource enlistment
While the application state is manipulated by components, it is still stored in transactional resource managers (for example, databases and message queue servers), which can be registered as resource managers in a distributed transaction. In Part 1, we talked about how multiple resource managers can be enlisted in a single transaction, coordinated by a transaction manager. Resource managers know how to associate changes in application state with specific transactions.
But this just moves the focus of our question from the component to the resource manager -- how does the container figure out what resources are involved in the transaction so it can enlist them? Consider the following code, which might be found in a typical EJB session bean:
Listing 1. Transparent resource enlistment with bean-managed transactions
con1 and con2 appear to be ordinary JDBC connections such as those that would be returned fromDriverManager.getConnection() . We get these connections from a JDBC DataSource , which was obtained by looking up the name of the data source in JNDI. The name used in our EJB component to find the data source (java:comp/env/OrdersDB ) is specific to the component; the resource-ref section of the component's deployment descriptor maps it to the JNDI name of some application-wide DataSource managed by the container.
The hidden JDBC driver
Every J2EE container can create transaction-aware pooled DataSource objects, but the J2EE specification doesn't show you how, because it's outside the spec. If you browse the J2EE documentation, you won't find anything on how to create JDBC data sources. You'll have to look in the documentation for your container instead. Depending on your container, creating a data source might involve adding a data source definition to a property or configuration file, or might be done through a GUI administration tool.
Each container (or connection pool manager, like PoolMan) provides its own mechanism for creating a DataSource , and it is in this mechanism that the JTA magic is hidden. The connection pool manager obtains a Connection from the specified JDBC driver, but before returning it to the application, wraps it with a facade that also implements Connection , interposing itself between the application and the underlying connection. When the connection is created or a JDBC operation is performed, the wrapper asks the transaction manager if the current thread is executing in the context of a transaction, and automatically enlists the Connection in the transaction if one exists.
The other types of transactional resources, JMS message queues and JCA connectors, rely on a similar mechanism to hide resource enlistment from the user. When you make a JMS queue available to a J2EE application at deployment time, you again use a provider-specific mechanism to create the managed JMS objects (queue connection factories and destinations), which you then publish in a JNDI namespace. The managed objects created by the provider contain similar auto-enlistment code as the JDBC wrapper added by the container-supplied connection pool manager.
Transparent transaction control
The two types of J2EE transactions -- container-managed and bean-managed -- differ in how they start and end a transaction. Where a transaction starts and ends is referred to as transaction demarcation. The example code in Listing 1 demonstrates a bean-managed transaction (sometimes called a programmatic transaction.) Bean-managed transactions are started and ended explicitly by a component using the UserTransaction class. UserTransaction is made available to EJB components through theejbContext and to other J2EE components through JNDI.
Container-managed transactions (or declarative transactions) are started and ended transparently on behalf of the application by the container, based on transaction attributes in the component's deployment descriptor. You indicate whether an EJB component uses bean-managed or container-managed transactional support by setting the transaction-type attribute to eitherContainer or Bean .
With container-managed transactions, you can assign transactional attributes at either the EJB class or method levels; you can specify a default set of transactional attributes for the EJB class, and you can also specify attributes for each method if different methods are to have different transactional semantics. These transactional attributes are specified in the container-transaction section of the assembly descriptor. An example assembly descriptor is shown in Listing 2. The supported values for the trans-attribute are:
trans-attribute determines if the method supports execution within a transaction, what action the container should take when the method is called within a transaction, and what action the container should take if it is called outside of a transaction. The most common container-managed transaction attribute is Required . When Required is set, a transaction in process will enlist your bean in that transaction, but if no transaction is running, the container will start one for you. We will investigate the differences between the various transaction attributes, and when you might want to use each, in Part 3 of this series.
Listing 2. Sample EJB assembly descriptor
methodA() of object A starts a transaction, and then calls methodB() of object B , which acquires a JDBC connection and updates the database. The connection acquired by B will be automatically enlisted in the transaction created by A . How did the container know to do this?
When a transaction is initiated, the transaction context is associated with the executing thread. When A creates the transaction, the thread in which A is executing is associated with that transaction. Because local method invocations execute in the same thread as the caller, any methods called by A will also be in the context of that transaction.
Skeletons in the closet
What if object B is really a stub to an EJB component executing in another thread or even another JVM? Amazingly, resources accessed by remote object B will still be enlisted in the current transaction. The EJB object stub (the part that executes in the context of the caller), the EJB protocol (RMI over IIOP), and the skeleton object on the remote end all conspire to make this happen transparently. The stub determines if the caller is executing a transaction. If so, the transaction ID, or Xid, is propagated to the remote object as part of the IIOP call along with the method parameters. (IIOP is the CORBA remote-invocation protocol, which provides for propagating various elements of execution context, such as transaction context and security context; seeResources for more information on RMI over IIOP.) If the call is part of a transaction, the skeleton object on the remote system automatically sets the remote thread's transaction context, so that when the actual remote method is invoked, it is already part of the transaction. (The stub and skeleton objects also take care of beginning and committing container-managed transactions.)
Transactions can be initiated by any J2EE component -- an EJB component, a servlet, or a JSP page (or an application client, if the container supports it). This means that your application can start a transaction in a servlet or JSP page when a request arrives, do some processing within the servlet or JSP page, access entity beans and session beans on multiple servers as part of the page's logic, and have all of this work be part of one transaction, transparently. Figure 1 demonstrates how the transaction context follows the path of execution from servlet to EJB to EJB.
Figure 1. Multiple components in a single transaction
![]() Connection to the database; it may well be the case that each is accessing the exact same database. JTS can detect whether multiple resources are involved in the transaction or not, even if multiple connections are made to the same resource from different components, and optimize the execution of the transaction. As you'll recall from Part 1, involving multiple resources managers in a single transaction requires the use of the two-phase commit protocol, which is more expensive than the single-phase commit used by a single resource manager. JTS is able to determine if only a single resource manager is enlisted in a transaction. If it detects that all the resources involved in the transaction are the same, it can skip the two-phase commit and let the resource manager handle the transaction by itself.
Conclusion
The magic that allows for transparent transaction control, resource enlistment, and transaction propagation is not a part of JTS, but instead a part of how J2EE containers use JTA and JTS services behind the scenes on behalf of J2EE applications. There are many entities that conspire behind the scenes to make this magic happen transparently; the EJB stubs and skeletons, the JDBC driver wrappers provided by the container vendor, the JDBC drivers provided by the database vendor, the JMS providers, and the JCA connectors. All of these entities interact with the transaction manager so that your application code doesn't have to.
In Part 3, we'll look at some of the practical issues associated with managing transactions in a J2EE context -- transaction demarcation and isolation -- and their effects on application consistency, stability, and performance.
Resources
|