콘텐츠로 이동

3. XA Interface#

이 장은 XA 표준을 소개하고, 분산 트랜잭션의 기본 개념과 XA 인터페이스를 설명한다.

ODBC, Embedded SQL, JDBC 프로그램에서 글로벌 트랜잭션을 사용하여 Altibase에 접근하는 방법에 대해 설명한다. 또한 XA의 제약사항 및 애플리케이션에서 발생할 수 있는 문제를 다루는 법을 살펴본다.

XA 개요#

XA 구조는 X/Open에서 정한 분산 트랜잭션 (또는 글로벌 트랜잭션) 처리를 위한 표준 모델이다.

분산 트랜잭션이란 2개 또는 그 이상의 네트워크 상에서의 데이터베이스 시스템 간의 트랜잭션으로서 글로벌 트랜잭션 (Global Transaction)이라고도 한다. 데이터베이스 시스템은 트랜잭션의 자원 역할을 하고, 트랜잭션 매니져 (Transaction Manager, TM)는 이러한 자원에 관련된 모든 동작에 대하여 트랜잭션을 생성하고 관리한다. 즉 다수의 데이터베이스 서버들이 제공하는 자원을 분산 애플리케이션이 공유할 수 있도록 해주거나 또는 글로벌 트랜잭션으로 처리할 수 있도록 한다.

XA는 하나 이상의 데이터베이스에서 트랜잭션을 처리하는 애플리케이션에 유용한 구조이다.

XA 관련 용어#

  • Application (AP)
    애플리케이션은 필요한 트랜잭션과 트랜잭션으로 구성된 작업을 정의한다. 애플리케이션은 Embedded SQL 또는 ODBC CLI로 작성할 수 있다.

  • 글로벌 트랜잭션 (Global Transaction)
    TM에 의해 관리되는 트랜잭션 전체를 의미하며, 분산 트랜잭션이라고도 한다.

  • 휴리스틱 완료 (Heuristic Completion)
    in-doubt 트랜잭션에서 어떤 원인으로 커밋 등의 다음 명령을 받지 못 해서 RM 스스로 커밋 또는 롤백을 수행한 경우 휴리스틱 커밋 (Heuristic Commit)이나 휴리스틱 롤백 (Heuristic Rollback)이라고 한다. 이러한 상태를 휴리스틱 완료 (Heuristic Completion)가 되었다고 한다. 일반적으로 네트워크 실패 또는 트랜잭션 타임아웃에 의해 발생한다.

  • In-doubt 트랜잭션
    RM 즉 DBMS에 prepare 된 후 커밋 또는 롤백 메시지를 받기 전까지의 트랜잭션 브랜치을 의미한다. 또는 Pending 트랜잭션이라고도 한다.

  • 리소스 매니저(Resource Manager, RM)
    리소스 매니저는 XA 트랜잭션에 의해 접근되는 자원을 관리한다. 예를 들어 관계형 데이터베이스, 트랜잭션 큐, 또는 파일 시스템 등이 있을 수 있다.

  • 트랜잭션 브랜치 (Transaction Branch)
    이는 하위 트랜잭션으로 글로벌 트랜잭션의 일부이다. 이는 글로벌 트랜잭션에 참여하는 RM들 중 하나에서 실행된다. 하나의 트랜잭션 브랜치는 하나의 XID (XA의 트랜잭션 아이디)와 일대일로 대응한다.

  • Transaction Manager (TM)
    이는 트랜잭션을 정의하는 API를 제공한다. 트랜잭션의 커밋과 롤백을 책임지고, 복구를 수행한다. TM은 모든 RM이 서로 일관성을 가지도록 2단계 커밋 엔진을 가지고 있다.

  • Transaction Processing Monitor (TPM)
    이는 하나 이상의 AP가 하나 이상의 RM으로 요청하는 트랜잭션의 흐름을 조정한다. RM은 이기종이 가능하며 네트워크를 통해서 분산될 수 있다.
    TPM은 커밋과 롤백 작업을 조정함으로써 분산 트랜잭션을 완료한다. TPM에 속하는 TM부분은 분산된 커밋과 롤백 작업의 타이밍을 결정할 책임이 있다. 즉 TPM은 2단계 커밋을 제어할 책임이 있다.
    TM은 분산된 커밋과 롤백 작업을 관리하기 때문에, 모든 RM에 대해 알 수 있어야 하며 직접 통신할 수 있어야 한다. 이를 위해 TM은 XA 인터페이스를 사용한다. Altibase의 경우, TM 은 Altibase에서 제공하는 XA 라이브러리를 사용할 수 있다.

  • TX 인터페이스
    AP는 TX 인터페이스를 사용하여 TM을 통해서 트랜잭션을 제어한다. AP가 직접 XA 인터페이스를 사용하지는 않는다. AP는 개별 트랜잭션 브랜치의 작업을 알지 못하고, 애플리케이션 쓰레드가 직접 트랜잭션 브랜치 작업에 참여하지도 않는다. 글로벌 트랜잭션의 브랜치들은 AP를 대신하여 TM에 의해서 관리된다. AP는 다만 TM에게 글로벌 트랜잭션을 커밋 또는 롤백할 것을 요청할 뿐이다.

XA의 구조#

아래 그림에서 보는 것처럼, 하나 이상의 AP (Application Program), TM (Transaction Manager)과 하나 이상의 RM (Resource Manager)이 분산 트랜잭션에 관여한다.

[그림 3‑1] XA 구조

AP가 TX 인터페이스를 사용하여 TM에게 분산 트랜잭션이 시작됐다고 알리면, TM은 어떤 RM (데이터베이스 시스템)이 분산 트랜잭션의 대상인지 확인한다. TM은 내부적으로 RM에서 수행할 트랜잭션 브랜치를 위한 XID를 생성하여 XA인터페이스를 호출하여 RM에게 XID를 전달한다.

각각의 RM (DB 노드)은 전송된 XID에 대응하는 트랜잭션 브랜치를 처리하기 시작한다. 그리고 TM으로부터 그 트랜잭션의 종료 요청이 올 때까지는 AP로부터 요청된 작업을 그 XID에 해당하는 글로벌 트랜잭션내의 작업으로 인지하고 트랜잭션 브랜치에서 작업을 진행한다.

트랜잭션을 종료하려면, AP는 TX 인터페이스를 사용하여 TM에게 분산 트랜잭션이 종료됐다고 알린다. 그러면 TM은 XA인터페이스를 사용하여 분산 트랜잭션을 진행한 RM에게 커밋 또는 롤백을 명령한다.

XA와 2단계 커밋#

Altibase XA Interface는 2단계 커밋(2-Phase Commit, 2PC)을 따른다. 2PC는 Prepare 단계와 Commit 단계로 구성된다.

1단계인 Prepare에서 TM은 분산 트랜잭션에 참여하는 모든 데이터베이스 노드들 즉 RM에게 커밋의 가능성을 확인한다. RM은 커밋이 가능하다면 Prepare 상태를 TM에게 전달한다. 그러나 가능하지 않다면 RM은 이를 롤백시키기 위한 상태 값을 반환한다.

2단계인 Commit에서 TM은 Prepare의 상태를 전달받을 때까지 대기한다. 그리고 Prepare가 정상적으로 진행되었다면, 모든 RM에게 커밋 명령을 보낸다. 그러나 하나의 RM이라도 Prepare가 되지 않았다면, 롤백 명령을 보낸다.

