이번 포스트에서는 오픈소스 JTA 구현체인 Atomikos를 스프링 웹 어플리케이션 및 Tomcat과 연동하여 사용하는 방법에 대해 다뤄 보겠습니다. 기본적인 내용들은 Atomikos 홈페이지에서 다루고 있기는 하지만, 그대로 따라해보니 몇 가지 부족한 부분이 있더군요. 이 포스트에서는 원문을 보지 않고도 최대한 간단히, 그리고 빠르게 적용할 수 있도록 설명해 보도록 하겠습니다.

Atomikos 홈페이지에서 소개하고 있는 Tomcat Integration 정보는 [여기]에서 확인하실 수 있습니다. 이 페이지에서 자신이 사용하는 버전에 맞는 항목을 참고하시면 됩니다. 이 포스트에서는 Tomcat 7.x와 연동하는 방법에 대해 살펴보도록 하겠습니다.


이 포스트의 내용을 진행하기 위한 사전 준비사항은 다음과 같습니다.

  1. 설치 및 설정 완료된 Apache Tomcat 7.x
  2. 사용하는 DB에 대한 JDBC Driver JAR 파일
  3. Tomcat에서 실행할 스프링 웹 어플리케이션
  4. Atomikos에서 제공하는 JAR 파일 
    1. atomikos-integration-extension-3.7.1-20120529.jar


아래 내용에서 사용되는 환경변수들은 다음과 같습니다.

  • $CATALINA_HOME : 톰캣 설치 디렉토리.
  • $CATALINA_LIB : $CATALINA_HOME/lib - 톰캣이 사용할 라이브러리가 위치하는 디렉토리
  • $CATALINA_CONF : $CATALINA_HOME/conf - 톰캣에 대한 환경설정 파일 이 위치한 디렉토리
  • $WEB_CONTEXT_ROOT : 웹 어플리케이션이 설치된 디렉토리


  1. 먼저 Tomcat에 대한 설정을 진행합니다. (2~7)

  2. 사용하는 DB에 대한 JDBC 드라이버 JAR 파일을 $CATALINA_LIB 디렉토리에 복사해 넣습니다.
    (저의 경우에는 Oracle10g를 위한 ojdbc6.jar 파일을 넣었습니다.)

  3. atomikos-integration-extension-3.7.1-20120529.jar 파일을 $CATALINA_LIB 디렉토리에 복사해 넣습니다.

  4. 그 외 JTA와 Spring 연동을 위한 갖가지 라이브러리들을 $CATALINA_LIB 디렉토리에 복사해 넣습니다.
    (atomikos-integration-extension-3.7.1-20120529.jar 파일은 2번에서 이미 넣은 것이니 여기서는 생략해도 됩니다.)


    이 파일들은 [JAR Finder]에서 검색하여 다운로드 받으실 수 있습니다. (이 파일들이 없으면 Tomcat 기동 시 수많은 오류를 보게 될 것입니다.)

  5. $CATALINA_CONF/server.xml 파일에 다음 내용을 입력합니다.
    <Listener className="com.atomikos.tomcat.AtomikosLifecycleListener" />
    


  6. $CATALINA_CONF/context.xml 파일에 다음 내용을 입력합니다. 이 정보는 context.xml 파일에 등록되어 있는 <WatchedResource> 태그 이후에 입력합니다.
        <Transaction factory="com.atomikos.icatch.jta.UserTransactionFactory" />
    	<!-- Also register Atomikos TransactionManager as java:comp/env/TransactionManager -->
        <Resource name="TransactionManager"
                auth="Container"
                type="com.atomikos.icatch.jta.UserTransactionManager"
                factory="org.apache.naming.factory.BeanFactory" />
    
        <!-- Spring LoadTimeWeaver Support for the Tomcat server. -->
        <Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"
              useSystemClassLoaderAsParent="false"/>
        <Resource
    			name="defaultDatasource"
    			auth="Container"
    			type="com.atomikos.jdbc.AtomikosDataSourceBean"
    			factory="com.atomikos.tomcat.EnhancedTomcatAtomikosBeanFactory"
    			uniqueResourceName="defaultDatasource"
    			xaDataSourceClassName="oracle.jdbc.xa.client.OracleXADataSource"
    			
    			xaProperties.serverName="xxx.xxx.xxx.xxx" 
    			xaProperties.portNumber="xxxx"  
    			xaProperties.user="xxxx"
    			xaProperties.password="xxxx"
    			xaProperties.databaseName="xxxx"
    			xaProperties.URL="jdbc:oracle:thin:@xxx.xxx.xxx.xxx:xxxx:xxxx"
    			
    			maxPoolSize="10"
    			minPoolSize="1"
    	/>      
    


  7. 여기까지 하면 일단 Tomcat에 대한 설정은 마무리된 것입니다. 이제 웹 어플리케이션에 대한 설정을 진행합니다.

  8. $WEB_CONTEXT_ROOT/WEB-INF/web.xml 파일에 다음과 같이 resource-ref 정보를 입력합니다. 8에서 입력했던 Datasource 정보를 이용합니다.
    	<resource-ref>
    		<res-ref-name>defaultDatasource</res-ref-name>
    		<res-type>com.atomikos.jdbc.AtomikosDataSourceBean</res-type>
    		<res-auth>Container</res-auth>
    		<res-sharing-scope>Unshareable</res-sharing-scope>
    	</resource-ref>
    


  9. 스프링 웹 어플리케이션에서 사용할 데이터소스 정보와 트랜잭션 매니저 정보를 다음과 같이 스프링 컨텍스트 파일에 입력합니다. 이 예제에서는 MyBatis를 같이 이용하고 있습니다.
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
    	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jee="http://www.springframework.org/schema/jee"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
    		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
    		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
    		http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd">
    
    	<tx:annotation-driven transaction-manager="transactionManager" />
    
    	<jee:jndi-lookup id="defaultDataSource" jndi-name="defaultDatasource" />
    
    	<!-- Construct Atomikos UserTransactionManager, needed to configure Spring -->
    	<bean id="AtomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init"
    		destroy-method="close">
    
    		<!-- when close is called, should we force transactions to terminate or not? -->
    		<property name="forceShutdown" value="false" />
    	</bean>
    
    	<!-- Also use Atomikos UserTransactionImp, needed to configure Spring -->
    	<bean id="AtomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
    
    		<property name="transactionTimeout" value="300" />
    	</bean>
    
    	<!-- Configure the Spring framework to use JTA transactions from Atomikos -->
    	<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
    		<property name="transactionManager" ref="AtomikosTransactionManager" />
    		<property name="userTransaction" ref="AtomikosUserTransaction" />
    	</bean>
    
    
    	<bean id="oracleSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    		<property name="dataSource" ref="defaultDataSource" />
    		<property name="configLocation" value="classpath:mybatis/oracle-configuration.xml" />
    	</bean>
    
    	<bean id="oracleSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
    		<constructor-arg ref="oracleSessionFactory" />
    	</bean>
    
    	<bean id="myBatisMapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    		<property name="basePackage" value="com.bory.app.dao" />
    		<property name="annotationClass" value="org.springframework.stereotype.Repository" />
    	</bean>
    
    </beans>
    
    


  10. $WEB_CONTEXT_ROOT/WEB-INF/classes 디렉토리에 jta.properties 파일을 생성하고 그 내용을 적절히 수정합니다. jta.properties 파일에 대한 내용은 [여기]에서 확인하실 수 있습니다.


