4 장. JBoss에서의 트랜잭션

JTA 트랜잭션 서비스

이번 장에서는 JBoss에서의 트랜잭션 관리와 JBossTX 아키텍쳐에 관하여 논의하도록 하겠습니다. JBossTX 아키텍쳐에서는 어떠한 자바 트랜잭션 API (JTA) 트랜잭션 관리자 임플리멘테이션의 사용도 가능하게 합니다. JBossTX에는 디폴트 트랜잭션 관리자로써 사용되는 JTA 호환 트랜잭션 관리자의 in-VM 임플리멘테이션을 포함하고 있습니다. 우리는 제일 먼저 트랜잭션의 중요한 컨셉에 대해서 개략적으로 살펴본 후 JBossTX 아키텍쳐 논의에 필요한 충분한 기본기식을 제공할 수 있도록 JTA의 개념을 살펴볼 것입니다. 그런 다음 JBossTX 아키텍쳐를 구성하고 있는 인터페이스들에 대해서 살펴본 후 마지막으로 교환가능한 트랜잭션 관리자들과의 통합 가능성을 지원하는 MBean들에 대해서 살펴보겠습니다.

4.1. Transaction/JTA 개요

이번 논의의 목적은 트랜잭션을 ACID 속성들을 갖는 하나 이상의 공유된 리소스들을 포함한 하나 이상의 오퍼레이션을 포함한 작업 단위로써 정의할 수 있게 하는 것입니다. ACID는 4개의 트랜잭션의 중요한 속성인 원자성(atomicity), 일관성(consistency), 독립성(isolation) 그리고 내구성(durability)의 앞자만 따서 만들어진 용어입니다. 이 용어의 의미는 다음과 같습니다:

이러한 개념들을 표현하기위해서 간단한 은행 이체 어플리케이션을 생각해보겠습니다. 은행 어플리케이션은 많은 계좌이체를 갖는 데이터베이스를 갖습니다. 모든 계좌이체에 들어있는 돈의 합은 항상 0이어야만 합니다. M만큼의 돈이 A고객으로부터 B고객으로 이체될때는 A고객의 계좌에서 M만큼의 돈을 빼서 B고객의 계좌로 M만큼의 돈을 더해줍니다. 이러한 오퍼레이션은 반드시 트랜잭션으로 실행되어야만 하며 위에서 언급된 4개의 ACID 속성들이 중요한 사항이 됩니다.

원자성 속성은 인출과 예금 모두에서 개별적인 단위로써 수행됩니다. 만약 어떤 이유가 되었던간에 이 두개중 어느 한 작업이라도 실패한다면, 두 개 모두 실행되지 말아야 합니다.

일관성 속성은 트랜잭션후에 계좌이체의 총합이 여전히 0으로 되어지도록 한다는 것입니다.

독립성 속성은 한 사람 이상의 은행 직원이 동시에 시스템을 사용하려할 때 더욱 중요합니다. 인출 또는 예금 과정은 3단계 과정으로 수행되어질 수 있습니다: 먼저 데이터베이스로부터 해당 계좌에서 예금액을 읽어옵니다. 그런 다음 데이터베이스로부터 읽어온 액수에서 빼거나 더합니다. 마지막으로 새로운 액수가 데이터베이스에 쓰여집니다. 트랜잭션의 독립성이 없다면, 안좋은 상황이 벌어질 수 있습니다. 즉, 만일 A고객의 예금액을 동시에 두개의 프로세스에서 읽어서 데이터베이스에 새로운 금액을 쓰기전에 각각의 프로세스에서 독립적으로 더하거나 빼주게 되면 첫번째 변경한 프로세스는 두 번째(최종적으로 수행된) 프로세스에 의해 덮여쓰여지므로 잘못된 작업이 일어나게 됩니다.