xa_switch_t 구조체#

XA 인터페이스를 지원하는 모든 RM은 RM에 대한 정보와 각 인터페이스의 entry point를 가지는 xa_switch_t 구조체를 제공한다. Altibase는 altibase_xa_switch라는 이름으로 제공한다.

struct xa_switch_t {
    char name[RMNAMESZ];        /* name of resource manager */
    long flags;                 /* resource manager specific options */
    long version;               

    int (*xa_open_entry)(/*_ char *, int, long _*/);            /* xa_open fn pointer */
    int  (*xa_close_entry)(/*_ char *, int, long _*/);          /* xa_close fn pointer */
    int  (*xa_start_entry)(/*_ XID *, int, long _*/);           /* xa_start fn pointer */
    int  (*xa_end_entry)(/*_ XID *, int, long _*/);             /* xa_end fn pointer */
    int  (*xa_rollback_entry)(/*_ XID *, int, long _*/);        /* xa_rollback fn pointer */
    int  (*xa_prepare_entry)(/*_ XID *, int, long _*/);         /* xa_prepare fn pointer */
    int  (*xa_commit_entry)(/*_ XID *, int, long _*/);          /* xa_commit fn pointer */
    int  (*xa_recover_entry)(/*_ XID *, long, int, long _*/);   /* xa_recover fn pointer */
    int  (*xa_forget_entry)(/*_ XID *, int, long _*/);          /* xa_forget fn pointer */
    int  (*xa_complete_entry)(/*_ int *, int *, int, long _*/); /* xa_complete fn pointer */
};

XA 라이브러리#

Altibase XA인터페이스를 사용하는 애플리케이션을 연결하기 위해서는 별도의 라이브러리가 필요하지 않다. ODBC 프로그램을 위한 odbccli 라이브러리에 포함되어 제공하기 때문에, 사용자가 XA 관련 기능을 사용하기 위해서는 XA를 사용하는 애플리케이션에 libodbccli.a 라이브러리만 링크하면 된다.


XA 인터페이스#

XA 인터페이스는 RM과 TM간의 상호 인터페이스이다. TM은 글로벌 트랜잭션을 수행하기 위해 RM을 제어하는 XA 루틴과 RM이 동적으로 TM에게 요청하는 AX 루틴으로 구성된다.

Note: Altibase는 동적인 등록을 지원하지 않기 때문에, 트랜잭션을 시작하기 전에 TM이 RM으로 xa_start를 호출해야 한다.

XA 함수#

Altibase는 xa_switch_t의 Altibase 구현인 altibase_xa_switch 구조체에 XA 관련 함수들을 제공한다.

XA 인터페이스 설명
xa_open Resource Manager 에 연결한다.
xa_close Resource Manager로부터 연결을 해제한다.
xa_start 새로운 트랜잭션 브랜치 또는 기존의 트랜잭션 브랜치를 다시 시작하고, 주어진 트랜잭션 식별자(XID)와 연계시킨다.
xa_end 트랜잭션 브랜치로부터 분리한다.
xa_rollback 주어진 XID와 연계된 트랜잭션 브랜치를 롤백한다.
xa_prepare 트랜잭션 브랜치의 커밋을 준비한다.
xa_commit 트랜잭션 브랜치를 커밋한다.
xa_recover prepare, 휴리스틱 커밋 또는 휴리스틱 롤백된 트랜잭션의 XID 리스트를 보여준다.
xa_forget 휴리스틱하게 완료된 트랜잭션 브랜치에 대한 정보를 RM에서 폐기하도록 한다.

[표 3‑1] XA 인터페이스

xa_open#

RM에 접속한다.

int xa_open(char *xa_info, int rmid, long flags);

xa_info는 null-terminated 문자열로, 서버 정보를 포함하며 최대 길이는 256byte이다. SQLDriverConnect의 인자와 동일한 포맷을 가지며, 추가적으로 XA_NAME, XA_LOG_DIR 필드가 존재한다. 다른 필드에 대한 자세한 설명한 CLI User's Manual 의 SQLDriverConnect 함수를 참조하기 바란다.

NAME=value;NAME=value;NAME=value;…

예) DSN=127.0.0.1;UID=SYS;PWD=MANAGER ;XA_NAME=conn1

XA 파라미터 설명
XA_NAME Altibase Embedded SQL 프로그램에서 연결의 식별자로 사용되는 이름이다. Altibase Embedded SQL로 애플리케이션을 작성할 때 이 값을 생략하면, 기본 연결을 사용하게 된다. 만약 XA_NAME 속성에 이름을 명시했다면, SQL문 수행시 AT 절에 이 이름을 사용하면 된다.
XA_LOG_DIR Altibase XA 라이브러리에서 발생한 에러 정보를 로깅하는 디렉터리를 명시한다. 기본값은 ALTIBASE_HOME 환경변수가 설정되었을 경우 $ALTIBASE_HOME/trc이고, 그렇지 않다면 현재 디렉터리다.

[표 3‑2] XA 인터페이스에 추가된 필드

rmid는 접속할 서버의 ID를 기록하며, 아무 값이나 쓸 수 있다.

플래그 (flags)는 다음의 값을 사용할 수 있다.

  • TMNOFLAGS

xa_close#

지정된 RM과 연결을 종료한다.

int xa_close(char *xa_info, int rmid, long flags);

xa_info는 서버에 대한 정보를 기록하는 문자열로, 최대 길이는 256byte이다.

Note: 연결이 이미 종료된 것에 대해 xa_close가 수행되어도, XA_OK 값이 반환된다.

플래그 (flags)는 다음의 값을 쓸 수 있다.

  • TMNOFLAGS

xa_start#

트랜잭션 브랜치를 시작한다. xid는 글로벌 트랜잭션에 대한 식별자이다.

int xa_start(XID *xid, int rmid, long flags);

플래그(flags)는 다음의 값을 쓸 수 있다.

  • TMRESUME
    이전에 suspend된 트랜잭션 브랜치를 계속한다.

  • TMNOWAIT
    xa_start가 차단될 경우, 기다리지 않고 XA_RETRY 값을 반환한다.

  • TMASYNC
    비동기 모드로 트랜잭션 브랜치를 시작한다 (Altibase는 지원하지 않음).

  • TMNOFLAGS
    다른 플래그를 지정하지 않을 경우 반드시 이 값을 지정해야 한다.

  • TMJOIN
    존재하는 트랜잭션 브랜치에 연결한다.

xa_end#

트랜잭션 브랜치를 끝낸다.

int xa_end(XID *xid, int rmid, long flags);

플래그(flags)는 다음의 값을 쓸 수 있다.

  • TMSUSPEND
    해당 트랜잭션 브랜치를 suspend 상태로 변경하고 종료한다. 이 트랜잭션 브랜치는 xa_start에 의해서 다시 계속될 수 있다.

  • TMSUCCESS
    성공적으로 종료했다는 것을 나타내는 것으로 TMSUSPEND 또는 TMFAIL과 같이 사용될 수 없다.

  • TMFAIL
    비정상적으로 종료했다는 것을 나타낸다. 이 트랜잭션 브랜치의 상태는 roll-back only로 지정된다. TMSUCCES 또는 TMSUSPEND와 같이 사용될 수 없다.

