에디블로그
Engineer's Field Notes

AI 자동화로 매일
한 편씩 쓰는
엔지니어 운영 노트

Claude Code · 자동화 파이프라인 · 사고 회고까지. 잘 굴러간 기록 + 깨진 흔적도 같이 남깁니다.

사람이 할 수 있는 일은,
AI도 할 수 있어야 합니다.
매일 한 편 쓰면서 검증 중.
— 이번 주 가장 많이 읽힌 글 TOP 3
백엔드/분산 & 운영

[분산시스템] 사가(Saga) 패턴: 보상 트랜잭션, 코레오그래피 vs 오케스트레이션

반응형
[분산시스템] 사가(Saga) 패턴: 보상 트랜잭션, 코레오그래피 vs 오케스트레이션

[분산시스템] 사가(Saga) 패턴: 보상 트랜잭션, 코레오그래피 vs 오케스트레이션

주문·결제·재고가 한 DB에 있을 땐 @Transactional 하나로 "전부 성공 아니면 전부 취소"가 됐어요. 서비스를 쪼개는 순간 그 마법이 사라져요 — DB가 다르니 하나의 롤백이 없거든요. 그 자리를 채우는 게 사가 패턴이에요. "롤백"을 "보상"으로 바꾸는 사고방식이죠.

분산 트랜잭션 문제에서 출발해, 사가의 핵심인 보상 트랜잭션, 두 스타일(코레오그래피 vs 오케스트레이션), 그리고 따라오는 어려운 숙제(보상 불가 작업·중간 상태 노출)까지 짚어요. 아웃박스가 "이벤트를 확실히 전달"하는 패턴이라면, 사가는 그 위에서 "흐름 전체의 정합성"을 다루는 패턴이에요.

01. 하나의 롤백이 사라졌다

분산 트랜잭션 문제. 주문, 결제, 재고가 각자의 DB를 가진 서비스로 갈라지면 재고 차감 실패 시 이미 커밋된 주문과 결제를 하나의 롤백으로 되돌릴 수 없다

재고 차감이 실패한 시점에, 주문과 결제는 이미 각자의 DB에 커밋돼 있어요. 전통적인 답인 분산 트랜잭션(2PC)은 모든 참여자가 락을 쥔 채 서로를 기다리는 구조라 느리고 조정자가 죽으면 다 같이 멈춰요. 그래서 MSA에서는 사실상 기피 대상이고 실무 표준이 사가예요.

02. 사가, 롤백 대신 보상

사가 패턴 동작. 주문 생성, 결제 승인이 로컬 트랜잭션으로 각각 커밋된 뒤 재고 차감이 실패하면, 결제 취소와 주문 취소라는 보상 트랜잭션을 역순으로 실행해 전체를 되돌린다

사가는 큰 트랜잭션을 로컬 트랜잭션의 연쇄로 풀어요. 각 단계는 자기 DB에 바로 커밋하고 중간에 실패하면 이미 커밋된 단계들의 보상 트랜잭션(undo)을 역순으로 실행해요. 재고 실패 → 결제 취소(환불) → 주문 취소, 이런 식이죠.

여기서 중요한 인식 전환이 있어요. 보상은 롤백이 아니에요. "없던 일로"가 아니라 "되돌리는 새로운 일"이에요. 결제 취소는 환불 기록이 남는 별도 트랜잭션이고 이미 나간 알림은 "취소 알림"으로 덮을 수밖에 없어요. 그래서 사가의 설계 규칙 1번은 — 모든 단계는 보상 가능해야 한다예요. 단계를 설계할 때 "이걸 어떻게 되돌리지?"를 같이 설계하는 거예요.

보상이 안 되는 작업(이메일 발송, 외부사 확정 통보)도 현실엔 있어요. 요령은 순서예요 — 보상 불가능한 단계를 사가의 맨 뒤로 보내요. "다 확정된 다음에야 메일을 보낸다"면 보상할 일 자체가 안 생기니까요. 그래도 남는 건 "예약" 개념으로 풀어요. 재고를 바로 차감하는 대신 선점(예약)하고 사가 성공 시 확정·실패 시 해제하는 식이에요.

03. 코레오그래피, 이벤트로 릴레이

사가 두 스타일 비교. 코레오그래피는 각 서비스가 이벤트를 구독해 반응하는 방식으로 단순한 흐름에 가볍지만 전체 흐름이 보이지 않고, 오케스트레이션은 중앙 조정자가 상태 머신으로 순서와 보상을 관리해 복잡한 사가에 유리하다

