영화표 결제는 됐는데 예매는 안 됐다구요?😱 (Saga 패턴)

8 minute read

Published:

목차

  1. 트랜잭션을 구현하다가 생각난 일화
  2. 시스템 관점에서 본 그날의 결제 과정
  3. 의심해본 개념들🧐
    • 트랜잭션(Transaction)
    • 비동기(Asynchronous)
    • 스레드(Thread)
    • 결론
  4. 분산 트랜잭션
    • 분산 트랜잭션의 핵심 원칙(ACID)
    • 분산 트랜잭션 관리 기법
      • 2단계 커밋(Two-Phase Commit, 2PC)
      • 사가(Saga) 패턴
      • 2PC vs Saga 패턴 비교
  5. 정리

트랜잭션을 구현하다가 생각난 일화

현재 프로젝트에서 트랜잭션을 구현하면서 트랜잭션에 대해 학습했다. ACID 특성, 커밋과 롤백, 그리고 “모두 성공하거나 모두 실패해야 한다”는 원칙이 트랜잭션의 특성이다.

학습을 진행할 수록 작년 여름에 겪었던 한 경험이 떠올랐다. 영화표를 예매하던 날이었다. 결제 수단에서는 분명 결제가 완료됐다고 나왔는데, 영화관 마이페이지에는 예매 내역이 없었다. 그땐 그저 오류라고만 생각했고, 사건 발생 약 1시간 후 자동으로 환불 처리되었다.

트랜잭션을 학습하고 나니 문득 이런 의문이 들었다. ‘그때 내가 겪었던 일이 혹시 트랜잭션이었을까?’ ‘만약 트랜잭션이었다면 왜 롤백은 즉시 일어나지 않았을까?’ ‘결제는 성공했고 예매는 실패한 그 과정을 트랜잭션 개념으로 설명할 수 있을까?’ ‘롤백이 한 시간이나 걸린다는 건 잘못된 거 아닐까?’ 이 질문은 곧 또 다른 의문으로 이어졌다. ‘혹시 트랜잭션이 아니라 스레드나 비동기 처리였던 건 아닐까?’ 이런 생각에 이르러 각 개념을 다시 정리해보고, 당시의 결제 과정을 하나씩 추적해보기로 했다.


시스템 관점에서 본 그날의 결제 과정

기억을 바탕으로, 당시의 결제 과정을 시스템 관점에서 다시 정리해보면 다음과 같다.

[사용자]
   ↓ 결제 버튼
[영화관 서버]
   ↓ 결제 요청
[결제 앱 / PG]
   → 결제 승인 완료 ✅
   ↑ 승인 응답
[영화관 서버]
   ❌ 응답 처리 중 에러 (중복 클릭 / 타임아웃 / 화면 이탈)
   ❌ 예매 데이터 생성 실패


의심해본 개념들🧐

이때의 결제 과정을 이해하기 위해 의심해본 개념들은 다음과 같다.

트랜잭션(Transaction)

트랜잭션은 여러 작업을 하나의 묶음으로 처리하는 개념이다. 데이터베이스에서 “전부 성공 아니면 전부 실패”를 보장한다. 모든 작업이 성공하면 커밋되고, 하나라도 실패하면 이전 상태로 되돌아간다.

만약 영화표 결제가 하나의 트랜잭션이었다면, 예매 생성에 실패한 순간 결제 역시 즉시 취소되어야 했을 것이다. 하지만 실제로는 결제가 완료된 상태로 한 시간가량 유지되었고, 환불은 나중에 별도의 처리로 이루어졌다. 즉, 영화관 DB와 결제사 DB가 따로 놀았고, 1시간이라는 시차가 발생했다.이 부분에서 트랜잭션이라고 부르기엔 무리가 있다.

비동기(Asynchronous)