xa_rollback#

지정된 트랜잭션 브랜치에 대해서 수행한 연산을 롤백한다.

int xa_rollback(XID *xid, int rmid, long flags);

플래그(flags)는 다음의 값을 쓸 수 있다.

  • TMASYNC
    xa_rollback이 비동기 모드로 동작하도록 한다 (Altibase는 지원하지 않음).

  • TMNOFLAGS
    다른 플래그를 지정하지 않을 경우 반드시 이 값을 지정해야 한다.

xa_prepare#

2단계 커밋 프로토콜에서 트랜잭션을 커밋하거나 롤백하기 이전에 수행한다.

int xa_prepare(XID *xid, int rmid, long flags);

플래그(flags)는 다음의 값을 쓸 수 있다.

  • TMASYNC
    (Altibase는 지원하지 않음)

  • TMNOFLAGS
    다른 플래그를 지정하지 않을 경우 반드시 이 값을 지정해야 한다.

다음의 값이 리턴될 수 있다.

  • XA_RDONLY
    트랜잭션이 RM (DBMS)의 어떤 데이터도 변경하지 않았을 경우에 반환된다. RM 에서 수행된 트랜잭션은 커밋이나 롤백이 필요하지 않다.

  • XA_OK
    정상적으로 수행되었을 경우에 이 값을 반환한다.

xa_commit#

특정 트랜잭션 브랜치를 커밋한다.

int xa_commit(XID *xid, int rmid, long flags);

플래그(flags)는 다음의 값을 쓸 수 있다.

  • TMONEPHASE
    one phase commit을 수행할 경우 설정한다.

  • TMNOFLAGS
    다른 플래그를 지정하지 않을 경우 반드시 이 값을 지정해야 한다.

xa_recover#

RM에서 prepare 상태로 있는 트랜잭션 브랜치에 해당하는 xid의 목록을 얻는다.

int xa_recover(XID *xids, long count, int rmid, long flags);

리턴 값은 xids 에 반환되는 xid의 갯수이다.

count 매개변수에는 xids의 사이즈를 지정한다.

플래그(flags)는 다음의 값을 쓸 수 있다.

  • TMSTARTRSCAN
    자세한 설명은 XA Specification 문서를 참고한다.

  • TMENDRSCAN
    자세한 설명은 XA Specification 문서를 참고한다.

  • TMNOFLAGS
    현재 커서 위치에서 시작하여 XID 목록을 반환한다.

xa_forget#

휴리스틱하게 (heuristically) 완료된 트랜잭션을 Altibase 서버가 관리하지 않도록 한다.

int xa_forget(XID * xid, int rmid, long flags);

플래그(flags)는 다음의 값을 쓸 수 있다.

  • TMNOFLAGS
    항상 이 값을 지정한다.

xa_complete#

비동기 모드의 연산을 수행할 때 해당 연산이 종료될 때까지 대기한다. Altibase에서는 지원하지 않으며, 항상 오류 메시지를 리턴한다.


XA 사용#

이 절에서는 XA 환경에서 ODBC, Embedded SQL, JDBC 등을 사용하기 위한 기본적인 절차를 살펴본다.

ODBC/XA 수행 순서#

  1. xa_open
    지정한 서버에 접속한다.

  2. SQLAllocHandle
    ODBC에 연결하기 위해서 connection과 environment 핸들을 생성한다.

  3. SQLSetConnectAttr
    XA connection을 connection 핸들에 연결한다.

  4. SQLConnect
    실제 연결은 xa_open으로 연결되었으므로, 이 호출에서 새로운 접속을 수행하지는 않는다. 다만 SQLConnect는 ODBC 에서 connection의 내부 상태값을 변경한다. 이 과정을 생략하면 DML 연산을 수행할 수 없다.

  5. xa_start
    특정 XID에 대응하는 트랜잭션 브랜치를 시작한다.

  6. SQL 구문 실행
    SQLPrepare, SQLExecute 등의 연산을 수행한다. 만일 여기에서 commit문을 실행한다면 서버는 에러 메시지를 반환한다.

  7. xa_end
    트랜잭션 브랜치를 종료한다.

  8. xa_prepare
    커밋을 위해 prepare를 수행한다.

  9. xa_commit
    트랜잭션을 커밋한다.

  10. SQLDisconnect
    ODBC에서 connection의 내부 상태를 연결되지 않은 상태로 변경한다. 그러나 실제 XA에 의해서 생성된 연결은 그대로 유지된다.

  11. xa_close
    xa의 연결을 종료한다.

SQLSetConnectAttr#

ODBC 애플리케이션이 분산 트랜잭션을 사용할 수 있도록, SQLSetConnectAttr을 호출하여 XA connection을 ODBC connection에 연결시킨다.

XA 연결하기 위해서는 SQLSetConnectAttr에 다음과 같은 매개변수를 준다.

SQLRETURN SQLSetConnectAttr (SQLHDBC hdbc,  
SQLINTEGER fAttr,  
SQLPOINTER vParam,  
SQLINTEGER sLen);
  • fAttr = ALTIBASE_XA_RMID
    hdbc로 지정한 연결을 XA 연결로써 사용하도록 한다. XA 연결에 대한 자세한 정보는 vParam에 다음의 구조체 포인터를 지정하여 설정한다.
  • vParam
    xa_open으로 연결할 때 사용한 rmid 값을 갖는다. rmid를 지정하지 않고 서버에 XA 연결을 하려면 다음의 매개변수를 사용한다.
    fAttr = SQL_ATTR_ENLIST_IN_XA
    지정한 hdbc 연결을 마지막 XA 연결과 맺는다.

Embedded SQL#

XA_NAME 설정에 따른 프로그램 작성 방법#

XA 프로그램을 작성할 때, 커서는 한 트랜잭션 내에서만 유효하다. 즉 트랜잭션이 시작한 후에 커서를 열어야 하고, 트랜잭션이 커밋 또는 롤백 되기 전에 커서를 닫아야 한다.

기본 연결을 사용한 프로그램 작성 방법#

기본 연결을 사용려면 다음과 같이 xa_open의 연결 정보를 가지는 xa_info 인자에 XA_NAME 필드를 지정하지 않은 문자열을 사용해야 한다.

DSN=127.0.0.1;UID=SYS;PWD=MANAGER

그리고 SQL 구문에서는 다음 예제처럼 AT 절을 사용하지 않아도 된다.

EXEC SQL UPDATE emp SET empno = 5;
한 개 이상의 연결을 사용하기 위해 XA_NAME을 사용한 프로그램 작성 방법#

Embedded SQL 프로그램에서 명시적으로 연결의 이름을 사용하려면, xa_open의 연결 정보를 가지는 xa_info 인자에 XA_NAME=conn1 필드가 포함된 문자열을 사용해야 한다.

기본 연결과 한 개 이상의 이름이 명시된 연결이 있는 프로그램을 작성하려면, 다음과 같이 한다.

연결 이름이 conn1, conn2로 존재한다면 TM의 환경 설정에서 open_string은 다음과 같이 XA_NAME을 사용해서 연결 이름을 명시한다.

DSN=127.0.0.1;UID=SYS;PWD=MANAGER;XA_NAME=conn1
DSN=127.0.0.1;UID=SYS;PWD=MANAGER;XA_NAME=conn2
DSN=127.0.0.1;UID=SYS;PWD=MANAGER

