1. 분산 트랜잭션의 이해
- 분산 트랜잭션은 2개 그 이상의 네트워크 상의 시스템 간의 트랜잭션입니다.
- 일반적으로 시스템은 트랜잭션 리소스의 역할을 하고, 트랜잭션 매니저는 이러한 리소스에 관련된 모든 동작에 대해 트랜잭션의 생성 및 관리를 담당합니다.
트랜잭션 리소스(Transaction Resource)
- 데이터베이스, 메시지 큐 등 실제 데이터를 저장하고 처리하는 시스템
- 로컬에서 커밋이나 롤백 등의 트랜잭션 연산을 수행할 수 있는 능력 보유
- 예: MySQL 데이터베이스, RabbitMQ 메시지 브로커, MongoDB 등
트랜잭션 매니저(Transaction Manager)
- 여러 트랜잭션 리소스들을 조율하여 하나의 논리적 트랜잭션으로 관리
- 전체 트랜잭션의 시작, 커밋, 롤백을 결정하고 실행
- 각 리소스의 상태를 모니터링하고 문제 발생 시 복구 처리
- 예: Java의 JTA 트랜잭션 매니저, 분산 트랜잭션 코디네이터
1.1 마이크로서비스 아키텍처와 분산 트랜잭션
- 마이크로서비스 아키텍처(MSA)는 대표적인 분산 트랜잭션 환경입니다.
- MSA의 주요 특징 중 하나인 'Database per Service' 패턴으로 인해 다음과 같은 장점을 제공합니다:
- 각 서비스는 독립적인 데이터베이스에서 자신의 데이터를 관리
- 서비스별로 최적화된 데이터베이스 선택 가능 (예: 주문 이력은 MongoDB, 결제는 MySQL)
- 다른 서비스의 장애로부터 데이터 보호
- 서비스 간 낮은 결합도 유지
- 하지만 이러한 데이터베이스 분리는 비즈니스 트랜잭션 처리를 복잡하게 만듭니다.
2. 분산 트랜잭션의 어려움
2.1 ACID 속성 유지의 어려움
- 마이크로서비스 환경에서 트랜잭션은 두 가지 관점에서 볼 수 있습니다:
로컬 트랜잭션 (각 서비스 내부)
- 각 서비스는 자신의 데이터베이스에 대해 ACID 특성을 보장할 수 있음
- 예: 주문 서비스 내에서 주문 테이블에 데이터를 추가하는 트랜잭션은 ACID 보장
비즈니스 트랜잭션 (여러 서비스에 걸친 전체 흐름)
- 여러 서비스의 로컬 트랜잭션들이 모여 하나의 논리적인 트랜잭션을 구성
- 각각의 로컬 트랜잭션은 ACID를 보장하지만, 전체 비즈니스 트랜잭션은 ACID 보장이 어려움
- 예: 주문 생성 → 결제 처리 → 재고 감소 → 배송 정보 생성 과정에서 중간에 실패하면 일부 데이터만 반영되는 문제 발생
2.2 실제 사례로 보는 문제점
상품 주문 프로세스를 예로 들어보겠습니다:
- 여러 서비스가 관여하며 서비스 별로 Database가 분리되어 있습니다
- 주문 서비스: 주문 정보 생성
- 재고 서비스: 상품 재고 감소
- 결제 서비스: 결제 처리
- 배송 서비스: 배송 정보 생성
- 이 모든 작업은 하나의 '주문' 프로세스 에서 전부 성공하거나 전부 실패해야 합니다
- 결제가 실패하면 재고도 원복되어야 함
- 배송 생성이 실패하면 결제도 취소되어야 함
- 어느 한 단계라도 실패하면 모든 것이 처음 상태로 돌아가야 함
- 이처럼 여러 서비스에 걸친 데이터 일관성을 보장하기 위해 분산 트랜잭션이 필요합니다.
- 단일 데이터베이스였다면 하나의 트랜잭션으로 해결될 문제가, 데이터베이스가 분리되면서 더 복잡한 분산 트랜잭션 처리가 요구되는 것입니다.
3. 2단계 커밋(2-Phase Commit)
3.1 2단계 커밋 개요
- 2단계 커밋(2PC)은 분산 트랜잭션 환경에서 데이터 일관성을 보장하기 위한 프로토콜입니다.
- X/Open의 분산 트랜잭션 표준인 XA를 따르며, Java에서는 JTA(Java Transaction API)를 통해 구현됩니다.
팁
대표적인 JTA 구현체로는 Atomikos와 Bitronix가 있습니다.
3.2 동작 프로세스
1단계 - Prepare Phase (투표 단계)
- 코디네이터가 모든 참여자(데이터베이스 노드)에게 커밋 준비 요청을 전송합니다.
- 각 참여자는 다음 작업을 수행합니다:
- 트랜잭션을 실행하여 커밋 가능한 상태까지 준비
- undo log와 redo log를 기록
- 필요한 락(lock)을 획득하여 다른 트랜잭션의 간섭 방지
- 각 참여자는 코디네이터에게 응답을 전송합니다:
- 성공 시: 'Yes' 응답 (커밋 가능)
- 실패 시: 'No' 응답 (커밋 불가능)
2단계 - Commit Phase (완료 단계)
성공 시나리오 (모든 참여자가 'Yes' 응답)
- 코디네이 터가 모든 참여자에게 commit 메시지 전송
- 각 참여자는:
- 트랜잭션을 최종 커밋
- 모든 락과 리소스 해제
- 코디네이터에게 완료 응답 전송
- 코디네이터는 모든 완료 응답을 받으면 트랜잭션 종료
실패 시나리오 (하나라도 'No' 응답 또는 타임아웃 발생)
- 코디네이터가 모든 참여자에게 rollback 메시지 전송
- 각 참여자는:
- undo log를 사용하여 트랜잭션 롤백
- 모든 락과 리소스 해제
- 코디네이터에게 완료 응답 전송
- 코디네이터는 모든 완료 응답을 받으면 트랜잭션 종료
노트
각 참여자는 코디네이터의 최종 결정(commit/rollback)을 수신할 때까지 리소스를 점유한 상태로 대기합니다. 네트워크 장애 등으로 결정을 수신하지 못한 경우, 코디네이터에게 직접 질의하여 최종 결정을 확인할 수 있습니다.