핵심 요약
- Saga는 분산 트랜잭션을 rollback으로 되돌리는 기술이 아니라 여러 로컬 트랜잭션과 보상 작업으로 원하는 최종 상태에 도달시키는 방식임
- 상태 머신의 어려움은 상태 이름을 정의하는 데 있지 않고 중복 event·timeout·동시 전이·구버전 실행을 모두 일관되게 처리하는 데 있음
- 오케스트레이터는 복잡성을 제거하지 않고 workflow 상태, retry, compensation, version 관리가 모이는 별도 운영 시스템으로 이동시킴
- choreography는 중앙 구성 요소를 줄이지만 참여자가 늘수록 event 의존성, 순환 호출, 전체 장애 원인 추적이 어려워짐
- 보상은 원래 상태로 시간을 되돌리는 작업이 아니며 실패하고 중복될 수 있는 새로운 비즈니스 명령임
- 작은 서비스가 Saga·상태 머신·오케스트레이터를 한꺼번에 도입하면 배보다 배꼽이 커질 수 있으므로 단순한 대안을 먼저 검증해야 함
정상 흐름 하나가 여러 실패 흐름으로 확장됨
-
주문·재고·결제의 정상 흐름은 세 단계지만 실제 운영 상태는 세 개로 끝나지 않음
- 주문 생성 전에 취소될 수 있음
- 재고 예약 요청이 timeout됐지만 실제 예약됐을 수 있음
- 결제 승인 뒤 주문 서비스가 결과 event를 받지 못할 수 있음
- 보상으로 재고를 해제하는 도중 다시 실패할 수 있음
- 운영자가 수동 환불한 뒤 늦은 자동 보상이 도착할 수 있음
-
각 원격 단계에는 최소 다섯 종류의 결과가 생김
- 실행 전인
pending - 상대가 처리 중인
running - 성공이 확인된
succeeded - 실패가 확인된
failed - 성공 여부를 모르는
unknown
- 실행 전인
-
세 단계의 상태를 독립 boolean으로 표현하면 도달해서는 안 되는 조합까지 코드에 존재함
inventoryReserved=true,paymentCaptured=true,orderCancelled=true가 동시에 나타날 수 있음- boolean을 enum으로 바꿔도 event 순서와 중복을 검증하지 않으면 잘못된 전이가 남음
- 상태 모델은 데이터 표현뿐 아니라 허용 명령, 전이 조건, 부작용, 복구 소유자를 포함해야 함
Saga는 ACID rollback과 다른 문제를 품음
-
Saga는 여러 서비스의 로컬 트랜잭션을 순서대로 실행하고 실패 시 forward recovery나 compensation을 선택함
- 일시적 인프라 오류에는 같은 단계를 재시도해 앞으로 진행할 수 있음
- 영구적인 비즈니스 오류에는 이미 성공한 단계를 보상할 수 있음
- pivot 이후에는 되돌리기보다 남은 단계를 반드시 완료하는 편이 맞을 수 있음
-
Saga에는 단일 ACID 트랜잭션의 isolation이 없음
- 중간 상태를 다른 요청이 읽거나 변경할 수 있음
- 보상 시점에는 원래 작업 뒤에 다른 정상 거래가 이미 반영됐을 수 있음
- 단순히 이전 값을 복원하면 다른 거래의 변경까지 덮어쓸 수 있음
-
보상은 역연산이 아니라 도메인 규칙을 수행하는 새 트랜잭션임
- 결제 취소는 결제 row 삭제가 아니라 환불 원장을 추가하는 작업임
- 예약 취소에는 시점에 따른 수수료와 환불 가능 금액이 적용될 수 있음
- 이메일·배송·외부 원장처럼 완전히 되돌릴 수 없는 부작용이 있음
- 보상 자체도 timeout·중복·영구 실패를 가질 수 있음
-
어떤 실패를 보상할지 결정하는 일도 비즈니스 정책임
- 호텔 예약 실패 뒤 항공권을 즉시 취소할지 대체 호텔을 찾을지 결정해야 함
- 결제 성공 뒤 재고 부족이면 backorder를 허용할지 환불할지 결정해야 함
- 금액과 위험도가 높으면 자동화보다 운영자 승인이 필요할 수 있음
상태 머신은 전이보다 시간과 버전이 어려움
-
상태 목록보다 event 수명주기를 설계하는 비용이 큼
- 같은 event가 두 번 도착할 때 두 번째 처리를 무시해야 함
- 오래된 event가 최신 상태 뒤에 도착할 때 거절하거나 별도로 기록해야 함
- 두 worker가 같은 상태를 전이하려 할 때 조건부 갱신이나 version 검사가 필요함
- deadline을 넘긴 상태를 누가 언제 다음 상태로 옮길지 정해야 함
-
명시적 상태 머신은 누락된 경로를 드러내지만 상태 공간을 없애지 않음
| 추가 요구사항 | 새로 필요한 설계 |
|---|---|
| retry | 시도 횟수·다음 실행 시각·마지막 오류 |
| timeout | deadline·timeout event·늦은 성공 처리 |
| compensation | 보상 상태·원인·재시도·수동 종료 |
| operator intervention | 승인자·감사 기록·재개 명령 |
| concurrent update | version·compare-and-set·충돌 정책 |
| deployment during workflow | workflow version·구버전 handler·migration |
-
오래 실행되는 workflow는 배포가 곧 데이터 migration 문제가 됨
- 어제 시작한 실행이 오늘 제거된 상태 이름을 참조할 수 있음
- 새 코드가 구버전 event payload를 읽을 수 있어야 함
- 전이 순서를 바꾸면 이미 중간까지 진행한 실행의 재개 위치가 달라질 수 있음
- 실행 이력 replay를 사용하는 엔진은 workflow 코드의 결정성·버전 규칙을 따라야 함
-
failed하나로 모든 실패를 표현하면 운영자가 복구할 수 없음- 일시 오류, 영구 오류, 결과 불명확, 보상 실패, 수동 검토를 구분해야 함
- 상태에는 다음 허용 명령과 책임 팀을 찾을 수 있는 reason·condition이 필요함
- terminal state처럼 보이지만 실제로는 재조정 대상인 상태를 명확히 구분해야 함
오케스트레이터는 또 하나의 상태 저장 서비스가 됨
-
오케스트레이터의 장점은 전체 workflow의 상태와 정책을 한곳에서 볼 수 있다는 것임
- 참여 서비스는 자신의 로컬 명령만 구현할 수 있음
- 중앙에서 timeout, retry, branch, compensation 순서를 관리할 수 있음
- 하나의
workflow_id로 감사와 운영 조회를 연결할 수 있음
-
중앙화된 가시성은 중앙화된 운영 책임을 함께 만듦
- 오케스트레이터 DB와 queue가 내구성과 고가용성을 가져야 함
- scheduler·timer·worker가 중복 실행돼도 안전해야 함
- workflow definition과 참여 서비스 API의 호환성을 함께 배포해야 함
- 정지된 실행을 조회·수정·재개·종료하는 운영 도구가 필요함
-
오케스트레이터가 성공 event를 받지 못한 경우 참여 서비스의 실제 상태를 다시 조회해야 함
- 단순 재호출만 하면 외부 부작용이 중복될 수 있음
- event가 유실됐는지 참여 단계가 실패했는지 오케스트레이터 상태만으로 알 수 없음
- 각 참여자는 멱등 명령과 authoritative status API를 제공해야 함
-
오케스트레이터 replica를 늘리는 것만으로 고가용성이 완성되지 않음
- leader election이나 경쟁 consumer 정책이 필요함
- lease가 만료된 이전 worker가 계속 실행되는 zombie 상황에는 fencing이 필요함
- DB·broker·timer storage가 같은 장애 도메인에 있으면 replica 수와 무관하게 함께 멈출 수 있음
choreography와 orchestration은 비용 위치가 다름
-
choreography는 각 서비스가 event를 구독해 다음 동작을 결정하는 분산형 구성임
- 참여자가 적고 흐름이 단순하면 중앙 coordinator 없이 시작할 수 있음
- 생산자와 소비자가 느슨하게 배포될 수 있음
- 한 서비스의 장애가 전체 중앙 엔진의 장애가 되는 구조를 피할 수 있음
-
참여자가 늘면 choreography의 workflow가 코드와 topic 사이에 흩어짐
- 전체 순서를 설명하려면 여러 repository와 schema를 함께 읽어야 함
- 순환 event 의존성과 의도하지 않은 재진입이 생길 수 있음
- global timeout·보상 순서·진행률을 한곳에서 판단하기 어려움
| 기준 | choreography | orchestration |
|---|---|---|
| 흐름 소유권 | 참여 서비스에 분산 | orchestrator에 집중 |
| 전체 가시성 | trace·event 상관관계 구축 필요 | workflow 상태로 제공 가능 |
| 결합 위치 | event schema와 구독 관계 | workflow definition과 command 계약 |
| 공통 retry·timeout | 각 참여자가 구현 | 중앙 정책으로 구현 가능 |
| 주요 장애 위험 | event chain 추적과 순환 의존성 | 중앙 상태 시스템과 실행 엔진 장애 |
| 적합한 규모 | 적은 참여자와 단순한 반응 | 많은 단계와 명시적 중앙 정책 |
- 어느 방식도 멱등성, Outbox, schema version, 관측성을 생략하게 해주지 않음
- choreography는 참여자마다 이 책임이 반복됨
- orchestration은 중앙 엔진과 참여자 사이의 계약으로 이 책임이 이동함
패턴 도입은 복잡성 예산으로 결정함
- 가장 단순한 정합성 경계부터 한 단계씩 검토하는 방식이 적절함
| 단계 | 우선 적용 조건 | 추가되는 비용 |
|---|---|---|
| 단일 DB 트랜잭션 | 한 저장소에서 불변식 보장 가능 | lock·격리 수준·deadlock 관리 |
| 멱등성 + 결과 조회 | 응답 유실과 요청 재전송 존재 | 키 보존·충돌·응답 저장 |
| Outbox + 멱등 consumer | DB와 broker dual write 존재 | publisher 적체·중복·DLQ |
| 상태 컬럼 + reconciler | 비동기 완료와 unknown 존재 | stale 탐지·재처리 도구 |
| Saga | 여러 독립 시스템에 실제 보상 필요 | 보상·isolation·장기 상태 |
| 전용 workflow orchestrator | 분기·timer·병렬·장기 재개가 핵심 | 플랫폼·버전·운영 인력 |
-
두 단계뿐이고 한 팀과 한 DB로 경계를 줄일 수 있다면 Saga가 과도할 가능성이 큼
- 서비스 분리가 아직 확정되지 않았다면 데이터 소유권을 먼저 재검토함
- 낮은 빈도의 예외는
failed_operations화면과 승인된 재처리 명령이 더 단순할 수 있음 - 단순 상태 컬럼과 조건부
UPDATE로 충분한지 먼저 검증함
-
Saga는 다음 질문에 모두 답할 수 있을 때 도입 근거가 생김
- 하나의 원자적 저장소로 경계를 줄일 수 없는 이유가 있음
- 비즈니스가 중간 상태의 외부 노출을 허용함
- 각 단계의 멱등성과 실제 결과 조회 방법이 있음
- compensable·pivot·retryable 단계가 구분됨
- 보상 실패와 수동 개입의 책임자가 정해짐
- 실행 중 workflow의 version migration과 보존 기간을 운영할 팀이 있음
결론 및 다음 글
- Saga·상태 머신·오케스트레이터의 핵심 비용은 도구 사용법이 아니라 실패 경로를 영구적으로 운영하는 책임임
- 보상 규칙과 운영 소유권이 없는 도입은 실패를 해결하지 않고 이름이 붙은 중간 상태만 늘릴 가능성이 큼
- 작은 흐름에서는 배보다 배꼽이 커지지 않도록 단일 트랜잭션, 멱등성, Outbox, 단순 reconciler 순서로 비용을 지불해야 함
- 다음 글에서는 OpenStack와 Kubernetes가 복잡한 분산 상태를 조정 루프로 수렴시키는 방식을 분석함