애플리케이션 서버의 서비스 함수 프로그램에서는 아래처럼 AT절을 포함한 Embedded SQL 구문을 작성한다.

EXEC SQL AT conn1 UPDATE emp SET empno = 5;
EXEC SQL AT conn2 UPDATE emp SET empno = 5;
EXEC SQL UPDATE emp SET empno = 5;

JDBC/XA 수행 순서#

Altibase의 JDBC 드라이버가 정의하는 XA관련 클래스는 다음과 같다.

Altibase.jdbc.driver.AltibaseXADataSource
Altibase.jdbc.driver.AltibaseXAResource
Altibase.jdbc.driver.AltibaseXID

사용자가 직접 사용하는 클래스는 AltibaseXADataSource이다. 나머지는 JTA 인터페이스 클래스를 구현한 클래스로 사용자가 직접 사용할 필요는 없다.

  1. AltibaseXADataSource 객체 생성

    AltibaseXADataSource xaDataSource = new AltibaseXADataSource();
    xaDataSource.setUrl(args[0]);
    xaDataSource.setUser("SYS");
    xaDataSource.setPassword("MANAGER");
    
  2. XAConnection 객체 생성
    XAConnection을 XADataSource 클래스의 getXAConnection 매소드를 호출하여 생성한다.

    XAConnection xaConnection = xaDataSource.getXAConnection("SYS", "MANAGER:");
    
  3. XAResource 객체 생성
    XAResource를 XAConnection 클래스의 getXAResource 매소드를 호출하여 생성한다.

    XAResource xaResource = xaConnection.getXaResource();
    
  4. Connection 객체 생성
    SQL을 수행할 커넥션을 XAConnection 클래스의 getConnection 매소드를 호출하여 생성한다.

    Connection conn1 = xaConnection.getConnection();
    
  5. XAResource 객체를 이용하여 XA 함수 실행
    xa_start, xa_end 등의 XA 함수들은 XAResource 클래스의 매소드를 사용하여 실행된다.

    xaResource.start(AltibaseXID, XAResource.TMNOFLAGS);
    
  6. Connection 객체를 이용하여 SQL 구문 수행

    Statement stmt = conn.createStatement();
    int cnt = stmt.executeUpdate("insert into t1 values (4321)");
    

XA 트랜잭션 제어#

Altibase XA 환경에서 트랜잭션을 제어하는 방법에 대해 설명한다.

XA 라이브러리를 사용할 때는 commit이나 rollback구문을 사용하여 트랜잭션을 처리하면 안된다. 대신에 AP는 아래 표의 TX인터페이스를 사용하여 TM이 트랜잭션을 시작하거나 종료하도록 해야 한다.

TM은 일반적으로 XA 인터페이스를 사용해서 트랜잭션을 제어한다.

TX 인터페이스 설명
tx_open RM에 logon한다.
tx_close RM에서 logout한다.
tx_begin 새로운 트랜잭션을 시작한다.
tx_commit 트랜잭션을 커밋한다.
tx_rollback 트랜잭션을 롤백한다.

[표 3‑3] TX 인터페이스

TX 인터페이스와 XA 인터페이스의 호출 흐름을 살펴보면, 다음 그림과 같다.

[그림 3‑2] TX 인터페이스와 XA 인터페이스의 호출 흐름도

TPM 애플리케이션은 애플리케이션 클라이언트가 애플리케이션 서버에서 제공하는 서비스를 요청하는 client/server 구조로 되어 있다. 서비스란 논리적인 일의 단위로써, Altibase를 RM으로 사용하는 경우에는 SQL 구문의 집합으로 구성된다고 볼 수 있다.

예제#

다음 예제들은 애플리케이션 서버가 TPM 시스템에 이미 로그온 되었다고 가정한 예제이다.

애플리케이션 서버에서 트랜잭션 시작하기#

애플리케이션 서버에 의해서 트랜잭션이 시작되는 예제이다.

Client:
tpm_service("SERVICE1");

Server:
SERVICE1()
{
<get service specific data>
tx_begin();
EXEC SQL UPDATE....;
tpm_service("SERVICE2");
tx_commit();
<return service status back to the client>
}
애플리케이션 클라이언트에서 트랜잭션 시작하기#

애플리케이션 클라이언트에 의해서 트랜잭션이 시작되는 예제이다.

Client:
tx_begin();
tpm_service("SERVICE1");
tmp_service("SERVICE2");
tx_commit();

Server:
SERVICE1()
{
<get service specific data>
EXEC SQL UPDATE...;
<return service status back to the client>
}
SERVICE2()
{
<get service specific data>
EXEC SQL UPDATE...;
<return service status back to the client>
}

기존 애플리케이션을 TPM 애플리케이션으로 변경#

기존에 작성되어 있던 애플리케이션 (Precompiler 또는 ODBCCLI)을 Altibase XA 라이브러리를 이용한 TPM (Transaction Performance Monitoring) 애플리케이션으로 변경하기 위해서는 다음의 절차를 따라야 한다.

  1. 애플리케이션을 '서비스'라는 프레임워크(framework) 구조로 전환한다. 여기서 프레임워크란 애플리케이션 클라이언트가 애플리케이션 서버에게 '서비스'를 요청하는 구조를 의미한다. 어떤 TPM은 tx_open, tx_close 함수를 사용할 것을 요구하고, 어떤 TPM은 묵시적으로 logon, logoff 를 하기도 한다.
  2. 일반적인 connect 구문을 TPM호환성이 있는 형태로 변경한다. 예를 들어, Embedded SQL프로그램의 경우에는 EXEC SQL CONNECT 구문을 tx_open()으로 변경하고, ODBCCLI에서는 SQLDriverConnect 구문을 tx_open과 SQLConnect로 변경한다. 실제 연결은 tx_open으로 연결되었으나, ODBC 내부에서 XA연결과 맺어주는 SQLConnect 과정을 생략하면 DML 연산을 수행할 수 없다. 자세한 설명은 ODBC/XA 수행 순서를 참조한다.

  3. 일반적인 disconnect 구문을 TPM호환성이 있는 형태로 변경한다. Embedded SQL프로그램의 EXEC SQL DISCONNECT 또는 ODBCCLI의 SQLDisconnect 구문을 tx_close()로 변경한다.

  4. Commit, rollback 구문을 TPM호환성이 있는 형태로 변경한다. 예를 들어 EXEC SQL COMMIT/ROLLBACK (Embedded SQL프로그램), SQLEndTran(ODBCCLI) 을 tx_commit/tx_rollback으로 변경하고, tx_begin()을 호출하여 트랜잭션을 시작하도록 한다.
  5. 애플리케이션은 트랜잭션을 종료(end)하기 전에 fetch 상태를 리셋해야 한다. 커서를 사용해서 fetch를 했으면 트랜잭션을 종료하기 전에 CLOSE RELEASE를 사용해서 커서를 닫고 자원을 해제해야 한다.

    Altibase 구문 TPM 함수
    CONNECT tx_open
    암묵적인 트랜잭션 시작 tx_begin
    SQL SERVICE에서 SQL구문 수행
    COMMIT tx_commit
    ROLLBACK tx_rollback
    DISCONNECT tx_close
    SET TRANSACTION READ ONLY 허용하지 않음


