개발 일기

[Building Microservices] CHAPTER 6 - 워크플로(트랜잭션 처리) 본문

도서/Building Microservices 마이크로서비스 아키텍처 구축

[Building Microservices] CHAPTER 6 - 워크플로(트랜잭션 처리)

개발 일기장 주인 2025. 4. 27. 21:51
워크플로(Workflow)란?
특정 순서에 따라 발생하는 반복적인 프로세스와 작업을 관리하는 시스템이다.
즉, 마이크로서비스 기준으로 보면, 하나의 비즈니스 로직을 처리하기 위해 여러 개의 마이크로서비스가 순차적 또는 병렬적으로 협업하는 일련의 흐름을 의미한다. 각 마이크로서비스는 자신만의 책임 영역을 가지고 있으며, 전체적인 프로세스가 성공적으로 완료되기 위해서는 이들 서비스 간의 올바른 상호작용이 필요하다.

 

앞 챕터들에서는 마이크로서비스 간 통신을 다뤘고 이번 챕터는 여러 마이크로서비스가 협력해서 비즈니스 프로세스를 구현하는 방법에 대해 다룬다.

분산 시스템에서 워크플로우를 모델링하고 구현하는 것은 쉽지 않을 수 잇다.

이를 해결하기 위해 분산 트랜잭션을 사용하는 것과 그와 관련된 함정을 살펴보고, (필자 기준) 훨씬 더 나은 방식으로 마이크로서비스 워크플로를 모델링할 수 있는 개념인 사가(Saga)를 살펴보자.


✅  Database Transactions (데이터베이스 트랜잭션)

  • 트랜잭션: 단일 단위로 취급하고 싶은 하나 이상의 작업
  • 예를 들어, 데이터 삽입/삭제/수정 같은 여러 상태 변경 작업을 하나로 묶어 처리.

➡️ ACID Transactions

  • ACID는 신뢰성 있는 데이터베이스 트랜잭션을 보장하기 위한 속성
Atomicity (원자성) 트랜잭션 내 모든 작업이 모두 성공하거나, 모두 실패해야 함. 중간 실패 시 전체 롤백.
Consistency (일관성) 데이터베이스 변경 후에도 유효하고 일관된 상태 유지
Isolation (격리성) 여러 트랜잭션이 독립적으로 간섭 없이 작동. 트랜잭션 중 이뤄진 모든 중간 상태 변경이 다른 트랜잭션에 안보이게 하는 방법으로 달성된다.
Durability (지속성) 커밋되어 트랜잭션이 완료되면, 시스템 장애가 발생해도 데이터 손실 없어야 함.
  • 모든 데이터베이스가 ACID를 지원하는 것은 아님.

✅  마이크로서비스와 ACID 트랜잭션

  • 마이크로서비스도 자체 데이터베이스 안에서는 ACID 트랜잭션을 사용할 수 있음.
  • 단, 트랜잭션의 범위가 그 서비스 내부로만 제한된다.
  • 즉, 로컬 데이터베이스의 상태 변경만 트랜잭션으로 감싸는 것이다.

➡️ 예시: 고객 온보딩 프로세스 (Figure 6-1) - 단일 데이터베이스

  • MusicCorp의 고객 온보딩 과정.
  • 고객 2346의 Status를 PENDING → VERIFIED로 변경함과 동시에 PendingEnrollments 테이블에서 고객의 대기 등록 항목을 삭제해야하는 로직

  • 하나의 데이터베이스 안에서는 이 두 작업을 단일 ACID 트랜잭션으로 묶을 수 있음.
    • 둘 다 성공하거나, 둘 다 롤백됨.(Atomicity 보장)

 

 

➡️ 예시: 고객 온보딩 프로세스 (Figure 6-2) - 분리된 데이터베이스

  • 동일한 변경 작업이지만, 각 작업이 다른 데이터베이스에 저장되어 있다면?
  • 결과적으로 두 개의 독립적인 트랜잭션이 생김.
    • 각각의 성공/실패 여부가 독립적으로 발생할 수 있음.
      Atomicity 깨짐

작업을 여러 개의 분리된 트랜잭션으로 나누는 순간, 원자성은 깨진다.

 

 

 

 

 

 

위와 같은 원자성의 결여로 인해,

트랜잭션 하나로 묶는 걸 포기하지 않고(단일 트랜잭션),
여러 프로세스에 걸쳐서라도 하나의 트랜잭션처럼 처리하기 위해 분산 트랜잭션을 사용하는 것을 고려했고

가장 일반적인 알고리즘 중 하나가 2PC이다.

 

즉, 분산 트랜잭션을 해당 도서에서는 여러 시스템에 걸쳐 하나의 트랜잭션처럼 원자성을 강제하는 것으로 보고 Saga Pattern을 경우 원자성이 강제로 보장되지 않기 때문에 분리된 개념으로 보고 있다.