이제 모든 작업이 완료되었습니다. Tomcat을 재기동하면 Atomikos에 의해 트랜잭션 처리가 수행되는 웹 어플리케이션을 이용할 수 있게 됩니다.

신고
Posted by Layered 트랙백 0 : 댓글 0

댓글을 달아 주세요

현재 우리나라 자바 엔터프라이즈 개발 시장에서 가장 빈번히 사용되고 있고, 그에 걸맞게 강력한 기능을 제공하는 개발 프레임워크는 단연 스프링 프레임워크일 것입니다. (여담입니다만, 혹자는 "소프트웨어 프레임워크는 그저 유행일 뿐이다" 라고 얘기하기도 하지만, 저 개인적으로는 분명 그 발전에는 방향성이 있다고 생각합니다. 소프트웨어 공학, 혹은 공학을 넘은 철학이 그 방향성을 잡아주고 있다고 할까요.) 사실상의(de facto) 표준이라 불리고 있으며, 이 프레임워크의 핵심 기능 중 하나인 의존성 주입(Dependency Injection) 개념은 이미 JavaEE6에 정식으로 채택되어 사용되고 있을 정도입니다. 

(또 여담입니다만, 프레임워크를 개발하는 엔지니어 입장에서, 너무 강력한 프레임워크가 나오니 허탈해졌다라고 할 정도랄까요. 그런데 어쩌겠습니까... 좋으면 제대로 알고 잘 쓰고, 이를 확장해 나갈 줄 알아야겠죠. ㅋ)