XA 사용시 제약사항#

XA를 사용할 때 다음과 같은 몇가지 제약사항이 존재한다.

  • SQL 사용시 제약사항

  • 트랜잭션 브랜치 관련 제약사항

  • 연계 이주 비지원

  • 비동기 호출 비지원

  • 동적 등록 비지원

  • 서버 종료

SQL 사용시 제약사항#

롤백과 커밋#

TM이 글로벌 트랜잭션을 관리하기 때문에 XA 애플리케이션에서는 COMMIT 또는 ROLLBACK 구문 같은 트랜잭션 제어 구문을 사용해서 글로벌 트랜잭션을 제어해서는 안 된다.

글로벌 트랜잭션을 종료하기 위해서는 tx_commit이나 tx_rollback 을 사용해야 한다. Precompiler 애플리케이션에서 EXEC SQL ROLLBACK이나 EXEC SQL COMMIT 구문을 사용할 수 없다. ODBCCLI 애플리케이션에서도 SQLEndTran을 사용해서 커밋이나 롤백을 수행하면 안된다.

DDL 구문#

DDL SQL 구문은 Altibase 서버 내부적으로 커밋을 하기 때문에 Altibase XA 애플리케이션에서 사용할 수 없다.

AUTOCOMMIT 세션 프로퍼티#

글로벌 트랜잭션은 non-autocommit 모드에서 수행되기 때문에, ALTER SESSION SET AUTOCOMMIT = TRUE 구문을 사용하여 AUTOCOMMIT 프로퍼티를 변경할 수 없다.

SET TRANSACTION#

XA 애플리케이션에서 SET TRANSACTION { READ ONLY | READ WRITE | ISOLATION LEVEL ... } 구문을 사용하면 안 된다.

EXEC SQL 구문으로 연결 또는 해제#

Embedded SQL 프로그램에서 연결 또는 연결 해제를 위해서 EXEC SQL CONNECT, EXEC SQL DISCONNECT 구문을 사용해서는 안 된다.

트랜잭션 브랜치 관련 제약사항#

하나의 글로벌 트랜잭션에는 다수의 애플리케이션 쓰레드들이 참여하는데, 이들 쓰레드는 서로 tightly-coupled 또는 loosely-coupled 관계일 수 있다.

Tightly-coupled 관계는 자원을 공유하는 쓰레드의 관계이다. 이들 쓰레드들은 하나의 개체로 처리된다. Tightly-coupled 쓰레드에서 RM은 트랜잭션 브랜치가 자원의 데드락이 발생하지 않도록 해야 한다. 그러나 Loosely-coupled 관계에서는 이런 보장을 하지 않는다. RM에서는 loosely-coupled 관계의 트랜잭션 브랜치를 서로 다른 글로벌 트랜잭션처럼 다룬다.

XID 와 쓰레드의 관계#

TM이 XID의 branch qualifier를 새로운 값으로 생성하여 RM에 전달한다면, 이 쓰레드는 같은 브랜치 안의 다른 쓰레드들과 loosely-coupled 관계가 된다. RM은 이 쓰레드를 별도의 글로벌 트랜잭션처럼 처리한다. 그리고 만약 TM이 XID의 branch qualifier를 재사용한다면, 이 쓰레드는 해당 브랜치를 공유하는 다른 쓰레드들과 tightly-coupled 한 관계가 된다.

RM은 이러한 tightly-coupled 쓰레드들을 하나의 개체처럼 다뤄야 하고, 이들 tightly-coupled 쓰레드간에 자원 데드락이 발생하지 않도록 보장해야 한다.

연계 이주 비지원#

Altibase는 연계 이주 (Association Migration, TM이 suspended 브랜치를 다른 브랜치에서 연계하여 다시 시작하는 것)를 지원하지 않는다.

비동기 호출 비지원#

Altibase는 비동기 XA 호출을 지원하지 않는다.

동적인 등록 비지원#

Altibase 서버는 동적 등록을 지원하지 않고, 정적 등록만 지원한다. 동적 등록 (Dynamic Registration)이란 RM이 글로벌 트랜잭션을 시작하기 전에 자신이 TM에 등록하는 것을 말한다.

정적 등록에서는 TM이 xa_start를 꼭 호출하여 트랜잭션 시작을 RM에 알려야 한다.

서버 종료#

Shutdown abort 를 사용한 Altibase 종료 또는 비정상 종료시 prepared 트랜잭션이 존재하였다면, 종료 후 서버 재 구동시 이들 트랜잭션이 recovery 과정을 거치게 되므로 구동 후에 xa_recover를 통해서 이들 트랜잭션을 처리할 수 있다.

Shutdown immediate 또는 shutdown normal로 서버를 종료할 때 prepared 트랜잭션이 존재하면, 정상 종료를 명령했더라도 Altibase는 abort로 종료시킬 때와 같은 방식으로 서버를 종료한다. 따라서 재 구동시 recovery 과정을 거쳐 이들 트랜잭션이 그대로 유지되며 xa_recover를 통해서 이들 트랜잭션을 처리할 수 있다.

그러나 prepared 트랜잭션이 존재하지 않을 경우에는 정상 종료하며, 다음 구동시 recovery 과정을 거치지 않게 된다.


JDBC 분산 트랜잭션#

분산 트랜잭션을 구현한 Altibase JDBC는 JDBC 2.0 extension API에서 Connection Pooling 기능과 분산 트랜잭션을 위한 Open XA 표준을 준수한다.

XA 표준에 부합하는 분산 트랜잭션 기능을 구현한 모든 클래스는 Altibase JDBC 드라이버 패키지에 기본으로 포함되어 제공된다.

JTA(Java Transaction API)와 애플리케이션 서버#

분산 트랜잭션에서 애플리케이션이 애플리케이션 서버를 통해서 트랜잭션을 수행하는 과정을 그림과 같이 설명한다.

[그림 3‑3] 분산 트랜잭션 과정

애플리케이션 서버는 각각의 자원들과 연결될 수 있는 XAConnection을 지원한다.

애플리케이션은 애플리케이션 서버에 접속해서 Connection을 얻어 쿼리를 수행한다. 그리고 애플리케이션 서버는 TM (Transaction Manager)을 통해 트랜잭션을 관리한다. 이 때 TM은 DBMS 벤더에서 제공하는 Resource Adapter를 이용해서 자원에 접근할 수 있다.

Resource 가 DBMS일 경우, Resource Adapter는 JDBC 드라이버 패키지가 될 수 있다. Resource Adapter는 ResourceFactory, Transactional Resource(XAConnection), Connection, XAResource 등 4가지 종류의 클래스로 구성된다.

ResourceFactory는 XAConnection을 생성하며, JDBC 스펙에서 XADataSource가 여기에 해당한다. 애플리케이션 서버는 XADataSource에서 가져온 XAConnection(DBMS로 연결)을 얻는다. 그리고 XAConnection은 애플리케이션에서 사용할 connection(java.sql.Connection) 인스턴스와 TM에서 사용할 XAResource 인스턴스를 얻어온다.

XA 컴포넌트#

JDBC 2.0 Optional 패키지의 표준 XA 인터페이스들과 이를 구현한 Altibase 클래스를 설명한다.

XADataSource Interface#