비동기는 요청을 보낸 쪽이 결과를 기다리지 않고, 다음 작업을 계속 수행할 수 있도록 하는 처리 방식이다. 사용자는 즉각적인 응답을 받고, 실제 처리는 백그라운드에서 진행된다. 비동기는 일을 처리하는 방식일 뿐, 책임을 지는 개념이 아니다. 비동기 통신을 하다가 에러가 나면 그냥 “메시지 전달 실패”로 끝날 수도 있다.

결제 버튼을 누른 뒤 몇 초 후에 화면이 전환되었던 점이나, 결과가 즉시 확정되지 않았던 상황은 비동기 처리로 충분히 설명이 가능해 보였다. 하지만 비동기는 처리 방식의 문제이지, 결제와 예매라는 두 작업의 성공/실패 상태를 어떻게 일관되게 유지할지까지는 설명해주지 않는다. 비동기라는 이유만으로 “결제는 성공했고 예매는 실패했다”는 상태가 발생하지 않는다. 그리고 1시간 뒤에 굳이 다시 찾아내서 환불을 해준다는 것은, 비동기라는 수단을 이용해 누군가(트랜잭션 관리자)가 끝까지 추적해서 결과를 맞췄다는 뜻이다.

스레드(Thread)

스레드는 하나의 프로세스 내에서 실제로 작업을 실행하는 가장 작은 단위이다. 멀티스레드는 작업을 병렬로 처리해 성능을 높이기 위해 사용된다.

처음에는 버튼을 누른 것이 스레드 충돌이나 동시성 문제를 일으킨 건 아닐까 하는 생각도 들었다. 하지만 곧 이 가설은 맞지 않다는 걸 알게 됐다. 스레드는 영화관 서버 내부에서만 돌아간다. 결제 담당은 결제 앱 (외부 PG)에 있고, 티켓은 영화관 예매 시스템(내부 DB)에 있다. 즉, 결제 앱과 영화관 시스템은 같은 프로세스 안에서 동작하는 스레드가 아니라, 서로 다른 서비스, 서로 다른 시스템이다. 이 문제는 실행 흐름의 문제가 아니라, 시스템 간 상태가 어긋난 문제였다. 그리고 시스템간의 데이터 정합성을 맞추는 건 쓰레드의 관리 영역이 아니다.

결론

트랜잭션도, 스레드도, 비동기 처리만으로도 이 결제 과정을 온전히 설명하기는 어려웠다. 결제는 성공했지만 예매는 실패한 상태가 일정 시간 유지되었고, 환불 역시 즉시 이루어지지 않고 한참 뒤에 처리되었다. 그러나 확실히 알 수 있는 건 이 과정이 단일 시스템 안에서 발생한 일이 아니라 서로 다른 시스템들 사이에서 일어난 일이라는 점이었다.

그리고 이 과정을 이해하기 위해 조사를 이어가며 분산 트랜잭션이라는 개념을 알게 되었다.


분산 트랜잭션

분산 트랜잭션(Distributed transaction)은 여러 개의 네트워크 노드나 서로 다른 데이터베이스(DB)에 걸쳐 있는 데이터를 하나의 작업 단위로 묶어 처리하는 트랜잭션이다. 여러 시스템(또는 서비스)에 걸친 작업을 하나의 트랜잭션처럼 일관성 있게 처리한다. 데이터베이스는 흔한 트랜잭션 자원이며 트랜잭션은 이러한 데이터베이스들을 통과한다.

분산 트랜잭션은 하나의 작업이 여러 시스템에 걸쳐 실행될 때, 이 작업들을 논리적으로 하나의 트랜잭션처럼 다루려는 시도를 말한다.

분산 트랜잭션의 핵심 원칙(ACID)

분산 환경에서도 트랜잭션은 다음의 네 가지 성질을 지켜야 한다.

  • 원자성(Atomicity)
    • 전체가 성공하거나, 전체가 실패해야 함.
  • 일관성(Consistency)
    • 트랜잭션 전후의 데이터 상태가 규칙에 맞아야 함.
  • 격리성(Isolation)
    • 동시에 실행되는 트랜잭션들이 서로 방해하면 안 됨.
  • 지속성(Durability)
    • 성공한 결과는 시스템 장애가 나도 보존되어야 함.