그런데, 스프링 프레임워크를 이용하여 이클립스에서 웹 개발을 해 보신 분들이라면 어플리케이션이 여러번 재배포 되었을 때 어느 순간 java.lang.OutOfMemoryError:PermGen space라는 오류가 발생하면서 WAS(혹은 서블릿 컨테이너)가 정상적으로 동작하지 못하게 되는 것을 본 적이 있을 것입니다. 그리고 그럴 때마다 WAS를 재기동하셨을 것입니다. 아, 물론 그것이 스프링 프레임워크의 잘못이라는 것은 아닙니다. 하지만 대부분의 여러분들은 "스프링 프레임워크를 쓰지 않던 시절보다 더 빈번히 이 오류를 보게" 되었을 것이라는 말입니다. 


그럼 도대체 이 에러는 왜 발생하는 것일까요? 어떻게 하면 제거할 수 있을까요? 이번에 번역한 글은 이에 대한 내용을 다루고 있습니다. 이 글을 통해 JVM이 인스턴스를 만들 때, 어떤 메커니즘이 내부적으로 수행되는지, 클래스에 대한 정보는 어디에 저장되는지, 그리고 그것들이 제대로 처리되지 않을 때 어떤 문제가 발생하는지 알게 될 것입니다. 아울러, 이 글에서는 다루고 있지 않지만, 마지막에 JVM 실행 옵션을 이용하여 어플리케이션 재배포 시에 발생하는 PermGen space 에러가 발생하지 않도록 하는 방법에 대해서도 다뤄 보겠습니다. 


서론이 길었습니다. 이제 시작하도록 하죠. 이 번역문의 원문은 http://plumbr.eu/blog/what-is-a-permgen-leak에서 찾아볼 수 있습니다.




What is a PermGen leak?


이 글에서 다룰 내용은 자바 어플리케이션 내에서 발생할 수 있는 특정 메모리 문제에 대한 실제적인 소개이다. , Stack trace 상에서 가끔씩 보는 java.lang.OutOfMemoryError: PermGen space의 원인에 대해 분석할 것이다.

그에 앞서 이 주제를 제대로 이해할 수 있도록, 이와 관련된 핵심 개념을 다뤄보기로 하겠다. 객체는 무엇인지, 클래스와 클래스로더(ClassLoader)는 무엇인지, 그리고 JVM 메모리 모델은 무엇인지도 같이 다룰 것이다. 이 개념들이 익숙하다면 바로 이 다음 장으로 넘어가도 된다. 그 다음, PermGen 메모리 누수의 전형적인 두 가지 경우를 서술하고 그에 대한 힌트나 그것을 해결하기 위한 제안을 다룰 것이다.


Objects, Classes and ClassLoaders

뭐 완전 기초부터 다룰 생각은 없다. 이 글을 읽는 사람이라면 자바 내의 모든 것들이 객체(Object)라는 개념에 익숙할 것이다. 그리고 모든 객체들은 클래스에 의해 정의된다는 것도, , 모든 객체는 그 객체에 대한 클래스의 구조(Structure)를 기술하는 java.lang.Class 객체의 참조(reference)를 갖고 있는 것도 알 것이다.

하지만 코드 내에서 새로운 객체를 생성할 때, 내부적으로는 도대체 어떤 일이 일어나는 것일까? 예를 들어, 다음과 같이 정말로 복잡한 코드를 작성했다고 했다면 말이다.


Person boss = new Person();


자바 가상 머신(Java Virtual Machine, JVM)이 객체를 생성하려면 객체의 구조를 이해해야 한다. 그리고 그러기 위해, JVMPerson이라는 클래스를 찾는다. 그리고 이 어플리케이션이 실행되는 과정에서 이 Person이라는 클래스에 최초로 접근하였다면, JVM은 이 클래스를, 정상적인 경우, Person.class라는 파일로부터 로드한다. 드라이브(디스크 드라이브)로부터 Person.class 파일을 검색하고, 그것을 메모리에 적재하고 그것의 구조를 분석(parsing)하는 것을 클래스 로딩이라고 한다. 그리고 클래스를 제대로 로딩하는 것을 보장하는 역할은 클래스로더(ClassLoader)가 맡고 있다. 클래스로더들은 java.lang.ClassLoader 클래스의 인스턴스(instance)인데, 자바 프로그램 내의 모든 개별 클래스들은 동일한 클래스로더에 의해 로드 되어야 한다. 어쨌든 여기까지 다음 그림과 같은 관계를 얻게 된다.