내구성 속성 또한 중요합니다. 계좌 이체에 대한 트랜잭션이 커밋되었다면, 다음에 오는 일련의 비정상적인 시스템 실패때문에 이미 성공적으로 끝난 계좌 이체에 대한 행위가 취소되지 않도록 하는 신뢰된 프로세스가 반드시 필요합니다.

4.1.1. 비관적인(Pessimistic) 그리고 낙관적인(optimistic) 락킹(locking)

Transactional isolation은 트랜잭션으로 어떤것을 사용하던지 일반적으로 락킹(locking)이 구현됩니다. Transactional locking은 2가지 다른 접근 방법이 존재합니다: 비관적인(Pessimistic) 락킹과 낙관적인(optimistic) 락킹.

비관적인 락킹의 단점은 트랜잭션에 첫번째 엑세스하는 프로세스가 트랜잭션을 종료하기전까지는 다른 트랜잭션이 접근할 수 없도록 하기위해 리소스가 락이 걸린다는 것입니다. 대부분의 트랜잭션이 그저 리소스를 살펴보는 것만 하고 변경하지 않을 경우에는 배타적인(exclusive) 락은 락의 경쟁에 과당경쟁을 초래하므로 낙관적인(optimistic) 락으 보다 알맞는 방법이 될 것입니다. 비관적인 락킹을 사용하면, 락들은 장애발생에 안전한 대처가 가능합니다. 은행 어플리케이션 예제에서 계좌이체는 트랜잭션에 액세스하는 순간 락이 걸립니다. 이렇게 락이 걸린동안 다른 트랜잭션들에서 계좌를 사용하려고 할 때는 락이 풀릴때까지 다른 프로세스들은 지체된 후 결과를 얻게되거나 롤백이 됩니다. 락은 트랜잭션이 커밋되거나 롤백될때까지 존재하게 됩니다.

난관적인 락킹을 사용하면, 리소스는 트랜잭션에 첫번째 액세스가 되는 동안에도 실제 락킹이 일어나지 않습니다. 대신 비관적인 락킹 방식으로 락이걸렸던 때의 리소스 상태가 저장됩니다. 다른 트랜잭션들은 리소스에 동시에 액세스가 가능하며 변경에 따른 충돌의 가능성이 남게 됩니다. 커밋이 될때 리소스는 영속적인 스토리지를 갱신(update)하기위해 처음 트랜잭션에 액세스할 때 저장하였던 리소스의 상태와 한번 더 스토리지로부터 리소스의 상태를 읽어와 비교합니다. 만약 두 상태가 다르게 되면, 갱신에 충돌이 생기게 되어 트랜잭션은 결국 롤백됩니다.

은행 어플리케이션 예제에서 계좌의 금액은 처음 트랜잭션 접근시 저장됩니다. 트랜잭션에서 계좌의 금액을 변경하면, 금액이 갱신되려고 하기 바로전에 다시 한번 더 저장되어 있는 금액을 읽어옵니다. 트랜잭션이 시작된 후 금액이 변경되었을 경우, 트랜잭션은 실패하게 되며 금액이 변경되지 않았다면 새로운 금액이 영속적인 스토리지에 쓰여집니다.

4.1.2. 분산된 트랜잭션의 컴포넌트들

분산된 트랜잭션에는 다수의 컴포넌트들이 참가하고 있습니다. 그 참가 컴포넌트들은 다음과 같습니다:

4.1.3. Two-phase XA 프로토콜

하나의 트랜잭션이 커밋되기 직전에 트랜잭션 관리자는 트랜잭션내의 모든 것들이 전부 커밋되거나 혹은 전무 롤백되도록 확실히 해줄 책임을 갖습니다. 트랜잭션내에 오직 하나의 복구가능한 리소스만이 포함되어 있다면, 트랜잭션 관리자의 임무는 단순합니다: 그냥 리소스에게 안정적인 스토리지에 변경을 커밋하라고만 하면 됩니다.

