Cloudflare Workflows를 위한 사가 롤백 구축 방법 (새 탭에서 열림)
Cloudflare Workflows에 Saga 패턴 기반의 롤백 기능이 추가되어, 각 step.do()에 보상 작업을 함께 선언할 수 있게 되었습니다. 여러 외부 시스템을 거치는 워크플로에서 중간 단계가 실패해도 이전 작업을 역순으로 되돌릴 수 있으며, 롤백 자체도 내구성 있는 단계로 실행됩니다. 이를 통해 개발자가 별도의 try-catch, 실행 이력 추적, 수동 롤백 순서 관리를 구현할 필요가 줄어듭니다.
분산 작업에서 롤백이 필요한 이유
- Workflow는 여러 단계에 걸쳐 외부 시스템을 호출하고, 각 단계의 상태를 저장하며 실패 시 재시도합니다.
- 그러나 이미 완료된 외부 작업은 단순히 “취소”할 수 없습니다.
- 예: Bank A에서 출금이 성공한 뒤 Bank B 입금이 실패하면, Bank A의 출금을 삭제하는 대신 다시 입금해야 합니다.
- 원래 작업과 이를 의미적으로 되돌리는 보상 작업의 조합을 Saga 패턴이라고 합니다.
- 기존에는 개발자가 성공한 단계를 추적하고, 실패 시 어떤 작업을 어떤 순서로 취소할지 직접 관리해야 했습니다.
step.do()에 보상 로직 선언
- 이제
step.do()의 마지막 인자로rollback함수를 전달할 수 있습니다.
await step.do(
"debit-bank-a",
() => bankA.debit(from, amount),
{
rollback: async ({ output }) =>
bankA.credit(from, amount, output.id),
}
);
- 각 정방향 작업과 롤백 작업이 같은 위치에 정의됩니다.
- 새로운 단계를 추가할 때 해당 단계의 보상 로직도 함께 추가할 수 있습니다.
- 별도의 대형
catch블록이나 성공 단계 추적 변수, 수동 실행 순서 관리가 필요하지 않습니다. - 롤백 함수는 정방향 작업의 결과인
output을 받아 보상 작업에 활용할 수 있습니다.
롤백 실행 순서와 실패한 단계 처리
- 어떤 단계에서 오류가 발생하면, 롤백 핸들러는 단계가 시작된 순서의 역순으로 실행됩니다.
- 출금 → 입금 → 알림 순서라면, 롤백은 입금 취소 → 출금 환불 순서입니다.
- 오류가 발생한 단계 자체도 롤백 대상이 될 수 있습니다.
- 외부 시스템에는 작업이 반영됐지만, 결과를 Workflow에 반환하기 전에 단계가 실패할 수 있기 때문입니다.
- 예를 들어 결제 제공자가 금액을 승인한 뒤
chargeId를 반환하기 전에 오류가 발생할 수 있습니다. - 따라서 롤백 함수는
output === undefined인 경우도 안전하게 처리해야 합니다. - 사용자가 오류를 잡고 Workflow를 정상적으로 계속 진행하면 롤백은 시작되지 않습니다.
- 다만 오류를 잡은 뒤 Workflow가 나중에 다른 이유로 실패하면, 그때까지 등록된 롤백 핸들러가 역순으로 실행될 수 있습니다.
롤백도 내구성 있는 작업으로 실행
- 롤백은 단순한 메모리상의 정리 코드가 아니라 Workflow의 내구성 모델에 따라 실행됩니다.
- 재시작이나 일시적인 장애가 발생해도 롤백 작업을 추적하고 재시도할 수 있습니다.
- 롤백 과정에서 하나의 보상 작업이 실패하더라도 이후 롤백을 계속 진행할 수 있도록 설계해야 합니다.
- 롤백 실패는 운영자가 대응할 수 있도록 알림이나 별도 모니터링을 연결하는 것이 필요합니다.
멱등성 보장의 중요성
- 일반 Workflow 단계와 마찬가지로 롤백 함수도 멱등적이어야 합니다.
- 같은 롤백이 여러 번 실행되어도 결과가 중복 적용되면 안 됩니다.
- 권장 방식:
- 결제 환불에는 결제 제공자의 멱등성 키 사용
- 재고 해제는 여러 번 호출해도 한 번만 해제되도록 구현
- 출금·입금과 롤백 각각에 고유한 멱등성 키 부여
- 예시에서는 다음과 같이 작업별 키를 사용합니다.
`${transferId}:debit-account-a`
`${transferId}:rollback-debit-account-a`
- 이를 통해 Workflow 재시도나 롤백 재실행으로 인해 동일한 이체가 중복 처리되는 것을 방지합니다.
실용적인 적용 권장사항
- 외부 시스템을 변경하는 모든 단계에 가능한 한 명시적인 롤백 함수를 함께 정의하세요.
- 롤백 함수는
output이 없거나 일부 작업만 반영된 상황도 처리해야 합니다. - 정방향 작업과 롤백 모두에 안정적인 멱등성 키를 사용하세요.
- 롤백 실패는 조용히 무시하지 말고 알림, 재처리 큐, 운영 대시보드 등으로 추적하세요.
- Saga 롤백은 트랜잭션을 원자적으로 만드는 기능이 아니라, 실패 후 상태를 보정하는 보상 처리机制이므로 외부 API의 보상 연산을 신중히 설계해야 합니다.