, 다음 그림에서 볼 수 있듯, 모든 클래스로더는 자신이 로드한 모든 클래스에 대한 참조를 보유하고 있다. 이 그림은 우리가 다루고자 하는 내용과 아주 밀접하니 주의 깊게 살펴보기 바란다.




다시 한 번 말하지만, 이 그림은 다음에 또 필요하게 될 것이다.


Permanent Generation

현존하는 거의 모든 JVM, 자바 클래스들에 대한 내부적인 표상(internal representation)을 위해, Permanent Generation(줄여서 PermGen)이라고 하는 구분된 메모리 영역을 사용한다. PermGen은 그 외에 추가 정보를 저장하기 위해서도 사용되지만, -관심이 있다면 그에 대한 상세한 내용은 포스트를 참고하기 바란다. 이 글에서는 클래스에 대한 정의만 저장된다고 가정하기로 하자. 필자가 사용하고 있는 Java1.6이 실행되고 있는 장비에서PermGen 영역에 대한 기본 크기는 그리 대단할 것 없는 82MB이다.


 이전 포스트에서 다뤘듯이, 메모리 누수(Memory Leak)라고 하는 것은, 어떤 객체들이 더 이상 사용되지 않고 있지만 가비지컬렉터(Garbage Collector, GC)가 그 클래스들이 사용되고 있지 않다는 것을 인지하지 못하는 것이다. 이것이 반복되어, 사용되지 않는 객체들이, 어플리케이션의 다음 메모리 할당 요청이 처리될 수 없을 만큼, heap 메모리를 점유하게 되면 OutOfMemoryError가 발생하게 된다.

java.lang.OutOfMemoryError: Permgen space의 원인도 이와 완벽하게 동일하다: JVM이 새로운 클래스 정의를 로드해야 하는데 이를 수행하기 위한 PermGen 공간이 충분하지 않기 때문이다. , 이미 너무 많은 클래스들이 로딩되어 있다는 말이다. 이것이 발생하는 것은, 어플리케이션이나 서버가 현재 크기의 PermGen이 처리할 수 없을 만큼 많은 클래스들을 사용하고 있기 때문일 수도 있다. 그것이 아니라면, 메모리 누수 때문일 수 있다.


Permanent Generation Leak

그런데, 도대체 어째서 PermGen 영역에서 메모리 누수가 발생하는 것일까? PermGen 영역에는 클래스에 대한 정의가 등록되어 있을 것이고, 이것들이 사용되지 않게 되는 경우는 없을 건데 말이다. 혹시 그런 경우가 있는 것일까?

실제로, 그런 경우가 있다. 어플리케이션 서버에 배포되어 있는 자바 웹 어플리케이션이 서버로부터 제거(undeployed)되면 그 어플리케이션과 관계된 EAR/WAR내의 클래스들은 모두 쓸모 없어진다. 어플리케이션 서버가 여전히 사용 중에 있으므로 JVM은 동작을 계속할 것인데, 이 수많은 클래스에 대한 정의들이 더 이상 사용할 필요가 없어진 것이다. 이들은 PermGen에서 제거되어야 하고, 만약 그렇지 않다면 PermGen 영역에서 Memory Leak이 발생한 것이다.

Tomcat의 개발자들은 Tomcat 6.0.24와 그 이후 버전에서 발생한 다양한 메모리 누수 상황들과 그것을 해결하는 방법을 다룬 위키 페이지를 작성해 두었다. 참고하기 바란다.


Leaking Threads

클래스로더 누수가 발생하는 다른 시나리오는 스레드들의 실행이 너무 길어질 때 이다. 이것은 당신이 작성한 어플리케이션이나, (내 경험으로는 다음 경우가 더 많았는데) 당신의 어플리케이션이 사용한 서드파티 라이브러리가, 과도하게 오랫동안 실행되는 스레드들을 사용할 때 발생할 수 있다. 이것의 한 예로, 특정 코드를 주기적으로 수행하는 타이머 스레드 같은 것이 있을 수 있다.

스레드의 수명이 지정되지 않았다면, 바로 문제가 될 소지가 있다. 어플리케이션의 어떤 부분이 스레드를 하나라도 시작했다면, 그 스레드가 어플리케이션보다 더 오래 살아남지 않도록 제어해야 한다. 개발자들이 이것을 책임져야 한다는 것을 아예 모르거나, 이것을 처리하는 클린업 코드를 작성하는 것을 깜빡하는 것은 흔한 일이다.