분산 트랜잭션 관리 기법

MSA 환경에서 분산 트랜잭션의 일관성을 유지하기 위한 여러 기법들이 존재한다. 여기서는 대표적인 두 가지 기법인 2단계 커밋(Two-Phase Commit, 2PC)과 사가(Saga) 패턴을 중심으로 설명한다.

CAP 정리

분산 시스템에서는 일관성(Consistency), 가용성(Availability), 분할 내성(Partition Tolerance) 세 가지를 동시에 완벽히 만족할 수 없다는 CAP 정리가 있다. 분산 트랜잭션을 고집하면 가용성(속도나 접속 가능성)이 떨어질 수밖에 없는 숙명을 가지고 있다.

2단계 커밋(Two-Phase Commit, 2PC)

2PC는 여러 데이터베이스에 걸친 트랜잭션을 처리하기 위한 전통적인 방법이다. 트랜잭션을 처리하는 데 참여하는 모든 데이터베이스를 참여자(Participant)라고 부르며, 트랜잭션의 전체적인 진행을 관리하는 조정자(Coordinator)가 존재한다. 2PC는 조정자가 모든 참여자에게 의사를 묻는 방식이다.

동작 과정

1. 준비 단계 (Prepare)

조정자는 모든 참여자에게 트랜잭션을 준비하라는 메시지를 전송한다. 각 참여자는 자신의 트랜잭션 준비를 완료하고, 코디네이터에게 준비 완료 응답을 보낸다.

2. 커밋 단계 (Commit)

조정자는 모든 참여자로부터 준비 완료 응답을 받으면, 모든 참여자에게 트랜잭션 커밋 메시지를 전송한다. 참여자들은 메시지를 받은 후 트랜잭션을 커밋한다. 만약 준비 단계에서 한 참여자라도 실패하면, 코디네이터는 모든 참여자에게 롤백 메시지를 보내 트랜잭션을 취소한다.

특징

다음과 같은 특징으로 2PC는 높은 일관성을 보장해야 하고, 데이터 불일치가 심각한 결과를 초래할 수 있는 상황에서 적합하다. 예를 들어 금융 거래 시스템과 같이 데이터의 정확성이 매우 중요한 시스템에서 사용할 수 있다.

  • 강력한 일관성
    • 2PC는 트랜잭션의 원자성을 보장하므로 모든 참여자가 성공하거나 모두 실패하는 것을 보장한다.
  • ACID 속성 준수
    • 분산 환경에서도 ACID 속성(원자성, 일관성, 격리성, 지속성)을 유지할 수 있다.

주의할 점

  • 성능 저하
    • 준비 단계와 커밋 단계에서 코디네이터와 참여자 간의 통신이 필요하므로 성능 오버헤드가 발생할 수 있다.
  • 가용성 문제
    • 코디네이터가 실패하면 트랜잭션은 진행될 수 없고, 모든 참여자는 코디네이터가 복구될 때까지 대기해야 한다.
    • 한 참여자가 실패하면 전체 트랜잭션이 실패하므로 가용성에 문제가 발생할 수 있다.
  • 확장성 문제
    • 많은 참여자를 처리해야 할 때 코디네이터의 부담이 증가하여 시스템 전체의 확장성에 제약을 줄 수 있다.

사가(Saga) 패턴

사가 패턴은 트랜잭션을 여러 개의 보상 트랜잭션(Compensation Transaction)으로 분해하고, 각 트랜잭션은 성공하거나 실패할 수 있다. 만약 하나의 트랜잭션이 실패하면, 이전에 성공한 트랜잭션들을 취소하는 보상 트랜잭션을 통해 수행한다.

구현 방식

Saga Pattern

  • 코레오그래피 사가(Choreography Saga)
    • 각 서비스가 이벤트를 발행하고, 다른 서비스는 해당 이벤트를 구독하여 자신의 작업을 수행한다.
  • 오케스트레이션 사가(Orchestration Saga)
    • 사가 오케스트레이터가 전체 트랜잭션의 흐름을 제어하고, 각 서비스에게 작업을 지시한다.