트랜잭션내에 하나 이상의 복구가능한 리로스가 포함되어 있을 경우, 커밋의 관리는 보다 복잡해 집니다. 그저 각각의 복구가능한 리소스들에게 안정적인 스토리지에 변경들을 커밋하라는 요청만으로는 트랜잭션의 중요한 속성중에 하나인 원자성을 유지시키는데 충분하지 않습니다. 이것은 만일 복구가능한 리소스중 하나가 커밋되고 다른 하나는 커밋에 실패한 경우 트랜잭션의 일부만 커밋되고 나머지 부분은 롤백될 수 있기 때문입니다.

이러한 문제를 해결하기위해서는 two-phase XA 프로토콜이 사용되어야 합니다. XA 프로토콜에는 실제 커밋 상태(phase)전에 추가적인 준비된 상태(phase)가 포함되어 있습니다. 복구가능한 리소스에게 변경에 대해 커밋을 요청하기전에 트랜잭션 관리자는 모든 복구가능한 리소스들에게 커밋을 준비하도록 요청합니다. 이 지시를 받은 복구가능한 리소스에서 트랜잭션 커밋을 준비할때 트랜잭션의 커밋이 가능하도록 보장하게 됩니다. 그 이유는 여전히 필요하다면 트랜잭션의 롤백도 가능하기 때문입니다.

따라서 첫번째 상태(phase)는 트랜잭션 관리자가 모든 복구가능한 리소스들에게 커밋을 준비하라는 요청으로 구성됩니다. 복구가능한 리소스들중 어느것이라도 준비가 실패할 경우, 트랜잭션은 롤백됩니다. 하지만 모든 복구가능한 리소스들에서 커밋할 준비가 되어있다고 가리킨다면, XA 프로토콜의 두번째 상태가 시작됩니다. 여기에는 트랜잭션 관리자가 모든 복구가능한 리소스들에게 트랜잭션을 커밋하라는 요청으로 구성됩니다. 모든 복구가능한 리소스들이 준비되었다고 알려주었기 때문에 이 단계는 실패할 수 없습니다.

4.1.4. 발견적 예외상황들(Heuristic exceptions)

분산된 환경에서의 커뮤니케이션들은 실패할 수도 있습니다. 트랜잭션 관리자와 복구가능한 리소스사이의 커뮤니케이션이 지정된 시간안에 가능하지 않을 경우, 복구가능한 리소스는 트랜잭션의 컨텍스트내에서 변경된 사항을 일방적인 커밋이나 롤백중 하나를 결정할 것입니다. 이러한 결정을 발견적 결정(heuristic decision)이라 부릅니다. 트랜잭션의 일부분은 커밋되는 중 다른 부분들이 롤백될 수 있기 때문에 이것은 트랜잭션 시스템내에서 발생될 수 있는 가장 나쁜 오류중에 하나입니다. 따라서 트랜잭션의 원자성 속성을 위배하고 데이터 정합성이 깨질수 있는 가능성이 있습니다.

발견적 예외상황으로부터 일어날 수 있는 위험성때문에, 발견적 결정을 일으키는 복구가능한 리소스는 트랜잭션 관리자가 발견적 결정에 대한 사항을 잊도록 통보하기전까지 안정적인 스토리지내에 결정에 대한 모든 정보를 관리할 필요가 있습니다. 안정적인 스토리지내에 저장되는 발견적 결정에 대한 실제 데이터는 복구가능한 리소스의 타입에 따라 다르며 표준화시킬 수 없습니다. 시스템 관리자가 데이터를 살펴보고 이중 정합성에 문제가 있는 데이터를 정정할 수 있도록 리소스를 편집가능하게 하는 것이 해결방안입니다.

JTA에서 정의된 발견적 예외상황에는 몇가지 다른 종류가 있습니다. 복구가능한 리소스가 롤백을 요청할 때는 javax.transaction.HeuristicCommitException이 발생되어 발견적 결정이 이루어졌다는 것을 보고하고 이와 관련된 모든 갱신이 커밋되어집니다. 이와 상반되는 것이 javax.transaction.HeuristicRollbackException이며, 이것은 복구가능한 리소스가 커밋을 요청할 때 발견적 결정이 이루어졌다는 것을 가리켜주며 이와 관련된 모든 갱신들이 롤백됩니다.

