05 데이터 정합성 - Saga Pattern
05 데이터 정합성 - Saga Pattern
1. Note
- 결론은 정합성을 맞추다가 문제가 생기면 보상트랜잭션으로 해결함.
- 근데 보상트랜잭션은 개발자가 직접 만든거라 검토가 많이 필요
- 예외는 어떤 방식으로든 전달해야하는데,
- 로직들이 흘러가는 구조에 따라서 방법이 다름.
2. Saga Pattern
1. Saga 패턴 (★MSA에서 주로 사용★)
- Saga Pattern은 MSA 환경에서 서비스별 로컬 트랜잭션을 사용하면서 데이터 정합성을 유지하는 패턴
- 각 서비스가 자신의 트랜잭션을 처리한 뒤 다음 서비스로 작업을 전달
- 중간에 실패가 발생하면
이전 작업을 보상 트랜잭션(Compensation Transaction)으로 취소
- 2PC처럼 전역 트랜잭션을 사용하지 않아 확장성과 서비스 독립성에 유리
- MSA 환경에서 가장 많이 사용되는 데이터 정합성 패턴 중 하나
2. saga패턴의 중요성
- MSA는 Database-per-Service 구조를 사용하며, 각 서비스가 독립적인 데이터베이스를 가짐
- 기존처럼 단일 트랜잭션(ACID)으로 처리할 수 없음
1 2 3
Order Service → 주문 생성 # 트랜잭션1 Payment Service → 결제 처리 # 트랜잭션2 Inventory Service → 재고 차감 # 트랜잭션3
- 비교하면
- 기존 모놀리식 환경에서는 rollback으로 해결할 수 있었지만,
- MSA에서는 서비스별 DB가 분리되어 있어 전체 rollback이 불가함.
- 따라서
- 각 서비스를 독립적인 Local Transaction으로 처리하고,
- 실패 시 보상 트랜잭션(Compensation Transaction)을 통해 정합성을 유지
3. 보상 트랜잭션 (Compensation Transaction)
이미 완료된 작업을 비즈니스 로직을 통해 취소하는 방식- 보상 트랜잭션은 “완벽한 원상복구”가 아닐 수 있음.
- 소스 로직상에서 try/catch로 실패할 경우 필요한 작업들을 명시하는 방법
1 2 3 4 5 6 7 8 9 10 11
try { ... // 외부 API와 연동되는 도메인 작업 paymentService.pay(orderId); inventoryService.decrease(); } catch (Exception e) { // 보상 트랜잭션 역할 paymentService.refund(orderId); // 결제를 취소, <별도 구현> orderService.cancel(orderId); // 주문도 취소, <별도 구현> }
3. 동작흐름
1. Saga Pattern 동작 흐름
- 하나의 큰 트랜잭션을 여러 개의
Local Transaction으로 분리하여 순차적으로 처리 - 각 서비스는 자신의 작업을 독립적으로 수행하고 commit 하며, 모든 과정이 성공하면 트랜잭션이 완료
중간 서비스에서 실패한 경우
Compensation Transaction작동1 2 3 4
1. Order Service # 주문 생성 -> 트랜잭션1 -> 커밋(local Transaction) 2. Payment Service # 결제 처리 -> 트랜잭션2 -> 커밋(local Transaction) 3. Inventory Service # 재고 차감 -> 트랜잭션3 -> 커밋(local Transaction) 4. 주문 완료
2. Rollback / 보상트랜잭션
| 구분 | Rollback(Local Transaction) | Compensation Transaction |
|---|---|---|
| 처리 방식 | DB가 자동 복구 | 비즈니스 로직으로 취소 |
| 범위 | 하나의 트랜잭션 | 여러 서비스 |
| 사용 환경 | 모놀리식 | MSA |
| 처리 시점 | 즉시 복구 | 이미 commit 후 취소 |
3. 실행 구조
- Local Transaction 실행
- 각 서비스가 자신의 작업을 독립적으로 수행
- 여기서 실패할 경우 내부에서는 롤백, 외부에서는 보상 트랜잭션
1 2 3
주문 생성 → 결제 처리 → 재고 차감
- Compensation Transaction 실행
- 실패 시 이전 작업을 취소
혼자서 발생하게 되면 내부 롤백, 다른 곳에서 발생하면 보상트랜잭션이 작동
1 2 3 4 5
재고 실패 ↓ 결제 취소 ↓ 주문 취소
4. Saga 구현 방식
1. Choreography
1. Choreography 방식
- 중앙 제어자 없이
각 서비스가 이벤트를 기반으로 작업을 수행하는 방식 - 각 서비스는 특정 이벤트를 구독하고, 이벤트 발생 시 자신의 작업을 수행한 후 다음 이벤트를 발행
2. 흐름
성공
1 2 3 4 5 6 7 8 9 10 11 12
1. Order Service 주문 생성 → OrderCreated 이벤트 발행 2. Payment Service OrderCreated 수신 → 결제 처리 → PaymentCompleted 이벤트 발행 3. Inventory Service PaymentCompleted 수신 → 재고 차감
실패
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Order Service(주문 생성) ↓ OrderCreated Event (성공이벤트 발행) Payment Service(결제 성공) ↓ PaymentCompleted Event (성공이벤트 발행) Inventory Service(재고 차감 실패 - 롤백) ↓ InventoryFailed Event 발행 # InventoryFailed Event를 보고 Pament Service가 보상 트랜잭션 + PaymentCanceled Event 발행 # Order Service는 PaymentCanceled Event보고 보상 트랜잭션 작동 # 각 서비스들은 여러 이벤트를 바라보고 있을 수 있음!
3. 장단점
| 구분 | 내용 |
|---|---|
| 장점 | 서비스 간 결합도가 낮음 확장성이 높음 Event-Driven Architecture와 자연스럽게 연결됨 Kafka 같은 메시지 브로커 활용에 적합 |
| 단점 | 서비스가 많아질수록 이벤트 흐름 파악이 어려움 이벤트 의존성 증가로 복잡도 상승 장애 추적 및 디버깅이 어려움 |
2. Orchestration
1. Orchestration 방식
중앙 조정자(Orchestrator)가 전체 트랜잭션 흐름을 제어하는 방식- 독립적으로 작업을 수행하지만, 다음 작업을 누가 실행할지 Orchestrator가 결정
- 이벤트를 사용하지 않으면 거의 Orchestrator 방식으로 잡히게됨.(☆)
2. 흐름
성공
1 2 3 4 5
Saga Orchestrator │ ├─ Order Service → 주문 생성 ├─ Payment Service → 결제 요청 └─ Inventory Service → 재고 차감 요청
실패
흐름
1 2 3 4 5 6 7 8 9 10 11 12
Client ↓ Order API (진입점) ↓ Order 생성 ↓ Saga 시작 ↓ Orchestrator가 흐름 관리 (★) ├─ Payment Service 호출 ├─ Inventory Service 호출 └─ ...
소스
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
... try { orderId = orderService.createOrder(request); paymentId = paymentService.pay(orderId); inventoryService.decreaseStock(orderId); } catch (Exception e) { // 보상 트랜잭션 실행 compensate(orderId, paymentId); throw new RuntimeException("Saga Failed", e); } private void compensate(Long orderId, Long paymentId) { // 결제 취소 if (paymentId != null) { paymentService.cancel(paymentId); } // 주문 취소 if (orderId != null) { orderService.cancel(orderId); } }
3. 장단점
| 구분 | 내용 |
|---|---|
| 장점 | 트랜잭션 흐름이 명확함 모니터링 및 디버깅이 쉬움 복잡한 비즈니스 로직 관리에 유리 |
| 단점 | 중앙 Orchestrator 의존성 증가 Coordinator 장애 시 영향 범위가 커질 수 있음 상대적으로 서비스 간 결합도가 높아질 수 있음 |
5. Saga Pattern 한계와 고려사항
1. Eventually Consistency (최종 일관성)
Saga Pattern은 즉시 일관성(Strong Consistency)을 보장하지 않음
1 2 3 4 5 6
주문 생성 완료 Order -> commit → 결제 진행 중 Payment -> 진행중 → 재고 처리 중 Inventory -> 진행전 # 이 타이밍에는 DB 3개의 데이터가 다른 상황. # 중간에 조회하게 되면 달라 질 수 있음.
2. 보상 트랜잭션의 한계
- Compensation Transaction은 DB rollback이 X
- 롤백과 같은 기능을 하도록 구현한 방법
- 모든 작업이 완벽하게 복구되지는 않을 수 있음
특정 작업에 따라 보상 트랜잭션이 불가할 수 있음
1 2 3 4 5
결제 성공 ↓ SMS 발송 완료 (복구 불가) ↓ 재고 실패
3. 멱등성 (Idempotency)
- 분산 환경에서는 네트워크 오류나 재시도로 인해 동일 요청이 여러 번 실행될 수 있음
- 동일 요청이 여러 번 실행되어도 결과가 동일해야 함
중복 실행 방지 로직이 필요
1 2 3 4
결제 취소 API 호출 # 결제 취소 APi 실행후 응답을 받던 중 네트워크 오류 발생 # 첫 번째 취소는 성공했지만 오류 발생으로 재요청하는 상황 발생
4. 재시도(Retry) 처리
- 일시적인 장애는 재시도로 복구 가능한 경우가 많음
바로 실패 처리보다는 시간을 두고 몇회 시도후 보상트랜잭션을 실행필요
1 2 3 4 5 6 7
# 일시적인 타임 아웃 발생 Inventory Service Timeout Retry 재요청1 -> 실패 Retry 재요청2 -> 실패 Retry 재요청3 -> 실패 보상 트랜잭션 요청
5.장애 추적 및 디버깅 어려움
- Saga는 여러 서비스에 걸쳐 동작하여 문제의 원인을 찾기 어려움
어디서 실패했는지, 누가 어떤 상태인지, 어떤 보상 작업이 실행됐는지 추적 필요함.
1 2 3
Correlation ID # 하나의 요청 흐름을 식별하기 위한 고유 ID Distributed Tracing # 서비스 호출 흐름 + 실행 시간까지 추적 Centralized Logging # 여러 서버 로그를 한곳에 모으는 것
This post is licensed under CC BY 4.0 by the author.