Saga Pattern (feat. MSA)

Saga Pattern은..

마이크로 서비스들끼리 이벤트를 주고 받아 특정 마이크로 서비스에서의 작업이 실패하면..

이전까지의 작업이 완료된 마이크로 서비스들에게 보상(Complemetary) 이벤트를 소싱함으로써

분산 환경에서 원자성(Atomicity)을 보장하는 패턴이다.

 

쉽게 풀어보자..

 

트랜잭션의 관리 주체가 DB 서버 자신들이 아닌 Application에 있다.

Application이 분산되었을 때 각 Application 하위에 존재하는 DB는

자신의 트랜잭션만 처리하는 구조에 사용된다.

따라서 MSA 구조에 사용하기 좋다.

 

2PC와 비교

2PC 와는 다르게 각 Application 은 자신의 트랜잭션만 처리하며..

Application 개발자가 트랜잭션 로직을 Application에 구현해야 한다.

(MSA 구조상 각 Application에서 실패 이벤트(보상 이벤트)를 수신했을 때 Rollback 처리(보상 트랜잭션) 부분)

 

2PC와 다르게 Saga를 활용한 트랜잭션은 데이터 격리성(Isolation)을 보장해주진 않는다.

하지만.. Application 트랜잭션 관리를 통해 최종적 일관성(Eventually Consistency)을 보장해준다.

 

트랜잭션 관리가 Application 수준에서 되기 때문에 서로다른 DBMS 제품군을 조합해서 사용가능 하다.

 

 

 

Saga 패턴의 종류

1. Choreography-Based Saga

2. Orchestration-Based Saga

두 종류의 차이는 Saga 인스턴스를 별도로 사용하여 처리하느냐 아니냐이다.

 

Saga의 트랜잭션 유형 (추후 포스팅)

1. Compensatable transaction (원복 가능 트랜잭션)

2. Pivot transaction (피봇 트랜잭션)

3. Retriable transaction (재시도 트랜잭션)

 

 

1. Choreography-Based Saga (코레오그래피 베이스 사가)

Saga 인스턴스가 별도로 없다.

Saga 인스턴스가 가진 책임을 각 Application에 분산한 형태이다.

따라서..

Saga의 참여자들은 서로의 이벤트를 구독하고 이에 따라 연쇄적으로 반응한다.

 

<참고>

각 Application은 이벤트를 수신하면 트랜잭션을 열고 비즈니스 로직을 수행 후,

트랜잭션을 종료하고 이벤트를 발행한다. (트랜잭션이 종료되었으므로 실제 DB에 Commit 된 것)

(트랜잭션을 열어두고 이벤트를 발행하는 방식으로 Blocking 된다고 생각하지 말자..)

 

의문점 : 보상 트랜잭션이 발행되었으나.. 보상 트랜잭션에 의한 Rollback이 수행되기 전에

다른 서비스가 해당 데이터를 읽는 상황은.. 어떻게 되는지 궁금하다.. (격리성 저하, 동시성 문제)

-> Saga Pattern 의 주의사항에서 계속.. 

 

Choreography-Based Saga 패턴은 자신이 보유한 서비스 내 DB만의 트랜잭션을 관리한다.

트랜잭션이 종료되면 다음 순서의 Application 대상으로 완료 이벤트를 발행한다.

해당 이벤트를 받은 Application은 트랜잭션을 이어 수행하고 발행하고 반복한다.

마지막에 도달하면 Main Application(Order) 에 그 결과를 전달한다.

 

중간에.. 트랜잭션이 실패한다면?

트랜잭션이 수행된 역순으로 보상 트랜잭션(Rollback)이 수행된다.

 

이벤트 발행과 구독을 위해 Kafka, RabbitMQ 와 같은 메세지 큐 를 사용하면 적절하다.

 

장점

서로의 이벤트를 구독만 하므로.. 느슨한 연결(Loose Coupling)이 보장된다.

-> 시스템이 복잡해지면 결국 강한 결합으로 갈 확률이 높다.

 

단점

동작 과정의 이해 및 분석이 어렵다.

이벤트만 구독하는 방식이다 보니.. 때때로 순환 의존성이 발생하기도 한다.

 

주의사항

Saga 참여자들은 자신이 수신한 메세지와 처리할 데이터를 매핑할 수 있어야한다. (Correlation Id)

모든 트랜잭션이 정상 처리되고 발행되는 이벤트와 각 데이터 베이스 업데이트는 원자적으로 처리되어야 한다. (트랜잭션 메세지 패턴)

 

 

 

2. Orchestration-Based Saga (오케스트레이션 베이스 사가)

트랜잭션 처리를 위한 인스턴스가 별도로 존재하며 이를 Manager(Orchestrator)라고 부른다.

Spring Framework에서 해당 방법을 사용할 수 있는 것으로 Axon Framwork가 있다.

 

모든 관리를 중앙의 매니저가 해주며.. MSA이지만 Monolithic 한 형태를 띈다.

비즈니스 로직상 마지막 트랜잭션이 끝나면 Saga 인스턴스는 전체 트랜잭션을 종료 후, 소멸된다.

 

 

장점

Orchestrator 만 참여자들을 호출하고 참여자들은 Orchestrator 를 호출하지 않기 때문에..

순환 의존성이 발생하지 않는다.

Saga에 참여하는 서비스들은 Orchestrator 에 의해 호출될 API를 구현하기 때문에..

상대적으로 느슨한 연결이 보장된다.

Saga의 조율 로직을 Orchestrator에서 구현하기 때문에 도메인 객체의 비즈니스 로직은 단순해지고 관심사 분리가 실현된다.

 

주의사항

Orchestrator에 비즈니스 로직을 녹이면 안된다.

-> Orchestrator는 커멘드의 순서에 대해서만 책임을 져야한다.

 

 

Saga Pattern의 주의사항

Saga에 참여한 로컬 트랜잭션에 의해 만들어진 변경사항은 트랜잭션 종료와 함께 바로 커밋 되어 외부로 노출된다.

-> 보상 트랜잭션에 의해 Rollback의 가능성이 존재함에도 불구하고.. 말이다..

 

DB ACID의 I 인 격리성(Isolation) 부족이라 볼 수 있다.

 

<참고>

격리성은 복수의 트랜잭션을 동시에 실행 시킨 결과가

해당 트랜잭션들을 순차적으로 실행시킨 결과와 같도록 보장하는 것을 의미한다.

 

그래서 다음과 같은 문제가 발생한다.

특정 Saga가 실행되는 동안 Saga에 의해 수정된 데이터가

다른 Saga에 의해 변경될 수 도.. 다른 Saga에서 읽어 갈 수도.. 있다..

이런 상태에서 Rollback을 해야한다면 어떡해..

 

결국 Saga를 사용하면 격리성의 부족으로 데이터의 부정합이 발생하는데..

이를 데이터베이스 용어로 이상 현상(Anormal) 이라 부른다.

 

구체적 Case 별 현상 명칭 (추후 포스팅)

- Lost Updates

- Dirty Reads

- Fussy/Nonrepeatable reads

 

 

격리성 부족에 대한 대응책 (추후 포스팅)

1. Semantic Lock

2. Commutative Updates

3. Pessimistic View

4. Reread Value

5. Version File

6. By Value

'대규모 시스템 설계' 카테고리의 다른 글

Hexagonal Architecture 정리  (0) 2023.05.15
CQRS Pattern  (0) 2023.02.15
Two Phase Commit (feat. MSA)  (0) 2023.02.13
안정 해시 설계 2  (0) 2022.09.03
안정 해시 설계 1  (0) 2022.09.03