javax.transaction.HeuristicMixedException은 가장 안좋은 발견적 예외상황입니다. 다른 부분들이 롤백되는 동안 트랜잭션의 일부분이 커밋되어졌다는 것을 알려주기위해 발생됩니다. 트랜잭션 관리자는 다른 복구가능한 리소스들이 발견적 롤백이되는 동안 일부 복구가능한 리소스가 발견적 커밋을 하게될 때 이 예외상황을 발생시킵니다.

4.1.5. Transaction IDs 와 branches

JTA에서는 트랜잭션의 식별이 javax.transaction.xa.Xid 인터페이스를 구현하는 객체내에 감싸집니다. 트랜잭션 ID는 세 부분으로 결합되어 있습니다.:

트랜잭션 분기들은 동일한 전역 트랜잭션의 다른 부분들을 식별하는데 사용됩니다. 트랜잭션 관리자가 새로운 복구가능한 리소스를 트랜잭션에 참여시키면 새로운 트랜잭션 분기가 만들어집니다.

4.2. JBoss 트랜잭션의 내부

JBoss 어플리케이션 서버는 사용하는 실제 트랜잭션 관리자와 독립적으로 만들어져있습니다. JBoss는 서버의 트랜잭션 관리자의 뷰로 JTA javax.transaction.TransactionManager 인터페이스를 사용합니다. 따라서, JBoss는 JTA TransactionManager 인터페이스를 구현하는 어떠한 트랜잭션 관리자도 사용할 수 있습니다. 트랜잭션 관리자는 잘 알려진(well-known) JNDI 위치인 java:/TransactionManager에서 얻어진 것을 사용하게 됩니다. 이것은 서버 트랜잭션 관리자를 위해 전역적으로 사용이 가능한 액세스 포인트입니다.

트랜잭션 컨텍스트들이 RMI/JRMP 호출을 통해 파급되어진다면, 트랜잭션 관리자는 트랜잭션 파급(propagation) 컨텍스트(TPC)의 import와 export에 대한 두개의 간단한 인터페이스들을 또한 구현해주어야만 합니다. 이 인터페이스들은 org.jboss.tm.TransactionPropagationContextImporterorg.jboss.tm.TransactionPropagationContextFactory 입니다.

또한 사용된 실제 트랜잭션 관리자의 독립적인 성격이 의미하는 것은 JBoss가 사용되는 트랜잭션 파급 컨텍스트의 타입에 대한 포맷을 지정하지 않는다는 것입니다. JBoss에서는 한 TPC가 Object 타입이며 필요한 사항은 단지 TPC가 반드시 java.io.Serializable 인터페이스를 구현해야만 한다는 것입니다.

원격 호출에 대해 RMI/JRMP 프로토콜을 사용할 경우, TPC는 원격 메쏘드 호출 요청을 포워딩하는데 사용되는 org.jboss.ejb.plugins.jrmp.client.RemoteMethodInvocation 클래스내에 하나의 필드로 전송됩니다.

4.2.1. JBoss에 트랜잭션 관리자 적용시키기

하나의 트랜잭션 관리자는 JBoss와 쉽게 통합될 수 있도록 자바 트랜잭션 API를 구현해야 합니다. JBoss내에서의 거의 모든 것들에서 처럼 트랜잭션 관리자도 하나의 MBean으로 관리되어집니다. 모든 JBoss의 서비스들처럼, 적절한 생명-주기(life-cycle) 관리를 보장하기위해 org.jboss.system.ServiceMBean 구현을 해야만 합니다.

트랜잭션 관리자 서비스를 구동하는데 꼭 필요한 것은 JNDI쪽으로 3개의 필요한 인터페이스의 구현부를 연결하는 것입니다. 이 인터페이스들과 이들의 JNDI 위치는 다음과 같습니다:

이러한 JNDI 바인딩이 성립되는것은 자신의 임플리멘테이션을 JBoss 서버 트랜잭션 관리자로 인스톨하도록 해야할 필요가 있는 모든 트랜잭션 관리자 서비스들입니다.

4.2.2. 디폴트 트랜잭션 관리자

JBoss는 디폴트 트랜잭션 관리자로 빠르게 동작하는 VM에 내장된 트랜잭션 관리자를 사용합니다. 이 트랜잭션 관리자는 매우 빠르기는 하지만, 다음의 2가지 제한을 갖습니다:

대응되는 디폴트 트랜잭션 관리자 MBean 서비스는 org.jboss.tm.TransactionManagerService MBean 입니다. 이 서비스는 두개의 환경 설정 속성을 갖습니다:

4.2.2.1. org.jboss.tm.XidFactory

XidFactory MBean은 org.jboss.tm.XidImpl 형태의 javax.transaction.xa.Xid 인스턴스를 위한 팩토리입니다. XidFactory는 다음과 같은 속성들을 통해 구성되는 XidImpl의 커스터마이징을 가능하게 합니다:

4.2.3. UserTransaction 지원

JTA javax.transaction.UserTransaction 인터페이스는 어플리케이션이 트랜잭션을 명확하게 제어할 수 있도록 합니다. 스스로 트랜잭션을 관리하는 엔터프라이즈 세션 빈즈(BMT)의 경우, 빈 컨텍스트 객체인 javax.ejb.SessionContext상에서의 getUserTransaction 메쏘드를 호출함으로써 UserTransaction을 얻을 수 있습니다.

주의: BMT 빈즈의 경우, JNDI 룩업을 사용해서 UserTransaction 인터페이스를 얻지말아야 합니다. 이렇게 해서 얻게되면 EJB 시방서(specification)를 위배하게 되며 반환받은 UserTransaction 객체는 중요한 점검을 할 수 있도록 하는데 필요한 EJB 컨테이너의 hook을 갖지 않습니다.

다른 곳에서 UserTransaction 인터페이스를 사용하기 위해서는 org.jboss.tm.usertx.server.ClientUserTransactionService MBean이 반드시 설정되고 시작되어야만 합니다. 이 MBean은 UserTransaction JNDI 이름 밑에서 UserTransaction 구현을 생성합니다. 이 MBean은 표준 JBoss 배포판에서 기본적으로 설정되어져 있으며 설정가능한 속성을 갖지 않습니다.

스탠드 어론 클라이언트(즉, 서버가 아닌 가상 머신내에서 동작하는 클라이언트)로부터 JNDI 룩업을 통해 UserTransaction을 얻게되면, 씬(thin) 클라이언트에 적당한 매우 단순한 UserTransaction 이 반환됩니다. 이런 UserTransaction 구현에서는 UserTransaction 객체가 획득되어진 서버측의 트랜잭션들만 제어합니다. 클라이언트내에서 이루어진 로컬 트랜잭셔널 작업은 이 UserTransaction 객체에 의해 시작되어진 트랜잭션내에서 이루어지지 않습니다.

UserTransaction 객체가 동일한 가상 머신 즉 하나의 JBoss 서버내에서 UserTransaction JNDI 이름을 룩킹함으로써 얻어지는 경우, JTA TransactionManager에 대한 단순한 인터페이스가 반환됩니다. 이것은 JBoss내에 내장된 웹 컨테이너내에서 동작하는 웹 컴포넌트들에 적당합니다. 웹 컴포넌트들이 내장된 웹 서버에 배치될 때, 배치자(deployer)는 표준 java:comp/UserTransaction ENC 이름을 전역적인 UserTransaction 바인딩으로 링크시키는 JNDI를 만들기 때문에 웹 컴포넌트들은 J2EE에서 지정한 것 처럼 JNDI 이름 아래의 UserTranaction 인스턴스를 룩업할 수 있습니다.