이 블로그에 담긴 내용은 순전히 개인적인 견해이며, 특정 제품이나 회사의 공식 입장을 대변하지 않습니다.
지원 환경
데이터베이스 | 버전 | 비고 |
---|---|---|
Oracle | 23ai | 23.6 |
들어가며
미션 크리티컬 애플리케이션은 복잡한 OLTP(Online Transaction Processing) 작업을 처리해야 할 때가 많습니다. 여기서 복잡한 작업이란 데이터베이스 호출과 애플리케이션 로직이 얽혀 있는 상황을 말합니다. 여러 데이터베이스 호출 작업이 하나의 트랜잭션으로 묶여 처리되어야 하고, 이 모든 작업은 짧은 시간 안에 완료되어야 합니다. 동시에 높은 성능과 확장성을 유지하는 것이 필수적입니다. 이는 이러한 애플리케이션의 핵심적인 요구사항이기도 합니다.
오늘날 비즈니스 요구사항이 점점 더 복잡하고 다양해지면서, 애플리케이션 아키텍처도 끊임없이 진화하고 있습니다. 성능, 안정성, 확장성이라는 주요 요구를 충족하기 위해 다양한 설계 패턴과 기술 조합이 필요합니다.
특히 마이크로서비스(분산) 아키텍처를 도입하면 데이터 처리가 분산됨에 따라 트랜잭션 관리가 더욱 어려워질 수 있습니다. 마이크로서비스는 확장성 측면에서는 장점을 제공하지만, 성능 문제와 트랜잭션 관리의 복잡성을 동반합니다. 분산된 서비스 간의 통신은 데이터 처리 지연을 초래할 수 있으며, 트랜잭션 관리가 불안정하면 시스템 전체의 안정성이 저하될 위험이 있습니다.
대체로 기업들은 이러한 문제를 해결하기 위해 두 가지 접근 방식을 택합니다
- 처음부터 클라우드 네이티브 방식으로 설계 및 구현 : 분산 환경에 적합한 트랜잭션 설계와 보상 프로세스를 포함한 솔루션을 구현합니다.
- 레거시 시스템의 단계적 전환 : 파일럿 서비스를 통해 새로운 방식을 검증한 후, 이를 전체 시스템으로 확산합니다. 이 과정에서 레거시 서비스와의 데이터 연계 및 중복 관리가 중요합니다.
이번 글에서는 레거시 시스템에서 모던 애플리케이션으로 전환하는 과정에서 활용할 수 있는 효율적인 트랜잭션 처리 방식을 소개합니다. 이를 통해 기업이 직면한 데이터 처리와 안정성 문제를 해결하는 방법을 알아보겠습니다.
일반적인 트랜잭션의 특징
애플리케이션에서 데이터베이스 호출과 로직 실행이 결합된 트랜잭션을 상호작용 트랜잭션(interactive transaction)이라고 합니다. 이러한 트랜잭션에서는 데이터베이스 호출이 애플리케이션 로직 사이에서 실행되며, 로직이 정상적으로 완료되든 중단되든 데이터베이스 작업은 애플리케이션의 로직에 영향을 받습니다.
비즈니스 예시: 계좌이체
계좌이체 작업은 상호작용 트랜잭션의 전형적인 예입니다. 아래는 계좌이체 작업의 일반적인 트랜잭션 단계를 보여줍니다
- 세션 생성
- 데이터베이스와 연결을 설정합니다.
- 트랜잭션 처리
- 다음 작업들이 순차적으로 수행됩니다:
- 작업 내역을 기록하기 위해 감사 테이블을 업데이트합니다.
- 소스 계좌의 데이터를 잠금(Lock) 처리합니다.
- 소스 계좌와 타겟 계좌의 잔액 및 기타 정보를 조회(Select)합니다.
- 소스 계좌와 타겟 계좌에 대해 비즈니스 검증 로직을 실행합니다.
- 소스 계좌와 타겟 계좌의 잔액을 업데이트(Update)합니다.
- 마지막으로 작업을 커밋(Commit)하여 완료합니다.
- 다음 작업들이 순차적으로 수행됩니다:
- 세션 종료
- 데이터베이스 연결을 종료합니다.
트랜잭션의 특징과 한계
- 트랜잭션 단위 처리: 소스 계좌와 타겟 계좌의 작업이 하나의 트랜잭션으로 묶여 동시에 처리됩니다. 이 과정에서 애플리케이션 레이어가 트랜잭션 매니저의 역할을 수행합니다.
- 연결 유지 요구 : 트랜잭션이 완료될 때까지 애플리케이션은 데이터베이스와 연결 상태를 유지해야 합니다. Connection Pool을 사용하는 경우, 연결이 점유된 상태가 되어 다른 트랜잭션에서 이를 사용할 수 없습니다.
- 리소스 비효율성: 대부분의 시간이 데이터베이스 작업이 아닌 비즈니스 검증 로직 처리에 소요됩니다. 이로 인해 연결 리소스가 유휴 상태로 남아 리소스 활용도가 낮아집니다.
이러한 트랜잭션 처리 방식은 단일 시스템에서는 적합하지만, 분산된 환경에서는 심각한 리소스 낭비와 성능 저하를 초래할 수 있습니다.
분산 환경에서의 문제점
마이크로서비스 아키텍처와 같은 분산 환경에서는 전통적인 트랜잭션 관리 방식이 여러 한계를 드러냅니다. 서비스 간 통신과 데이터 처리의 복잡성이 증가하면서 성능과 리소스 효율성이 저하될 수 있습니다. 이러한 환경에서 주로 나타나는 문제는 다음과 같습니다.
- 리소스 증가 문제
- Connection Pool 활용 비효율 : 각 서비스가 Connection Pool을 사용하지만, 연결 객체(Connection)의 활용도가 낮아지면 Pool 크기를 크게 설정해야 할 수 있습니다.
- 세션 관리 부담 증가 : 서비스 수가 늘어날수록 데이터베이스 세션(Session) 수가 증가합니다. 오라클 데이터베이스의 경우, 각 세션은 약 14MB의 PGA 메모리를 소모하므로 리소스 부담이 커집니다.
- 트랜잭션 정합성 관리의 어려움
- 트랜잭션 로직 분리 : 하나의 트랜잭션 로직이 두 개 이상의 서비스로 분리되면, 정합성을 유지하기 위해 새로운 관리 방식이 필요합니다.
- 정합성 요구 사항 충족 : 데이터 정합성이 중요한 업무에서는 다음과 같은 관리 기법이 필요할 수 있습니다
- XA 프로토콜: 분산 트랜잭션 관리를 위한 표준 프로토콜.
- LRA(Long Running Activities) 또는 TCC(Try-Confirm-Cancel): 보상 로직을 처리하기 위한 대안적 방식.
- 성능 저하 및 시스템 안정성 문제
- 서비스 간 통신 지연 : 분산 환경에서는 네트워크 통신이 필수적이며, 이로 인해 데이터 처리 지연이 발생할 가능성이 높습니다.
- 트랜잭션 불안정성: 트랜잭션 관리가 제대로 이루어지지 않으면 시스템 전반의 안정성이 저하될 위험이 있습니다.
이러한 문제들을 해결하기 위해 효율적이고 안정적인 트랜잭션 관리 방식이 필요합니다.
차세대 솔루션: 세션리스 트랜잭션
분산 환경에서의 문제를 해결하기 위해서는 기존의 트랜잭션 관리 방식을 넘어선 새로운 접근 방식이 필요합니다. 세션리스 트랜잭션(Sessionless Transaction)은 이러한 문제를 해결하기 위해 설계된 차세대 솔루션입니다.
세션리스 트랜잭션의 개념
세션리스 트랜잭션은 트랜잭션 상태를 데이터베이스에서 직접 관리하여 애플리케이션과의 연결 상태에 의존하지 않는 방식입니다. 이를 통해 애플리케이션이 Connection을 장기간 유지할 필요가 없어지고, 분산 환경에서도 안정적이고 효율적인 트랜잭션 관리를 제공합니다.
동작 방식
- 트랜잭션 시작: 트랜잭션이 데이터베이스에서 시작되고, 고유 식별자(GTRID)를 사용하여 상태를 관리합니다.
- 트랜잭션 중단(Suspend): 애플리케이션에서 작업을 중단해도 트랜잭션은 데이터베이스 내에 유지됩니다.
- 트랜잭션 재개(Resume): 중단된 트랜잭션은 다른 세션에서 재개할 수 있습니다.
- 트랜잭션 종료: 트랜잭션이 완료되면 데이터베이스에서 커밋 또는 롤백을 수행합니다.
세션리스 트랜잭션의 장점
세션리스 트랜잭션은 다음과 같은 여러 가지 이점을 제공합니다
- 트랜잭션 유지 및 재개: 트랜잭션 상태를 데이터베이스에서 관리하므로, 중단된 작업을 다른 세션에서 이어받아 처리할 수 있습니다. 이를 통해 pod 장애와 같은 서비스 중단 상황에서도 트랜잭션 상태를 안정적으로 유지할 수 있습니다.
- 데이터 무결성 보장: 트랜잭션 중인 데이터는 데이터베이스 내에서 잠금(lock) 상태로 유지되므로, 애플리케이션 수준에서 추가적인 검증 작업이 필요하지 않습니다.
- Connection 리소스 효율성: 데이터베이스 호출 간에 Connection을 해체하여 Connection Pool의 활용도를 극대화할 수 있습니다.
- RAC(Real Application Clusters) 환경 지원: Oracle RAC 환경에서는 여러 인스턴스에 걸쳐 단일 트랜잭션을 처리할 수 있어 확장성과 안정성을 동시에 제공합니다.
세션리스 트랜잭션과 XA 프로토콜의 차이점
세션리스 트랜잭션은 기존의 XA 프로토콜과 유사한 점이 있지만, 다음과 같은 차별점을 가지고 있습니다
- 트랜잭션 매니저 불필요: 트랜잭션 매니저가 branch를 관리할 필요가 없으며, 네트워크 통신도 간소화됩니다.
- 2단계 커밋 생략: 복잡한 2단계 커밋(2PC) 과정을 생략하고, 데이터베이스에서 트랜잭션을 직접 관리합니다.
- 복구 메커니즘 단순화: 트랜잭션 커밋이 데이터베이스 내에서 이루어지므로, in-doubt 트랜잭션이 발생하지 않습니다.
- 성능 향상: 간소화된 동작 방식 덕분에 트랜잭션 처리 속도가 크게 개선됩니다.
활용 시 고려사항
세션리스 트랜잭션은 매우 강력한 방식이지만, 다음과 같은 제한 사항도 있습니다
- 단일 데이터베이스 또는 RAC 환경에서만 사용 가능: 물리적으로 분리된 여러 데이터베이스 간의 트랜잭션 관리에는 사용할 수 없습니다. 이 경우에는 여전히 트랜잭션 매니저나 XA 프로토콜과 같은 추가 도구가 필요합니다.
- 특정 환경 의존성: 세션리스 트랜잭션은 오라클 데이터베이스 환경에 최적화되어 있습니다. 다른 데이터베이스를 사용하는 경우 대체 솔루션을 고려해야 할 수 있습니다.
세션리스 트랜잭션은 현대적인 분산 환경에서 안정성과 성능을 동시에 달성할 수 있는 강력한 솔루션입니다.
비즈니스 예시: 계좌이체(세션리스 트랜잭션 사용)
세션리스 트랜잭션을 활용하여 계좌이체 작업을 처리하는 과정을 살펴보겠습니다. 이 방식에서는 트랜잭션을 중단(suspend)하고, 다른 세션에서 재개(resume)할 수 있어 pod 장애나 연결 단절과 같은 상황에서도 작업을 안정적으로 처리할 수 있습니다.
- 세션 #1 생성
- 데이터베이스와 연결을 설정합니다.
- 세션 #1에서 초기 작업 수행
- 다음 작업들을 처리합니다:
- 감사 테이블 업데이트 (Update)
- 소스 계좌에서 타겟 계좌의 데이터 잠금 (Lock)
- 소스 계좌와 타겟 계좌의 잔액 및 기타 정보 조회 (Select)
- 다음 작업들을 처리합니다:
- 세션 #1 중단
- 트랜잭션을 중단(suspend)하고 세션을 종료합니다.
- 세션 #2 생성
- 새로운 세션에서 연결을 생성합니다.
- 세션 #2에서 트랜잭션 재개
- 중단된 트랜잭션을 재개(resume)하고 다음 작업을 처리합니다:
- 소스 계좌와 타겟 계좌의 잔액 업데이트 (Update)
- 커밋(Commit)을 수행하여 작업을 완료합니다.
- 중단된 트랜잭션을 재개(resume)하고 다음 작업을 처리합니다:
- 세션 #2 종료
- 연결을 종료합니다.
세션리스 트랜잭션 예제
세션리스 트랜잭션은 일반적인 트랜잭션과 달리 트랜잭션의 시작(Start), 중단(Suspend), 재개(Resume)를 명시적으로 선언해야 합니다. 이러한 방식을 통해 애플리케이션이 stateless 환경에서도 안정적으로 트랜잭션을 관리할 수 있습니다. 아래에서는 오라클 데이터베이스 환경에서 세션리스 트랜잭션을 구현하는 두 가지 방법(PL/SQL과 Java)을 예제로 설명합니다.
PL/SQL 코드
아래는 PL/SQL을 사용하여 세션리스 트랜잭션을 구현하는 예제입니다. 이 코드는 트랜잭션의 시작, 중단, 재개 및 종료 단계를 보여줍니다.
-- 테스트 테이블 생성
DROP TABLE IF EXISTS mytab1;
CREATE TABLE mytab1(c1 NUMBER, c2 NUMBER);
-- 세션리스 트랜잭션을 시작을 선언
DECLARE
gtrid VARCHAR2(128);
BEGIN
gtrid := DBMS_TRANSACTION.START_TRANSACTION
( UTL_RAW.CAST_TO_RAW('user_specified_gtrid')
, DBMS_TRANSACTION.TRANSACTION_TYPE_SESSIONLESS
, 20
, DBMS_TRANSACTION.TRANSACTION_NEW
);
END;
/
-- 세션리스 트랜잭션 상태에서 데이터 입력
INSERT INTO mytab1(c1, c2) values (1, 1);
-- 세션리스 트랜잭션 상태에서 데이터 조회
SELECT * FROM mytab1;
-- 결과 확인
C1 C2
---------- ----------
1 1
-- 세션리스 트랜잭션을 잠시 중지시킴
EXEC DBMS_TRANSACTION.SUSPEND_TRANSACTION;
-- 세션리스 트랜잭션이 중지된 상태에서는 데이터가 보이지 않음
SELECT * FROM mytab1;
-- 결과 확인
no rows selected
-- 세션리스 트랜잭션을 재개함.
DECLARE
gtrid VARCHAR2(128);
BEGIN
gtrid := DBMS_TRANSACTION.START_TRANSACTION
( UTL_RAW.CAST_TO_RAW('user_specified_gtrid')
, DBMS_TRANSACTION.TRANSACTION_TYPE_SESSIONLESS
, 20
, DBMS_TRANSACTION.TRANSACTION_RESUME
);
END;
/
-- 커밋수행하여 세션리스 트랜잭션을 종료
COMMIT;
JAVA코드
Java 환경에서는 Oracle JDBC를 사용하여 세션리스 트랜잭션을 구현할 수 있습니다. 아래 코드는 세션리스 트랜잭션을 시작, 중단, 재개 및 종료하는 과정을 보여줍니다.
import oracle.jdbc.*;
// 데이터 소스
PoolDataSource ds;
// GTRID 정보
byte[] gtrid;
// 세션 생성
Connection conn = ds.getConnection();
conn.setAutoCommit(false);
// 세션리스 트랜잭션 시작을 선언
gtrid = ((OracleConnection)conn).startTransaction();
// 데이터 변경작업 수행
....
// 세션리스 트랜잭션 잠시 중지
((OracleConnection)conn).suspend();
// 세션 종료
conn.close();
// 세션 생성
Connection conn2 = ds.getConnection();
conn2.setAutoCommit(false);
// gtrid를 이용하여 세션리스 트랜잭션을 재개
((OracleConnection)conn2).resumeTransaction(gtrid);
// 데이터 변경작업
..
// 커밋 수행
conn2.commit();
// 세션 종료
conn2.close();
마무리
지금까지 세션리스 트랜잭션의 개념과 필요성, 그리고 구현 방법에 대해 살펴보았습니다. 전통적인 트랜잭션 관리 방식이 분산 환경에서 가지는 한계를 극복하기 위해 설계된 세션리스 트랜잭션은 현대 애플리케이션 아키텍처에서 중요한 역할을 하고 있습니다.
상호작용 트랜잭션은 단일 시스템에서는 유효하지만, 분산 환경에서는 리소스와 성능 문제를 초래할 수 있습니다. 반면, 세션리스 트랜잭션은 stateless 서비스 설계와 호환되는 트랜잭션 관리 방식을 제공하며, pod 장애 시에도 트랜잭션 상태를 유지하여 서비스 안정성을 크게 높일 수 있습니다.
이 방식은 복잡한 XA 프로토콜을 대체하며, 성능 개선과 함께 트랜잭션 관리의 복잡성을 단순화합니다. 다만, 세션리스 트랜잭션은 단일 데이터베이스 또는 RAC 환경에서만 활용 가능하므로, 분산 데이터베이스 환경에서는 추가적인 설계와 관리 방식이 필요합니다.
세션리스 트랜잭션은 현대화된 애플리케이션에서 안정성과 성능을 동시에 달성할 수 있는 강력한 솔루션으로, 앞으로 다양한 비즈니스 환경에서 중요한 역할을 할 것으로 기대됩니다.
참고자료
- 세션리스 트랜잭션 이해 및 구현 방법 https://docs.oracle.com/en/database/oracle/oracle-database/23/adfns/developing-applications-sessionless-transactions.html
- DBMS_TRANSACTION 패키지 : https://docs.oracle.com/en/database/oracle/oracle-database/23/arpls/DBMS_TRANSACTION.html
- JDBC환경에서 세션리스 트랜잭션 처리방법 : https://docs.oracle.com/en/database/oracle/oracle-database/23/jjdbc/sessionless-transactions.html
댓글남기기