분산 환경의 실패 처리

분산 장애 처리 2편: Saga와 오케스트레이터의 숨은 비용

Saga, 상태 머신, choreography, orchestrator가 정상 흐름 밖에서 만드는 전이·보상·버전·운영 비용과 도입 조건을 분석합니다.

검증일 근거 자료

핵심 요약

  • 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 순서와 중복을 검증하지 않으면 잘못된 전이가 남음
    • 상태 모델은 데이터 표현뿐 아니라 허용 명령, 전이 조건, 부작용, 복구 소유자를 포함해야 함
Forward, unknown, and compensation paths in an order workflow

Saga는 ACID rollback과 다른 문제를 품음

  • Saga는 여러 서비스의 로컬 트랜잭션을 순서대로 실행하고 실패 시 forward recovery나 compensation을 선택

    • 일시적 인프라 오류에는 같은 단계를 재시도해 앞으로 진행할 수 있음
    • 영구적인 비즈니스 오류에는 이미 성공한 단계를 보상할 수 있음
    • pivot 이후에는 되돌리기보다 남은 단계를 반드시 완료하는 편이 맞을 수 있음
  • Saga에는 단일 ACID 트랜잭션의 isolation이 없음

    • 중간 상태를 다른 요청이 읽거나 변경할 수 있음
    • 보상 시점에는 원래 작업 뒤에 다른 정상 거래가 이미 반영됐을 수 있음
    • 단순히 이전 값을 복원하면 다른 거래의 변경까지 덮어쓸 수 있음
  • 보상은 역연산이 아니라 도메인 규칙을 수행하는 새 트랜잭션

    • 결제 취소는 결제 row 삭제가 아니라 환불 원장을 추가하는 작업임
    • 예약 취소에는 시점에 따른 수수료와 환불 가능 금액이 적용될 수 있음
    • 이메일·배송·외부 원장처럼 완전히 되돌릴 수 없는 부작용이 있음
    • 보상 자체도 timeout·중복·영구 실패를 가질 수 있음
  • 어떤 실패를 보상할지 결정하는 일도 비즈니스 정책

    • 호텔 예약 실패 뒤 항공권을 즉시 취소할지 대체 호텔을 찾을지 결정해야 함
    • 결제 성공 뒤 재고 부족이면 backorder를 허용할지 환불할지 결정해야 함
    • 금액과 위험도가 높으면 자동화보다 운영자 승인이 필요할 수 있음

상태 머신은 전이보다 시간과 버전이 어려움

  • 상태 목록보다 event 수명주기를 설계하는 비용이 큼

    • 같은 event가 두 번 도착할 때 두 번째 처리를 무시해야 함
    • 오래된 event가 최신 상태 뒤에 도착할 때 거절하거나 별도로 기록해야 함
    • 두 worker가 같은 상태를 전이하려 할 때 조건부 갱신이나 version 검사가 필요함
    • deadline을 넘긴 상태를 누가 언제 다음 상태로 옮길지 정해야 함
  • 명시적 상태 머신은 누락된 경로를 드러내지만 상태 공간을 없애지 않음

추가 요구사항새로 필요한 설계
retry시도 횟수·다음 실행 시각·마지막 오류
timeoutdeadline·timeout event·늦은 성공 처리
compensation보상 상태·원인·재시도·수동 종료
operator intervention승인자·감사 기록·재개 명령
concurrent updateversion·compare-and-set·충돌 정책
deployment during workflowworkflow 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·보상 순서·진행률을 한곳에서 판단하기 어려움
기준choreographyorchestration
흐름 소유권참여 서비스에 분산orchestrator에 집중
전체 가시성trace·event 상관관계 구축 필요workflow 상태로 제공 가능
결합 위치event schema와 구독 관계workflow definition과 command 계약
공통 retry·timeout각 참여자가 구현중앙 정책으로 구현 가능
주요 장애 위험event chain 추적과 순환 의존성중앙 상태 시스템과 실행 엔진 장애
적합한 규모적은 참여자와 단순한 반응많은 단계와 명시적 중앙 정책
  • 어느 방식도 멱등성, Outbox, schema version, 관측성을 생략하게 해주지 않음
    • choreography는 참여자마다 이 책임이 반복됨
    • orchestration은 중앙 엔진과 참여자 사이의 계약으로 이 책임이 이동함

패턴 도입은 복잡성 예산으로 결정함

  • 가장 단순한 정합성 경계부터 한 단계씩 검토하는 방식이 적절
단계우선 적용 조건추가되는 비용
단일 DB 트랜잭션한 저장소에서 불변식 보장 가능lock·격리 수준·deadlock 관리
멱등성 + 결과 조회응답 유실과 요청 재전송 존재키 보존·충돌·응답 저장
Outbox + 멱등 consumerDB와 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가 복잡한 분산 상태를 조정 루프로 수렴시키는 방식을 분석

참고 문서