javax.sql.XADataSource는 XA Connection의 factory 기능을 갖는 인터페이스이다. 이 인터페이스의 getXAConnection 메소드가 XA Connection 인스턴스를 반환한다.

public interface XADataSource
    {
       XAConnection getXAConnection() throws SQLException;
       XAConnection getXAConnection(String user, String password)
          throws SQLException;
       ...
}

Altibase.jdbc.driver.AltibaseXADataSource는 Altibase에서 제공한 JDBC 드라이버에 존재하는 XADataSource 인터페이스를 구현한 클래스이다. 동시에 Altibase.jdbc.driver.AltibaseConnectionPoolDataSource를 상속한 클래스이다. AltibaseConnectionPoolDataSource 클래스는 Altibase.jdbc.driver.DataSource를 상속한다.

따라서, AltibaseXADataSource 클래스는 DataSource와 AltibaseConnectionPoolDataSource의 connection properties를 모두 포함한다.

[그림 3‑4] AltibaseXADataSource 클래스

AltibaseXADataSource 클래스의 getXAConnection 메소드는 XAConnection 타입의 인스턴스를 반환한다. 이 인스턴스는 실제로 ABPooledConnection 클래스의 인스턴스로서 ABPooledConnection 클래스는 XAConnection 인터페이스를 구현하고 있다.

XA data source는 Java Naming Directory와 Interface(JNDI)에 등록 및 사용할 수 있다.

XAConnection Interface#

XAConnection 인터페이스는 PooledConnection 인터페이스의 하위 인터페이스이다. getConnection, close, addConnectionEventListener, removeConnectionEventListener 메소드를 포함한다.

public interface XAConnection extends PooledConnection
    {
   javax.jta.xa.XAResource getXAResource() throws SQLException;
   …
    }

XAConnection의 인스턴스는 데이터베이스와 물리적으로 연결하고 있다. 또한 XAConnection 인스턴스를 통해 분산 트랜잭션을 관리하는데 사용되는 XAResource를 얻을 수 있다.

Altibase JDBC driver에서는 Altibase.jdbc.driver.ABPooledConnection 클래스의 인스턴스가 실질적인 XAConnection 타입의 인스턴스가 된다.

ABPooledConnection 클래스의 getXAResource 메소드는 AltibaseXAResource 인스턴스를 반환하고, getConnection 메소드는 ABConnection 인스턴스를 반환한다.

[그림 3‑5] ABPooledConnection 클래스

getConnection 메소드를 통해 반환되는 ABConnection 인스턴스는 데이터베이스와의 물리적인 연결에 대한 임시 핸들이고, 이 Connection은 글로벌 트랜잭션에 참여되기까지는 일반적인 Connection처럼 동작한다. 글로벌 트랜잭션에 참여되는 순간에 auto-commit 상태는 false가 되며, 글로벌 트랜잭션이 끝난 후에 auto-commit 상태는 글로벌 트랜잭션이 시작되기 이전 상태로 돌아간다.

XAConnection의 getConnection 메소드가 호출될 때마다 새로운 Connection 인스턴스를 반환하는데, 동일한 XAConnection 인스턴스에 의해 반환되어 이전에 존재하던 Connection 인스턴스는 close 된다. 그럼에도 불구하고, 새로운 Connection이 오픈되기 전에 이전의 Connection은 명시적으로 close하기를 권장한다. XAConnection 인스턴스의 close 메소드가 호출되면 데이터베이스로의 물리적인 연결이 끊어진다.

XAResource Interface#

TM은 모든 트랜잭션 브랜치들을 조정하기 위해서 AltibaseXAResource 인스턴스를 사용한다.

Altibse.jdbc.driver.AltibaseXAResource 타입의 인스턴스는 javax.transaction.xa.XAResource 인터페이스를 구현하는 클래스의 인스턴스이다.

[그림 3‑6] AltibaseXAResource 클래스

Altibase JDBC driver 는 ABPooledConnection 클래스의 getXAResource 메소드를 호출할 때마다 AltibaseXAResource 인스턴스를 생성하여 반환하고, Altibase JDBC driver AltibaseXAResource 인스턴스와 connection 인스턴스를 연결시킨다. 트랜잭션 브랜치는 이 connection으로 동작한다.

AltibaseXAResource 클래스는 분산된 트랜잭션의 트랜잭션 브랜치를 조정하기 위해 몇 개의 메소드를 갖고 있다.

TM은 애플리케이션 서버와 같은 중간층의 컴포넌트로부터 AltibaseXAResource 인스턴스를 받으며, 아래의 메소드를 갖고 있다.

void start(Xid xid, int flags)
void end(Xid xid, int flags)
int prepare(Xid xid)
void commit(Xid xid, boolean onePhase)
void rollback(Xid xid)
public void forget(Xid xid)
public Xid[] recover(int flag)

자세한 내용은 java API Spec.의 javax.transaction.xa.XAResource를 참조하기 바란다.

Xid interface#

TM은 트랜잭션 ID 인스턴스를 생성하고, 분산 트랜잭션의 브랜치를 관리하는데 이를 사용한다. 각각의 트랜잭션 브랜치는 유일한 트랜잭션 ID를 부여받으며, 다음의 정보를 포함한다.

Format identifier

Global transaction identifier

Branch qualifier

Altibase는 javax.transaction.xa.Xid 인터페이스를 구현한 클래스가 Altibase.jdbc.driver 패키지에 XID 클래스로 존재한다.

Note: AltibaseXAResource 호출에는 Altibase.jdbc.driver.AltibaseXID를 반드시 사용할 필요는 없다. 여기에는 javax.transaction.xa.Xid 인터페이스를 구현한 어떤 클래스도 사용될 수 있다.

에러 처리#

XA 관련 메소드는 에러가 발생할 때, ABXAException을 throw한다. ABXAException 클래스는 javax.transaction.xa.XAException 클래스의 하위 클래스이다.

애플리케이션 서버에서 XA설정#

WebLogic에서 XA 설정#

  1. 웹로직 콘솔에서 Services -> JDBC -> Connection Pools에서 Configure a new JDBC Connection Pool을 선택한 후 JDBC 연결정보를 입력한다.

    구분 NON-XA XA
    URL jdbc:Altibase://[ip]:[port]/dbname jdbc:Altibase://[ip]:[port]/dbname
    Driver Classname Altibase.jdbc.driver.AltibaseDriver Altibase.jdbc.driver.AltibaseXADataSource
    Properties User=[username] User=[username]

    [표 3‑4] NON-XA와 XA의 연결정보 비교

    [그림 3‑7] JDBC 연결정보 입력

  2. 생성된 Connection Pool을 이용해서 DataSource를 만든다.
    Services->JDBC->Data Sources에서 Configure a new JDBC Data Source를 선택한다.
    Name과 JNDI Name을 입력하고 "Honor Global Transactions"에 체크한다.
    다음 페이지에서 PoolName에 앞서 만든 Pool의 이름을 입력한다. (weblogic 8.1) ([그림 6-7] 데이터 소스 생성 참조)

    Note: weblogic8.1 이전 버전에서는 Services->JDBC->XA Data Sources에서 새로운 DataSource를 생성한다.

    [그림 3‑8] 데이터 소스 생성

Weblogic 애플리케이션 예제#