중앙 조정자 없이, 각 서비스가 앞 단계의 이벤트를 구독해 자기 일을 해요. 주문이 "주문생성됨"을 발행하면 결제가 받아 처리하고 "결제완료됨"을 발행해요. 재고가 받아 차감하고... 실패 시엔 "결제실패됨" 같은 이벤트를 거꾸로 타고 보상이 연쇄돼요.

서비스가 서로를 모르니 결합이 낮아요. 2~3단계짜리 단순한 흐름엔 가볍고 좋아요. 문제는 성장할 때예요 — 단계가 늘면 "이 주문이 지금 어디까지 갔지?"를 아무도 몰라요. 흐름이 코드 어디에도 없고 이벤트 구독 관계에 흩어져 있으니까요. 장애 추적이 "이벤트 고고학"이 돼요.

04. 오케스트레이션, 지휘자를 둔다

사가 오케스트레이터라는 중앙 조정자가 흐름을 상태 머신으로 관리해요. "결제 서비스, 승인해" → 응답 보고 → "재고 서비스, 차감해" → 실패 응답 → "결제 서비스, 취소해". 명령과 보상의 순서를 조정자가 다 알아요.

전체 흐름이 한곳에 코드로 보여요. 각 사가 인스턴스의 현재 상태를 조회할 수 있고 보상 순서가 명시적이에요. 대가는 조정자라는 컴포넌트의 구현·운영이고요(직접 상태 머신을 짜거나 임시 프레임워크를 써요). 기준은 단순해요 — 3~4단계를 넘고 보상이 얽히기 시작하면 오케스트레이션이 결국 싸요. "지금 어디까지 갔나"를 물어볼 곳이 있다는 게 운영에서 그만큼 커요.

05. 사가가 데려오는 숙제들

  • 중간 상태가 보여요 — 사가 진행 중엔 "주문은 됐는데 결제는 아직"인 순간이 존재하고 다른 조회가 그걸 볼 수 있어요(격리성 부재). 상태 필드(PENDING→CONFIRMED)로 중간 상태를 명시하고 화면·API가 그 상태를 해석하게 설계해요.
  • 메시지 신뢰성이 전제예요 — 단계 사이의 이벤트·명령이 유실되면 사가가 영원히 멈춰요. 그래서 발행은 아웃박스로, 처리는 멱등 컨슈머로 — 앞 편들이 사가의 토대예요.
  • 타임아웃 처리 — 응답이 영영 안 오는 단계가 있을 수 있어요. 단계별 타임아웃을 두고 초과 시 보상으로 전환하는 규칙까지가 사가 설계예요.

06. 자주 만나는 문제

사가가 중간에 멈춰서 안 움직여요

메시지 유실이나 타임아웃 규칙 부재예요. 아웃박스로 전달을 보장하고 오케스트레이션이라면 "N분째 같은 상태인 사가" 알림을 걸어요.

보상이 실패하면요?

보상은 "반드시 결국 성공"하도록 설계해요 — 멱등하게 만들어 재시도를 무한히 안전하게 하고 그래도 안 되면 사람에게 에스컬레이션(수동 처리 큐)해요. 보상의 실패 처리를 또 보상으로 푸는 무한 후퇴는 없어요. 어딘가엔 "재시도 + 알림"이 바닥이에요.

이벤트 흐름을 추적 못 하겠어요

코레오그래피가 복잡해진 신호예요. 사가 ID를 모든 이벤트에 실어 로그로 묶고(MDC) 그래도 힘들면 오케스트레이션 전환을 검토해요.

보상이라는 사고방식

사가는 분산 환경에서 "전부 아니면 전무"를 보상 트랜잭션으로 흉내 내는 패턴이에요. 모든 단계를 보상 가능하게 설계하고(불가능한 건 맨 뒤로 보내거나 예약으로 풀고) 흐름이 단순하면 코레오그래피, 단계가 얽히면 오케스트레이션이에요. 그리고 사가는 혼자 못 서요 — 아웃박스가 전달을 확실하게, 멱등성이 재시도를 안전하게 받쳐줘야 비로소 굴러가요.

설계 습관으로는, 단계마다 "이걸 어떻게 되돌리지"의 답을 같이 적어두는 게 좋아요. 그 답이 곧 보상 트랜잭션이고 답이 안 나오는 단계는 순서를 뒤로 밀라는 신호예요.

그 재시도와 짝꿍인 서킷 브레이커를 다음 글에서 다뤄요.

출처: microservices.io — Saga · Microsoft — Saga pattern

반응형

📚 같이 보면 좋은

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 일정액의 수수료를 제공받습니다."