내용 목차
본 장에서는 분산 트랜잭션(Distributed Transaction)를 처리하는 데 사용되는 XA를 설명한다.
하나의 데이터베이스 인스턴스 내에서는 한 트랜잭션으로 묶인 여러 개의 SQL 문장이 모두 커밋되거나 롤백된다. 네트워크로 연결된 여러 개의 데이터베이스 인스턴스가 참여하는 트랜잭션에서도 마찬가지로 각각 다른 데이터베이스 인스턴스에서 수행한 SQL 문장이 모두 동시에 커밋되거나 롤백될 수 있는 방법이 필요하다. 이렇게 여러 개의 노드 또는 다른 종류의 데이터베이스가 참여하는 하나의 트랜잭션을 분산 트랜잭션(Distributed Transaction)이라고 한다.
분산 트랜잭션에 대한 자세한 내용은 "Tibero 관리자 안내서"를 참고한다.
Tibero는 X/Open DTP(Distributed Transaction Processing) 규약의 XA를 지원한다. XA는 Two-phase commit를 이용하여 분산 트랜잭션을 처리한다. 분산 환경에서 트랜잭션의 무결성을 보장하기 위해서 사용하는 커밋 방법은 Two-phase commit이다.
보통 두 개 이상의 노드가 특정 트랜잭션을 함께 수행하고 있다면, 일반적으로 사용자의 요청을 받아 트랜잭션을 시작한 노드가 코디네이터가 된다. TP-Monitor가 있는 시스템일 경우 TP-Monitor가 그 역할을 한다.
Two-phase commit mechanism은 크게 두 단계로 작업이 이루어진다. 위 예제를 기준으로 Two-phase commit를 설명하면 다음과 같다.
커밋을 준비하는 단계로 다음의 세부 과정으로 실행된다.
단계 | 설명 |
---|---|
send "prepare" | 각 노드는 코디네이터 노드로부터 커밋을 준비하라는 메시지를 받는다. |
reply "prepared" | 각 노드는 메시지를 받은 후 복구를 위해 로그 등에서 커밋이 가능한지를 검사한다. 또는 필요에 따라 자체적으로 롤백을 한다. 만약 커밋이 가능하다면 최종으로 커밋한 로그를 제외한 모든 작업을 수행한 후 커밋이 준비되었다는 메시지를 코디네이터 노드로 전달한다. |
실제로 커밋한 기록을 저장하는 단계로 Second Phase은 다음의 세부 과정으로 실행된다.
단계 | 설명 |
---|---|
send "commit" | 코디네이터 노드는 모든 노드에서 커밋이 되었다는 메시지를 전달받는다. 이를 확인한 후 실제로 커밋을 실행하라는 메시지를 각 노드에 보낸다. |
reply "committed" | 각 노드는 커밋을 기록한 후 커밋이 완료되었다는 메시지를 코디네이터 노드로 전달한다. |
다음은 Two-phase commit의 일반적인 예를 나타내는 그림이다.
Two-phase commit mechanism에 의해 첫 번째 prepare 메시지를 받으면 데이터베이스는 분산 트랜잭션에 해당하는 리소스를 잠금을 설정하거나 로그를 남김으로써 커밋할 준비를 한다. 그런데 prepare까지 마친 상태에서 네트워크의 이상으로 다음 메시지(커밋 또는 롤백)를 받지 못하는 경우가 발생할 수 있다.
이 경우에 데이터베이스는 해당 트랜잭션을 커밋해야 할 지 롤백 해야 할지 판단할 수 없다. 따라서 다음 메시지가 올 때까지 prepare된 리소스에 잠금 설정을 한 채로 기다리게 된다. 이렇게 prepare는 되었는데 그 다음 메시지를 받지 못한 채 리소스만 소유하고 기다리고 있는 트랜잭션을 In-doubt 트랜잭션이라 한다.
예를 들어 First Phase에서 코디네이터 노드가 커밋을 준비하라는 메시지를 보냈음에도 불구하고 어떤 특정 노드의 서버가 다운되었거나 네트워크 상태가 불안정하여 그 메시지를 못 받았거나 또는 메시지는 받았지만 커밋이 준비되었다는 답변을 받지 못했을 때 In-doubt 트랜잭션이 발생한다.
이러한 경우 코디네이터 노드는 다른 모든 노드의 응답을 받았어도 한 노드의 응답을 받지 못했으므로, 이 트랜잭션을 In-doubt 트랜잭션으로 표시하고, 모든 노드에 Second Phase의 커밋 명령을 실행하라는 메시지를 보내는 대신에 롤백하라는 메시지를 보낸다.
예를 들면 다음 그림과 같다.
또한 모든 노드에서 커밋이 준비되었다는 메시지를 받아서 확인했더라도 그 이후에 코디네이터 노드가 보낸 Second Phase의 커밋 명령을 실행하라는 메시지를 특정 노드가 받지 못했거나, 그 노드가 커밋 명령은 잘 수행하였으나 커밋이 완료되었다는 응답을 코디네이터 노드에 전달하지 못했을 수도 있다. 이와 같은 경우도 마찬가지로 In-doubt 트랜잭션으로 분류된다.
트랜잭션은 커밋이나 롤백이 확정되지 않은 채로 데이터베이스 리소스에 대해 잠금(Lock)이 설정된 상태 즉 정체(pending) 상태가 된다. 따라서 이를 해결하려면 첫 번째 경우처럼 코디네이터 노드가 자동으로 전체 트랜잭션을 롤백해 준다거나 DBA가 이러한 트랜잭션을 추출하여 자체적으로 판단하여 수동으로 처리할 수 있다.
이때 DBA가 In-doubt 트랜잭션을 추출하기 위해 정보를 볼 수 있는 테이블과 뷰로는 VT_XA_BRANCH와 DBA_2PC_PENDING이 있다.
TP-Monitor를 이용하여 분산 트랜잭션을 수행하기 위해서는 XA API를 사용해야 한다.
Tibero는 X/Open DTP 규약의 XA를 지원하기 때문에 표준에 맞는 XA 애플리케이션 프로그램을 작성할 수 있다.
Tibero에서는 XA를 지원하기 위해 다음과 같은 XA 함수를 제공한다. 단, 이 함수는 C 프로그래밍 언어용으로만 제공된다.
다음은 XA 함수를 나열한 표이다.
함수 | 설명 |
---|---|
xa_open | 리소스 매니저(Resource Manager)에 접속한다. 자세한 설명은 “4.2.2. xa_open 함수의 속성”을 참고한다. |
xa_close | 리소스 매니저에서 데이터베이스 접속을 해제한다. |
xa_start | XID에 새로운 트랜잭션을 시작하거나, 이미 존재하는 트랜잭션에 현재 프로세스를 연결한다. |
xa_end | XID에서 현재 프로세스를 분리한다 |
xa_rollback | XID의 트랜잭션을 롤백한다. |
xa_prepare | XID에 커밋을 준비한다. Two-phase commit의 First Phase이다. |
xa_commit | XID에 커밋을 완료한다. Two-phase commit의 Second Phase이다. |
xa_recover | prepare 상태인 트랜잭션의 목록을 검사하여 커밋이나 롤백을 수행한다. |
xa_forget | XID의 트랜잭션이 이미 처리된 경우 로그 기록을 삭제한다. |
다음은 xa_open 함수를 호출하는 데 필요한 속성이다.
속성 | 필수 | 설명 |
---|---|---|
user | O | 접속할 사용자의 이름이다. (예: user=tibero |
pwd | O | 접속할 사용자의 패스워드이다. (예: pwd=1234) |
db | X | 접속할 데이터베이스의 DSN 이름(tbdsn.tbr 파일 내의 DSN 이름)이다. (예: db=sample) |
conn_id | X | XA 연결에 이름을 부여한다. 이 이름을 ESQL의 AT 구문에서 사용할 수 있다. (예: conn_id=db1) |
Loose_Coupling | O | 다른 브랜치이지만 동일한 글로벌 트랜잭션끼리 같은 리소스를 사용하는지 여부를 설정한다. (true/false) loose coupling이면 (1,0)과 (1,2)는 서로 다른 내부 리소스를 사용한다. 예를 들어 TX가 해당된다. 이와는 반대로 tight coupling이면 두 xtb는 하나의 TX를 서로 잠금 처리를 설정해가며 공유하여 사용한다. (예: Loose_Coupling=false) |
sestm | O | 시스템에 의해 중단(abort)되기 전까지의 트랜잭션의 inactive time limit이다. 클라이언트로부터 한 요청에 대한 답변을 전달한 후 그 다음 요청이 오기 전까지 대기하는 시간이다. 그 이상의 시간이 지나면 클라이언트 측에서 문제가 있다고 판단하고 해당 xtb를 롤백한다. (예: sestm=10) |
seswt | X | XA_RETRY가 반환되기 전까지 데이터베이스 서버가 트랜잭션을 기다리는 대기 시간이다. flag 내에 TMNOWAIT이 설정되어 있지 않을 경우에는 클라이언트의 요청을 데이터베이스가 곧바로 처리해줄 수 없을 때 해당 리소스를 사용할 수 있을 때까지 기다린 후에 클라이언트에 답변을 주게 된다. 이때 데이터베이스가 seswt로 설정한 시간까지 답변을 줄 수 없는 경우 XARETRY를 반환한다. (예: seswt=30) |
logdir | X | XA의 로그를 저장할 디렉터리를 지정할 수 있다. (예: logdir=/home/test/log) |
일반적으로 C 프로그래밍 언어에서 XA 애플리케이션 프로그램을 작성할 때 ESQL을 이용하여 개발하는 예가 많다.
다음은 Tibero에서 제공하는 tbESQL를 이용하여 XA 애플리케이션 프로그램을 작성한 예이다.
#define XA_CONN_STR_TIGHT "TIBERO_XA:user=tibero, pwd=tmax," \ "db=sample, sestm=60, logdir=/home/path/to/xa_log" /* 동일한 글로벌 트랜잭션에서 다른 브랜치로 xa_start했을 경우 * Tight-Coupling */ void test_xa_2branch_2pc_tight() { int rc; XID xid1; XID xid2; struct xa_switch_t *tbxa = &XA_SWITCH_NAME; char *conn_str = XA_CONN_STR_TIGHT; /* tightly coupled */ long gtrid = _GTRID_BASE; long bqual = 1; EXEC SQL BEGIN DECLARE SECTION; int cnt; EXEC SQL END DECLARE SECTION; /* xa_open */ tbxa->xa_open_entry (conn_str, 0, TMNOFLAGS); xid1.formatID = 1; xid1.gtrid_length = sizeof(gtrid); xid1.bqual_length = sizeof(bqual); memcpy(&xid1.data[0], >rid, sizeof(gtrid)); memcpy(&xid1.data[sizeof(gtrid)], &bqual, sizeof(bqual)); bqual = 2; xid2.formatID = 1; xid2.gtrid_length = sizeof(gtrid); xid2.bqual_length = sizeof(bqual); memcpy(&xid2.data[0], >rid, sizeof(gtrid)); memcpy(&xid2.data[sizeof(gtrid)], &bqual, sizeof(bqual)); EXEC SQL DELETE FROM PERSON; EXEC SQL COMMIT WORK; /* (1, 1) 시작 */ /* xa_start -- sql statements starts */ tbxa->xa_start_entry (&xid1, 0, TMNOFLAGS); EXEC SQL INSERT INTO PERSON VALUES ('1', 'LEE'); EXEC SQL INSERT INTO PERSON VALUES ('2', 'KIM'); /* xa_end -- */ tbxa->xa_end_entry (&xid1, 0, TMSUCCESS); /* (1, 2) 시작 */ /* xa_start -- sql statements starts */ tbxa->xa_start_entry (&xid2, 0, TMNOFLAGS); EXEC SQL INSERT INTO PERSON VALUES ('2', 'PARK'); EXEC SQL INSERT INTO PERSON VALUES ('3', 'JAKE'); EXEC SQL INSERT INTO PERSON VALUES ('4', 'KID'); EXEC SQL INSERT INTO PERSON VALUES ('5', 'CHANHO'); /* xa_end -- */ tbxa->xa_end_entry (&xid2, 0, TMSUCCESS); /* xa_prepare */ tbxa->xa_prepare_entry (&xid1, 0, TMNOFLAGS); /* Tightly-Coupled 가정 */ /* xa_prepare */ tbxa->xa_prepare_entry (&xid2, 0, TMNOFLAGS); /* xa_commit */ tbxa->xa_commit_entry (&xid1, 0, TMNOFLAGS); /* xa_commit */ tbxa->xa_commit_entry (&xid2, 0, TMNOFLAGS); EXEC SQL SELECT COUNT(*) into :cnt FROM PERSON; CuAssertIntEq(tc, cnt, 6); /* xa_close */ tbxa->xa_close_entry ("", 0, TMNOFLAGS); return; } /* 같은 Global Trasaction의 다른 Branch로 xa_start했을 경우 * Loose-Coupling */ void test_xa_2branch_2pc_loose(CuTest *tc) { int rc; XID xid1; XID xid2; struct xa_switch_t *tbxa = &XA_SWITCH_NAME; char *conn_str = XA_CONN_STR_LOOSE; long gtrid = _GTRID_BASE; long bqual = 1; EXEC SQL BEGIN DECLARE SECTION; int cnt; EXEC SQL END DECLARE SECTION; /* xa_open */ tbxa->xa_open_entry (conn_str, 0, TMNOFLAGS); xid1.formatID = 1; xid1.gtrid_length = sizeof(gtrid); xid1.bqual_length = sizeof(bqual); memcpy(&xid1.data[0], >rid, sizeof(gtrid)); memcpy(&xid1.data[sizeof(gtrid)], &bqual, sizeof(bqual)); bqual = 2; xid2.formatID = 1; xid2.gtrid_length = sizeof(gtrid); xid2.bqual_length = sizeof(bqual); memcpy(&xid2.data[0], >rid, sizeof(gtrid)); memcpy(&xid2.data[sizeof(gtrid)], &bqual, sizeof(bqual)); EXEC SQL DELETE FROM PERSON; EXEC SQL COMMIT WORK; /* (1, 1) 시작 */ /* xa_start -- sql statements starts */ tbxa->xa_start_entry (&xid1, 0, TMNOFLAGS); EXEC SQL INSERT INTO PERSON VALUES ('1', 'LEE'); EXEC SQL INSERT INTO PERSON VALUES ('2', 'KIM'); /* xa_end -- */ tbxa->xa_end_entry (&xid1, 0, TMSUCCESS); /* (1, 2) 시작 */ /* xa_start -- sql statements starts */ tbxa->xa_start_entry (&xid2, 0, TMNOFLAGS); EXEC SQL INSERT INTO PERSON VALUES ('2', 'PARK'); EXEC SQL INSERT INTO PERSON VALUES ('3', 'JAKE'); EXEC SQL INSERT INTO PERSON VALUES ('4', 'KID'); EXEC SQL INSERT INTO PERSON VALUES ('5', 'CHANHO'); /* xa_end -- */ tbxa->xa_end_entry (&xid2, 0, TMSUCCESS); /* xa_prepare */ tbxa->xa_prepare_entry (&xid1, 0, TMNOFLAGS); /* Loosely-Coupled 가정 */ /* xa_prepare */ tbxa->xa_prepare_entry (&xid2, 0, TMNOFLAGS); /* xa_commit */ tbxa->xa_commit_entry (&xid1, 0, TMNOFLAGS); /* xa_commit */ tbxa->xa_commit_entry (&xid2, 0, TMNOFLAGS); EXEC SQL SELECT COUNT(*) into :cnt FROM PERSON; CuAssertIntEq(tc, cnt, 6); /* xa_close */ tbxa->xa_close_entry ("", 0, TMNOFLAGS); return; }
본 절에서는 XA(Extended Architecture)에서 지원하는 인터페이스와 이를 이용하여 작성한 프로그램에 대해 설명한다.
Tibero에서 지원하는 JDBC의 XA 인터페이스는 다음과 같다.
XA Datasource Interface
XA Connection Interface
XA Exception Interface
XA XID Interface
다음은 Tibero에서 구현된 XA 인터페이스의 목록이다.
표준 XA 인터페이스(JDK 1.3) | Tibero의 XA 인터페이스 |
---|---|
javax.sql.XADataSource | com.tmax.tibero.jdbc.ext.TbXADataSource |
javax.sql.XAConnection | com.tmax.tibero.jdbc.ext.TbXAConnection |
javax.transaction.xa.XAException | com.tmax.tibero.jdbc.ext.TbXAException |
javax.transaction.xa.Xid | com.tmax.tibero.jdbc.ext.TbXid |
다음은 tbJDBC 환경에서 XA 인터페이스를 이용하여 프로그래밍한 예이다.
package test.com.tmax.tibero.cases; import com.tmax.tibero.jdbc.ext.*; import test.com.tmax.tibero.AbstractBase; import javax.sql.XAConnection; import javax.transaction.xa.XAResource; import java.sql.*; public class TestXATwoBranch extends AbstractBase{ int formatID = 1; int row_count = 0; int pre1 , pre2 = 0; byte[] gtid1 = new byte[1]; byte[] bq1 = new byte[1]; byte[] gtid2 = new byte[1]; byte[] bq2 = new byte[1]; public TestXATwoBranch (String name) { super(name); } /* Tightly Coupled 일 때 */ public void test_xa_2branch_2pc_tight () throws Exception { debug("test_xa_2branch_2pc_tight " + this._getClassName()); create_table_for_xa(); gtid1[0] = (byte)1; bq1[0] = (byte)1; gtid2[0] = (byte)1; bq2[0] = (byte)2; TbXADataSource xads1 = new TbXADataSource(); xads1.setUrl(getXAurl()); xads1.setUser(getXAuser()); xads1.setPassword(getXApasswd()); TbXADataSource xads2 = new TbXADataSource(); xads2.setUrl(getXAurl()); xads2.setUser(getXAuser()); xads2.setPassword(getXApasswd()); XAConnection xacon1 = xads1.getXAConnection(); XAConnection xacon2 = xads2.getXAConnection(); Connection conn1 = xacon1.getConnection(); Connection conn2 = xacon2.getConnection(); XAResource xars1 = xacon1.getXAResource(); XAResource xars2 = xacon2.getXAResource(); /* XID (1.1) 생성 */ TbXid xid1 = new TbXid(formatID, gtid1,bq1 ); /* XID (1.2) 생성 */ TbXid xid2 = new TbXid(formatID, gtid2, bq2); try { /* (1.1) 시작 */ xars1.start(xid1, XAResource.TMNOFLAGS); PreparedStatement pstmt1; pstmt1 = conn1.prepareStatement("insert into author values (?,?)"); pstmt1.setInt(1, 1); pstmt1.setString(2, "FOSCHIA"); pstmt1.executeUpdate(); pstmt1.setInt(1,2); pstmt1.setString(2, "AGNOS"); pstmt1.executeUpdate(); /* (1.1) 종료 */ xars1.end(xid1, XAResource.TMSUCCESS); /* (1.2) 시작 */ xars2.start(xid2, XAResource.TMNOFLAGS); PreparedStatement pstmt2; pstmt2 = conn2.prepareStatement("insert into author values (?,?)"); pstmt2.setInt(1, 3); pstmt2.setString(2, "JELLA"); pstmt2.executeUpdate(); pstmt2.setInt(1,4); pstmt2.setString(2, "THIPHILO"); pstmt2.executeUpdate(); /* (1.2) 종료 */ xars2.end(xid2, XAResource.TMSUCCESS); /* (1,1) prepare */ pre1= xars1.prepare(xid1); assertEquals(pre1, XAResource.XA_RDONLY); /* (1,2) prepare */ pre2 = xars2.prepare(xid2); assertEquals(pre2, XAResource.XA_OK); /* (1.1) commit */ try { xars1.commit(xid1, false); } catch(TbXAException e) {} /* (1.2) commit */ try { xars2.commit(xid2, false); } catch(TbXAException e) {} Statement stmt1 = conn1.createStatement(); ResultSet rs1 = stmt1.executeQuery("select * from author"); while (rs1.next()) row_count ++; assertEquals(4, row_count); rs1.close(); rs1 = null; stmt1.close(); stmt1 = null; pstmt1.close(); conn1.close(); xacon1.close(); pstmt2.close(); conn2.close(); xacon2.close(); pstmt1= null; conn1= null; xacon1=null; pstmt2= null; conn2= null; xacon2=null; } catch (TbXAException e) {} } /* Loosely Coupled 일 때 */ public void test_xa_2branch_2pc_loose () throws Exception { debug("test_xa_2branch_2pc_loose " + this._getClassName()); create_table_for_xa(); gtid1[0] = (byte)1; bq1[0] = (byte)1; gtid2[0] = (byte)1; bq2[0] = (byte)2; TbXADataSource xads1 = new TbXADataSource(); xads1.setUrl(getXAurl()); xads1.setUser(getXAuser()); xads1.setPassword(getXApasswd()); TbXADataSource xads2 = new TbXADataSource(); xads2.setUrl(getXAurl()); xads2.setUser(getXAuser()); xads2.setPassword(getXApasswd()); XAConnection xacon1 = xads1.getXAConnection(); XAConnection xacon2 = xads2.getXAConnection(); Connection conn1 = xacon1.getConnection(); Connection conn2 = xacon2.getConnection(); XAResource xars1 = xacon1.getXAResource(); XAResource xars2 = xacon2.getXAResource(); TbXid xid1 = new TbXid(formatID, gtid1,bq1 ); TbXid xid2 = new TbXid(formatID, gtid2, bq2); try { xars1.start(xid1, TbXAResource.TBRTRANSLOOSE); PreparedStatement pstmt1; pstmt1 = conn1.prepareStatement("insert into author values (?,?)"); pstmt1.setInt(1, 1); pstmt1.setString(2, "FOSCHIA"); pstmt1.executeUpdate(); pstmt1.setInt(1,2); pstmt1.setString(2, "AGNOS"); pstmt1.executeUpdate(); xars1.end(xid1, XAResource.TMSUCCESS); xars2.start(xid2, TbXAResource.TBRTRANSLOOSE); PreparedStatement pstmt2; pstmt2 = conn2.prepareStatement("insert into author values (?,?)"); pstmt2.setInt(1, 3); pstmt2.setString(2, "JELLA"); pstmt2.executeUpdate(); pstmt2.setInt(1,4); pstmt2.setString(2, "THIPHILO"); pstmt2.executeUpdate(); xars2.end(xid2, XAResource.TMSUCCESS); pre1= xars1.prepare(xid1); assertEquals(pre1, XAResource.XA_OK); pre2 = xars2.prepare(xid2); assertEquals(pre2, XAResource.XA_OK); xars1.commit(xid1, false); xars2.commit(xid2, false); Statement stmt1 = conn1.createStatement(); ResultSet rs1 = stmt1.executeQuery("select * from author"); while (rs1.next()) row_count ++; assertEquals(4, row_count); rs1.close(); rs1 = null; stmt1.close(); stmt1 = null; pstmt1.close(); conn1.close(); xacon1.close(); pstmt2.close(); conn2.close(); xacon2.close(); pstmt1= null; conn1= null; xacon1=null; pstmt2= null; conn2= null; xacon2=null; } catch (TbXAException e) {} } }
TP-Monitor(Transaction Processing Monitor)는 각종 프로토콜에서 동작하는 세션과 시스템 및 데이터베이스 사이의 최소 처리 단위인 트랜잭션을 감시하여 일관성 있게 보관 및 유지하는 역할을 하는 트랜잭션 관리 미들웨어이다.
본 절에서는 대표적인 상용 TP-Monitor인 Tmax와 Tuxedo를 Tibero와 연동하는 예제를 설명한다.
Tmax는 Transaction Maximization의 약어로 트랜잭션 처리 극대화를 의미한다. Tmax는 시스템의 분산 환경에서 이기종 컴퓨터 간의 트랜잭션 처리를 완벽히 보장하면서 부하를 분산시키고 에러가 발생하는 경우 적절한 조치를 담당하는 TP-Monitor이다. 트랜잭션의 특성을 지원하면서 사용자에게는 최적의 개발환경을 제공하고, 클라이언트/서버 환경에서 최적의 솔루션을 제공하며, 성능 개선은 물론 모든 장애에 완벽하게 대처한다.
Tmax는 분산 트랜잭션 프로세싱의 국제 표준인 X/Open DTP(Distributed Transaction Processing) 모델을 준수하고 국제 표준기구인 OSI(Open Systems Interconnection group)의 DTP 서비스에 대한 기능적 분산과 기능 구성 요소 간 API 및 시스템 인터페이스 정의에 따라 개발되었다. 또한 분산 환경에서 이기종 간의 투명한 업무 처리 및 OLTP(On-Line Transaction Processing)를 지원하고 트랜잭션 처리의 ACID(Atomic, Consistent, Isolated, Durable: transaction properties) 특성을 만족하게 한다.
아래에서 소개할 Tmax와 Tibero 연동 예제 프로그램을 테스트해보기 위해서는 Tmax와 Tibero가 정상적으로 설치되어 있어야 한다.
1. 연동할 Tmax와 Tibero가 다른 머신에 설치된 경우 Tmax가 설치된 머신에서 Tibero클라이언트를 따로 설치하여 Tibero 서버에 정상적으로 접속할 수 있는 환경이 구축되어야 한다.
2. Tmax 설치 및 관리에 대한 자세한 내용은 "Tmax Installation Guide"나 "Tmax Administration Guide"를 참고한다. Tibero로 설치 및 관리에 관한 자세한 내용은 "Tibero 설치 안내서"와 "Tibero 관리자 안내서"를 참고한다.
여기서 소개하는 예제 프로그램은 Tmax를 설치할 때 인스톨러가 기본으로 제공하는 것이다. 클라이언트가 Tmax 서버를 통하여 Tibero DB를 접속하여 특정 테이블의 데이터를 조회, 추가, 변경, 삭제하는 작업을 한다.
테스트 환경과 프로그램에 사용된 각종 파일들은 다음과 같다.
테스트 환경
구분 | 설명 |
---|---|
운영체제 | Ubuntu Linux 2.6.32-24-server x86-64 |
셸 | bash |
$TMAXDIR | Tmax 설치 디렉터리 |
프로그램 파일
파일 | 설명 |
---|---|
sample.m | Tmax 환경설정 파일($TMAXDIR/config) |
tms_tbr.mk | Tibero용 TMS makefile($TMAXDIR/sample/server) |
tbrtest.tbc | 서버 프로그램 tbESQL/C 파일($TMAXDIR/sample/server) |
tbrtest.h | 서버 프로그램 헤더 파일($TMAXDIR/sample/server) |
Makefile.tbr | 서버 프로그램 makefile($TMAXDIR/sample/server) |
compile | 서버 프로그램 빌드 스크립트($TMAXDIR/sample/server) |
tbr_main.c | 클라이언트 프로그램 파일($TMAXDIR/sample/client) |
Makefile.c | 클라이언트 프로그램 makefile($TMAXDIR/sample/client) |
compile | 클라이언트 프로그램 빌드 스크립트($TMAXDIR/sample/client) |
다음에 제시된 순서대로 따르면 Tmax와 Tibero가 연동하는 것을 확인할 수 있다.
Tmax 기본 환경설정
TMS 컴파일
TMS 컴파일
서버 프로그램 컴파일
클라이언트 프로그램 컴파일
DB 테이블 생성
예제 프로그램 실행
다음은 Tmax 기본 환경을 설정하는 방법이다.
Tmax 시스템 환경 파일 설정
$TMAXDIR/config 디렉터리에 있는 sample.m은 Tmax를 기동시킬 때 필요한 각종 정보들이 들어있는 Tmax 시스템 환경 파일이다. ASCII 파일 형태로 작성하며, cfl 유틸리티로 컴파일 하여 이진 파일을 생성한다. 생성된 이진 파일은 Tmax 기동 및 종료할 때 참조된다.
Tibero 서버와 연동하는 서비스를 활성화시키기 위해 sample.m 환경 파일의 SVRGROUP 절, SERVER 절, SERVICE 절 항목을 아래와 같이 수정한다.
### tms for Tibero ### svg4 NODENAME = "integrity", DBNAME = TIBERO, OPENINFO = "TIBERO_XA:user=tibero, pwd=tmax,sestm=60,db=tibero", TMSNAME = tms_tbr ### server for Tibero sample program ### tbrtest SVGNAME = svg4 ### services for tbrtest ### TBRINS SVRNAME = tbrtest TBRSEL SVRNAME = tbrtest TBRUPT SVRNAME = tbrtest TBRDEL SVRNAME = tbrtest
SVRGROUP 절의 NODENAME은 Tmax를 설치할 때에 hostname 값으로 자동 설정된다. DB 벤더를 구분하기 위한 DBNAME은 TIBERO로 설정되어 있다. OPENINFO는 XA 모드 설정을 위한 TIBERO_XA가 앞쪽에 쓰여 있고 그 뒤에 “4.2.2. xa_open 함수의 속성”이 나열되어 있다. TMSNAME에는 XA를 담당할 모듈 이름이 설정되어 있다.
SERVER 절에는 예제 서버 프로그램의 이름인 tbrtest가 설정되고, 서버 프로그램이 제공하는 서비스 4가지가 설정되어 있다.
Tmax 시스템 환경 파일 설정에 대한 자세한 내용은 "Tmax Administration Guide"를 참고한다.
Tmax 시스템 환경 파일 컴파일
수정한 sample.m 파일을 아래와 같은 명령으로 컴파일한다.
cfl -i sample.m
성공적으로 컴파일 된 후에는 다음과 같은 메시지가 출력된다.
CFL is done successfully for node(<nodename>)
서비스 테이블 생성
서비스 테이블은 각각의 Tmax 시스템 내 서버 프로세스가 생성될 때 필요한 파일로서 각각의 프로세스들이 어떤 서비스를 처리하는지에 대한 정보가 담겨 있다. 아래와 같은 명령으로 서비스 테이블을 생성한다.
gst
성공적으로 처리되면 다음과 같은 메시지가 출력된다.
SVC tables are successfully generated GST is successfully done
구조체 정의 이진 파일 및 필드 키 정의 이진 파일 생성
서버나 클라이언트 프로그램내에서 구조체나 필드 키를 쓰는 경우 이와 관련된 이진 파일을 생성해 주어야 한다. 하지만 이 예제 프로그램에서는 구조체나 필드 키를 사용하지 않으므로 그 과정을 생략한다.
TMS(Transaction Management Server)는 Tmax 시스템의 구성요소로서 데이터베이스 관리 및 분산 트랜잭션 처리를 담당하는 프로세스이다. Tibero용 TMS를 컴파일하기 전에 Tibero 관련 환경변수 TB_HOME, TB_SID, LD_LIBRARY_PATH, PATH 등이 제대로 설정되었는지 확인한다.
확인한 다음 아래와 같이 $TMAXDIR/sample/server 디렉터리로 이동하여 Tibero용 TMS makefile을 이용하여 TMS를 컴파일한다.
cd $TMAXDIR/sample/server make –f tms_tbr.mk all
$TMAXDIR/sample/server 디렉터리로 이동하여 실제적으로 서비스를 제공하는 서버 프로그램을 빌드 스크립트를 이용하여 컴파일한다.
cd $TMAXDIR/sample/server ./compile tbc tbrtest
$TMAXDIR/sample/client 디렉터리로 이동하여 서비스를 요청하는 클라이언트 프로그램을 빌드 스크립트를 이용하여 컴파일한다.
cd $TMAXDIR/sample/client ./compile c tbr_main
Tibero 서버에 tibero/tmax 계정으로 접속하여 아래와 같은 emp 테이블을 생성한다.
tbsqltibero/tmax create table emp ( empno number, ename char(16), job char(16), hiredate char(16), sal number );
Tmax 시스템 기동
다음과 명령으로 Tmax를 기동한다.
tmboot
성공적으로 기동되면 다음과 같은 메시지가 출력된다.
TMBOOT for node(<nodename>) is starting: Welcome to Tmax demo system: it will expire 2012/3/11 Today: 2012/1/13 TMBOOT: TMM is starting: Fri Jan 13 14:18:31 2012 TMBOOT: CLL is starting: Fri Jan 13 14:18:31 2012 TMBOOT: CLH is starting: Fri Jan 13 14:18:31 2012 (I) CLH9991 Current Tmax Configuration: Number of client handler(MINCLH) = 1 Supported maximum user per node = 680 Supported maximum user per handler = 680 [CLH0125] TMBOOT: TLM(tlm) is starting: Fri Jan 13 14:18:31 2012 TMBOOT: TMS(tms_tbr) is starting: Fri Jan 13 14:18:31 2012 (I) TMS0211 General Infomation : transaction recovery will be started [TMS0221] (I) TMS0211 General Infomation : transaction recovery was completed [TMS0222] TMBOOT: TMS(tms_tbr) is starting: Fri Jan 13 14:18:31 2012 TMBOOT: SVR(tbrtest) is starting: Fri Jan 13 14:18:31 2012
클라이언트 프로그램 실행
클라이언트 프로그램의 명령 옵션은 다음과 같다.
Usage: ./tbr_main empno loop_cnt ins_flag upt_flag del_flag flag : 1|0
원하는 옵션을 선택하여 수행시키면 아래와 같은 결과가 출력된다.
./tbr_main 12 3 1 0 0 LOOP COUNT = 1 >> INSERT : COMMIT TEST [./tbr_main] [[TBRINS] emp Insert Success] [./tbr_main] [[TBRSEL] emp Select Success [1]] LOOP COUNT = 2 >> INSERT : COMMIT TEST [./tbr_main] [[TBRINS] emp Insert Success] [./tbr_main] [[TBRSEL] emp Select Success [2]] LOOP COUNT = 3 >> INSERT : COMMIT TEST [./tbr_main] [[TBRINS] emp Insert Success] [./tbr_main] [[TBRSEL] emp Select Success [3]]
Tuxedo는 분산 트랜잭션 처리를 위한 플랫폼으로서, C, C++ 및 COBOL로 작성된 소프트웨어를 위한 개방형의 분산 시스템을 토대로 Mainframe 확장성 및 성능을 제공한다. 또한 메인스트림 하드웨어를 토대로 메인프레임 애플리케이션을 “리호스팅” 할 수 있는 플랫폼이다.
Tuxedo는 비용 효과적인 신뢰성과 초당 수십만 건의 트랜잭션을 지원할 수 있는 탁월한 확장성을 제공하는 것은 물론, SOA와 같은 혁신적 아키텍처의 일부로서 기존 IT 자산의 수명을 연장함으로써 투자 보호의 이점을 제공한다. Oracle Tuxedo는 Oracle Fusion Middleware의 전략적 트랜잭션 처리 제품이다
아래에서 소개할 Tuxedo와 Tibero 연동 예제 프로그램을 테스트하기 위해서는 Tuxedo와 Tibero가 정상적으로 설치되어 있어야 한다.
1. 연동할 Tmax와 Tibero가 다른 머신에 설치된 경우 Tmax가 설치된 머신에서 Tibero 클라이언트를 따로 설치하여 Tibero 서버에 정상적으로 접속할 수 있는 환경이 구축되어야 한다.
2. Tuxedo 설치 및 관리에 대한 자세한 내용은 "Tuxedo Documenation"을 참고한다. Tibero 로 설치 및 관리에 관한 자세한 내용은 "Tibero 설치 안내서"와 "Tibero 관리자 안내서"를 참고한다.
여기서 소개하는 예제 프로그램은 클라이언트가 Tuxedo 서버를 통하여 Tibero DB를 접속하여 특정 테이블의 데이터를 조회, 추가하는 작업을 한다. 테스트 환경과 프로그램에 사용된 각종 파일들은 다음과 같다. 편의상 테스트 서버는 tux_machine이라는 호스트 네임을 가지고 있으며, Tibero와 Tuxedo는 각각 path/to/tibero와 /path/to/tuxedo에 설치되어 있다고 가정한다. 그리고 예제 프로그램 파일들이 작업 디렉터리 /path/to/example에 있다고 가정한다.
테스트 환경과 프로그램에 사용된 각종 파일들은 다음과 같다.
테스트 환경
구분 | 설명 |
---|---|
운영체제 | AIX |
호스트 네임 | tux_machine |
셸 | bash |
Tibero 설치 홈 디렉터리 | /path/to/tibero |
Tuxedo 설치 홈 디렉터리 | /path/to/tuxedo |
예제 프로그램 홈 디렉터리 | /path/to/example |
프로그램 파일
파일 | 설명 |
---|---|
tb_tux.env | 시스템 환경변수 설정 파일 |
tb_tux.conf.m | Tuxedo 환경설정 파일 |
tmax32.fld | 필드 테이블 파일 |
trans_fml32.tbc | 서버 프로그램 tbESQL/C 파일 |
builds.sh | 서버 프로그램 빌드 스크립트 |
insert.c | INSERT 클라이언트 프로그램 파일 |
select.c | SELECT 클라이언트 프로그램 파일 |
buildc.sh | 클라이언트 프로그램 빌드 스크립트 |
create_table.sql | 테스트를 위한 DB 테이블 생성 파일 |
run.sh | Tuxedo 시스템 기동 스크립트 |
본 절에서 설명하고 있는 기본 프로그램의 전체 소스 코드는 “Appendix B. Tibero와 Tuxedo 연동 예제”를 참고한다.
다음에 제시된 순서대로 따르면 Tuxedo와 Tibero가 연동하는 것을 확인할 수 있다. 운영체제나 시스템 환경에 따라서 예제 파일을 수정하여 테스트해야 한다.
시스템 환경변수 설정
Tuxedo 기본 환경설정
TMS 컴파일
서버 프로그램 컴파일
클라이언트 프로그램 컴파일
DB 테이블 생성
예제 프로그램 실행
Tibero와 Tuxedo 연동 테스트를 하기 위해 필요한 시스템 환경변수 설정은 아래와 같다.
Tibero를 위한 기본 환경변수 설정
export TB_HOME=/path/to/tibero export TB_SID=tibero export PATH=$TB_HOME/bin:$TB_HOME/client/bin:$TB_HOME/scripts:$PATH export LD_LIBRARY_PATH=$TB_HOME/client/lib:$TB_HOME/lib:$LD_LIBRARY_PATH export LIBPATH=$TB_HOME/client/lib:$TB_HOME/lib:$LIBPATH
Tuxedo를 위한 기본 환경변수 설정
export TUXDIR=/path/to/tuxedo export JAVA_HOME=$TUXDIR/jre export JVMLIBS=$JAVA_HOME/lib/amd64/server:$JAVA_HOME/jre/bin export PATH=$TUXDIR/bin:$JAVA_HOME/bin:$PATH export COBCPY=:$TUXDIR/cobinclude; export COBCPY export COBOPT="-C ANS85 -C ALIGN=8 -C NOIBMCOMP -C TRUNC=ANSI -C OSEXT=cbl" export SHLIB_PATH=$TUXDIR/lib:$JVMLIBS:$SHLIB_PATH export LIBPATH=$TUXDIR/lib:$JVMLIBS:$LIBPATH export LD_LIBRARY_PATH=$TUXDIR/lib:$JVMLIBS:$LD_LIBRARY_PATH export WEBJAVADIR=$TUXDIR/udataobj/webgui/java
Tuxedo를 위한 추가 환경변수 설정
연동 테스트를 위해서 추가 설정해 주어야 할 환경변수들로서 상황에 맞게 설정한다.
export TUXCONFIG=/path/to/tuxedo/tuxconf
export FLDTBLDIR32=/path/to/tuxedo
export FIELDTBLS32=tmax32.fld
export TLOGDEVICE=/path/to/tuxedo/TLOG
export ULOGPFX=/path/to/tuxedo/ULOG
Tuxedo 시스템 환경 파일 설정
tb_tux.conf.m은 Tuxedo를 기동시킬 때 필요한 각종 정보들이 들어있는 Tuxedo 시스템 환경 파일이다. ASCII 파일 형태로 작성하며, tmloadcf 유틸리티로 컴파일하여 이진 파일을 생성한다. 생성된 이진 파일은 Tuxedo 기동 및 종료할 때 참조된다.
Tibero 서버와 연동하는 서비스를 활성화시키기 위하여 tb_tux.conf.m 환경 파일의 RESOURCES 절, MACHINES 절, GROUPS 절, SERVERS 절, SERVICES 절 항목을 아래와 같이 설정한다.
*RESOURCES IPCKEY 68300 DOMAINID tbrdomain MASTER tbrtest MAXACCESSERS 10 MAXSERVERS 5 MAXSERVICES 10 MODEL SHM LDBAL N *MACHINES DEFAULT: TUXDIR="path/to/tuxedo" APPDIR="path/to/example" TUXCONFIG="path/to/example/tuxconf" tux_machine LMID=tbrtest *GROUPS TBXA LMID=tbrtest GRPNO=1 TMSNAME=tms_tibero OPENINFO="TIBERO_XA:TIBERO_XA:user=sys,pwd=tibero, sestm=60,db=tibero" *SERVERS DEFAULT: CLOPT="-A -r" trans_fml32 SRVGRP=TBXA SRVID=1 *SERVICES SELECT_FML32 INSERT_FML32 ~
RESOURCES 절과 MACHINE 절은 일반적인 Tuxedo 시스템 환경 파일 설정처럼 한다. MACHINE 절의 tux_machine은 서버의 호스트 네임이므로 테스트 환경에 따라 다르게 설정해야 한다. LMID와 MASTER는 임의로 tbrtest라고 정하였고, DOMAINID는 임의로 tbrdomain이라고 정하였으로 원하는 대로 설정한다.
GROUPS 절도 일반적인 Tuxedo 시스템 환경 파일의 설정과 동일하게 한다. TMSNAME에는 Tibero 서버와 XA 통신을 담당할 모듈의 이름을 설정한다. OPENINFO는 XA 모드 설정을 위한 'TIBERO_XA:TIBERO_XA:'를 앞쪽에 쓰고 그 뒤에 “4.2.2. xa_open 함수의 속성”이 나열되어야 한다.
SERVERS 절에는 예제 서버 프로그램인 trans_fml32가 설정되어 있다. SERVICE 절에는 예제 서버 프로그램이 제공하는 서비스인 SELECT_FML32, INSERT_FML32가 설정되어 있다.
Tuxedo 시스템 환경 파일 설정에 대한 자세한 설명은 "Tuxedo Documentation"를 참고한다.
Tuxedo 시스템 환경 파일 컴파일
Tuxedo 시스템 환경 파일을 아래와 같은 명령으로 컴파일한다.
tmloadcf -y tb_tux.conf.m
필드 테이블 파일을 헤더 파일로 변환
서버나 클라언트 프로그램간에 데이터를 주고받을 때 공용 자료 구조체 정의해서 쓴다.
이번 연동 테스트에서는 FML(Field Manipulation Language)을 이용하여 아래 같은 필드 테이블 파일 tmax32.fld을 새로 정의해서 쓴다.
#name number type flag comment OUTPUT 302 string 0 - EMPNO 901 long 0 - ENAME 902 string 0 - JOB 903 string 0 - MGR 904 long 0 - SAL 905 float 0 - COMM 906 float 0 - DEPTNO 907 long 0 -
이 필드 테이블 파일 tmax32.fld로부터 헤더 파일을 생성하는 방법은 다음과 같다.
mkfldhdr32 tmax32.fld
이 결과 tmax32.fld.h이라는 헤더 파일이 생성되며, 예제 클라이언트 프로그램과 서버 프로그램에서 가져다 쓴다. 그 밖의 다른 구조체를 정의하여 파일을 만드는 방법은 "Tuxedo Documentation"을 참고한다.
다음 아래와 같은 명령어를 이용하여 Tibero용 TMS를 컴파일한다.
buildtms -o $TUXDIR/bin/tms_tibero -v -r TIBERO_XA
실제적으로 서비스를 제공하는 서버 프로그램을 아래와 같은 빌드 스크립트 builds.sh를 이용하여 컴파일한다.
#### transaction server precompile #### PRECOMP=$TB_HOME/client/bin/tbpc PRECOMPFLAGS="UNSAFE_NULL=YES" LIB=$TB_HOME/client/lib INC=$TB_HOME/client/include CFLAGS="-ltbcli -ltbxa -lm -lrt -lpthread -ltbertl -g " CC=gcc rm -f trans_fml32.c $PRECOMP INCLUDE=$TUXDIR/include UNSAFE_NULL=YES INCLUDE=$INC $PRECOMPFLAGS ONAME=trans_fml32.c trans_fml32.tbc #### transaction server build #### buildserver -o trans_fml32 -v -f trans_fml32.c -s INSERT_FML32, SELECT_FML32 -r TIBERO_XA
프리컴파일 옵션에 대한 자세한 설명은 "TiberotbESQL/C 안내서"를 참고한다.
insert와 select 서비스를 요청하는 클라이언트 프로그램을 아래와 같은 빌드 스크립트 buildc.sh를 이용하여 컴파일한다.
buildclient -o insert -v -f insert.c buildclient -o select -v -f select.c
Tibero 서버에 tibero/tmax 계정으로 접속하여 아래와 같은 emp 테이블을 생성한다.
tbsqltibero/tmax drop table emp; CREATE TABLE emp ( empno NUMBER, ename VARCHAR2(10), job VARCHAR2(9), mgr NUMBER(4), hiredate DATE, sal NUMBER(7,2), comm NUMBER(7,2), deptno NUMBER(2) );
Tuxedo 시스템 기동
다음 명령으로 기동한다.
tmboot -y
성공적으로 기동되면 다음과 같은 메시지가 출력된다.
Booting all admin and server processes in /path/to/example/tuxconf INFO: Oracle Tuxedo, Version 10.3.0.0, 64-bit, Patch Level (none) Booting admin processes ... exec BBL -A : process id=3457104 ... Started. Booting server processes ... exec tms_tibero -A : process id=4046910 ... Started. exec tms_tibero -A : process id=9265576 ... Started. exec tms_tibero -A : process id=1863802 ... Started. exec trans_fml32 -A -r : process id=3719284 ... Started. 5 processes started.
클라이언트 프로그램 실행
emp 테이블에 Empyee 정보를 추가하고 조회하는 클라이언트 프로그램은 다음과 같이 실행한다.
./insert ****************************************** | Employee Number : 1 | Employee Name : Kim | Employee Job : Manager ******************************************
insert 프로그램을 실행시키고 Employee Number, Employ Name, Employ Job를 위와 같이 입력하면 Tuxedo 서버 프로그램을 통하여 Tibero 서버의 emp 테이블에 레코드가 추가된다.
./select ****************************************** | Employee Number : 1 ****************************************** EMPNO: 1 ENAME: Kim JOB: Manager
select 프로그램을 실행시키고 Employee Number를 위와 같이 입력하면 Tuxedo 서버 프로그램을 통하여 Tibero 서버의 emp 테이블로부터 해당 레코드 정보를 가져와서 출력한다.