주문 과정에서 결제와 재고 예약은 각각 커밋됐지만 배송 요청이 실패했습니다. saga로 무엇을 보상하고 보상마저 실패한 주문은 어떻게 관리하나요?
잠깐, 답변을 보기 전에
먼저 내 말로 설명해 볼까요?
답변과 점검 포인트 펼치기답변 접기
구두 답변
정의를 외우기보다, 원리와 조건을 연결해 설명해 보세요.
각 서비스가 자기 데이터베이스에 커밋한 뒤에는 하나의 관계형 트랜잭션처럼 전부 되돌릴 수 없습니다. 이미 완료한 단계의 결과를 환불·예약 해제 등으로 상쇄하는 새 작업이 필요하며, 단계와 보상을 순서대로 관리하는 흐름을 saga라고 합니다. 보상은 과거 기록을 지우는 롤백이 아니라 또 하나의 변경이므로, 사용자와 감사 로그에 중간 과정이 남을 수 있습니다.
주문이 재고 예약→결제 승인→배송 생성 순서로 진행되고 배송 생성이 실패했다고 하겠습니다. 재고 예약 해제와 결제 취소를 시도할 수 있지만, 알림이 이미 전송됐거나 취소 수수료가 생겼다면 처음부터 없던 일과 같지 않습니다. 각 단계에 재시도 가능 여부, 취소 가능 여부, 보상 실패 시 운영자 확인 상태를 정의하고, 되돌릴 수 없는 단계를 어느 시점에 수행할지 배치하겠습니다.
진행 상태는 내구적으로 저장하고 단계·보상·외부 요청에 안정적인 멱등 키를 둡니다. 워커가 성공 후 응답 전에 죽어도 재시작이 같은 보상을 중복 수행하지 않아야 합니다. 오케스트레이터가 상태를 직접 지휘하거나 서비스들이 이벤트로 다음 단계를 알리는 코레오그래피를 선택할 수 있지만, 어느 쪽이든 타임아웃·재시도·보상 실패·대사를 추적할 상관 ID가 필요합니다.
사용자에게는 처리 중·취소 중·확인 필요를 실제 상태로 보여 주고, 오래 정체된 saga를 경보로 올리겠습니다. 테스트는 각 단계 전후 중단, 보상 실패, 정상 완료와 취소의 경합, 재시작을 주입해 최종 상태가 추적 가능하고 중복되지 않는지 확인하겠습니다.
보상은 과거를 지우는 것이 아니라 새 변경입니다
여러 서비스의 로컬 트랜잭션을 순서대로 진행하고 실패한 경우 이미 완료된 단계의 의미를 되돌리는 작업을 연결한 패턴을 사가(saga)라고 부릅니다. 결제 승인에 대한 취소나 재고 예약에 대한 해제가 보상 트랜잭션(compensating transaction)입니다. 데이터베이스 롤백과 달리 이미 외부에서 관찰한 사실을 없애지 못합니다. 승인 알림은 전송됐고 고객은 카드 한도가 줄어든 것을 봤을 수 있습니다. 그러므로 취소가 완료돼도 처음부터 주문이 없던 것과 모든 면에서 같지는 않습니다.
보상을 과거 값의 복원으로 구현하면 다른 정상 변경을 덮을 수도 있습니다. 잔액을 주문 전의 값으로 SET하는 대신 해당 승인이나 예약을 식별해 그 효과만 취소해야 합니다. 이는 의미적 보상이라는 관점입니다. 어떤 효과를 취소할 수 있고 수수료·정산·배송 이후에는 어떤 별도 절차가 필요한지 서비스 규칙으로 정해야 합니다. 모든 작업에 정확한 역연산이 있다고 가정해서는 안 됩니다.
재시도할지 보상할지 먼저 결정합니다
배송 서비스의 일시 장애라면 이미 결제된 주문을 곧바로 취소하기보다 배송 요청을 제한적으로 재시도하는 편이 맞을 수 있습니다. 주소가 영구적으로 잘못됐다면 수정 대기나 보상을 선택할 수 있습니다. 응답만 유실된 상황에서는 배송이 이미 생성됐을 수 있으므로 먼저 같은 요청 ID로 조회해야 합니다. 실패와 결과 불확실을 구분하지 않고 보상하면 정상 배송과 환불이 동시에 진행될 수 있습니다. 각 단계에는 성공 조건·재시도 가능한 오류·시간 상한·보상 가능 시점을 함께 정의하겠습니다.
중앙 조정자가 다음 단계와 보상을 지시하는 방식을 오케스트레이션(orchestration), 서비스들이 사건을 받아 다음 작업을 이어가는 방식을 코레오그래피(choreography)라고 합니다. 전자는 흐름을 찾기 쉽지만 조정 상태의 내구성·가용성과 정책 집중을 관리해야 합니다. 후자는 서비스별 반응을 분리할 수 있지만 전체 흐름과 숨은 이벤트 순환을 추적하기 어려워질 수 있습니다. 서비스 수만 보고 선택하기보다 실패 후 누가 미완료 상태를 찾아 복구할지 설명할 수 있어야 합니다.
중간 상태는 다른 요청에게 보일 수 있습니다
사가의 각 단계는 따로 커밋되므로 진행 중 상태가 외부에 노출됩니다. 이를 단일 트랜잭션의 격리와 같다고 볼 수 없습니다. 예약된 재고를 다른 주문이 다시 쓰지 못하도록 예약 ID·기한을 두거나, 취소 중인 주문에는 추가 변경을 제한하는 의미적 잠금을 사용할 수 있습니다. 다만 영원히 잠기지 않도록 만료와 복구를 함께 설계해야 합니다. 보상 단계도 중복 호출될 수 있으므로 원래 요청과 구분되는 안정적인 보상 ID를 두고 같은 환불이 한 번만 반영되게 하겠습니다.
정상 완료와 보상이 경합하면 현재 상태·버전을 조건으로 한 전이와 외부 시스템의 결과 확인이 필요합니다. 단순히 마지막 응답을 저장한 쪽이 이기면 이미 배송됐는데 취소 완료로 보일 수 있습니다. 오래 정체된 상태에는 자동 재시도 횟수·다음 시도 시각·원인·담당 복구 경로를 남기고, 사람이 처리할 때도 같은 가드를 통과하게 하겠습니다. 검증은 단계별 프로세스 중단, 응답 유실, 환불 성공 직후 재시작, 보상과 정상 완료의 경합을 포함합니다. ‘결국 맞아진다’가 아니라 어떤 기록과 책임으로 맞춰지는지까지 설명해야 안전한 설계입니다.
득점 포인트
이해가 드러나는 지점을 확인해 보세요.
- 분산 ACID 롤백과 환불·예약 해제 같은 보상을 구분한다.
- 단계별 재시도·취소·수동 확인 계약을 정한다.
- 내구 상태·멱등 키·정체 관측을 함께 설계한다.
감점 포인트
놓치기 쉬운 조건과 오해를 짚어 보세요.
- saga가 단일 트랜잭션과 같다고 말한다.
- 보상은 항상 성공한다고 가정한다.
- 메모리 호출 스택만으로 진행을 추적한다.
더 파고들 거리
하나 더 질문하며 이해의 범위를 넓혀 보세요.
- saga를 중앙 조정자나 이벤트 협력으로 만듭니다. 추적성·결합·순환·보상 책임은 어떻게 다른가요?
- 되돌릴 수 없는 단계를 어느 위치에 두고 어떤 확인을 받나요?
- 보상과 정상 완료가 경합할 때 전이를 어떻게 제한하나요?
득점·감점 포인트는 학습용 자기 점검 기준이며, 특정 회사의 실제 채점표가 아닙니다.
원리부터 공부하고 싶다면
답변을 보충하는 개념·예제·슈도코드를 읽어 보세요.