Saga Pattern은 언제 필요할까? 분산 트랜잭션을 피하는 설계 방법
마이크로서비스 아키텍처를 도입하면 서비스 간 독립성과 확장성은 높아지지만, 반대로 데이터 일관성을 유지하는 일이 훨씬 복잡해집니다. 단일 데이터베이스를 쓰던 시절에는 트랜잭션 하나로 해결하던 문제가, 서비스마다 데이터베이스가 분리된 환경에서는 전혀 다른 접근이 필요합니다. 이 문제를 다루는 대표적인 설계 패턴이 바로 Saga Pattern입니다.
분산 트랜잭션이 왜 문제가 되는가
전통적인 모놀리식 시스템에서는 하나의 데이터베이스 트랜잭션 안에 여러 작업을 묶어서 처리할 수 있습니다. 주문 생성, 재고 차감, 결제 처리를 하나의 트랜잭션으로 묶으면 중간에 오류가 발생해도 전체가 롤백됩니다. 이것이 ACID 트랜잭션이 보장하는 원자성입니다.
그런데 마이크로서비스 환경에서는 주문 서비스, 재고 서비스, 결제 서비스가 각각 독립된 데이터베이스를 가집니다. 이 세 서비스에 걸친 작업을 하나의 트랜잭션으로 묶으려면 분산 트랜잭션이 필요합니다.
분산 트랜잭션을 구현하는 고전적인 방법으로 2PC(2-Phase Commit) 프로토콜이 있습니다. 하지만 2PC는 여러 현실적인 문제를 안고 있습니다.
- 코디네이터가 단일 장애점(Single Point of Failure)이 됩니다.
- 참여 서비스가 잠금을 오래 유지해야 해서 성능 저하가 발생합니다.
- 네트워크 장애 시 블로킹 상태에 빠질 수 있습니다.
- NoSQL 데이터베이스나 메시지 브로커처럼 2PC를 지원하지 않는 시스템과 함께 쓰기 어렵습니다.
이러한 이유로 현대 마이크로서비스 설계에서는 분산 트랜잭션 자체를 피하고, 대신 최종 일관성(Eventual Consistency) 을 허용하는 방식을 선택하는 경우가 많습니다. Saga Pattern은 그 대표적인 해법입니다.
Saga Pattern이란 무엇인가
Saga Pattern은 하나의 긴 비즈니스 트랜잭션을 여러 개의 로컬 트랜잭션으로 나누고, 각 단계가 성공하면 다음 단계로 진행하며, 실패하면 이전에 완료된 단계를 되돌리는 보상 트랜잭션(Compensating Transaction) 을 실행하는 패턴입니다.
전체 흐름이 하나의 원자적 트랜잭션이 아니라, 여러 개의 작은 트랜잭션과 그에 대응하는 보상 트랜잭션으로 구성됩니다. 각 서비스는 자신의 로컬 데이터베이스에만 트랜잭션을 수행하므로, 서비스 간 데이터베이스 잠금이나 분산 코디네이터가 필요 없습니다.
예를 들어 주문 처리 흐름을 생각해보겠습니다.
- 주문 서비스 → 주문 생성 (로컬 트랜잭션)
- 재고 서비스 → 재고 차감 (로컬 트랜잭션)
- 결제 서비스 → 결제 처리 (로컬 트랜잭션)
- 배송 서비스 → 배송 요청 (로컬 트랜잭션)
3단계 결제에서 실패하면, 앞서 완료된 재고 차감과 주문 생성을 되돌리는 보상 트랜잭션이 역순으로 실행됩니다.
중요한 점은 보상 트랜잭션이 데이터베이스 롤백과 다르다는 것입니다. 이미 커밋된 상태이므로, 되돌리려면 명시적인 역작업(예: 재고를 다시 증가시키는 업데이트)을 수행해야 합니다.
Saga의 두 가지 구현 방식
Saga Pattern을 구현하는 방식은 크게 두 가지로 나뉩니다.
코레오그래피(Choreography)
중앙 조율자 없이 각 서비스가 이벤트를 발행하고 구독하는 방식입니다. 서비스 A가 작업을 마치면 이벤트를 발행하고, 서비스 B가 그 이벤트를 구독해서 자신의 작업을 수행합니다.
장점
- 서비스 간 결합도가 낮습니다.
- 별도의 오케스트레이터 서비스가 필요 없어 구조가 단순합니다.
단점
- 전체 흐름이 여러 서비스에 분산되어 있어 파악하기 어렵습니다.
- 이벤트 간 순환 의존이 생길 위험이 있습니다.
- 장애 추적과 디버깅이 복잡합니다.
오케스트레이션(Orchestration)
중앙 오케스트레이터가 각 서비스에 명령을 내리고 응답을 받아 다음 단계를 결정하는 방식입니다. 오케스트레이터는 Saga의 전체 상태를 관리합니다.
장점
- 전체 흐름이 오케스트레이터에 집중되어 있어 이해하기 쉽습니다.
- 장애 처리 로직이 한 곳에 모여 있어 유지보수가 용이합니다.
단점
- 오케스트레이터 자체가 복잡해질 수 있습니다.
- 서비스 간 결합도가 상대적으로 높아집니다.
팀 규모가 크거나 비즈니스 흐름이 복잡할수록 오케스트레이션 방식이 유리한 경우가 많습니다. 반면 서비스 수가 적고 흐름이 단순하다면 코레오그래피로 시작하는 것도 좋은 선택입니다.
Saga Pattern이 필요한 시점
Saga Pattern이 모든 상황에 적합한 것은 아닙니다. 다음과 같은 상황에서 도입을 고려하는 것이 적절합니다.
여러 마이크로서비스에 걸친 비즈니스 트랜잭션이 존재할 때
단일 서비스 내에서 완결되는 작업이라면 로컬 트랜잭션으로 충분합니다. Saga가 필요한 것은 주문, 결제, 재고, 배송처럼 서로 다른 서비스 경계를 넘나드는 비즈니스 흐름이 있을 때입니다.
분산 트랜잭션(2PC)을 쓰기 어려운 인프라 환경일 때
NoSQL 데이터베이스, 외부 API, 메시지 브로커가 혼합된 환경에서는 2PC를 일관되게 적용하기 어렵습니다. Saga는 각 서비스가 자신의 로컬 트랜잭션만 책임지므로 이런 이기종 환경에 잘 맞습니다.
긴 시간에 걸쳐 진행되는 비즈니스 프로세스일 때
결제 승인 후 외부 물류 시스템과 연동하거나, 사용자 확인 단계가 포함된 흐름처럼 작업이 수분에서 수시간에 걸쳐 진행될 수 있는 경우, 2PC처럼 잠금을 유지하는 방식은 현실적이지 않습니다.
최종 일관성을 비즈니스 요구사항이 허용할 때
Saga는 중간 상태가 잠시 불일치할 수 있다는 점을 전제합니다. 결제가 완료된 후 재고 차감이 약간의 지연으로 반영되는 것을 시스템이 수용할 수 있어야 합니다. 즉각적인 강한 일관성이 반드시 필요한 금융 코어 시스템 같은 경우에는 신중하게 판단해야 합니다.
설계할 때 고려해야 할 핵심 사항
Saga Pattern을 실제로 적용할 때는 몇 가지 중요한 설계 원칙을 함께 고려해야 합니다.
멱등성(Idempotency) 보장
메시지 재전송이나 네트워크 재시도로 인해 같은 이벤트나 명령이 두 번 이상 도달할 수 있습니다. 각 서비스는 동일한 요청이 여러 번 처리되어도 결과가 같도록 멱등하게 설계해야 합니다. 처리 이력을 저장하거나 유니크한 트랜잭션 ID를 활용하는 방법이 일반적입니다.
보상 트랜잭션의 완전성
보상 트랜잭션 자체가 실패하는 상황도 대비해야 합니다. 보상이 실패하면 데이터가 영구적으로 불일치 상태에 빠질 수 있습니다. 재시도 메커니즘, 데드레터 큐, 운영팀의 수동 개입 프로세스를 미리 설계해두는 것이 중요합니다.
관찰 가능성(Observability)
Saga는 여러 서비스에 걸쳐 있어서 전체 흐름을 한눈에 보기 어렵습니다. 각 단계의 상태를 추적하고 로깅하는 체계, 분산 추적(Distributed Tracing) 도구를 함께 구성해야 운영 중 장애를 효과적으로 진단할 수 있습니다.
격리 수준(Isolation) 문제
Saga는 중간 상태가 외부에 노출될 수 있어 ACID의 격리성을 완전히 보장하지 않습니다. 이를 완화하기 위해 시맨틱 잠금(Semantic Lock) 같은 기법을 사용하기도 합니다. 예를 들어 처리 중인 주문에 ‘PENDING’ 상태를 명시해 다른 요청이 간섭하지 못하도록 설계하는 방식입니다.
Saga가 적합하지 않은 경우
Saga Pattern이 항상 정답은 아닙니다. 다음과 같은 경우에는 다른 접근을 먼저 검토하는 것이 낫습니다.
- 단일 서비스 내 트랜잭션: 서비스 경계를 넘지 않는다면 Saga는 불필요한 복잡성을 추가합니다.
- 강한 일관성이 필수인 도메인: 금융 원장처럼 중간 불일치가 절대 허용되지 않는 경우, 서비스 경계 자체를 재설계하거나 다른 방법을 검토해야 합니다.
- 비즈니스 흐름이 매우 단순한 경우: 두세 개의 서비스가 관여하더라도 흐름이 단순하면 더 가벼운 이벤트 기반 접근이나 API 조합으로 충분할 수 있습니다.
마치며
Saga Pattern은 마이크로서비스 환경에서 분산 트랜잭션의 복잡성을 피하면서도 비즈니스 일관성을 유지하기 위한 실용적인 패턴입니다. 다만 이를 도입한다는 것은 단순히 코드 패턴을 바꾸는 것이 아니라, 최종 일관성을 전제로 한 설계 사고 전환을 의미합니다.
보상 트랜잭션 설계, 멱등성 보장, 관찰 가능성 확보라는 세 가지 과제를 함께 해결할 준비가 되었을 때, Saga Pattern은 비로소 실질적인 가치를 발휘합니다. 기술 선택 전에 자신의 시스템이 실제로 이 패턴이 필요한 상황인지를 먼저 냉정하게 판단하는 것이 출발점입니다.