// step 1. JNDI Lookup and get UserTransaction Object
Context ctx = null;
Hashtable env = new Hashtable();

// Parameter for weblogic
env.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
env.put(Context.PROVIDER_URL,"t3://localhost:7001");
env.put(Context.SECURITY_PRINCIPAL,"weblogic");
env.put(Context.SECURITY_CREDENTIALS,"weblogic");

ctx = new InitialContext(env);
System.out.println("Context Created :"+ctx);

// step 2. get User Transaction Object
UserTransaction tx = (UserTransaction)ctx.lookup("javax.transaction.UserTransaction");

// step 3 start Transaction
System.out.println("Start Transaction :"+tx);
tx.begin();

try{
// step 4. doing query
// step 4-1. get Datasource
DataSource xads1 = (DataSource)ctx.lookup("altiTXDS");

JEUS에서 XA 설정#

제우스에서 JDBC 데이터 소스를 생성하기 위한 기본 설정을 한다.

  1. JEUS 매니저 리소스->JDBC에서 새 JDBC 데이터 소스 생성을 선택한다.
  2. 기본 설정 창이 나타나면 다음의 정보를 입력한다.
    • DBMS : Other
    • 가능한 데이터 소스들 : Other DataSource
    • Data Source Class Name: Altibase.jdbc.driver.AltibaseXADataSource
    • Data Source Type : XADataSource
  3. Database Name, Port Number, Server Name, User, Password에는 해당하는 값을 입력한다.


    [그림 3‑9] 제우스에서 데이터 소스 설정하기

JEUS 애플리케이션 예제#

// step 1. JNDI Lookup and get UserTransaction Object
Context ctx = null;
Hashtable env = new Hashtable();

// Parameter for weblogic
env.put(Context.INITIAL_CONTEXT_FACTORY, "jeus.jndi.JNSContextFactory");
env.put(Context.URL_PKG_PREFIXES, "jeus.jndi.jns.url");    
env.put(Context.PROVIDER_URL, "127.0.0.1");
env.put(Context.SECURITY_PRINCIPAL,"jeus");
env.put(Context.SECURITY_CREDENTIALS,"jeus");

ctx = new InitialContext(env);
System.out.println("Context Created :"+ctx);

// step 2. get User Transaction Object
UserTransaction tx = (UserTransaction)ctx.lookup("java:comp/UserTransaction");

// step 3 start Transaction
System.out.println("Start Transaction :"+tx);
tx.begin();

try{
// step 4. doing query
// step 4-1. get Datasource
DataSource xads1 = (DataSource)ctx.lookup("altiTXDS");

예제#

Altibase XA 기능을 사용해서 분산 트랜잭션을 어떻게 구현하는지 예제를 통해 살펴본다.

이 예제는 아래의 순서대로 실행한다.

  1. Start transaction branch #1.

  2. Start transaction branch #2.

  3. Execute DML operations on branch #1.

  4. Execute DML operations on branch #2.

  5. End transaction branch #1.

  6. End transaction branch #2.

  7. Prepare branch #1.

  8. Prepare branch #2.

  9. Commit branch #1.

  10. Commit branch #2.

import java.sql.*;
import javax.sql.*;
import Altibase.jdbc.driver.*;
import javax.transaction.xa.*;

class XA4
{
  public static void main (String args [])
       throws SQLException
  {

    try
    {
        String URL1 = "jdbc:Altibase://localhost:25226/mydb";
        // You can put a database name after the @ sign in the connection URL.
        String URL2 = "jdbc:Altibase://localhost:25226/mydb";
        // Create first DataSource and get connection
        Altibase.jdbc.driver.DataSource ads1 = new Altibase.jdbc.driver.DataSource();
        ads1.setUrl(URL1);
        ads1.setUser("SYS");
        ads1.setPassword("MANAGER");
        Connection conna = ads1.getConnection();

        // Create second DataSource and get connection
        Altibase.jdbc.driver.DataSource ads2 = new Altibase.jdbc.driver.DataSource();
        ads2.setUrl(URL2);
        ads2.setUser("SYS");
        ads2.setPassword("MANAGER");
        Connection connb = ads2.getConnection();

        // Prepare a statement to create the table
        Statement stmta = conna.createStatement ();

        // Prepare a statement to create the table
        Statement stmtb = connb.createStatement ();

        try
        {
          // Drop the test table
          stmta.execute ("drop table my_table");
        }
        catch (SQLException e)
        {
          // Ignore an error here
        }

        try
        {
          // Create a test table
          stmta.execute ("create table my_table (col1 int)");
        }
        catch (SQLException e)
        {
          // Ignore an error here too
        }

        try
        {
          // Drop the test table
          stmtb.execute ("drop table my_tab");
        }
        catch (SQLException e)
        {
          // Ignore an error here
        }

        try
        {
          // Create a test table
          stmtb.execute ("create table my_tab (col1 char(30))");
        }
        catch (SQLException e)
        {
          // Ignore an error here too
        }

        // Create XADataSource instances and set properties.
        AltibaseXADataSource axds1 = new AltibaseXADataSource();
        axds1.setUrl("jdbc:Altibase://localhost:25226/mydb");
        axds1.setUser("SYS");
        axds1.setPassword("MANAGER");

        AltibaseXADataSource axds2 = new AltibaseXADataSource();

        axds2.setUrl("jdbc:Altibase://localhost:25226/mydb");
        axds2.setUser("SYS");
        axds2.setPassword("MANAGER");

        // Get XA connections to the underlying data sources
        XAConnection pc1  = axds1.getXAConnection();
        XAConnection pc2  = axds2.getXAConnection();

        // Get the physical connections
        Connection conn1 = pc1.getConnection();
        Connection conn2 = pc2.getConnection();

        // Get the XA resources
        XAResource axar1 = pc1.getXAResource();
        XAResource axar2 = pc2.getXAResource();

        // Create the Xids With the Same Global Ids
        Xid xid1 = createXid(1);
        Xid xid2 = createXid(2);

        // Start the Resources
        axar1.start (xid1, XAResource.TMNOFLAGS);
        axar2.start (xid2, XAResource.TMNOFLAGS);

        // Execute SQL operations with conn1 and conn2
        doSomeWork1 (conn1);
        doSomeWork2 (conn2);

        // END both the branches -- IMPORTANT
        axar1.end(xid1, XAResource.TMSUCCESS);
        axar2.end(xid2, XAResource.TMSUCCESS);

        // Prepare the RMs
        int prp1 =  axar1.prepare (xid1);
        int prp2 =  axar2.prepare (xid2);

        System.out.println("Return value of prepare 1 is " + prp1);
        System.out.println("Return value of prepare 2 is " + prp2);

        boolean do_commit = true;

        if (!((prp1 == XAResource.XA_OK) || (prp1 == XAResource.XA_RDONLY)))
           do_commit = false;

        if (!((prp2 == XAResource.XA_OK) || (prp2 == XAResource.XA_RDONLY)))
           do_commit = false;

        System.out.println("do_commit is " + do_commit);
        System.out.println("Is axar1 same as axar2 ? " + axar1.isSameRM(axar2));

        if (prp1 == XAResource.XA_OK)
          if (do_commit)
             axar1.commit (xid1, false);
          else
             axar1.rollback (xid1);

        if (prp2 == XAResource.XA_OK)
          if (do_commit)
             axar2.commit (xid2, false);
          else
             axar2.rollback (xid2);

         // Close connections
        conn1.close();
        conn1 = null;
        conn2.close();
        conn2 = null;

        pc1.close();
        pc1 = null;
        pc2.close();
        pc2 = null;

        ResultSet rset = stmta.executeQuery ("select col1 from my_table");
        while (rset.next())
          System.out.println("Col1 is " + rset.getInt(1));

        rset.close();
        rset = null;

        rset = stmtb.executeQuery ("select col1 from my_tab");
        while (rset.next())
          System.out.println("Col1 is " + rset.getString(1));

        rset.close();
        rset = null;

        stmta.close();
        stmta = null;
        stmtb.close();
        stmtb = null;

        conna.close();
        conna = null;
        connb.close();
        connb = null;

    } catch (SQLException sqe)
    {
      sqe.printStackTrace();
    } catch (XAException xae)
    {
      System.out.println("XA Error is " + xae.getMessage());
    }
  }

  static Xid createXid(int bids)
    throws XAException
  {
      byte[] gid = new byte[1]; gid[0] = (byte)9;
      byte[] bid = new byte[1]; bid[0] = (byte)bids;
      byte[] gtrid = new byte[4];
      byte[] bqual = new byte[4];

      System.arraycopy(gid,0,gtrid,0,1);
      System.arraycopy(bid,0,bqual,0,1);
      Xid xid = new XID(0x1234,gtrid,bqual);
      return xid;
  }

  private static void doSomeWork1 (Connection conn)
   throws SQLException
  {
      String sql ;
      Statement stmt = conn.createStatement();
      sql = "insert into my_table values(1)";
      stmt.executeUpdate(sql);
      stmt.close();
  }

  private static void doSomeWork2 (Connection conn)
   throws SQLException
  {
      String sql ;
      Statement stmt = conn.createStatement();
      sql = "insert into my_tab values('test')";
      stmt.executeUpdate(sql);
      stmt.close();
  }
}


XA를 사용한 애플리케이션의 문제 해결#

이 절에서는 문제가 발생할 경우 어떻게 원인을 찾는지를 설명한다.

XA 추적 정보 확인#

Altibase XA 라이브러리는 에러와 추적 정보들을 trace 파일에 기록한다. 해당 파일을 열면, 에러 코드 및 메시지 등의 정보를 확인할 수 있다.

예를 들어 xa_open이 실패할 경우 추적 정보를 보고 open string이 틀렸는지, Altibase 서버를 찾지 못한건지, 또는 로그온을 실패했는지 원인을 알 수 있다.

추적 파일 이름 및 위치#

altibase_xa_[XA_NAME][YYYYMMDD].log
  • XA_NAME : TM 환경 설정시 open string에 XA_NAME=value로 명시한 값이다. 만약 open string 에 XA_NAME=value 을 명시하지 않았다면 NULL 로 치환된다.
  • YYYYMMDD : trace 파일에 저장하는 날짜(YYYYMMDD)

ALTIBASE_HOME 환경변수가 설정되어 있을 경우에는 $ALTIBASE_HOME/trc에 생성되고, 설정되지 않았다면 현재 디렉터리에 생성된다.

예제#

104744.19381.1:
ulxXaOpen : XAER_RMERR : [ERR-4102E] Invalid password

'104744'는 로깅한 시간(HHMISS)이고, '19381'은 Process ID(PID) 이며, '1'은 Resource Manager ID 이다.

ulxXaOpen은 모듈 이름, XAER_RMERR은 XA spec.의 에러, [ERR-4102E]는 Altibase 서버로부터 반환된 에러 코드, 'Invalid password'는 Altibase 서버로부터 반환된 에러 메시지이다.

In-doubt 트랜잭션 처리#

TM은 in-doubt 또는 pending 트랜잭션이 발생할 경우 일반적으로 문제 상황을 인식하고, in-doubt 트랜잭션을 자동으로 복구하는 기능을 제공한다. 그러나 해당 RM은 복구되어 자동으로 커밋 명령을 받을 때 까지 prepare 된 자원에 대해 lock을 걸은 상태로 기다린다.

하지만 다른 트랜잭션이 in-doubt 트랜잭션이 lock을 건 데이터를 요구하거나, 적정 시간 동안 문제가 해결되지 않을 때에는 DBA가 임의로 트랜잭션을 처리할 수 있어야 한다.

Altibase는 in-doubt 트랜잭션을 처리하기 위해 in-doubt 트랜잭션의 상태를 검색할 수 있는 V$DBA_2PC_PENDING 성능 뷰를 제공한다. 이에 대한 자세한 설명은 General Reference를 참조하기 바란다.

DBA는 임의로 트랜잭션을 처리하기 위해 다음과 같이 강제로 커밋 또는 롤백을 할 수 있다.

COMMIT FORCE 'global_tx_id';
ROLLBACK FORCE 'global_tx_id';

예제#

in-doubt 트랜잭션을 확인하여, 임의로 특정 트랜잭션을 커밋한다.

iSQL> select * From v$dba_2pc_pending; 
LOCAL_TRAN_ID         GLOBAL_TX_ID                    
------------------------------------------------------
9280                  69.FAEDFAED.00000001        
21315                 69.FAEDFAED.00000002    
2 rows selected. 
iSQL> commit force '69.FAEDFAED.00000002'; 
Commit force success.

휴리스틱 트랜잭션 확인#

휴리스틱(heuristic) 트랜잭션이 발생한 경우 이에 대한 정보를 확인할 수 있다. 휴리스틱 트랜잭션이란 in-doubt 트랜잭션이 어떤 원인으로 커밋 등의 다음 명령을 받지 못한 경우 RM 스스로 커밋 또는 롤백을 수행한 것을 의미한다.

in-doubt 트랜잭션에 대해 Commit force를 수행하면, 이 트랜잭션은 휴리스틱하게 커밋된 상태로 변경된다. 그리고 이 정보는 SYS_XA_HEURISTIC_TRANS_ 메타 테이블에 입력된다.

만약 이 정보를 삭제하려면, xa_recover 후 xa_forget을 호출한다.

예제#

in-doubt 트랜잭션이 DBA에 의해 커밋된 경우, 정보는 변경되고 SYS_XA_HEURISTIC_TRANS_ 메타 테이블에 저장된다.

iSQL> select * From v$dba_2pc_pending; 
V$DBA_2PC_PENDING.LOCAL_TRAN_ID 
V$DBA_2PC_PENDING.GLOBAL_TX_ID         
------------------------------------------ 
100421               
69.FAEDFAED.00000001  
1 row selected. 

iSQL> commit force '69.FAEDFAED.00000001'; 
Commit force success. 

iSQL> select * from system_.sys_xa_heuristic_trans_; 
SYS_XA_HEURISTIC_TRANS_.FORMAT_ID 
SYS_XA_HEURISTIC_TRANS_.GLOBAL_TX_ID      
SYS_XA_HEURISTIC_TRANS_.BRANCH_QUALIFIER   
SYS_XA_HEURISTIC_TRANS_.STATUS 
SYS_XA_HEURISTIC_TRANS_.OCCUR_TIME 
--------------------------------------
69                   
FAEDFAED    
00000001    
1     
2008/08/29 10:09:53  
1 row selected.