자바스터디 네트워크 [www.javastudy.co.kr]
조대협 [bcho_N_O_SPAM@j2eestudy.co.kr]
실제로 IT 시스템을 구현함에 있어서 트렌젝션처럼 필수적인 요소도 없으리라 생각된다. 특히 금융권이나, 데이타의 정확성등이 요구되는 비지니스 업무에서는 이 트렌젝션 지원이 필수적인 요소로 대두 되고, 좀더 안정적이고, 빠른 트렌젝션 처리를 위해서, 많은 미들웨어 들이 사용되고 있다.
그러나,실제로 프로그래밍을 하는데 있어서, 제대로 트렌젝션 프로그래밍을 하는 개발자는 그리 많지 않다… 물론 많은 미들웨어가 이 트렌젝션에 관련된 많은 작업을 수행해주지만, 절대로 미들웨어는 만능이 아니다. 정확한 트렌젝션의 개념을 알고, 거기에 맞는 프로그래밍을 해야만 제대로 트렌젝션을 보장 받을 수 있다…
이번호 부터 3회에 걸쳐서, 트렌젝션에 대해서 알아보고, 이 트렌젝션을 자동적으로 관리해주는 미들웨어와, 그리고, 실제로 JAVA 기반에서 XA 인터페이스를 이용하거나 EJB를 이용하여 트렌젝션을 프로그래밍하는 방법에 대해서 알아보도록 하자.
이번호는 그 첫시간으로, 먼저 트렌젝션이 무엇인지 그리고 분산 트렌젝션은 어떤식으로 처리되는지 트렌젝션 관리는 어떻게 되는지, 그리고 마지막으로 트렌젝션의 격리 레벨에 대해서 살펴보도록 한다.. 결코 쉬운 내용은 아니지만 적어도 데이타 베이스 프로그래밍을 하는 개발자라면 필히 알고 있어야하는 내용이다.
트렌젝션이란, 중단없이 시작에서부터 종료까지 한번에 수행되어야 하는 하나의 작업단위를 이야기한다. 수행이 끝난후에는 중간에 작업이 실패되었을 경우에는 작업 수행전의 상태로 그대로 돌아가야 한다.
이해를 돕기위해서 쉽게 예를 들어서 설명하도록 하자, A계좌에서 B계좌로 1,000원을 계좌 이체를 한다고 가정하자. 이 작업은 다음과 같은 순서로 이루어지게 된다.
1) A계좌에서 1,000원을 인출한다.
2) B 계좌에 1,000원을 더한다.
만약 위의 작업을 수행하던 도중 A계좌에서 1,000원이 인출된 후에, 은행 시스템의 오류로 인해서 B계좌에 1,000원이 더해지지 않는다면, 중간에 1,000원이라는 돈은 공중에서 증발해 버린것이 된다. 이럴때는 다시 계좌이체를 수행하기 이전의 상태로 되돌려서 A계좌에로부터 1,000원을 인출하지 말아야 한다.
그래서 위의 1),2) 작업은 한꺼번에 이루어져야 한다. 계좌이체 작업과 같이 한번에 이루어져야 하는 작업을 트렌젝션이라고 부른다.
이처럼 트렌젝션은 쇼핑몰의 주문 결제나,예매와 같이 Mission Critical한 작업 있어서 필수적인 개념이라고 할 수 있다.
트렌젝션은 크게 4가지 특성을 가지는데 Atomicity,Consistency,Isolation,Durability로, 이 네가지를 줄여서 ACID라고 부른다.
그럼 이제 부터 이 ACID속성 각각에 대해서 좀 더 상세하게 알아보도록 하자.
Database modifications must follow an all or nothing.
원자성이란, 하나의 트렌젝션이 하나의 단위로만 처리가 되어야 한다는것이다. 하나의 트렌젝션안에 여러가지 step 의 트렌젝션이, 하나로 처리가 되어야한다. 위의 계좌 이체처럼, 계좌에서 돈을 빼고, 그 돈을 다른 계좌에 넣는 것과 같이 두개이상의 step으로 구성되어 있더라도, 계좌 이체라는 하나의 트렌젝션으로 처리가 된다.
그래서, 어느 step에서라도 트렌젝션이 실패가 되었을 경우에는 모든 상태가 트렌젝션 상태 전으로 rolled back되서, 이전 상태를 유지해야 한다.
즉 트렌젝션의 원자성은 트렌젝션이 완전히 수행되거나, 아무것도 수행되지 않은 All or Nothing의 이미를 가지게 된다.
states that only valid data will be written to the database
트렌젝션이 종료된후에 저장되는 데이타는 오류없는 데이타만 저장되어야 한다.
다시 풀어서 이야기하자면 계좌이체 과정에서, 인출은 되었는데, 다른 계좌로 돈이 안넘어갔다던지, 트렌젝션이 끝난후에, 잘못된 데이타 값으로 변경되었던지, 데이타베이스 constraint가 깨졌던지 했을때, Consistency가 잘못되었다고 이야기하고, 이런경우에 그 트렌젝션 내용을 저장하지 말고, 이전 상태로rollback되어야 한다.
Multiple transactions occurring at the same time not impact each other execution
격리성이란, 트렌젝션중에 의해서 변경된 내용이 트렌젝션이 완료되기전까지는 다른 트렌젝션에 영향을 주어서는 안된다는 이야기이다.
Tx A라는 트렌젝션 수행중 EMP 테이블의 값을 변경했을때, Tx A가 완료되지 않은 상태에서 Tx B가 EMP 테이블의 값을 참고할 경우에, Tx A에 의해 변경된 값을 참고하는것이 아니라(아직 TxA가 완료되지 않았기 때문에) 기존의 값을 참고하게 해야한다.
Ensures that any transaction committed to the database will not be lost.
지속성이랑, 트렌젝션이 완료된후에의 값이 영구저장소에 제대로 기록이 되어야한다는 의미이다. 트렌젝션이 완료되었는데, 디스크 IO에러,네트워크 등으로 그 값이 제대로 기록이되지 않거나 해서는 안된다는 이야기다.
그러면, 일반적으로 프로그래밍 코드 상에서, 트렌젝션을 사용하는 방법에 대해서 알아보도록하자.
Begin Transaction // Do transaction if(error) then roll back Prepare Transaction. if (prepare transaction failed) then rollback else Commit Transaction End Transaction |
일반적인 트렌젝션 프로그램은 위와 같은 코드 구조를 가지고 있다.
먼저 Begin Transaction을 시작하면, 여기부터가 하나의 Transaction임을 알리는 boundary의 역할을 한다.
// Do transaction 부분에서 Transaction에 대한 실질적인 트렌젝션 처리 코드가 들어간다.
(SQL문장등등..)
If(error) then roll back :트렌젝션 처리중에 에러가 발생하면, rollback을 실행해서, 트렌젝션 수행전 상태로 되돌린다. 트렌젝션에 대한 처리가 끝나면, Prepare Transaction 을 실행해서, commit이 가능한지 각각의 트렌젝션 대상 (Resource Manager, DBMS와 같은..)에 체크를 한다.
If(prepare transaction faield) then rollback
Else Commit Transaction
Commit이 될준비가 됐으면 commit을 실행하고, 아닌경우에는 rollback을 한다.
모든 트렌젝션이 완료되었으면 End Transaction으로 이 트렌젝션을 종료한다.
언뜻 보기에 당연하고 단순한 과정으로 보일 수 있으나, 이는 Transaction을 처리하는 대부분의 미들웨어에서 공통적으로 사용하는 방법이니, 꼭 머리속에 익혀두도록 하자. J2EE EJB에서의 트렌젝션은 대부분 이런 트렌젝션의 처리 과정을 EJB Container에서 자동으로 처리해주기 때문에(CMT : Container Managed Transaction Model) 실질적으로 개발자가, 직접적으로 트렌젝션을 Handling할일은 그렇게 많지는 않다.
그러면 이제 미들웨어의 핵심 기능이라고할 수 있는 분산 트렌젝션(Distributed transaction, or Global transaction이라고도 한다.)에 대해서 알아보도록 하자.
분산 트렌젝션이란, 두개 이상의 Resource (RDBMS등등)을 이용해서 하나의 트렌젝션으로 처리하는것이 이야기 한다. 분산 트렌젝션을 수행하기 위한 시스템의 구조를 잠깐 살펴보도록 하자.
Transsaction Manager (TM) RM Resource Manager (RM) XA javax.transaction.xa.XAResource javax.transaction.TransactionManager Application (AP) |
< 그림 1. 분산 트렌젝션을 수행하기 위한 미들웨어의 대략적 구조 >
분산 트렌젝션을 구성하는 구조에는 크게 3가지 요소가 결합된다. AP,TM,RM이다.
Application
각각을 알아보면, AP는 Application, 즉 User가 트렌젝션을 발생시키고 프로그래밍 하는 부분을 말한다. 우리가 개발하게될 부분은 이부분이며, J2EE의 EJB를 이용한 분산 트렌젝션의 경우에는 Web Application Server (이하 WAS)가 이역할을 자동으로 해결해주게 된다.
Resource Manager
다음으로는 RM은 Resource Manager로, 각각의 Resource를 컨트롤 해주는 기능을 갖는다. JAVA에서는 일종의 JDBC 드라이버와 같은 기능을 한다고 생각하면되고, 실제로 RM의 기능을 하는 모듈은 DBMS Vendor에서 JDBC드라이버 패키지에 넣어서 배포하도록 되어 있다. 단순히 RDBMS 뿐 아니라, RM을 가지고 있는 Resource로는 XA를 지원하는 ISAM,JMS와 같은 메시징 시스템에서 부터 상당히 많은 종류가 있다.
Transaction Manager
마지막으로 TM은 Transaction Manager의 약자로, 전체 분산 트렌젝션을 관리해주는 역할을 한다. WAS에 이 기능이 내장되어 있다.
XA는 eXtended Architecture로 Open Group에 의해서 정의되었으며, Global Transaction을 관리하기 위한 귀약과, RM과 TM사이의 프로토콜을 정의하고 있다.
그러면 어떻게 트렌젝션이 수행이 되는지, 대략적인 시나리오를 한번 살펴보도록 하자.
아래와 같은 트렌젝션 수행 코드가 있다고 하자.
Begin Transaction Xid = get new transation id doTransaction(Xid,RM1) doTransaction(Xid,RM2)
commitTransaction(Xid) End Transaction |
Begin Transaction
Xid = get new transaction id
먼저 트렌젝션이 수행되면, 그 트렌젝션에 대한 Global Transaction Id인 Xid가 생성이 된다.
그리고, DB1과 DB2에 각각 연결되어 있는 RM1과 RM2이 연결되어 있을때,
doTransaction(Xid,RM1)
doTransaction(Xid,RM1)
AP가 직접 RM을 통해서 DB작업을 실행한다.
이때 Xid를 같이 RM에게 보내서 지금 실행되는 작업이 어떤 트렌젝션에 관련된 작업인지를 식별할 수 있게 해준다. 물론, 이 작업중에, error가 발생했을경우에는 roll back을 수행한다. 여기까지 작업이 종료되면,
commitTransaction(Xid)
을 하게 되는데. 이 작업은 TM과 RM간의 작업이다. TM이 RM1,RM2에게 commit할 준비가 되었는지를 각각의 RM에 물어서 체크를 한다. 이때 RM은 동시에 여러개의 Transaction이 수행중일 수 있다. (RDBMS가 한번에 하나의 트렌젝션만 처리 하고 있지는 않다. 당연히 여러개의 트렌젝션을 처리하고 있다. ) 그래서, 어떤 트렌젝션을 체크할것인지를 구별하기 위해서, TM은 RM에게 Xid를 실어서 보낸다. 이렇게 TM이 RM에게 Xid로 식별된 트렌젝션이 commit할 준비가 되었는지를 묻는 과정을 prepare라고 한다.
Prepare가 되었으면 TM은 RM에게 commit을 하도록 지시 한다. 만약 prepare가 실패하면 TM은 RM에게 rollback을 지시한다.
이렇게 prepare과정과 commit과정 두단계를 거치는 것을 two phase commit (2pc)라고 한다. 분산 트렌젝션에서 많이 나오는 개념이니까는 잘 알아두도록 하자.
EndTransaction
트렌젝션을 종료하고, 그 내용을 반영한다.
지금까지 살펴본 내용은 분산 트렌젝션의 아주 일반적인 작동원리를 살펴보았다. (실제로 XA프로토콜을 이용해서, 분산 트렌젝션을 처리하는 과정은 이보다 좀더 복잡하다. 좀 더 자세한 사항은 XA 관련 Spec이나, Transaction 관련 서적을 참고하기 바란다.)
데이타베이스 환경에서는 일반적으로 Single User가 아니라 Multi User 환경을 지원하기 때문에, 여러명은 동시에 같은 데이타를 읽거나 insert/update하는 일이 생긴다. 이 과정에서, 데이타의 일관성(Consistency)와 동시성(Concurrency)를 보장해줘야한다.
ㅁData Consistency – 데이타 일관성은, 어느 사용자가 데이타를 ACCESS 하던지, 같은 내용의 데이타를 볼 수 있는 속성
ㅁData Concurrency – 데이타 동시성은 , 동시에 여러 사용자가 같은 데이타를 ACCESS할 수 있는 속성
일반적으로 데이타 베이스는 여러개의 트렌젝션을 하나씩 순차적으로 처리(serially) 하는것이 아니라 동시에 여러 트렌젝션을 처리한다. 이 과정에서 동시에 처리되는 트렌젝션간의 변경된 데이타를 각각 어떻게 적용할 것인가에 대한 규칙이 Transaction Isolation Level이다.
예를 들어 설명해보자.
1) 트렌젝션 TX_A가 시작 되었고, 동시에 트렌젝션 TX_B가 시작되었다.
2) TX_A에서 SELECT NAME FROM TABLE_A WHERE ID=1 을 수행하였다. 결과는 ORIGIN이 나왔다고 가정하자.
3) TX_B에서 UPDATE TABLE_A SET NAME=”CHANGED” WHERE ID =1
4) TX_A에서 다시 SELECT NAME FROM TABLE_A WHERE ID=1 을 수행하였다.
5) TX_B를 COMMIT하였다.
6) TX_A에서 다시 SELECT NAME FROM TABLE_A WHERE ID=1 을 수행하였다.
이런 과정을 거쳤을때, 4)에는 CHANGED가 나올까? 아니면 ORIGIN이 나올까? 6)에서도 CHANGED가 나올까 ORIGIN이 나올까?즉 어떤 트렌젝션이 데이타를 변경하였을때 같은 데이타를 억세스하는 트렌젝션에 대해서 어떤 영향을 미치느냐를 정하는 규칙(Isolation Level)에 따라서 나올 수 있는 결과가 틀리게 된다.
서로 다른 트렌젝션에 어떻게 영향을 주느냐에 따라서 3가지 Preventable Phenomena로 나뉘어 지는데.. 그 내용을 살펴보자.
Dirty Read
어떤 트렌젝션이 데이타를 변경하고, COMMIT을 하지 않았을때도, 다른 트렌젝션에서 그 값을 읽으면 변경된 값이 반영되는 경우이다.
1) TX_A가 시작되고, SELECT NAME FROM TABLE_A WHERE ID=1에서 ORIGIN이 출력되었다고 가정하자.
2) TX_B에서 UPDATE TABLE_A SET NAME=”CHANGED” WHERE ID =1 를 실행한후
3) TX_A가 시작되고, SELECT NAME FROM TABLE_A WHERE ID=1에서 CHANGED가 출력된다.
즉 2)에서 UPDATE한 내용을 COMMIT하지 않았는데도 반영이 되면 이를 Dirty Read라고 한다.
NonRepeatable Read (Fuzzy Read)
어떤 트렌젝션이 데이타를 변경 (update와 delete만 해당됨)하고, COMMIT을 하면, COMMIT된 내용은 다른 트렌젝션에 반영된다.insert된 내용은 반영되지 않는다.
1) TX_A가 시작되고, SELECT NAME FROM TABLE_A WHERE ID=1에서 ORIGIN이 출력되었다고 가정하자.
2) TX_B에서 UPDATE TABLE_A SET NAME=”CHANGED” WHERE ID =1 를 실행한후
3) TX_A에서 SELECT NAME FROM TABLE_A WHERE ID=1하면 ORIGIN이 출력된다.
4) TX_B를 COMMIT한후
5) SELECT NAME FROM TABLE_A WHERE ID=1하면 CHAGNED가 출력된다.
Phantom Read
어떤 트렌젝션이 새로운 데이트를 삽입(Insert)하고, COMMIT하면, COMMIT된 내용은 다른 트렌젝션에 반영된다. (NonRepeatable read와 같으나, NonRepetable을 update,delete가, Phantom Read는 insert된 내용이 반영된다.)
1) TX_A가 시작되고, SELECT COUNT(*) FROM TABLE_A WHERE GRP=1에서 10이 출력되었다고 가정하자.
2) TX_B에서 INSERT INTO TABLE_A (ID,GRP) VALUES(100,1);
3) TX_A에서 SELECT COUNT(*) FROM TABLE_A WHERE GRP=1하면 10 이 출력된다.
4) TX_B를 COMMIT한후
5) TX_A에서 SELECT COUNT(*) FROM TABLE_A WHERE GRP=1하면 11 이 출력된다
ANSI/ISO SQL 표준 (SQL92)에서는 4가지 Isolation Level을 지원하는데. 그 내용은 간단하게 다음과 같은 표로 정리된다.
Isolation Level |
Dirty Read |
NonRepetable Read |
Phantom Read |
Read uncommitted |
Possible |
Possible |
Possible |
Read committed |
Not Possible |
Possible |
Possible |
Repetable read |
Not Possible |
Not Possible |
Possible |
Serializable |
Not Possible |
Not Possible |
Not Possible |
많이 사용하고있는 Oracle DBMS의 경우에는 Default가 Read Commited로 설정이 되어 있으며, Oracle 9(현재)까지, Isolation Level은 Read commited와, Serializable 두가지만 지원하고 있다.
Oracle에서 Isolation Level을 변경하는 방법은 SQL PLUS에서 LEVEL에 따라서 다음과 같은 명령어를 치면된다.
SET TRANSACTION ISOLATION LEVEL READ COMMITED;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
JAVA에서도 데이타베이스 Isolation Level에 따라 알맞은 Isolation Level을 지정해 줘야하는데, 그 방법은 다음과 같다.
Connection conn = DriverManager.getConnection(url, uid, pwd);
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
System.out.println(conn.getTransactionIsolation());
지금까지 간단하게 Isolation Level에 대해서 살펴보았다.. 트렌젝션에 관련된 내용이고 심심지않게 언급되는 내용이라서 간단하게 정리해보았다. 좀더 자세한 내용은 Oracle 홈페이지에서 Oracle 8i Concepts Release 8.1.5 A67781-01 문서의 27장 Data Concurrency and Consistency 부분을 살펴보기 바란다.
지금까지 대략적인 트렌젝션에 대한 개념과, 어떤식으로 트렌젝션이 처리되는지, 그리고, 트렌젝션 격리 레벨에 대해서 알아보았다. 그럼 다음호에서는 JAVA에서의 트렌젝션 프로그래밍 즉 JTA,JTS 그리고 JDBC XA 드라이버를 이용한 분산 트렌젝션 프로그래밍에 대해서 알아보도록 한다.