이 처리를 제대로 하지 않았다면, 스레드들은 웹 어플리케이션이 제거(undeployed)된 이후에도 계속 동작하게 될 것이고, 그 스레드는 웹 어플리케이션이 시작할 때 사용된 Context ClassLoader라 불리는 클래스로더에 대한 참조를 보유하게 될 것이다. 이 말은 즉, 제거된 어플리케이션의 모든 클래스들이 메모리 내에 지속적으로 남아있게 될 것이라는 거다. 해결 방법은? 어플리케이션이 스레드를 시작했다면, 반드시 어플리케이션 제거 과정에서 Servlet Context Listener를 이용하여 그 스레드들을 멈추고 제거해야 한다. 만약 서드파티 라이브러리를 사용했다면, 그것이 실행한 스레드를 중지하기 위한 셧다운 훅(shutdown hook)을 찾아보아야 한다. 만약 그런 것이 없다면 버그 리포팅을 해야 한다.


Leaking Drivers

메모리 누수의 또 다른 전형적인 경우는 데이터베이스 드라이버에 의해 발생된다. 우리는 Plumbr에 탑재한 우리의 데모 어플리케이션에서 이 문제가 발생하는 것을 본 적이 있었다. 이 데모 어플리케이션은 스프링 프레임워크에서 제공하는 Pet Clinic 어플리케이션을 아주 조금 수정한 것이다. 이 어플리케이션을 어플리케이션 서버에 배포하는 과정에서 발생했던 일을 간략히 기술해 보겠다.

l  서버는 새로운 java.lang.ClassLoader 인스턴스를 생성하고 그것을 사용하여 어플리케이션의 클래스들을 로딩하기 시작한다.

l  PetClinic 어플리케이션은 HSQL 데이터페이스를 이용하므로, 그것을 사용하기 위해 그에 상응하는 JDBC 드라이버, org.hsqldb.jdbcDriver를 로딩한다.

l  이 클래스는, 제대로 만들어진 JDBC 드라이버이니, JDBC 규격에서 정의된 대로 초기화 과정에서 java.sql.DriverManager에 등록(register)한다. 이 등록 과정에서 드라이버매니저의 정적 필드(static field) org.hsqldb.jdbcDriver의 인스턴스에 대한 참조를 저장하게 된다.

이제 어플리케이션이 어플리케이션 서버에서 제거되어도, java.sql.DriverManager는 여전히 그 참조를 보유하고 있게 될 것이다. HSQLDB나 스프링 프레임워크나 어플리케이션 내의 어떤 코드에서도 그 참조를 제거하지 않으니 말이다. 위에서 설명했듯이, JDBC 드라이버 객체는 여전히 org.hsqldb.jdbcDriver에 대한 참조를 유지하게 되는데, 이것은 또 어플리케이션을 로드하는 데 사용된 클래스로더에 대한 참조를 포함하고 있을 것이다. 그리고 이 클래스로더가 이 어플리케이션의 모든 클래스에 대한 참조를 포함하고 있다. 우리의 데모 어플리케이션의 경우에서, 어플리케이션이 기동될 때 거의 2000개 정도의 클래스들이 로딩되는데, PermGen 영역에서 대략 10MB 정도를 사용한다. 이 말은 어플리케이션을 5~10회 가량 재배포하면 PermGen의 기본 크기를 모두 차지하게 되고 그로 인해 java.lang.OutOfMemoryError: PermGen space 에러가 발생하게 될 것이라는 말이다.

해결하는 방법은? 한 가지 방법은 Servlet Context Listener에 어플리케이션이 셧다운 되는 동안 드라이버매니저에서 HSQLDB 드라이버를 등록 해제하는 코드를 작성하는 것이다. 이는 아주 간단하게 처리할 수 있다. 하지만 이 드라이버를 사용하는 모든 어플리케이션에 코드를 작성해야 할 것이다.

최신 버전의 Plumbr와 데모 어플리케이션을 다운로드하여 메모리 누수가 발생하는지 확인하고, Plumbr가 그것을 어떻게 찾아내고 우리가 그것을 어떤 방식으로 설명하는지 확인해보기 바란다.


Conclusion

