제4장 XA의 사용

내용 목차

4.1. 분산 트랜잭션
4.1.1. Two-phase commit
4.1.2. In-doubt 트랜잭션
4.2. XA API
4.2.1. XA 함수
4.2.2. xa_open 함수의 속성
4.2.3. XA 애플리케이션 프로그래밍
4.3. JDBC에서의 XA 지원
4.3.1. XA 인터페이스
4.3.2. XA 인터페이스 프로그래밍
4.4. TP-Monitor와 Tibero 연동
4.4.1. Tmax와 Tibero 연동
4.4.2. Tuxedo와 Tibero 연동

본 장에서는 분산 트랜잭션(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를 설명하면 다음과 같다.

다음은 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_BRANCHDBA_2PC_PENDING이 있다.

TP-Monitor를 이용하여 분산 트랜잭션을 수행하기 위해서는 XA API를 사용해야 한다.

Tibero는 X/Open DTP 규약의 XA를 지원하기 때문에 표준에 맞는 XA 애플리케이션 프로그램을 작성할 수 있다.

다음은 xa_open 함수를 호출하는 데 필요한 속성이다.

속성필수설명
userO접속할 사용자의 이름이다. (예: user=tibero
pwdO접속할 사용자의 패스워드이다. (예: pwd=1234)
dbX

접속할 데이터베이스의 DSN 이름(tbdsn.tbr 파일 내의 DSN 이름)이다.

(예: db=sample)

conn_idX

XA 연결에 이름을 부여한다. 이 이름을 ESQL의 AT 구문에서 사용할 수 있다.

(예: conn_id=db1)

Loose_CouplingO

다른 브랜치이지만 동일한 글로벌 트랜잭션끼리 같은 리소스를 사용하는지 여부를 설정한다. (true/false)

loose coupling이면 (1,0)과 (1,2)는 서로 다른 내부 리소스를 사용한다. 예를 들어 TX가 해당된다. 이와는 반대로 tight coupling이면 두 xtb는 하나의 TX를 서로 잠금 처리를 설정해가며 공유하여 사용한다.

(예: Loose_Coupling=false)

sestmO

시스템에 의해 중단(abort)되기 전까지의 트랜잭션의 inactive time limit이다. 클라이언트로부터 한 요청에 대한 답변을 전달한 후 그 다음 요청이 오기 전까지 대기하는 시간이다. 그 이상의 시간이 지나면 클라이언트 측에서 문제가 있다고 판단하고 해당 xtb를 롤백한다. (예: sestm=10)

seswtX

XA_RETRY가 반환되기 전까지 데이터베이스 서버가 트랜잭션을 기다리는 대기 시간이다.

flag 내에 TMNOWAIT이 설정되어 있지 않을 경우에는 클라이언트의 요청을 데이터베이스가 곧바로 처리해줄 수 없을 때 해당 리소스를 사용할 수 있을 때까지 기다린 후에 클라이언트에 답변을 주게 된다. 이때 데이터베이스가 seswt로 설정한 시간까지 답변을 줄 수 없는 경우 XARETRY를 반환한다.

(예: seswt=30)

logdirXXA의 로그를 저장할 디렉터리를 지정할 수 있다. (예: 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], &gtrid, 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], &gtrid, 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], &gtrid, 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], &gtrid, 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)에서 지원하는 인터페이스와 이를 이용하여 작성한 프로그램에 대해 설명한다.

다음은 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
    $TMAXDIRTmax 설치 디렉터리
  • 프로그램 파일

    파일설명
    sample.mTmax 환경설정 파일($TMAXDIR/config)
    tms_tbr.mkTibero용 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가 연동하는 것을 확인할 수 있다.

  1. Tmax 기본 환경설정

  2. TMS 컴파일

  3. TMS 컴파일

  4. 서버 프로그램 컴파일

  5. 클라이언트 프로그램 컴파일

  6. DB 테이블 생성

  7. 예제 프로그램 실행

Tmax 기본 환경설정

다음은 Tmax 기본 환경을 설정하는 방법이다.

TMS 컴파일

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

DB 테이블 생성

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.mTuxedo 환경설정 파일
    tmax32.fld필드 테이블 파일
    trans_fml32.tbc서버 프로그램 tbESQL/C 파일
    builds.sh서버 프로그램 빌드 스크립트
    insert.cINSERT 클라이언트 프로그램 파일
    select.cSELECT 클라이언트 프로그램 파일
    buildc.sh클라이언트 프로그램 빌드 스크립트
    create_table.sql테스트를 위한 DB 테이블 생성 파일
    run.shTuxedo 시스템 기동 스크립트

다음에 제시된 순서대로 따르면 Tuxedo와 Tibero가 연동하는 것을 확인할 수 있다. 운영체제나 시스템 환경에 따라서 예제 파일을 수정하여 테스트해야 한다.

  1. 시스템 환경변수 설정

  2. Tuxedo 기본 환경설정

  3. TMS 컴파일

  4. 서버 프로그램 컴파일

  5. 클라이언트 프로그램 컴파일

  6. DB 테이블 생성

  7. 예제 프로그램 실행

시스템 환경변수 설정

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 기본 환경설정

TMS 컴파일

다음 아래와 같은 명령어를 이용하여 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

DB 테이블 생성

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 테이블로부터 해당 레코드 정보를 가져와서 출력한다.