사가 (Saga) 패턴 2PC 한계 극복 분산 트랜잭션
핵심 인사이트 (3줄 요약)
- 본질: 사가(Saga) 패턴은 마이크로서비스 아키텍처(MSA)에서 여러 서비스에 걸친 긴 트랜잭션을 처리하기 위해, 각 서비스의 로컬 트랜잭션을 순차적으로 실행하고 실패 시 **보상 트랜잭션(Compensating Transaction)**을 통해 논리적 롤백을 수행하는 분산 트랜잭션 패턴이다.
- 가치: 전통적인 2PC(Two-Phase Commit)가 가진 동기적 블로킹과 자원 락(Lock)으로 인한 성능 저하 한계를 극복하고, 시스템의 가용성(Availability)과 확장성을 유지하면서 최종 일관성(Eventual Consistency)을 확보할 수 있게 해준다.
- 융합: 사가 패턴은 중앙에서 흐름을 통제하는 오케스트레이션(Orchestration) 방식과 이벤트 기반으로 자율적으로 동작하는 코레오그래피(Choreography) 방식으로 나뉘며, 비동기 메시징 큐(Kafka, RabbitMQ) 및 아웃박스(Outbox) 패턴과 결합하여 구현된다.
Ⅰ. 개요 및 필요성 (Context & Necessity)
-
개념: 사가(Saga)는 단일 데이터베이스의 ACID(원자성, 일관성, 고립성, 지속성) 트랜잭션을 여러 서비스에 걸쳐 분할한 연속된 로컬 트랜잭션의 체인이다. 만약 중간 단계에서 비즈니스 규칙 위반(예: 잔고 부족)으로 실패하면, 이전에 성공했던 트랜잭션들을 되돌리기 위해 역순으로 보상 트랜잭션을 실행한다.
-
필요성: MSA에서는 '주문(Order)', '결제(Payment)', '재고(Inventory)' 서비스가 각각 독립적인 데이터베이스를 갖는다(Database per Service). 고객이 주문을 할 때 세 가지 DB가 모두 성공적으로 업데이트되어야 하는데, 이를 전통적인 분산 트랜잭션 기법인 2PC로 묶으면, 하나라도 지연될 경우 세 개의 DB에 모두 락(Lock)이 걸려 시스템 전체가 마비(Deadlock 위험 및 Throughput 저하)된다. 따라서 락을 걸지 않고도 데이터의 정합성을 맞출 대안이 필수적이다.
-
💡 비유: 친구들과 해외여행을 준비할 때, 한 명이 '항공권', '호텔', '렌터카'를 한 번에 동시에 예약(2PC)하려 하면 하나라도 자리가 날 때까지 아무것도 못 하고 기다려야 합니다. 반면 사가 패턴은 먼저 항공권을 예약(성공)하고, 다음으로 호텔을 예약하려는데 방이 없으면, **앞서 예약해둔 항공권을 취소(보상 트랜잭션)**하는 방식과 같습니다.
-
등장 배경 및 발전 과정:
- 단일 DB와 2PC (Two-Phase Commit): 과거 XA 프로토콜 기반의 2PC는 완벽한 일관성을 보장했으나, MSA의 클라우드 환경에서는 성능 저하와 NoSQL 등 미지원 DB의 등장으로 사용이 불가능해졌다.
- BASE와 최종 일관성 (Eventual Consistency): 분산 시스템에서는 항상 즉각적인 일관성을 보장하기보다, "결국에는(Eventual) 일치하게 된다"는 비동기적 철학이 대두되었다.
- Saga 패턴의 재조명: 1987년 논문에서 처음 등장한 장기 실행 트랜잭션 모델인 Saga가 MSA 시대에 이벤트 기반 아키텍처(EDA)와 결합하며 분산 트랜잭션의 표준 해결책으로 부활했다.
-
📢 섹션 요약 비유: 도미노를 세우다가 중간에 하나가 무너지면, 뒤로 계속 무너지는 것이 아니라 관리자가 재빨리 뛰어가 앞서 세워둔 도미노들을 안전하게 눕혀놓아 원상태로 복구하는 것과 같습니다.
Ⅱ. 아키텍처 및 핵심 원리 (Deep Dive)
구성 요소
| 요소명 | 역할 | 내부 동작 | 관련 기술 | 비유 |
|---|---|---|---|---|
| 로컬 트랜잭션 (Local Tx) | 단일 마이크로서비스 내부 DB에서의 ACID 트랜잭션 | 데이터 변경 및 이벤트 발행 | Spring @Transactional | 각자의 방에서 하는 청소 |
| 보상 트랜잭션 (Compensating Tx) | 이미 커밋된 로컬 트랜잭션의 결과를 논리적으로 취소(역산) | 예: 결제 승인 → 결제 취소, 재고 차감 → 재고 증가 | 비즈니스 로직 | 결제된 카드를 승인 취소함 |
| 코레오그래피 (Choreography) | 각 서비스가 이벤트를 구독하고 자율적으로 다음 행동을 결정 | 펍/섭 (Pub/Sub) 브로커를 통한 비동기 통신 | Kafka, RabbitMQ | 지휘자 없는 댄서들의 군무 |
| 오케스트레이션 (Orchestration) | 중앙 통제기(Orchestrator)가 트랜잭션의 흐름과 보상을 명령 | 상태 기계(State Machine) 기반 작업 조율 | AWS Step Functions, Camunda | 오케스트라 지휘자 |
사가 패턴 구현 방식 1: 코레오그래피 (Choreography)
코레오그래피 방식은 중앙의 통제자 없이, 각 서비스가 다른 서비스의 **이벤트(Event)**를 듣고 자신의 로컬 트랜잭션을 실행한 뒤 다음 이벤트를 발행하는 방식이다.
┌───────────────────────────────────────────────────────────────┐
│ 코레오그래피 (Choreography) Saga 흐름 (주문 실패 시나리오) │
├───────────────────────────────────────────────────────────────┤
│ │
│ [Order Svc] [Payment Svc] [Inventory Svc]│
│ 1. 주문 생성(PENDING) │
│ 2. OrderCreatedEvent ──▶ │
│ 3. 결제 시도 (성공) │
│ 4. PaymentBilledEvent ─▶ │
│ 5. 재고 확인 (실패!)│
│ 6. InventoryFailedEvent │
│ ◀───────────────────────────(보상 시작)─────────────────────────┘│
│ 7. [보상] 결제 취소 │
│ ◀── PaymentRefundedEvent ─── │
│ 8. [보상] 주문 취소(CANCELED) │
│ │
│ * 특징: 지휘자 없이 서로의 이벤트를 듣고 알아서 행동함! │
└───────────────────────────────────────────────────────────────┘
- 장점: 결합도가 낮고, 중앙 병목(SPOF)이 없다. 간단한 트랜잭션에 적합하다.
- 단점: 참여하는 서비스가 많아지면 이벤트의 흐름을 추적하기 매우 어려워진다(이벤트 스파게티).
사가 패턴 구현 방식 2: 오케스트레이션 (Orchestration)
오케스트레이션 방식은 Order Saga Orchestrator라는 중앙 관리자가 존재하여, 각 서비스에게 무엇을 해야 할지 **명령(Command)**을 내리고 결과를 받아 다음 단계를 제어한다.
┌───────────────────────────────────────────────────────────────┐
│ 오케스트레이션 (Orchestration) Saga 흐름 (주문 실패 시나리오)│
├───────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────┐ │
│ │ Order Saga Orchestrator│ (상태 관리자) │
│ └────────────────────────┘ │
│ │ ▲ │ ▲ │ ▲ │
│ 1.결제명령(Cmd)│ │ │ │ │ │ 6.재고실패 응답 │
│ ▼ │ │ │ ▼ │ │
│ ┌───────────────┐ │ │ │ ┌───────────────┐ │
│ │ Payment Svc │────┘ │ │ │ Inventory Svc │ │
│ │ (결제 성공) │ 2.성공응답│ │ │ (재고 부족!) │ │
│ └───────────────┘ ▼ │ └───────────────┘ │
│ 3.재고명령(Cmd) │ │
│ │ │
│ 7.결제취소명령(Cmd) │ │
│ (보상 트랜잭션 시작) ▼ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ Payment Svc │ │ Order Svc │ │
│ │ (결제 취소 처리)│ │ (주문 상태 취소)│ │
│ └───────────────┘ └───────────────┘ │
│ │
│ * 특징: Orchestrator가 전체 워크플로우와 보상 순서를 책임짐! │
└───────────────────────────────────────────────────────────────┘
-
장점: 복잡한 트랜잭션 흐름(예: 5개 이상 서비스 참여)도 한 곳에서 추적 가능하며, 순환 참조 문제가 없다.
-
단점: Orchestrator가 слишком 많은 비즈니스 로직을 가지면 또 다른 모놀리스(Smart Orchestrator, Dumb Services)가 될 위험이 있다.
-
📢 섹션 요약 비유: 코레오그래피가 신호등(이벤트)을 보고 알아서 차들이 움직이는 교차로라면, 오케스트레이션은 교통경찰(지휘자)이 수신호로 어느 차가 가고 멈출지를 일일이 지시하는 교차로입니다.
Ⅲ. 실무 적용 및 기술사적 판단
실무 시나리오
-
시나리오 — 보상 트랜잭션의 실패: 주문 취소로 인해 '결제 취소' 보상 트랜잭션을 실행했는데, PG사 네트워크 오류로 결제 취소가 실패한 상황.
- 판단: 보상 트랜잭션은 본질적으로 실패할 수 있다. 분산 시스템에서 '절대 실패하지 않는 것'은 불가능하다.
- 해결책: 보상 트랜잭션은 반드시 **멱등성(Idempotency)**을 보장하도록 설계되어야 한다. 네트워크 오류 시 무한 재시도(Retry)를 하거나, 실패 내역을 Dead Letter Queue(DLQ)에 쌓아두고 관리자가 수동(Manual Intervention)으로 개입할 수 있는 배치/어드민 알림 프로세스를 마련해야 한다.
-
시나리오 — 격리성(Isolation) 부재로 인한 Dirty Read: 사가 패턴 진행 중, 주문은 생성되었으나 아직 재고 확인이 끝나지 않은 시점에, 다른 트랜잭션이나 사용자가 해당 '주문' 데이터를 읽고 처리해버린 상황.
- 판단: 사가 패턴은 ACID 중 고립성(Isolation)이 부족하다. 즉, 트랜잭션 중간 상태가 외부에 노출(Dirty Read)된다.
- 해결책: 시맨틱 락(Semantic Lock) 기법을 사용한다. 레코드의 상태 플래그를
CREATED대신PENDING이나PROCESSING으로 표기하여, 다른 트랜잭션이 이 레코드를 섣불리 읽거나 수정하지 못하도록 비즈니스 로직 단에서 통제해야 한다.
도입 체크리스트
- 기술적: 이벤트 발행 시 DB 커밋과 메시지 전송의 원자성을 보장하기 위해 아웃박스(Outbox) 패턴이 결합되어 있는가? (메시지 유실 방지)
- 아키텍처적: 서비스의 개수가 3~4개 이하로 적고 워크플로우가 고정적이라면 코레오그래피를, 5개 이상이거나 보상 로직 분기가 매우 복잡하다면 오케스트레이션을 선택했는가?
안티패턴
-
오케스트레이터의 갓 객체(God Object)화: 오케스트레이터가 흐름 제어뿐만 아니라 '재고가 부족하면 대체 상품을 제안한다' 같은 타 도메인의 핵심 비즈니스 로직까지 품어버려, 결국 모든 코드가 오케스트레이터로 집중되는 안티패턴.
-
📢 섹션 요약 비유: 식당에서 주문이 잘못 들어갔을 때, 환불(보상 트랜잭션) 처리가 전산 오류로 안 될 수도 있기 때문에, 점장님(관리자)이 나중에 장부를 보고 직접 돈을 돌려줄 수 있는 수동 절차(DLQ/배치)도 반드시 준비해 두어야 합니다.
Ⅳ. 기대효과 및 결론
정량/정성 기대효과
| 구분 | 전통적인 2PC 분산 트랜잭션 | 사가(Saga) 패턴 | 개선 효과 |
|---|---|---|---|
| 정량 | 모든 DB에 Lock 유지 (수 초 소요) | 로컬 DB만 Lock 점유 후 즉시 해제 | 시스템 동시 처리량(Throughput) 수십~수백 배 증가 |
| 정량 | 단일 서비스 장애 시 전체 트랜잭션 대기 | 비동기 기반 보상 처리 | 시스템 전체의 가용성(Availability) 방어 |
| 정성 | DB 기술 종속적 (NoSQL 적용 불가) | 애플리케이션 레벨의 논리적 롤백 | RDBMS와 NoSQL 간의 트랜잭션 연동 가능 |
사가 패턴은 MSA에서 데이터를 나누었을 때 직면하는 가장 큰 고통(데이터 정합성)에 대한 현실적인 타협안이다. 완벽한 ACID를 포기하는 대신 BASE(Basic Availability, Soft-state, Eventual consistency) 철학을 받아들여 시스템의 성능과 생존성을 높인다. 기술사는 "왜 2PC를 쓰지 않느냐"는 질문에 동기식 락(Lock)의 위험성을 설명하고, 사가 패턴의 멱등성과 보상 실패 시나리오까지 대비된 아키텍처를 제시할 수 있어야 한다.
📌 관련 개념 맵 (Knowledge Graph)
| 개념 명칭 | 관계 및 시너지 설명 |
|---|---|
| 2PC (Two-Phase Commit) | 사가 패턴이 극복하고자 하는 과거의 동기식 분산 트랜잭션 모델로, 완벽한 일관성을 주지만 성능을 희생한다. |
| 이벤트 주도 아키텍처 (EDA) | 사가 패턴 중 코레오그래피 방식을 구현하기 위한 기반 아키텍처로 비동기 메시지 큐(Kafka)를 사용한다. |
| 아웃박스 패턴 (Outbox Pattern) | 로컬 트랜잭션 완료와 보상 트랜잭션 유발을 위한 이벤트 발행이 100% 원자적으로 일어나도록 보장하는 연계 패턴이다. |
| 멱등성 (Idempotency) | 보상 트랜잭션이 여러 번 재시도되더라도 최종 결과가 같음을 보장하여, 분산 환경의 네트워크 에러를 방어하는 핵심 속성이다. |
| 최종 일관성 (Eventual Consistency) | 사가 패턴 실행 도중에는 데이터가 불일치할 수 있으나, 결국 보상이나 성공으로 마무리되어 데이터가 맞춰지는 상태다. |
👶 어린이를 위한 3줄 비유 설명
- 레고로 큰 성을 만들 때, 친구들 3명이 각자 방에서 '탑', '벽', '문'을 만들어서 나중에 하나로 합치기로 했어요.
- 만약 한 친구가 블록이 모자라서 '문'을 못 만들게 되면, 나머지 친구들도 만들던 '탑'과 '벽'을 모두 부수고 처음 상태로 되돌려 놓기로 약속했어요.
- 이렇게 서로 멀리 떨어져서 일하다가 한 명이라도 실패하면, 다 같이 약속한 대로 자기가 만든 걸 예전으로 되돌리는 똑똑한 방법을 '사가(Saga) 패턴'이라고 한답니다.