어플리케이션에서 java.lang.OutOfMemoryError: PermGen space 가 발생하는 데에는 수많은 이유가 있다. 가장 주된 원인은 어플리케이션이 셧다운 된 이후에도 어플리케이션의 클래스 로더에 의해 로딩된 클래스나 클래스에 대한 참조들이 남아있기 때문이다. 혹은 클래스로더에 대한 직접적인 링크 때문일 수도 있다. 어쨌든 이것을 해결하는 방법은 대부분의 경우에 유사하다고 할 수 있다. 먼저 여전히 참조가 유지되고 있는 곳을 찾아내고, 그 다음, 어플리케이션이 제거되거나 셧다운 될 때 이들을 해제할 수 있는 셧다운 훅을 어플리케이션에 추가한다. 이것은 Servlet Context Listener나 서드파티 라이브러리가 제공하는 API를 이용하여 처리할 수 있을 것이다.




이 글은 Permgen Space나 클래스로더, 그리고 심지어 OutOfMemory에 대해 지나치게 단순화하여 설명하는 경향이 있습니다. 대신, java.lang.OutOfMemory : PermGen space 오류가 왜 발생하는지에 대해서는 깔끔하게 잘 설명하고 있죠. 목표로 하는 내용 외의 기타 등등의 세부사항을 적절히 배제한 것이 이해를 돕는데는 큰 도움이 되네요.

참고로, 자바에서 OutOfMemory는 메모리 누수에 의해서만 발생하는 것은 아닙니다. 그 외 여러 가지 경우에서도 발생할 수 있는데, 추후에 이에 대해 별도의 포스트로 언급해 보도록 하겠습니다.

또, 클래스로더 역시 이 글에서 다룬 것과 같이 아주 단순하지만은 않습니다. 이에 대해서도 따로 다뤄보도록 하겠습니다.


아참, 그런데... 이 원글은 스프링 프레임워크에 대해서는 전혀 다루지 않고 있는데, 저는 왜 스프링 프레임워크를 굳이 언급한 것일까요? 스프링 프레임워크의 어떤 특징 때문에 이 문제가 좀 더 빈번하게 발생한다고 생각하고 있는 것일까요? 혹시 그것이 아니라면, 사용자들이 스프링 프레임워크를 뭔가 잘 못 사용하기 때문에 이 문제가 빈번하게 발생하는 것은 아닐까요? 이 부분은 여러분 스스로 생각해 보시기 바랍니다. (최근에 어떤 책을 읽었는데, 가장 중요한 부분은 스스로 생각하라고 하더군요... 하지만 그 책의 문제는, 가장 중요한 것은 그렇다 쳐도 그 밖에 중요하다고 할 만한 것은 아무것도 다루고 있지 않는다는 것이었습니다.)


마지막으로, 자바 실행 옵션 중, PermGen 영역에 로딩되어 있는 클래스들을 메모리에서 해제하고 다시 로딩할 수 있도록 하는 것이 있습니다. 위에서 언급한 메모리 누수가 아닌, 단순 어플리케이션 재로딩시에 발생하는 Permgen space에러를 해결하는 데에 다음 실행 옵션이 도움이 될 것입니다.

-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled


그 외 자바 실행 옵션에 대한 상세한 내용은 [여기]에서 확인해 보시기 바랍니다.

신고
Posted by Layered 트랙백 0 : 댓글 0

댓글을 달아 주세요

이 글은 Atomikos라고 하는 오픈소스 JTA 라이브러리 사이트에서 제공하는 JTA와 XA에 대한 설명을 번역한 것입니다. 

사내 교육용으로 번역했었는데, 교육 효과는 개뿔, 읽어본 아이들이 손에 꼽을 정도내요. 이대로 사장(?)되는 것이 아쉬워 올려봅니다. 원문 주소는, http://www.atomikos.com/Documentation/GettingStarted 입니다. 

Atomikos는 스프링 프레임워크가 더 이상 JOTM(Java Open Transaction Manager)를 정식으로 지원하지 않으면서 더욱 각광을 받고 있는 듯 합니다. 나중에 기회가 되면 [Atomikos를 이용한 Spring 분산 Transaction처리]와 [Tomcat/Glassfish에 Atomikos 적용하기] 등을 다뤄볼까 합니다.


What is JTA

JTA(Java Transaction API)은 플랫폼마다 상이한 트랜잭션 매니저들과 어플리케이션들이 상호작용할 수 있는 인터페이스를 정의하고 있다. Java에서 제공되는 대부분의 API와 마찬가지로, JTA는 실제 구현은 다르지만 어플리케이션이 공통적으로 사용할 수 있는 하나의 인터페이스를 제공한다. 이 말은 트랜잭션 처리가 필요한 어플리케이션이 (API의 사용 방식 그대로만 사용한다면) 특정 벤더의 트랜잭션 매니저에 의존할 필요가 없음을 의미한다. Atomikos와 같이 JTA 구현체들을 오픈소스로 제공하는 벤더들도 있고, IBM 같이 JTA 구현체를 어플리케이션 서버의 한 부분으로 제공하는 벤더들도 있다.