✅ Distributed Transactions (분산 트랜잭션) - 2단계 커밋 : 2PC (Two-Phase Commit)

  • 가장 전통적인 분산 트랜잭션 알고리즘입니다.
    1. Voting Phase (1단계):
      모든 참여자(Worker)에게 "이 변경을 할 준비가 되었는가?"를 물어봄.
      각 참여자가 만약 OK인지 NO인지 조정자에게 결과를 반환하는데 만약 OK라면 Lock을 건다.
      → 모두 OK하면 다음 단계로, 하나라도 NO면 트랜잭션 전체를 중단(Rollback).

    2. Commit Phase (2단계):
      OK한 Worker들에게 실제로 변경을 적용하라는 커밋 요청을 보냄.
      → 각 Worker는 변경을 적용하고 잠금을 해제함.
  • 2PC의 문제점1  - 분리된 데이터베이스로 인한 커밋 요청 도달 차이로 인한 Isolation 위반
    • 단일 데이터베이스가 아닌 분리된 데이터베이스이기에 두 커밋은 동시에 일어날 것이라고 보장하기란 어떤 식으로도 불가능!!!
    • 네트워크 딜레이로 인해 커밋 요청이 서로 다른 시간에 도착해 처리될 수 있고 그에 따라 일시적인 불일치 상태가 생길 수 있음.
    • Commit Phase에서 조정자와 특정 참가자들 사이의 지연 시간이 길 수록 해당 불일치 구간이 커질 수 있음
      → 즉, ACID 중 Isolation인 격리성을 보장할 수 없게 된다.(트랜잭션 중 중간 상태 노출)
  • 2PC의 문제점2  - Lock으로 인한 성능 저하
    • 위에서 얘기했듯이 Voting 단계에서와 Commit 단계에서의 상태가 바뀌어서 문제가 발생하지않게 여러 워커(Worker)가 각각 자기 자원을 락으로 묶고 커밋 요청을 기다린다.
    • 이때 참여자가 많을수록, 시스템에서 지연 시간이 길수록 모두 OK되고 Commit 작업이 완료될때 까지 Lock이 유지되어야하기 때문에 성능 이슈 가능
    • 작업 소요 시간이 길수록 자원을 더 오래 잠가둬야 한다!
  • 이 외에도 투표를 했지만 나중에 커밋 요청 때 응답하지 않는 워커 문제 등 다양한 실패 모드가 있는데 일부는 자동으로 처리되고 일부는 수동으로 처리해야하는 부분이 있다고 한다.

→ 위와 같은 문제들로 수명이 짧은 작업(작업 시간이 오래 걸리지 않는)일때만 2PC사용 권장,

 


필자는 해당 분산트랜잭션 방식이 성능의 문제 및 중간 상태 노출을 막을 수 없기에 반대한다고 한다.

✅ Saga Pattern

  • 긴 시간 걸리는 작업(LLT: Long Lived Transaction)을 DB 트랜잭션 하나로 잡으면 → 긴 락 발생 → 시스템 성능 심각하게 저하.
  • 대신, 짧은 독립 트랜잭션으로 나눠서 처리하면 락 지속시간이 짧아지고 경합 줄어든다.
  • 따라서, Saga는 LLT를 짧은 로컬 트랜잭션들로 쪼개서 독립적으로 처리하는 방법.
사가는 일반적인 데이터베이스 트랜잭션의 ACID에서 원자성을 제공하지 않는다.
LLT를 개별 트랜잭션으로 나누기 대문에 사가 전체 수준에서 원자성을 갖지 않는다.
그러나 이때 분리된 각각의 트랜잭션 개별 작업에서는 원자성이 있다.

 

 

➡️ Saga Failure

복구 유형 2가지

  • Backward Recovery (후진 복구):
    • 실패가 발생했을 때 이미 커밋된 트랜잭션을 보정하여 원상 복구.
    • 이를 위해 보상 트랜잭션(compensating transaction) 이 필요.
  • Forward Recovery (전진 복구):
    • 실패가 발생한 지점에서 다시 재시도하여 처리 계속.
    • 이를 위해 필요한 정보를 지속성(persistence) 하여 재시도가 가능해야 함.

상황에 따라 오직 backward, 오직 forward, 또는 둘을 혼합하는 복구 전략을 사용할 수 있다.

 

실패 유형 2가지

 

  • Business Failure (비즈니스 실패):
    • 고객 잔액 부족 등, 비즈니스적인 문제 ➔ Saga가 복구해야 함.
  • Technical Failure (기술 실패):
    • 네트워크 오류, 500 에러 등 ➔ Saga 외부에서 별도로 처리 필요.

Saga는 기술적 실패가 아닌 비즈니스 실패 복구에 초점을 맞춘다.

 

 

 