특징

다음과 같은 특징으로 사가 패턴은 일관성보다 가용성과 확장성이 더 중요하고, 데이터 불일치가 시스템에 큰 영향을 미치지 않는 경우에 적합하다. 예를 들어 사용자 프로필 업데이트나 상품 정보 변경과 같은 트랜잭션에 사용할 수 있다.

  • 느슨한 결합
    • 각 서비스가 독립적으로 동작하므로 서비스 간의 결합도가 낮아진다.
  • 높은 가용성
    • 한 서비스가 실패해도 전체 시스템에 미치는 영향이 적고, 다른 서비스들은 계속해서 작업을 수행할 수 있다.
  • 확장성
    • 서비스의 확장과 축소가 용이하다.

2PC vs Saga 패턴 비교

구분2PC (Two-Phase Commit)Saga Pattern
데이터 일관성매우 높음 (강한 일관성)최종적 일관성 (Eventual Consistency)
성능낮음 (대기 시간 발생)높음 (비동기 방식 가능)
복잡도상대적으로 낮음높음 (보상 로직 설계 필요)
적합한 곳금융 결제 등 정밀한 시스템대규모 마이크로서비스 환경

정리

분산 트랜잭션을 학습하며 과거의 경험을 다시 떠올려보니, 당시 상황을 조금 더 명확하게 이해할 수 있게 되었다. 결제와 예매는 서로 분리된 시스템에서 처리되므로, 이 과정은 하나의 트랜잭션이 아니라 분산 트랜잭션에 해당한다. 실제로 결제 트랜잭션은 성공했지만 예매 트랜잭션에서 오류가 발생했고, 그 결과 결제 성공과 예매 실패라는 상태가 동시에 존재하게 되었다. 이는 분산 트랜잭션에서의 실패가 반드시 “전체 실패”가 아니라, 부분 성공의 형태로 나타날 수 있음을 보여준다. 그리고 과정은 보상 트랜잭션의 특징이기도 하다. 앞선 단계의 트랜잭션이 성공하더라도 이후 단계에서 실패가 발생하면, 이미 완료된 작업을 되돌리기 위한 보상 작업이 수행된다. 결제가 먼저 성공한 뒤 일정 시간이 지난 후 환불이 처리된 것도 메시지 큐를 통한 보상 트랜잭션의 흐름으로 이해할 수 있다. 표로 정리하면 다음과 같다.

단계상태
1결제 승인됨
2예매 생성 실패
3일정 시간 동안 재시도 / 대기
4끝내 예매 없음 → 보상 로직 실행
5결제 취소 (환불)

현실의 서비스 환경에서는 하나의 비즈니스 요청이 여러 시스템과 여러 서비스에 걸쳐 실행되는 경우가 많다. 이때 모든 작업을 하나의 트랜잭션으로 묶어 처리하는 것은 기술적·운영상의 부담이 크기 때문에, 분산 트랜잭션 구조를 선택하는 추세이다. 분산 시스템에서는 강한 정합성보다는 결과적 일관성(Eventual Consistency)이 더 중요해진다. 그만큼 사용자가 겪을 수 있는 혼란을 줄이기 위한 상태 표시, 안내 메시지, 재처리 전략과 같은 설계 요소가 중요해진다. 결제는 성공했지만 예매는 실패한 상태는 이상한 버그가 아니라, 분산 시스템에서는 충분히 발생 가능한 중간 상태였다.

참고

Gemini
ChatGPT
분산 트랜잭션
이제 나도 MSA 전문가: 개념부터 실무까지
사가 패턴(saga pattern)과 분산 트랜잭션(distributed transaction)
제12장 분산 트랜잭션
MSA 환경에서 SAGA 패턴으로 안전한 분산 트랜잭션 구현하기
[ETC] 분산 트랜잭션
MSA 환경에서 네트워크 예외를 잘 다루는 방법
메시지 큐의 이해와 실제 적용 사례