JTA의 구현체를 사용할 때에는 주의를 기울여야 한다: 자세히 들여다 보면 뭔가 잘 못 되어 있는 것처럼 보이기 때문이다. 믿기 어렵겠지만, J2EE 호환됨이라고 검증을 받은 어플리케이션 서버들도 트랜잭션 관리를 제대로 지원하지 않거나 가상적으로만 지원할 수도 있다.

 

What is XA

XA(eXtended Architecture)는 동일한 전역 트랜잭션(Global Transaction) 내에서 몇 개의 백엔드 데이터 저장소에 접근하기 위한 X/Open 그룹 표준의 하나이다. XA 표준 규격은 하나의 트랜잭션 매니저가 어떤 트랜잭션의 한 부분으로 어떤 작업이 수행되고 있는지를 데이터베이스에 통보하는 방식과, 각 트랜잭션이 완료될 때 2단계 커밋(2 Phase Commit)을 수행되는 방식을 관장한다. 또 데이터 저장소에서 지연되고 있는 트랜잭션을 회복시키는 방법도 포함하고 있다.

XA의 장점

XA 역시 하나의 표준이기 때문에, 모든 호환되는 데이터 저장소(혹은 드라이버)들이 전역 (분산) 트랜잭션의 부분으로서의 트랜잭션 매니저와 연동할 수 있다. 다른 말로, 2단계 커밋이 고려되어야 하는 상황이라면 XA는 트랜잭션 매니저와 데이터 저장소를 연결해 주는 역할 담당한다는 말이다. 이것이 Atomikos와 같은 솔루션들이 Oracle이나 Sybase와 같은 데이터베이스와 연동하여 커밋과 롤백 등의 모든 작업을 수행할 수 있는 이유이다.

 

When to use JTA / XA

JTA XA는 데이터가 오염되는 것(그리고 그로 인한 사업상의 손실)을 막아주는 일종의 보험 시스템 같은 것이다. 가장 흔한 경우는 다음과 같다.

l  (Queue)로부터 JMS 메시지를 받아 처리한 뒤 그 결과를 데이터베이스에 저장하는 경우: 데이터베이스에 대한 저장이 실패한 경우에도 JMS 메시지가 유실되어서는 안 된다.

l  두 개 이상의 레거시 백엔드 시스템에 대해 동일 트랜잭션으로 업데이트 해야 하는 경우

일반적으로 하나 이상의 백엔드 시스템에 대해 동일 트랜잭션으로 여러 작업을 수행해야 하는 경우에 JTA/XA의 사용을 강하게 권고한다. 그렇지 않으면 데이터의 유실이나 오염의 위험이 매우 커질 수 있다. (그리고 그것을 확인할 방법이 없을 수도 있다.)

많은 프로그래머들이 JTA/XA의 오버헤드를 피하기 위해 어플리케이션에 특정 복구 코드를 프로그래밍 하기도 한다. (예를 들어, 중복 요청 처리라던가, 데이터베이스에 부가 상태 정보를 저장하는 것과 같은) 그러나 이 방법들은 모두 (재사용 불가능하고, 어플리케이션에 종속적이고 테스트하기 어렵기 때문에) 불완전하다. 결과적으로, 모두가 예측할 수 있는 JTA/XA의 오버헤드가 같은 작업을 하지만 버그를 내포하고 있는 어플리케이션 레벨의 오버헤드로 대체된 것과 같다.

간단한 계산으로 JTA/XA의 이점을 보여줄 수 있다: 은행이 하나 있고, 일반적으로 하루에 백만 트랜잭션을 처리하며 평균적으로 트랜잭션 당 100$가 처리된다고 가정해 보자. 즉 하루에 총 100,000,000$가 처리되는 것이다. 만약 시스템에 문제가 발생하여 1%의 데이터가 유실된다면, 한 번의 문제로 전체 1,000,000$의 손실을 입게 되는 것이다. JTA/XA는 명백하게 이런 문제를 미연에 방지해준다.

 

When NOT to use JTA / XA

아래 표는 JTA/XA를 사용하지 말아야 할 경우와 몇몇 기회비용들을 보여주고 있다. 이 외의 경우에서는 JTA/XA가 신뢰성과 간결함이라는 측면에서 엄청난 혜택을 줄 수 있다.

시나리오

JTA/XA 없이 실행