➡️ Saga Rollback

  • 기존 ACID 트랜잭션은 커밋 전 롤백이 가능하지만, Saga는 이미 여러 서비스가 각각 커밋을 완료한 상태일 수 있다.
  • 따라서 "보상 트랜잭션(Compensating Transaction)"을 순차적으로 호출하여 이전 작업을 취소해야 한다.

  • 의미적 롤백(Semantic Rollback)이라고도 한다.
    만약 주문 처리 단계 중에서 하나의 주문이 진행 중임을 알리려고 고객에게 이메일을 보내는 것과 비슷하게 롤백하기로 결정한다 해도 전송된 이메일은 취소할 수 없다!
    → 그 대신에 보상 트랜잭션으로 고객에게 두 번째 메일을 발송하여 문제가 생겨 주문이 취소됐음을 알릴 순 있다.

롤백을 줄이기 위한 워크플로의 단계 재정렬

  • 실패 가능성이 높은 작업을 앞으로 땡기고,
  • 성공이 확정된 이후에 다음 단계(예: 포인트 적립)를 수행하면 불필요한 롤백/보상 트랜잭션을 줄일 수 있다.

✅ Implementing Saga Pattern

사가(Saga)에는 크게 두 가지 방식이 있다.
하나는 중앙 집중식 조정자가 존재하여 작업 흐름을 명확하게 추적할 수 있는 오케스트레이션형 사가(Orchestrated Saga) 이고,
다른 하나는 중앙 조정자 없이 서비스 간의 느슨한 결합을 선호하지만, 사가의 진행 상황을 추적하기가 더 복잡해지는 코레오그래피형 사가(Choreographed Saga) 이다.

 

➡️ Orchestrated Saga

: 중앙 조정자(Orchestrator)를 사용해 실행 순서를 정의하고 필요한 보상 조치를 트리거

  • 중앙 조정자(Orchestrator) 가 전체 Saga 프로세스를 통제하고 관리하는 방식
  • "Command-and-Control"(명령과 제어) 스타일: Orchestrator가 서비스 호출 순서와 보상(Compensating) 작업까지 모두 직접 관리.
  • 오케스트레이터는 어떤 일이 언제 일어나는지를 제어하며, 이를 통해 사가에서 어떤 일이 일어나는지를 추적할 수 있다.
  • 요청-응답(request-response) 기반 통신을 주로 사용
✅ 장점
가시성: 한 곳(Orchestrator)에서 전체 비즈니스 프로세스를 볼 수 있어 이해와 유지보수가 쉬움.
명확한 흐름 관리: 프로세스 단계별로 실패 여부를 체크하고 보상 로직을 수행할 수 있음.

❌ 단점
높은 결합도(Coupling): Orchestrator가 많은 서비스에 대한 지식과 제어권을 가져야 해서 결합도가 높아짐.
서비스 빈혈(Anemic Services): 서비스 자체 로직이 약해지고, "명령만 받는 존재"로 전락할 위험. (서비스는 여전히 자체 상태와 행동(state & behavior) 을 가져야 함.)
중앙 집중화 경향: 논리가 모일 수 있는 곳이 있으면 결국 몰리는 경향이 있음 → 과도한 중앙화 주의!

 

→ 이때 하나의 오케스트레이터에 너무 많은 로직들이 몰리는 것을 해결하기 위해 서로 다른 흐름에 대해 서로 다른 마이크로서비스가 Orchestrator 역할을 수행하도록 하는 것이다.
ex)
Order Processor → 주문 , Returns Processor → 반품 및 환불 , Goods Receiving Processor → 입고 관리

 

 

 

 

➡️ Choreographed Saga

: 여러 협력 서비스 사이에서 사가 운영에 대한 책임을 분산시키는 것을 목표로 trust-but-verify한 아키텍처를 나타낸다.

  • Orchestrated Saga와 달리, 중앙 조정자가 없고 각 서비스가 독립적으로 이벤트를 반응하며 작업을 처리
  • 마이크로서비스에 이벤트를 보내는 것이 아니라 이벤트를 단지 내보내면(발행하면) 그 이벤트에 관심이 있는 마이크로서비스가 이벤트를 수신하고 그에 따라 적절히 동작하는 것!
  • 서비스 간 결합도가 낮고, 도메인 결합이 최소화됩니다. 각 서비스는 상호작용할 이벤트만 알고 있으며, 다른 서비스들의 존재에 대해 알지 못한다.
✅ 장점
병렬 처리 가능: 이벤트가 발생하면 여러 서비스가 동시에 병렬로 처리
서비스 간 결합도 낮음: 특정 이벤트가 수신될때 자신이 할 일만 파악하면 되고 도메인 결합도를 크게 낮춘다.
중앙화된 조정자 없음: 비즈니스 로직이 각 서비스의 역할에 따라 분리

❌ 단점
비즈니스 프로세스 추적 어려움: 중앙 조정자가 없기 때문에 전체 비즈니스 흐름을 모델링하기 어렵다.
상태 추적의 어려움: 각 서비스가 독립적으로 동작하면서 사가 상태를 추적하기 어렵

 

이 두가지 Saga는 정 반대 개념이 아니라 혼합해서 사용할 수도 있다.