Foundry
분산 시스템
심화
핵심

분산 트랜잭션과 Saga

서비스마다 DB가 다르면 롤백은 누가 하나

분산 트랜잭션과 Saga

2PC를 잘 쓰지 않는 이유

  • 코디네이터가 커밋 직전에 죽으면 참여자들이 락을 잡은 채 대기한다(blocking)
  • 서비스마다 DB가 다르고 외부 API가 섞여 있어 애초에 참여가 불가능한 경우가 많다
  • 참여자가 늘수록 지연과 실패 확률이 곱으로 증가한다

Saga: 로컬 트랜잭션의 연쇄

주문생성 → 결제승인 → 재고차감 → 배송요청
              실패 ↓
주문취소 ← 결제취소     (보상 트랜잭션을 역순으로)
방식흐름 제어특징
코레오그래피각 서비스가 이벤트를 보고 스스로 반응결합도 낮음, 전체 흐름 추적이 어렵다
오케스트레이션중앙 오케스트레이터가 다음 단계를 지시흐름이 명시적, 오케스트레이터에 의존 집중

실무 포인트

  • Saga는 ACID의 격리(I)를 포기한다. 중간 상태가 외부에 보이므로 PENDING 같은 상태값으로 드러낸다
  • 보상은 물리적 되돌리기가 아니다. 이미 보낸 메일은 취소 메일로 갚는다
  • DB 커밋과 이벤트 발행의 원자성은 Outbox 패턴으로 맞춘다
  • 모든 단계가 재시도될 수 있으므로 각 단계는 멱등해야 한다
면접에서 이렇게 나옵니다

Q.여러 서비스에 걸친 트랜잭션을 어떻게 처리하시겠어요?

하나의 트랜잭션으로 묶지 않고, 각자 커밋하고 실패하면 보상하는 쪽으로 갑니다.

방식내용실무
2단계 커밋조정자가 전부 준비 확인 후 커밋거의 안 쓴다. 조정자 장애 시 멈춘다
사가단계별로 커밋하고 실패 시 역순 보상주로 이 방식
이벤트 기반 결과적 일관성이벤트로 전파해 각자 맞춘다흔하다
경계 재설계함께 바뀌어야 하는 것을 한 서비스로가능하면 이것이 최선

마지막을 먼저 검토해야 합니다. 분산 트랜잭션이 필요하다는 것은 서비스 경계가 잘못 그려졌다는 신호인 경우가 많습니다. 항상 함께 바뀌는 데이터라면 한 서비스 안에 있어야 합니다.

사가에서 정할 것들입니다.

항목내용
보상 동작취소가 아니라 되돌리는 별도 행동. 환불, 재고 복원
중간 상태 노출사용자에게 "처리 중"을 어떻게 보일지
멱등성각 단계와 보상이 여러 번 실행돼도 안전해야 한다
보상 실패보상도 실패할 수 있다. 그때는 사람이 개입한다

흔한 실수: 보상을 롤백과 같게 보는 것. 이미 커밋됐으므로 되돌릴 수 없고, 반대 방향의 새 작업을 해야 합니다. 이메일을 보냈다면 취소 메일을 보내는 식입니다.

Q.2PC를 실무에서 잘 쓰지 않는 이유는 무엇인가요?

이유내용
조정자가 단일 장애점커밋 결정 직전에 죽으면 참여자가 잠금을 쥔 채 멈춘다
잠금 유지 시간전체 합의가 끝날 때까지 자원을 잠근다
가용성 저하참여자 하나가 응답하지 않으면 전체가 진행되지 않는다
지원 범위외부 API 나 메시지 큐는 참여할 수 없다
규모참여자가 늘면 실패 확률과 지연이 함께 는다

첫 번째가 결정적입니다. 참여자는 준비 응답을 보낸 뒤 커밋도 취소도 못 하고 기다립니다. 조정자가 복구될 때까지 그 데이터는 잠겨 있습니다.

네 번째도 현실적인 제약입니다. 마이크로서비스 환경에서 트랜잭션에 참여하는 대상은 대개 다른 서비스의 HTTP API 이고, 그것은 2단계 커밋 규약을 지원하지 않습니다.

그래도 쓰는 곳이 있습니다. 같은 조직이 관리하는 소수의 DB 사이, 지연에 여유가 있는 배치 작업 정도입니다.

흔한 실수: 2단계 커밋이 정합성을 완전히 보장한다고 답하는 것. 조정자와 참여자가 동시에 죽는 경우 등 남는 구멍이 있고, 그것을 메우려면 합의 알고리즘까지 필요합니다.

Q.코레오그래피와 오케스트레이션 중 무엇을 고르시겠어요?

항목코레오그래피오케스트레이션
흐름 지식각 서비스가 자기 다음만 안다조정자가 전체를 안다
결합도낮다조정자가 모두를 안다
흐름 파악코드가 흩어져 있어 어렵다한곳에 모여 있다
디버깅어렵다. 트레이싱이 필수상태를 조회할 수 있다
단계 추가관련 서비스만 고친다조정자를 고친다
맞는 규모단계가 적고 단순할 때단계가 많고 분기가 있을 때

단계가 3개를 넘고 보상이 필요하면 오케스트레이션이 낫습니다. 결제, 재고, 배송이 이벤트로 얽히면 지금 어디까지 진행됐는지 아는 곳이 없어서 장애 대응이 어렵습니다.

반대로 단계가 둘이고 보상이 필요 없으면 코레오그래피가 가볍습니다. 주문 생성 후 알림 발송 정도라면 조정자를 둘 이유가 없습니다.

흔한 실수: 결합도가 낮다는 이유로 코레오그래피를 고르는 것. 낮아지는 것은 코드 결합이고, 흐름에 대한 이해는 오히려 흩어져 운영이 어려워집니다. 상태를 물어볼 곳이 있는지가 실무에서 더 중요합니다.

Q.DB 저장과 이벤트 발행이 함께 성공하도록 어떻게 보장하나요?

두 시스템에 각각 쓰는 것은 원자적이지 않습니다. DB 트랜잭션 안에 이벤트도 함께 저장하는 방식으로 해결합니다.

순서동작
1업무 데이터와 이벤트를 같은 트랜잭션으로 저장한다
2커밋된다. 둘 다 저장되거나 둘 다 안 된다
3별도 프로세스가 저장된 이벤트를 읽어 발행한다
4발행에 성공하면 표시한다

이것이 흔히 아웃박스 패턴이라 부르는 구조입니다. 발행이 실패하면 다시 시도하면 되고, 중복 발행될 수 있으니 소비자를 멱등하게 만듭니다.

대안도 있습니다.

방식내용
변경 로그 구독DB 의 변경 로그를 읽어 이벤트로 만든다. 코드 변경이 적다
이벤트를 원본으로이벤트를 먼저 저장하고 상태를 그것으로 만든다

흔한 실수: 커밋 후에 발행하는 것으로 충분하다고 보는 것. 커밋과 발행 사이에 프로세스가 죽으면 데이터는 있고 이벤트는 없습니다. 반대로 먼저 발행하면 롤백된 일에 대한 이벤트가 나갑니다. 어느 순서로도 두 시스템을 원자적으로 묶을 수 없습니다.

먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.

읽었으면 문제로 확인해보세요

분산 시스템 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.