Skip to content

사가 패턴

사가는 MSA에서 분산 트랜잭션 없이 데이터 일관성을 유지하는 메커니즘이다. 사가는 일종의 로컬 트랜잭션인데, 각 로컬 트핸잭션은 ACID 트랜잭션 프레임워크/라이브러리를 이용하여 서비스별 데이터를 업데이트한다.

주문 서비스의 createOrder() 작업은 6개의 로컬 트랜잭션을 가진 주문 생성 사가로 구현된다.

  • Txn:1 주문 서비스: 주문을 APPROVAL_PENDING 상태로 생성한다.
  • Txn:2 소비자 서비스: 주문 가능한 소비자인지 확인한다.
  • Txn:3 주방 서비스: 주문 내역을 확인하고 티켓을 CREATE_PENDING 상태로 생성한다.
  • Txn:4 회계 서비스: 소비자 신용카드를 승인한다.
  • Txn:5 주방 서비스: 티켓 상태를 AWATING_ACCEPTANCE 로 변경한다.
  • Txn:6 주문 서비스: 주문 상태를 APPROVED로 변경한다.

이 사가의 첫 번쨰 로컬 트랜잭션은 주문 생성이라는 외부 요청에 의해 시작되고 나머지 5개의 로컬 트랜잭션은 각자 자신의 선행 트랜잭션이 완료되면 트리거 된다. 이 트리거는 메시지 발행을 통해 전달되며, 이를 통해 사가 참여자를 느슨하게 결합함과 동시에 사가가 완료되도록 보장할 수 있다.

이 사가에서 도중에 에러가 발생하는 경우, 보상 트랜잭션을 통해 변경분에 대한 롤백을 수행한다.

보상 트랜잭션

사가는 단계마다 로컬 DB에 변경분을 커밋하기 때문에 ACID와 같은 자동 롤백은 불가능하다. 그렇기 때문에 적용된 변경분을 명시적으로 Undo할 수 있는 보상 트랜잭션(compensating transaction)을 미리 작성하여 롤백을 구현해야한다.

(N + 1)번째 사가 트랜잭션이 실패하면 이전 N개의 트랜잭션을 언두해야 한다. 개념적으로 단계 Ti에는 Ti의 작용(effect)을 Undo하는 보상 트랜잭션 Ci가 대응되며, 처음 N개 단계의 작용을 언두하려면 사가는 각 Ci를 역순으로 실행하면 된다. 아래의 그림과 같이 T1 … Tn 순서로 트랜잭션이 실행되다가 Tn+1에서 실패할 경우 T1 … Tn을 언두하고 Cn … C1을 순서대로 실행하게 되는 것이다.

이 보상 트랜잭션은, 로컬 트랜잭션이 실패하는 순간 가동된다. 하지만 모든 로컬 트랜잭션에 보상 트랜잭션이 필요한 것은 아니다. 소비자 검증같은 읽기 전용 단계(retriable transaction)나 항상 성공하는 단계 이전에 있는 단계(pivot transaction)는 보상 트랜잭션이 필요 없다.

예를들어 소비자의 신용카드 승인이 실패하면 보상 트랜잭션은 다음 순서대로 작동할 것이다.

  1. 주문 서비스: 주문을 APPROVAL_PENDING 상태로 생성한다.
  2. 소비자 서비스: 소비자가 주문을 할 수 있는지 확인한다.
  3. 주방 서비스: 주문 내역을 확인하고 티켓을 CREATE_PENDING 상태로 생성한다.
  4. 회계 서비스: 소비자의 신용카드 승인 요청이 거부된다.
  5. 주방 서비스: 티켓 상태를 CREATE_REJECTED로 변경한다.
  6. 주문 서비스: 주문 상태를 REJECTED로 변경한다.

5, 6단계는 주방 서비스, 주문 서비스가 수행한 업데이트를 Undo하는 보상 트랜잭션이다. 일반 트랜잭션과 보상 트랜잭션의 순서화는 사가 편성 로직이 담당한다.