JTA/XA 혜택

최대 하나의 리소스에 접근

안전함.
(
시스템 락이 트랜잭션 지연을 유지할 수 있음)

락이 무한정 걸리는 것을 방지함

하나의 백엔드 시스템 내에서 여러 자원에 접근

각 접근에 대해 동일한 커넥션을 이용하면 안전함. 서로 다른 접근 간에 데이터를 공유해야 할 경우에는 공유 커넥션 처리를 위해 Thread-local 조작이 필요할 수 있으며, 커밋 처리가 매우 복잡해 짐

데이터 공유가 XA에 의해 처리됨

상이한 백엔드 시스템들에 대한 다중 쿼리

가능함, 하지만 정확성은 보장되지 않음

모든 경우에 있어 정확한 데이터를 읽을 수 있음

다중 백엔드 시스템

안전하지 않음. 시스템 장애로 인해 데이터 손실이 발생해도 상관 없는 경우에만 사용할 것

데이터 손실이나 오염이 발생하지 않음

 

Getting Started with TransactionsEssentials

배경

Atomikos는 모듈화를 감안하여 설계되었다. icatch라고 하는 독점적인 API를 제공하는 트랜잭션 커널이 있는데, 이것을 바탕으로 수많은 트랜잭션 서비스들이 만들어졌다. 그 중 하나가 JTA 구현체이다: icatch의 상위 계층으로 JTA API 구현체가 존재한다.

수많은 다른 서비스들이 있긴 하지만 Atomikos TransactionsEssentials 배포에서 사용할 수 있는 것은 JTA가 유일하다. Atomikos ExtremeTransactions RMI와 웹서비스 인터페이스들을 포함하고 있다.

Atomikos를 이용하여 JTA 프로그래밍

일반적으로는 icatch API는 신경 쓸 필요 없이 JTA 인터페이스만 사용하면 된다.

트랜잭션 매니저를 초기화하려면, com.atomikos.icatch.jta.UserTransactionManager 인스턴스를 생성하고 생성된 인스턴스의 init () 메소드를 호출하면 된다. 어플리케이션이 종료될 때 close() 메소드를 호출하는 것을 잊어서는 안 된다.

이 클래스는 javax.transaction.TransactionManager 인터페이스를 구현했는데, 이 인터페이스에는 JTA 트랜잭션을 제어하기 위한 모든 작업 begin(), commit() 그리고 rollback()과 같은 메소드들이 정의되어 있다.

초기화하는 과정에서 이 클래스는 클래스패스의 루트 디렉토리에 있는 jta.properties 파일을 읽는다. 이 파일에 대한 설정 방법은 JTA Properties를 참고하기 바란다.

간단한 JTA 예제

이제 JTA API를 이용하여 트랜잭션 매니저를 초기화하고 시작하고 커밋 한 뒤 적절하게 셧다운 하는 예제 코드를 확인해 보도록 하겠다. 간결하게 설명하기 위해 예외 처리는 생략하였다.

JDBC JMS 자원을 설정하고 사용하는 상세한 방법은 JDBC와 TransactionsEssentials 사용하기JMS와 TransactionsEssentials 사용하기를 참고하기 바란다. 온라인에서 제공되는 Atomikos ExtremeTransactions 인증 코스를 통해 JDBC JMS에 대해 학습할 수도 있다.

import com.atomikos.icatch.jta.UserTransactionManager; 
import com.atomikos.jdbc.AtomikosDataSourceBean; 
 
import javax.jta.TransactionManager; 
import javax.sql.DataSource; 
 
public class AtomikosExample { 
 
  // Atomikos implementations 
  private static UserTransactionManager utm; 
  private static AtomikosDataSourceBean adsb; 
 
  // Standard interfaces 
  private static TransactionManager tm; 
  private static DataSource ds; 
 
  // initialize resources 
  private static void init() { 
    UserTransactionManager utm = new UserTransactionManager(); 
    utm.init(); 
    tm = utm; 
 
    adsb = ...; // omitted for clarity 
    ds = adsb; 
  } 
 
  // release resources 
  private static void shutdown() { 
    adsb.close(); 
    utm.close(); 
  } 
 
  public static void main(String[] args) { 
    init(); 
 
    tm.begin(); 
 
    Connection c = ds.getConnection(); 
    // use connection to execute SQL 
    c.close(); 
 
    tm.commit(); 
 
    shutdown(); 
  } 
 
} 

 

신고
Posted by Layered 트랙백 0 : 댓글 0

댓글을 달아 주세요